Skip to content

Commit 8fa3fe6

Browse files
claude[bot]claude
andauthored
pm(post-stamped): refuse a quoted stamp later than the clock the writing act holds (#17773)
Fixes #17763 `scripts/pm/post-stamped.mjs` validated only the SHAPE of a `{{WAS:...}}` value, never its direction in time, so a stamp no act can have read passed the refusal set and was rendered verbatim. That is the estimated stamp the tool exists to make unspellable, still spellable through the one escape the tool offers by name in its own refusal text — and the recorded failures it was built against were forward-skewed, which is exactly the half a shape check cannot see. ## The repro, before and after Measured on this branch's base `aaacf1d5c` (the card measured it on `813f8e9f`; it reproduces unchanged): ``` $ node --input-type=module -e " import { stampRefusals, renderBody } from './scripts/pm/post-stamped.mjs'; const NOW = Date.parse('2026-09-12T03:00Z'); const body = 'Verdict {{NOW}} — on the board read {{WAS:2099-01-01T00:00Z}}.'; console.log('refusals:', JSON.stringify(stampRefusals(body))); console.log(renderBody(body, NOW).body); " refusals: [] Verdict 2026-09-12T03:00Z — on the board read 2099-01-01T00:00Z. ``` After, at `8c806fce4`, the same body against the same clock: ``` refusals: [{"kind":"quoted-in-the-future","detail":"`{{WAS:2099-01-01T00:00Z}}` declares an instant LATER than the clock this act holds (`2026-09-12T03:00Z`). ... Write `{{NOW}}` if it is this act's own clock, or correct the value to the instant that was actually read. There is no skew tolerance here, forward: a stamp the clock has not reached is not nearly a reading."}] ok: false kind: stamp-contract ``` End to end through the CLI, same tree: the future body exits `2` (`EXIT_REFUSED`) and writes nothing; the same body with a past quoted stamp exits `0` and still renders it verbatim (`Verdict 2026-09-12T03:44Z — on the board read 2026-09-08T14:00Z.`). ## What changed - `stampRefusals(text, nowMs = Date.now())` now takes the clock the WRITING act holds — the same one `renderBody` substitutes, passed straight through, never a second read taken later. The CLI contract and the `{{NOW}}` route are untouched. - A well-shaped quoted value later than that clock joins the existing refusal register as `quoted-in-the-future`. A shape failure is not additionally filed as a direction failure: one typo, one refusal. - The positional and mixed refusals hand the seat `{{WAS:THE-TYPED-STAMP}}` with the stamp filled in. When that stamp is in the future they now say the quoted route is not open to it, instead of prescribing a remedy this same change refuses. Both texts keep the old offer for a past stamp, and both controls are pinned. ## The boundary, stated A stamp names the SPAN of its own grain — `stampSpan`, imported from `check-half-states.mjs` rather than re-derived, because two spellings of "which minute is this" would let the write side and the read-side patrol disagree about the edge. A value is a possible reading exactly while that span has STARTED: - **equal to the act's own minute: ACCEPTED.** A reading taken at any instant inside `06:37` is spelled `06:37Z`, and the minute's start is at or before any clock reading within it. - **the first minute the clock has not reached: REFUSED.** Acceptance opens ON the tick — a clock standing exactly at `06:37:00.000` accepts `06:37Z`, one millisecond earlier does not. Both sides of that tick are pinned. - **no clock-skew tolerance, and `H56_STAMP_TOLERANCE_MIN` is not one.** That number measures how far a WRITE may land after the read it carries — a backward gap on the read side. Spending it forward here would reopen a quarter-hour window on the very direction the recorded failures took. A stamp 14 minutes ahead — inside H56's drift tolerance — is refused, and that case is pinned beside the constant. ## Tests `--self-test` gains a declared battery, `the direction check: a stamp no act can have read`, floored at 22 in `SELF_TEST_BATTERIES`; `SELF_TEST_BATTERY_FLOOR` moves 6 to 7 so a dropped section still names itself. `pnpm check:pm-post-stamped` at `8c806fce4`: `✓ post-stamped self-test: 90 cases pass across 7 batteries — offline, no network, no token.` The battery contributes 22 of those 90. Reverse verification — three ablations, each mutating the committed file, each proving the bytes reached disk (`git hash-object` against the HEAD blob) and each restored with `git checkout HEAD -- PATH` proven by an empty `git diff HEAD` and a matching blob hash: | mutation | expected | observed | |:---|:---|:---| | `span.from > nowMs` to `false && …` (delete the predicate) | RED | exit 1, 8 named cases fail, incl. the filed repro | | `>` to `>=` (move the edge off the tick) | RED | exit 1, the on-the-tick acceptance fails | | `span.from` to `span.to` (require the whole span to be past) | RED | exit 1, 4 named cases fail, incl. the equal-to-now boundary | The second and third legs are why the tick cases exist: the first version of the battery pinned the minute but not the instant, and the `>=` ablation stayed green through it. Restored self-test after each leg: `exit 0`, 90 cases. Gate sweep — `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` derives 32 families for the one path in this diff; all 32 run, all `exit 0`, reconciled with `--ran`: `✓ dispatch-gates --ran: 32 derived famil(ies) accounted for — 32 run, 0 NOT-MEASURED (a DERIVED zero — all 32 recorded an exit code and none of them is 3)`. Exit codes were captured before any pipe. The derivation's own caveats stand and are not claimed as cleared here: the 50 artifact-roster families, the 11 wide-population families, the 14 changeset-derived families, the one path-scheduled CI job and the always-runs tail are each outside that total and belong to CI. ## Scope and acceptance notes - One path: `scripts/pm/post-stamped.mjs`. `skip-changeset` — `scripts/pm/**` publishes nothing from any released package (it is on the fast-track list, not in any package's `files[]`). - The mixed refusal's text was corrected alongside the positional one because THIS change makes its offer false: it hands back `{{WAS:A-FUTURE-STAMP}}` as a remedy the same commit refuses. Leaving it would have landed a tool arguing with itself. Both of its branches are pinned, future and past. - The new battery's assertions are optional-chained and compared against an explicit `true`/`false`. The first ablation showed the original spelling dying on a `TypeError` before the battery floor ever ran — exit 1 either way, but a stack trace names no case and silences every section after it, which is what the floor exists to prevent. - `check-half-states.mjs`'s H56 read side was NOT touched, per the dispatch. Noted, not filed: H56 has no direction rule of its own, but it does not need the same one — it measures a stamp against the platform's own `created_at`..`updated_at` write window (ground truth, not the writer's clock), and `stampDriftMinutes` is unsigned, so a forward-skewed stamp beyond the 15-minute tolerance is already reported. What it tolerates is 15 minutes in either direction, deliberately and documented as such. Carrier: the next card touching `scripts/pm/check-half-states.mjs`. --- _Generated by [Claude Code](https://claude.ai/code/session_01MCLBsUgfykL74aU716rzVK)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent a24eaee commit 8fa3fe6

1 file changed

Lines changed: 133 additions & 22 deletions

File tree

scripts/pm/post-stamped.mjs

Lines changed: 133 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,9 @@
4747
* else. The author knows the token and typed a time anyway; that
4848
* typed one is the estimate. This is the case the filing card
4949
* names by hand.
50+
* QUOTED a `{{WAS:…}}` value is not one stamp and nothing else, or names
51+
* an instant LATER than the clock this act holds. See the
52+
* direction section below.
5053
* UNKNOWN a `{{…}}` token survives substitution. A mistyped `{{now}}`
5154
* would otherwise post literally AND leave the artefact
5255
* unstamped, which is the quiet direction.
@@ -56,6 +59,35 @@
5659
* `{{NOW}}` is never refused — so mixed alone would not have caught the
5760
* recorded failure, whose comments carried no token at all.
5861
*
62+
* ## The quoted route has a DIRECTION, not only a shape (#17763)
63+
*
64+
* Checking that a `{{WAS:…}}` value is shaped like a stamp leaves the estimate
65+
* spellable through the one escape this tool offers by name: a seat whose sense
66+
* of elapsed time runs AHEAD types its guess, is refused positionally, and the
67+
* refusal's own second option takes that same guess unchanged. The recorded
68+
* failures were forward-skewed, which is exactly the half a shape check cannot
69+
* see. So a quoted value is judged against the clock this act holds as well:
70+
* the quoted route renders a reading of something ELSE, and an instant that has
71+
* not happened yet is provably not a reading of anything.
72+
*
73+
* The boundary, because the format is minute-grained: a stamp names the SPAN of
74+
* its own grain (`stampSpan`, imported — the same widening H56 makes before it
75+
* measures drift), and the value is a possible reading exactly while that span
76+
* has STARTED. So a stamp equal to the act's own minute is ACCEPTED — a reading
77+
* taken at any instant inside this minute is spelled exactly that way — and the
78+
* refusal begins at the first minute whose start the clock has not reached.
79+
*
80+
* ⛔ There is no skew tolerance, and `H56_STAMP_TOLERANCE_MIN` is not one.
81+
* That number measures how far a WRITE may land after the read it carries — a
82+
* backward gap on the read side. Spending it forward here would reopen a
83+
* quarter-hour window on the very direction the recorded failures took.
84+
*
85+
* The same judgement corrects what the OTHER refusals offer: the positional and
86+
* mixed texts hand the seat `{{WAS:<the typed stamp>}}` with the stamp filled
87+
* in, so when that stamp is in the future they must stop offering a route that
88+
* will refuse it — a refusal text prescribing a refused remedy is a tool
89+
* arguing with itself.
90+
*
5991
* A bare stamp OUTSIDE those two positions is passed through with a note on
6092
* stderr rather than refused: prose quoting a ruling's date is a reading of
6193
* something else, and refusing it would push seats back onto the channel this
@@ -98,6 +130,7 @@ import {
98130
proxyRearmPlan,
99131
resolveSweepRepo,
100132
stampDriftMinutes,
133+
stampSpan,
101134
} from './check-half-states.mjs';
102135

103136
const SELF_PATH = fileURLToPath(import.meta.url);
@@ -158,35 +191,75 @@ export function quotedStampValues(text) {
158191
return out;
159192
}
160193

194+
/**
195+
* Whether `stamp` names an instant the clock this act holds has NOT reached.
196+
*
197+
* The stamp is widened to its own grain first (`stampSpan`, imported rather
198+
* than re-derived — H56 measures drift against the same span, and two spellings
199+
* of "which minute is this" would let the write side and the read side disagree
200+
* about the boundary). A value is a possible reading exactly while its span has
201+
* started, so the current minute is accepted and the next one is not.
202+
*
203+
* An unparseable value is NOT future — it is the shape refusal's business, and
204+
* answering `true` here would file one typo under two kinds.
205+
*/
206+
export function stampIsFuture(stamp, nowMs = Date.now()) {
207+
const span = stampSpan(String(stamp ?? '').trim());
208+
return span !== null && span.from > nowMs;
209+
}
210+
161211
/**
162212
* Every reason this body may not be posted, in the order a reader should fix
163213
* them. An empty array is a body that may be written.
214+
*
215+
* `nowMs` is the clock the WRITING act holds — the same one `renderBody`
216+
* substitutes, passed through so the direction check judges against the instant
217+
* this body is being written at, never a second read taken later.
164218
*/
165-
export function stampRefusals(text) {
219+
export function stampRefusals(text, nowMs = Date.now()) {
166220
const raw = String(text ?? '');
167221
const masked = maskQuotedStamps(raw);
168222
const refusals = [];
223+
const now = stampNow(nowMs);
169224

170225
for (const value of quotedStampValues(raw)) {
171-
if (protocolStamps(value).length === 1 && protocolStamps(value)[0] === value.trim()) continue;
226+
if (protocolStamps(value).length !== 1 || protocolStamps(value)[0] !== value.trim()) {
227+
refusals.push({
228+
kind: 'quoted-not-a-stamp',
229+
detail:
230+
`\`{{WAS:${value}}}\` does not declare a stamp. The quoted route renders a reading of ` +
231+
'something else VERBATIM, so its contents must be one `YYYY-MM-DDThh:mmZ` and nothing else — ' +
232+
'it is a declaration, not a free-text escape from the contract.',
233+
});
234+
continue;
235+
}
236+
if (!stampIsFuture(value, nowMs)) continue;
172237
refusals.push({
173-
kind: 'quoted-not-a-stamp',
238+
kind: 'quoted-in-the-future',
174239
detail:
175-
`\`{{WAS:${value}}}\` does not declare a stamp. The quoted route renders a reading of ` +
176-
'something else VERBATIM, so its contents must be one `YYYY-MM-DDThh:mmZ` and nothing else — ' +
177-
'it is a declaration, not a free-text escape from the contract.',
240+
`\`{{WAS:${value}}}\` declares an instant LATER than the clock this act holds (\`${now}\`). The ` +
241+
'quoted route renders a reading of something ELSE verbatim, and a time that has not happened yet ' +
242+
'is provably not a reading of anything — it is an estimate with a declaration wrapped round it, ' +
243+
`which is the defect this tool exists to make unspellable. Write \`${STAMP_TOKEN}\` if it is this ` +
244+
'act\'s own clock, or correct the value to the instant that was actually read. There is no skew ' +
245+
'tolerance here, forward: a stamp the clock has not reached is not nearly a reading.',
178246
});
179247
}
180248

181249
const positional = h56StampedReadings(masked);
182250
for (const hit of positional) {
251+
const opener =
252+
`${hit.where} carries the bare stamp \`${hit.stamp}\`. That position belongs to the writing ` +
253+
'act, so a stamp typed there is the act\'s own time written from memory — the defect this tool ' +
254+
'exists to make unspellable. ';
183255
refusals.push({
184256
kind: 'positional',
185-
detail:
186-
`${hit.where} carries the bare stamp \`${hit.stamp}\`. That position belongs to the writing ` +
187-
'act, so a stamp typed there is the act\'s own time written from memory — the defect this tool ' +
188-
`exists to make unspellable. Write \`${STAMP_TOKEN}\` there, or \`{{WAS:${hit.stamp}}}\` if it ` +
189-
'is genuinely a reading of something else.',
257+
detail: stampIsFuture(hit.stamp, nowMs)
258+
? `${opener}Write \`${STAMP_TOKEN}\` there. The quoted route is NOT open to this one: ` +
259+
`\`${hit.stamp}\` is later than the clock this act holds (\`${now}\`), so it cannot be a reading ` +
260+
'of something else either.'
261+
: `${opener}Write \`${STAMP_TOKEN}\` there, or \`{{WAS:${hit.stamp}}}\` if it ` +
262+
'is genuinely a reading of something else.',
190263
});
191264
}
192265

@@ -195,13 +268,18 @@ export function stampRefusals(text) {
195268
for (const stamp of protocolStamps(masked)) {
196269
if (seen.has(stamp)) continue;
197270
seen.add(stamp);
271+
const opener =
272+
`this body uses \`${STAMP_TOKEN}\` and also carries the bare stamp \`${stamp}\`. One of the ` +
273+
'two clocks was read by this act and the other was typed; a reader cannot tell which. ';
198274
refusals.push({
199275
kind: 'mixed',
200-
detail:
201-
`this body uses \`${STAMP_TOKEN}\` and also carries the bare stamp \`${stamp}\`. One of the ` +
202-
'two clocks was read by this act and the other was typed; a reader cannot tell which. Declare ' +
203-
`it with \`{{WAS:${stamp}}}\` if it is a quoted reading, or make it \`${STAMP_TOKEN}\` if it ` +
204-
'is this act\'s own.',
276+
detail: stampIsFuture(stamp, nowMs)
277+
? `${opener}Make it \`${STAMP_TOKEN}\` if it is this act's own. The quoted route is NOT open to ` +
278+
`it: \`${stamp}\` is later than the clock this act holds (\`${now}\`), so it cannot be a ` +
279+
'reading of something else either.'
280+
: `${opener}Declare ` +
281+
`it with \`{{WAS:${stamp}}}\` if it is a quoted reading, or make it \`${STAMP_TOKEN}\` if it ` +
282+
'is this act\'s own.',
205283
});
206284
}
207285
}
@@ -216,8 +294,9 @@ export function refusalText(refusals) {
216294
`post-stamped: REFUSED — ${rows.length} stamp-contract problem(s) in the body. Nothing was written.\n` +
217295
`${rows.join('\n')}\n\n` +
218296
` The contract has exactly two spellings: \`${STAMP_TOKEN}\` for the clock this act reads, and\n` +
219-
' `{{WAS:YYYY-MM-DDThh:mmZ}}` for a stamp that is a reading of something else. There is no flag\n' +
220-
' that turns it off — a stamp typed from memory is the defect, not a formatting preference.'
297+
' `{{WAS:YYYY-MM-DDThh:mmZ}}` for a stamp that is a reading of something else — an instant the\n' +
298+
' clock has already reached, since nothing can be read out of the future. There is no flag that\n' +
299+
' turns it off — a stamp typed from memory is the defect, not a formatting preference.'
221300
);
222301
}
223302

@@ -236,7 +315,7 @@ export function renderBody(text, nowMs = Date.now()) {
236315
' reader has to judge; supply a body with --file=PATH or on stdin.',
237316
};
238317
}
239-
const refusals = stampRefusals(raw);
318+
const refusals = stampRefusals(raw, nowMs);
240319
if (refusals.length > 0) return { ok: false, kind: 'stamp-contract', refusals, error: refusalText(refusals) };
241320

242321
const stamp = stampNow(nowMs);
@@ -438,7 +517,8 @@ const USAGE = [
438517
' With no --file the body is read from stdin.',
439518
` In the body: \`${STAMP_TOKEN}\` is the clock this run reads; \`{{WAS:YYYY-MM-DDThh:mmZ}}\` declares a`,
440519
' stamp that is a reading of something else. A bare stamp on the opening line, or on a subscript',
441-
' reading-time line, is REFUSED — that position belongs to the writing act.',
520+
' reading-time line, is REFUSED — that position belongs to the writing act. A quoted stamp LATER',
521+
' than the clock this run reads is REFUSED too — the future is not a thing anyone read.',
442522
' The attribution footer is the caller\'s: its form differs by channel and act, so this tool adds none.',
443523
].join('\n');
444524

@@ -550,12 +630,13 @@ async function main(argv) {
550630
const SELF_TEST_BATTERIES = Object.freeze({
551631
'the token contract: the two spellings, and nothing else': 9,
552632
'the refusals: every route that must not reach the board': 20,
633+
'the direction check: a stamp no act can have read': 22,
553634
'the substitution: one clock, read once, written everywhere': 9,
554635
'the read-back: what the transcript can actually prove': 11,
555636
'the CLI: the one decision a typo must never make': 12,
556637
'the shared rule: this tool and H56 cannot come to disagree': 6,
557638
});
558-
const SELF_TEST_BATTERY_FLOOR = 6;
639+
const SELF_TEST_BATTERY_FLOOR = 7;
559640
const UNATTRIBUTED_BATTERY = '(unattributed)';
560641

561642
let selfTestReachedVerdict = false;
@@ -574,7 +655,7 @@ export function selfTest() {
574655
};
575656

576657
const NOW_MS = Date.parse('2026-09-10T06:37:48Z');
577-
const kinds = (text) => stampRefusals(text).map((r) => r.kind);
658+
const kinds = (text, ms) => stampRefusals(text, ms).map((r) => r.kind);
578659

579660
battery('the token contract: the two spellings, and nothing else');
580661
t('the act-clock token is `{{NOW}}`', STAMP_TOKEN === '{{NOW}}');
@@ -610,6 +691,36 @@ export function selfTest() {
610691
t('…and states that no flag turns the contract off', refusalText(stampRefusals(OPENING)).includes('There is no flag'));
611692
t('…and that nothing was written', refusalText(stampRefusals(OPENING)).includes('Nothing was written'));
612693

694+
// The clock this act holds is 06:37:48 — so 06:37Z is the minute it is IN,
695+
// 06:38Z the first minute it has not reached, and 06:51Z sits 14 minutes
696+
// ahead, inside H56's drift tolerance and still not a thing anyone read.
697+
battery('the direction check: a stamp no act can have read');
698+
const CARD_REPRO = 'Verdict {{NOW}} — on the board read {{WAS:2099-01-01T00:00Z}}.';
699+
t('⭐ the filed repro: a 2099 quoted stamp is REFUSED, not rendered verbatim', kinds(CARD_REPRO, NOW_MS).includes('quoted-in-the-future'));
700+
t('…and the refusal names the clock it was judged against', stampRefusals(CARD_REPRO, NOW_MS)[0]?.detail?.includes('2026-09-10T06:37Z') === true);
701+
t('…and says a time that has not happened is not a reading of anything', stampRefusals(CARD_REPRO, NOW_MS)[0]?.detail?.includes('provably not a reading of anything') === true);
702+
t('⭐ …so the whole body is refused and NOTHING is rendered', renderBody(CARD_REPRO, NOW_MS).ok === false && renderBody(CARD_REPRO, NOW_MS).body === undefined);
703+
t('⛔ a PAST quoted stamp is untouched — the route this closes is one direction only', renderBody('Verdict {{NOW}} — on the board read {{WAS:2026-09-08T14:00Z}}.', NOW_MS).body?.includes('board read 2026-09-08T14:00Z.') === true);
704+
t('⭐ BOUNDARY: a stamp equal to the act\'s OWN minute is ACCEPTED — a reading taken inside 06:37 is spelled 06:37Z', stampRefusals('read {{WAS:2026-09-10T06:37Z}}', NOW_MS).length === 0);
705+
t('⭐ …and the first minute the clock has NOT reached is refused', kinds('read {{WAS:2026-09-10T06:38Z}}', NOW_MS).includes('quoted-in-the-future'));
706+
t('…the acceptance opens ON the tick: a clock standing exactly at 06:37:00 accepts 06:37Z', stampRefusals('read {{WAS:2026-09-10T06:37Z}}', Date.parse('2026-09-10T06:37:00Z')).length === 0);
707+
t('…and one millisecond before it does not — the span must have STARTED, not be about to', kinds('read {{WAS:2026-09-10T06:37Z}}', Date.parse('2026-09-10T06:37:00Z') - 1).includes('quoted-in-the-future'));
708+
t('⛔ no forward skew window: 14 minutes ahead is refused though H56 tolerates 14 minutes of DRIFT', kinds('read {{WAS:2026-09-10T06:51Z}}', NOW_MS).includes('quoted-in-the-future') && H56_STAMP_TOLERANCE_MIN === 15);
709+
t('the grain is the stamp\'s own: 06:37:49Z is one second ahead of 06:37:48 and is refused', kinds('read {{WAS:2026-09-10T06:37:49Z}}', NOW_MS).includes('quoted-in-the-future'));
710+
t('…while the same instant spelled to the minute is not', stampRefusals('read {{WAS:2026-09-10T06:37Z}}', NOW_MS).length === 0);
711+
t('⛔ a shape failure is NOT also filed as a direction one — one typo, one refusal', kinds('{{WAS:2099-01-01T00:00Z ruling}}', NOW_MS).join() === 'quoted-not-a-stamp');
712+
t('…and the shape refusals still fire, unchanged', kinds('{{WAS:yesterday}}', NOW_MS).includes('quoted-not-a-stamp'));
713+
t('an unreadable value is nobody\'s idea of the future', stampIsFuture('yesterday', NOW_MS) === false);
714+
t('⭐ the `{{NOW}}` route is untouched: a token-only body still renders on this act\'s clock', renderBody('Claim: {{NOW}} — dispatched.', NOW_MS).body === 'Claim: 2026-09-10T06:37Z — dispatched.');
715+
const POSITIONAL_FUTURE = 'Claim: skills seat, 2099-01-01T00:00Z — dispatched.';
716+
t('⭐ the POSITIONAL refusal stops handing a future estimate back through the quoted route', stampRefusals(POSITIONAL_FUTURE, NOW_MS)[0]?.detail?.includes('{{WAS:2099-01-01T00:00Z}}') === false);
717+
t('…and says instead that the stamp is later than the clock this act holds', stampRefusals(POSITIONAL_FUTURE, NOW_MS)[0]?.detail?.includes('later than the clock this act holds') === true);
718+
t('…while a PAST typed stamp is still offered the quoted route, which is the whole control', stampRefusals(OPENING, NOW_MS)[0]?.detail?.includes('{{WAS:2026-09-10T06:37Z}}') === true);
719+
const MIXED_FUTURE = 'Claim: {{NOW}}\n\nThe board was read at 2099-01-01T00:00Z.';
720+
t('the MIXED refusal likewise stops offering a route that would refuse it', stampRefusals(MIXED_FUTURE, NOW_MS).find((r) => r.kind === 'mixed')?.detail?.includes('{{WAS:2099-01-01T00:00Z}}') === false);
721+
t('…and a past bare stamp keeps the declaration on offer', stampRefusals('Claim: {{NOW}}\n\nread 2026-09-08T14:00Z', NOW_MS).find((r) => r.kind === 'mixed')?.detail?.includes('{{WAS:2026-09-08T14:00Z}}') === true);
722+
t('⛔ the clock defaults to Date.now() rather than to "no judgement" when a caller omits it', stampRefusals('read {{WAS:2099-01-01T00:00Z}}').map((r) => r.kind).includes('quoted-in-the-future'));
723+
613724
battery('the substitution: one clock, read once, written everywhere');
614725
const TWO = renderBody('Claim: {{NOW}}\n\nRound opened {{NOW}}.', NOW_MS);
615726
t('a body that clears the contract renders', TWO.ok === true);

0 commit comments

Comments
 (0)