agents/skills/daml/temporal-parameter-staleness/SKILL.md
Trigger Pattern interval|period|duration|delay|cooldown|lock_period|timelock|deadline|maturity|expiry|getTime - Inject Into Breadth agents, depth-state-trace
npx skillsauth add plamentsv/plamen temporal-parameter-stalenessInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Trigger Pattern:
interval|period|duration|delay|cooldown|lock_period|timelock|deadline|maturity|expiry|valid_until|getTimeInject Into: Breadth agents, depth-state-trace Finding prefix:[DML-TPS-N]Rules referenced: R2, R8, R10, R13, R14
Cached parameters in multi-step (multi-transaction) operations become stale when an authority changes the source between steps. On DAML, time is read with getTime : Update Time inside a choice, returning ledger time (a record time the submitter does NOT choose; it is bounded by the transaction's ledger-time window). A deadline/duration is a template field (Time or a RelTime/Int of seconds); a value is "cached" when a choice copies a config field into a successor contract at Step 1, and Step N reads the cached copy instead of re-fetching the live config. There are no slots/epochs/blocks — time is the ledger Time value. This skill is the staleness/cross-transaction lens; ENSURE_INVARIANTS §4 is the single-choice deadline-enforcement boundary lens — cross-reference, do not duplicate.
Find all operations that span multiple transactions, typically a Propose/Accept or initiate/complete pattern:
| Operation | Step 1 (Initiate) | Wait Condition | Step N (Complete) | Time Source |
|-----------|-------------------|----------------|-------------------|-------------|
| {op_name} | {Propose choice / create} | {deadline / cooldown} | {Accept / Complete choice} | getTime (ledger time) |
For each multi-step operation:
getTime (ledger time) or taken from a caller-supplied Time argument? A caller-supplied time lets the caller choose "now" — always a finding.| Source | Type | Who Controls | Notes |
|--------|------|--------------|-------|
| getTime | Time (in Update/Script) | Ledger (record time, within the tx ledger-time window) | The ONLY trustworthy "now" inside a choice |
| Caller-supplied Time/RelTime argument | choice argument | The submitting party | NOT trustworthy as "now" — the caller picks it |
| A deadline : Time field on a contract | template field | Whoever authorized the create | Only enforced if a choice compares it to getTime |
Critical property: within a single transaction (one choice and its consequences), all getTime calls return the same Time — there is no intra-transaction time variation. Multi-step time attacks require separate transactions.
For each parameter used across steps:
| Parameter (field) | Source Contract | Copied Into (Step 1 successor) | Cached? | Authority-Changeable? (which choice) | Re-Fetched at Step N? | |--------------------|-----------------|--------------------------------|---------|--------------------------------------|------------------------|
DAML caching patterns:
config.feeRate/config.deadline into the Proposal contract; Accept reads the Proposal's copy, not the live config.deadline = addRelTime now period computed at Step 1; if period later changes in config, the Proposal's deadline is stale.cfg <- fetchByKey @Config ... and reads live values — no staleness for those fields.Red flags: a field is copied into the Step-1 successor AND an authority can change the source config AND Step N does NOT re-fetch the source.
For each cached parameter that can become stale:
Scenario A: Parameter INCREASES between steps
1. Party initiates at Step 1 — successor contract stores param = X
2. Authority exercises a setter choice: config param = X + delta
3. Party completes at Step N — uses cached X from the successor
4. Impact: {what happens with stale X when live config is X + delta}
Scenario B: Parameter DECREASES between steps
1. Party initiates at Step 1 — successor stores param = X
2. Authority setter: config param = X - delta
3. Party completes at Step N — uses cached X
4. Impact: {what happens with stale X when live config is X - delta}
BOTH directions are mandatory — increase and decrease often differ.
| Vector | Description | Severity Modifier |
|--------|-------------|-------------------|
| Stale snapshot in successor | Proposal/initiate contract holds a copied config field; Accept never re-fetches the live config. | HIGH if it governs value movement; MEDIUM otherwise |
| Cached absolute deadline | deadline computed at Step 1 from a period that later changes; the Proposal's deadline no longer matches policy. | Medium if the deadline is safety-critical |
| Caller-supplied time | A choice compares against a Time passed as an argument instead of getTime. | High — the caller chooses "now", defeating the gate |
| Config archived mid-operation | The config contract a Step-N choice fetches/fetchByKeyes is archived between steps. | High — Step N aborts CONTRACT_NOT_FOUND / NO_SUCH_KEY (liveness brick) |
| Retroactive rate-at-complete | Step N reads the LIVE config rate (not the Step-1 snapshot), so an authority change retroactively alters all pending operations. | Medium–High depending on who is harmed |
For each parameter updated from another contract (a price/rate feed contract fetched in a choice):
ContractId be substituted via a caller-supplied argument (cross-reference CID_CAPABILITY_SAFETY)?For fee/rate/deadline fields that apply to existing in-flight state:
| Parameter (field) | Set By (choice) | Applies To | Retroactive? | Impact | |-------------------|-----------------|-----------|--------------|--------|
DAML retroactive patterns:
feeRate. All pending completions that re-fetch the live config now use the new rate — retroactively changes returns for parties who initiated under the old rate.cooldownPeriod in config makes parties who already initiated wait longer/shorter — retroactive on in-flight Proposals (if Step N recomputes from live period).Rule 2 direction check: can the authority's change make a party-facing choice behave unexpectedly (e.g. setting cooldownPeriod = 0 removes a withdrawal-delay protection; setting maxWithdrawal = 0 blocks all withdrawals)? Does the change retroactively affect parties in active operations?
For each staleness issue:
ensure, a max delay)Use worst realistic operational state:
Severity assessed at: pending_ops=MAX, rate_delta=MAX_RATE-MIN_RATE, value=$XX
Rationale: protocol designed for up to {N} concurrent pending operations per documentation
ensure)?getTime (ledger time) or a caller-supplied Time?ensure limits the change magnitude, impact may be Low{TEMPLATES} - Templates to analyze
{MULTI_STEP_OPS} - Identified multi-step (Propose/Accept) operations
{CACHED_PARAMS} - Fields snapshotted into Step-1 successors
{AUTHORITY_PARAMS} - Authority-changeable config fields
{DELAY_PARAMS} - Deadline/cooldown fields (Time / RelTime / seconds)
{FEE_PARAMS} - Fee/rate fields that may apply retroactively
{TIME_SOURCE} - getTime (ledger time) vs caller-supplied Time argument
| Field | Required | Description | |-------|----------|-------------| | multi_step_ops | yes | Multi-transaction operations found | | cached_params | yes | Fields snapshotted across steps (into which successor) | | staleness_vectors | yes | How cached fields can become stale | | retroactive_fees | yes | Fees/rates applied retroactively | | time_source_audit | yes | getTime vs caller-supplied, and whether appropriate | | config_archival_risks | yes | Config contracts that could be archived mid-operation | | finding | yes | CONFIRMED / REFUTED / CONTESTED | | evidence | yes | Code locations with line numbers | | step_execution | yes | Status for each step |
**ID**: [DML-TPS-N]
**Verdict**: CONFIRMED / PARTIAL / REFUTED / CONTESTED
**Step Execution**: ✓1,2,3,3b,4,5 | ✗(reasons) | ?(uncertain)
**Rules Applied**: [R2:___, R8:___, R10:___, R13:___, R14:___]
**Severity**: Critical/High/Medium/Low/Info
**Location**: {Module}.daml:LineN (template X, choice Y)
**Title**: {stale cached field / caller-supplied time / retroactive rate / config-archival brick}
**Description**: {which field is cached at Step 1, which authority changes it, why Step N is stale}
**Impact**: {quantified at worst-state — who is harmed by the stale/retroactive value}
**PoC steer**: multi-transaction Script — initiate (snapshot field), authority `submit` changes config, complete and assert the stale value governs (or the live value retroactively applies); for caller-supplied time, `submit` a past/future Time and show the gate passes; `passTime`/`setTime` for deadline windows.
| Step | Required | Completed? | Notes | |------|----------|------------|-------| | 1. Enumerate Multi-Step Operations | YES | | | | 2. Identify Cached Parameters | YES | | | | 3. Model Staleness Impact (both directions) | YES | | | | 3b. Update-Source Audit | YES | | | | 4. Retroactive Application Analysis | YES | | | | 5. Assess Severity | YES | | |
After Step 2: if cached fields are authority-changeable → MUST complete Step 3 with BOTH increase and decrease scenarios.
After Step 3: if any time comparison uses a caller-supplied Time instead of getTime → FLAG (the caller chooses "now").
After Step 3b: if the source config/feed is referenced by a caller-supplied ContractId → cross-reference CID_CAPABILITY_SAFETY.
After Step 4: cross-reference SEMI_TRUSTED_ROLES for the authority choices that change these fields, and ENSURE_INVARIANTS §4 for single-choice deadline enforcement.
data-ai
Trigger Pattern Always (run during recon TASK 0, not breadth) - Inject Into Recon agent only (meta_buffer.md enrichment)
data-ai
Trigger Pattern Always (run during recon TASK 0, not breadth) - Inject Into Recon agent only (meta_buffer.md enrichment)
data-ai
Trigger Pattern Always (run during recon TASK 0, not breadth) - Inject Into Recon agent only (meta_buffer.md enrichment)
data-ai
Trigger STABLESWAP_FORK flag (fork-ancestry detects Curve/StableSwap parent via get_d/get_y/ramp_a/StableSwap patterns) - Agent Type general-purpose (standalone niche agent, 1 budget slot)