Repository navigation
Commit 06dbc4e
Part of #15953
Clause-②: no (narrowing)
## What this is
Slice 6 of #15953 adds no new proof family. It does three things in
`packages/verify`:
- it carries the two notes slice 5's contract review owed on the next
push to `dashboard-window.ts` (record `6138334190` on PR #23150);
- it removes `DashboardWindowProofOptions.now`, as the claim
(`6140521245`) ruled;
- it moves the fixture helpers that four families copied privately into
one home, `derive.ts`.
**Why the removal is declared breaking.** The version commit
`d889448b93` (#21988) landed after the claim and consumed slice 5's
changeset. It published `@objectstack/verify@18.0.0-next.0` to dist-tag
`next`, and that version carries the option. The dev therefore stopped
and asked (report `6141945855`). The seat ruled **A2** (`6142005900`):
the removal stands, declared as a narrowing of the published prerelease.
The consumed note under `.changeset/pre/` is restored byte for byte, and
one new changeset carries the narrowing and the fix. Per that ruling, a
`CONTRACT_REVIEW_TIER` review is owed before the queue.
## What changed
- **`runDashboardWindowProofs(stack, token, config)`.** The fourth
parameter is gone, and so is its type `DashboardWindowProofOptions`,
whose only member was `now`.
- The server resolves a dashboard's date preset per request on its own
clock, so any `now` other than the current instant made every window
read `contradicted`.
- Nothing in the tree passed it, and neither does hotcrm at `1d7148bf`,
which pins `^17.7.0`.
- The presets now resolve once, when the run starts, on `new Date()`,
and the function's docblock says so.
- **The day-turn guard.** `const startDay = todayAt(start)` sits beside
the presets' resolution. In `probeMeasure`, directly after `const day0 =
todayAt(new Date())`, a measure with `day0 !== startDay` is `not-run`
and writes no fixture. Its detail reads "the day turned after the run
started (START → DAY0), so the windows the presets resolved then are not
the ones the server resolves now".
- Before this, a run that crossed UTC midnight between `start` and a
measure's probe judged that measure against the previous day's edges,
and could read it `contradicted`: a false red, never a false green.
- The end-of-probe guard, "the day turned during the probe", is
unchanged.
- **`derive.ts` is the one home for the shared fixture helpers:**
`laterBy`, `vary(base, k, tag)`, `uniqueFields` and `fixtureBody(body,
objectDef, n, tag, full = false)`.
- They are exported from that module for the families only, and ⛔ not
from `index.ts`.
- `unique-scope`, `parent-write-reach` and `dashboard-window` import
them, each passing its own `FIXTURE_TAG` (`'u'`, `'p'`, `'w'`).
- `delete-behavior` imports `uniqueFields` and `laterBy`, and keeps its
own `fixtureBody`. The reason is in the next section.
- **`index.ts`** drops `type DashboardWindowProofOptions`.
- **`README.md`**: the API line reads `runDashboardWindowProofs(stack,
token, config)`, and the `inconclusive` row says "the UTC day turned
after the run started".
- **Changesets:**
- `.changeset/15953-verify-dashboard-window-now-removed.md` is new:
`@objectstack/verify` `minor`, a `fix(verify)!:` summary, `Clause-②: no
(narrowing)`, one ADR-0087 marker, the BREAKING line, the FROM → TO line
and a fix paragraph for the guard.
- `.changeset/pre/15953-verify-dashboard-window-family.md` is unchanged
from `main`: the edit an earlier push made to it is reverted, and its
blob equals `main`'s, `144d744b53`.
## The helper measurement
Each top-level function's text was extracted verbatim from `git show
d4aaeaf:packages/verify/src/FAMILY.ts` and hashed with sha1 (first 10
hex digits).
| helper | unique-scope | parent-write-reach | dashboard-window |
delete-behavior | disposition |
|---|---|---|---|---|---|
| `uniqueFields` | `85f6a84965` | `85f6a84965` | `85f6a84965` |
`85f6a84965` | moved; all four import it |
| `laterBy` | `f48197955d` | `f48197955d` | `f48197955d` | `9d2c4c7538`
(takes `unknown`) | moved as one function (see below) |
| `vary` | `a1664fcca2` | `78966f349c` | `6b2779c503` | none | moved as
`vary(base, k, tag)`; all three equal `076d9a28df` once the tag letter
is normalised |
| `fixtureBody` | `69df6c1389` | `69df6c1389` | `32dccc53e0` |
`e9e1202f4e` | moved for the three; equals `69df6c1389` once `full =
false` is taken out. delete-behavior's **stays private** |
- **`laterBy`.** The string and `unknown` copies agree on every string
input. The shared one is delete-behavior's body byte for byte (body sha
`1a71ae0923` at base and in `derive.ts`), with two overloads: string to
string, and unknown to unknown.
- **Why delete-behavior's `fixtureBody` stays private.** It makes a
unique string distinct with a plain `-n` (before the `@` of an email),
with no family tag and no date, URL or phone branch. It also advances
its date offset for a date value that is not a string. No parameter
carries that without changing a value this family writes, so it stays
where it is, and its docblock says why.
- **The shared `uniqueFields` body** is `dd7aa50492` in all four copies
and in `derive.ts`.
## Byte identity: method and result
1. **Capture.** On `d4aaeaf0e2`, an uncommitted, env-gated
(`OS_TEST_CAPTURE_FIXTURES`) append-only shim wrapped every private
helper in the four families. The families' own five test files then ran
with it, and it logged each call's arguments and return value: 1,984
calls, 0 capture errors, 5 files / 49 tests passed.
- unique-scope: `fixtureBody` 218, `uniqueFields` 218, `vary` 266.
- parent-write-reach: `fixtureBody` 197, `uniqueFields` 197.
- dashboard-window: `fixtureBody` 322, `uniqueFields` 322, `laterBy` 40.
- delete-behavior: `fixtureBody` 102, `uniqueFields` 102.
The shim was restored with `git checkout HEAD --` on absolute paths. The
restore was proven by each file's blob equalling HEAD's and by an empty
`git diff HEAD`.
2. **Differential.** A tsx script outside the repo called three
implementations on every captured call, plus 1,455 synthetic calls that
walk every branch:
- OLD: each family's private copy, extracted verbatim from `d4aaeaf0e2`;
- NEW: `derive.ts`'s helper with the family's tag. For delete-behavior,
its still-private `fixtureBody`, taken from the working tree and bound
to the shared helpers;
- CAPTURED: what the private copy returned in the family's own run.
The synthetic calls cover email, URL with and without a trailing slash,
phone, date, datetime, unparsable values, numbers, `k = 0`, `days = 0`,
non-strings, unique scopes and indexes, and `full`. Each output is
serialised so that `undefined` and `Set` stay distinguishable.
3. **Result: PASS.** All 3,439 records were byte-identical, NEW to OLD
and NEW to CAPTURED. Report fields carry only these values plus
run-dependent ids.
4. **Control: FAIL.** Running the same script with unique-scope's tag
set to `x` gave 860 mismatches, the first `verify-sample-u1` against
`verify-sample-x1`.
5. **Families' own suites.** At `59507b26a3`, the five family files plus
`derive.test.ts` gave 6 files / 76 tests passed: 50 in the family files
(the 49 above plus the new day-turn pin) and 26 in `derive.test.ts` (22
plus 4 new pins).
## `dist/index.d.ts` diff
This diff compares the **published** `@objectstack/verify@18.0.0-next.0`
tarball's `dist/index.d.ts` with this branch's build at `75877cc2e2`. It
shows four hunks and nothing else (the same four as a pristine
`d4aaeaf0e2` build against `59507b26a3`):
```diff
-interface DashboardWindowProofOptions {
- /** The reference instant the ground truth resolves on. Defaults to the start of the run. */
- now?: Date;
-}
+ *
+ * The presets resolve once, when the run starts, on the current instant: the
+ * server resolves them per request on its own clock, so no other instant
+ * could agree with it. A measure whose fixture day is no longer the day the
+ * run started on is not run.
-declare function runDashboardWindowProofs(stack: VerifyStack, token: string, config: any, options?: DashboardWindowProofOptions): …
+declare function runDashboardWindowProofs(stack: VerifyStack, token: string, config: any): …
```
The return type is unchanged. The export list differs only by `type
DashboardWindowProofOptions, `. `laterBy`, `vary`, `uniqueFields` and
`fixtureBody` have 0 hits in `dist/index.d.ts`. `derive.ts` imports only
`@objectstack/spec/data` and `./artifact-collections.js`, so there is no
import cycle, and `index.ts` re-exports it by name.
## The day-turn guard: pin and ablation
**Pin.** `dashboard-window.test.ts` › "THE DAY TURN" runs a one-widget
app (`wnt_case.opened_at`, one count measure).
- The module reads the clock only through `new Date()`: once at `start`,
then per measure for `day0` and for the end guard.
- So after boot and sign-in, the test calls `vi.useFakeTimers({ toFake:
['Date'] })`. Only `Date` is faked, frozen at the real instant, and
every timer stays real.
- The run gets a copy of the stack whose `apiAs`, on its first call,
does `vi.setSystemTime(Date.now() + DAY_MS)`. That first call is the
measure's control leg, which falls between the two clock reads.
`vi.useRealTimers()` runs in `finally`.
- No public option was added.
- The pin asserts:
- the widget reads `inconclusive`;
- all 13 outcomes are `not-run`;
- `days` is undefined, so no fixture was written;
- the detail names the start day and the next day;
- the summary has `contradictions: 0`, `hardFailures: 0` and `notRun:
13`.
**Ablation (a)**, at `59507b26a3`, through
`scripts/ablation-replace.mjs` in WRAP mode with its own restore trap:
- The guard block was deleted. The anchor count went from 1 to 0, and
the blob from `2bdb9b1ca1a7` to `616f4a616d95`.
- **RED**: 1 failed, 10 skipped; expected `inconclusive`, received
`contradicted`. The report read:
- `last_7_days`: "the window counted 4 fixture row(s), and it selects 5
(narrower)";
- `last_30_days`: 9 against 10;
- `last_90_days`: 13 against 14.
- Restored: blob equal to HEAD, and `git diff HEAD` empty.
- The test imports `./dashboard-window.js` relatively, so vitest reads
`src` and no dist build is in the path.
## `vary`'s tag: pin and ablation
**Pin.** `derive.test.ts` › "the fixture helpers the proof families
share" covers:
- `vary` marking a string with each family's letter (`Acme-u3`,
`Acme-p3`, `Acme-w3`, `ada-u2@example.com`, `https://example.com/p1`);
- a number, a date and a phone number taking `k` alone;
- `laterBy` on date and datetime spellings and on non-strings;
- the fields `uniqueFields` returns;
- `fixtureBody`'s smallest and `full` shapes under two tags.
**Ablation (b)**, at `59507b26a3`:
- The first pass was a **no-op**: its replacement contained its own
anchor, so the tool refused it and restored, and its reading is void.
- The second pass replaced the parameter `tag` with a renamed one plus
`const tag = 'u'`. The anchor count went from 1 to 0, and the blob from
`655a802e3813` to `201cb8a6d1df`.
- **RED**: 2 failed, 2 passed, 22 skipped of 26. `vary` expected
`Acme-p3` and received `Acme-u3`, and the `fixtureBody` pin failed with
tag `w`.
- Restored: blob equal to HEAD, and `git diff HEAD` empty.
## The changeset
**The ADR-0087 marker** is `not-required (runtime-export-retired
@objectstack/verify#DashboardWindowProofOptions)`. `node
scripts/check-adr-0087-registration.mjs --base origin/main` verifies it
as an export of the published entry at the merge base that is absent at
HEAD.
**The FROM → TO line names the retired export.** Its TO side carries the
call change: `runDashboardWindowProofs(stack, token, config, { now })`
becomes `runDashboardWindowProofs(stack, token, config)`, and the fix is
to drop the fourth argument.
The ruling's literal shape, a FROM line on the four-argument call, was
measured and refused:
- the gate's predicate 3 reads `runDashboardWindowProofs` as the FROM
operand, which is out of range because the marker does not name it;
- naming it would fail predicate 1, because the function is still
exported;
- the probe was an uncommitted edit, restored and proven by the file's
blob equalling HEAD's.
## Verification
At **`75877cc2e2`**, the merged head (`origin/main` `76815b9013`, which
carries #23179). `main` was merged twice; the second merge followed
#23179's landing, and everything below was re-run on its tree:
- **Builds:**
- `turbo run build --filter='!@objectstack/docs' --concurrency=2` (the
full build): 74/74, exit 0.
- **Tests:**
- `pnpm --filter @objectstack/verify test`: 38 files / 306 tests passed,
exit 0.
- core's `#22661` pin, `pnpm --filter @objectstack/core exec vitest run
--project repo --maxWorkers=2
src/security/second-object-read-exposure.pin.test.ts`: 1 file / 4 tests,
exit 0.
- **Typechecks:**
- `pnpm --filter @objectstack/verify typecheck`: exit 0.
- `pnpm --filter @objectstack/cli typecheck`: exit 0.
- **Changeset gates:**
- `node scripts/check-empty-changeset.mjs --base origin/main`: exit 0,
"No changeset from the merge base modified or deleted by this diff".
- `node scripts/check-adr-0087-registration.mjs --base origin/main`:
exit 0, "1 declared-breaking changeset(s), each carrying an ADR-0087
disposition".
- `node scripts/check-changeset-no-major.mjs --base origin/main`: exit
0, "This diff introduces no `major` bump".
- **Derived gates:** `node scripts/pm/dispatch-gates.mjs --commands
--repo objectstack-ai/objectstack` with no paths gave 61 commands, run
with exit codes captured before any pipe, plus `pnpm
check:verify-stand-in`.
- All 62 exited 0, including `check:dual-build-cjs-loads` after the full
build: "108 published require entry point(s) across 67 package(s) load".
- `check:verify-stand-in`: "2 stand-in check(s) guarded, 17 candidate(s)
explicitly exempt, 10 call site(s) reached".
- `check:nul-bytes`: OK.
- `dispatch-gates --ran` with every line exit-coded reconciled 61
derived, 61 run, 0 NOT-MEASURED and 0 UNRUN. `check:verify-stand-in`
lies outside the derivation.
- **Lint**, a proven narrowing over the touched files:
1. The population comes from `eslint.config.mjs`'s code-extension
`files` globs: the 8 touched `.ts` files are linted, and the 2 touched
`.md` files (README and the new changeset) answer "File ignored because
no matching configuration was supplied".
2. eslint `--no-inline-config --format json` reports 8 files / 0 errors
/ 0 warnings.
3. The config enables no type-aware linting (no `parserOptions.project`)
and no cross-file rule, so this diff cannot move an untouched file's
verdict.
Repo-wide `pnpm lint` is CI's.
## Acceptance notes
- **delete-behavior's private `fixtureBody`** gives a unique required
date, URL or phone value a plain `-n` suffix (`2026-10-13-1` is not a
date). An app with such a field would get that family's fixture refused,
which reports `not-run`, never a false verdict. This is a reading with
no repro, carried and not filed (ruling `6142005900`), and it is why
that copy stays private.
- **The 18.0.0-next.0 `CHANGELOG.md` entry** still shows `{ now? }`. It
is release-owned, and the new changeset is the next version's record of
the removal.
- **#23179**, which edits `parent-write-reach.test.ts`, landed while
this slice ran. It is merged in, and verify's suite was re-run on the
merged tree (38 files / 306 tests). This PR does not touch that file.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01VoSxBQujKLZKPwK2u5ehQ6)_
---------
Co-authored-by: Claude <noreply@anthropic.com>
1 parent 78c13bd commit 06dbc4e
10 files changed
Lines changed: 294 additions & 206 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
500 | 500 | | |
501 | 501 | | |
502 | 502 | | |
503 | | - | |
| 503 | + | |
504 | 504 | | |
505 | 505 | | |
506 | 506 | | |
| |||
556 | 556 | | |
557 | 557 | | |
558 | 558 | | |
559 | | - | |
| 559 | + | |
560 | 560 | | |
561 | 561 | | |
562 | 562 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
11 | 11 | | |
12 | 12 | | |
13 | 13 | | |
14 | | - | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
15 | 18 | | |
16 | | - | |
| 19 | + | |
17 | 20 | | |
18 | 21 | | |
19 | 22 | | |
| |||
371 | 374 | | |
372 | 375 | | |
373 | 376 | | |
| 377 | + | |
| 378 | + | |
| 379 | + | |
| 380 | + | |
| 381 | + | |
| 382 | + | |
| 383 | + | |
| 384 | + | |
| 385 | + | |
| 386 | + | |
| 387 | + | |
| 388 | + | |
| 389 | + | |
| 390 | + | |
| 391 | + | |
| 392 | + | |
| 393 | + | |
| 394 | + | |
| 395 | + | |
| 396 | + | |
| 397 | + | |
| 398 | + | |
| 399 | + | |
| 400 | + | |
| 401 | + | |
| 402 | + | |
| 403 | + | |
| 404 | + | |
| 405 | + | |
| 406 | + | |
| 407 | + | |
| 408 | + | |
| 409 | + | |
| 410 | + | |
| 411 | + | |
| 412 | + | |
| 413 | + | |
| 414 | + | |
| 415 | + | |
| 416 | + | |
| 417 | + | |
| 418 | + | |
| 419 | + | |
| 420 | + | |
| 421 | + | |
| 422 | + | |
| 423 | + | |
| 424 | + | |
| 425 | + | |
| 426 | + | |
| 427 | + | |
| 428 | + | |
| 429 | + | |
| 430 | + | |
| 431 | + | |
| 432 | + | |
| 433 | + | |
| 434 | + | |
| 435 | + | |
| 436 | + | |
| 437 | + | |
| 438 | + | |
| 439 | + | |
| 440 | + | |
| 441 | + | |
| 442 | + | |
| 443 | + | |
| 444 | + | |
| 445 | + | |
| 446 | + | |
| 447 | + | |
| 448 | + | |
| 449 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
65 | 65 | | |
66 | 66 | | |
67 | 67 | | |
68 | | - | |
| 68 | + | |
69 | 69 | | |
70 | 70 | | |
71 | 71 | | |
| |||
485 | 485 | | |
486 | 486 | | |
487 | 487 | | |
488 | | - | |
489 | | - | |
490 | | - | |
491 | | - | |
492 | | - | |
493 | | - | |
494 | | - | |
495 | | - | |
496 | | - | |
497 | | - | |
498 | | - | |
499 | | - | |
500 | | - | |
501 | | - | |
502 | | - | |
503 | | - | |
504 | | - | |
505 | | - | |
506 | | - | |
507 | | - | |
508 | | - | |
509 | | - | |
510 | | - | |
511 | | - | |
512 | | - | |
513 | | - | |
514 | | - | |
515 | | - | |
516 | | - | |
517 | | - | |
518 | | - | |
519 | | - | |
520 | | - | |
521 | | - | |
522 | | - | |
523 | | - | |
524 | | - | |
525 | | - | |
526 | | - | |
527 | | - | |
528 | | - | |
529 | | - | |
530 | | - | |
531 | | - | |
532 | | - | |
533 | | - | |
534 | | - | |
535 | | - | |
536 | | - | |
537 | | - | |
538 | | - | |
539 | | - | |
| 488 | + | |
| 489 | + | |
540 | 490 | | |
541 | 491 | | |
542 | 492 | | |
| |||
592 | 542 | | |
593 | 543 | | |
594 | 544 | | |
595 | | - | |
596 | | - | |
597 | | - | |
598 | | - | |
599 | | - | |
600 | 545 | | |
601 | 546 | | |
602 | 547 | | |
| |||
605 | 550 | | |
606 | 551 | | |
607 | 552 | | |
| 553 | + | |
| 554 | + | |
| 555 | + | |
| 556 | + | |
| 557 | + | |
608 | 558 | | |
609 | 559 | | |
610 | 560 | | |
611 | 561 | | |
612 | 562 | | |
613 | | - | |
614 | 563 | | |
615 | 564 | | |
616 | 565 | | |
| |||
623 | 572 | | |
624 | 573 | | |
625 | 574 | | |
626 | | - | |
| 575 | + | |
627 | 576 | | |
628 | 577 | | |
629 | 578 | | |
630 | 579 | | |
631 | 580 | | |
632 | 581 | | |
| 582 | + | |
633 | 583 | | |
634 | 584 | | |
635 | 585 | | |
| |||
687 | 637 | | |
688 | 638 | | |
689 | 639 | | |
690 | | - | |
| 640 | + | |
691 | 641 | | |
692 | 642 | | |
693 | 643 | | |
| |||
843 | 793 | | |
844 | 794 | | |
845 | 795 | | |
| 796 | + | |
| 797 | + | |
| 798 | + | |
| 799 | + | |
| 800 | + | |
| 801 | + | |
| 802 | + | |
846 | 803 | | |
847 | 804 | | |
848 | 805 | | |
| |||
869 | 826 | | |
870 | 827 | | |
871 | 828 | | |
872 | | - | |
| 829 | + | |
873 | 830 | | |
874 | 831 | | |
875 | 832 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
65 | 65 | | |
66 | 66 | | |
67 | 67 | | |
68 | | - | |
| 68 | + | |
69 | 69 | | |
70 | 70 | | |
71 | 71 | | |
| |||
259 | 259 | | |
260 | 260 | | |
261 | 261 | | |
262 | | - | |
263 | | - | |
264 | | - | |
265 | | - | |
266 | | - | |
267 | | - | |
268 | | - | |
269 | | - | |
270 | | - | |
271 | | - | |
272 | | - | |
273 | | - | |
274 | | - | |
275 | | - | |
276 | | - | |
277 | | - | |
278 | | - | |
279 | | - | |
280 | | - | |
281 | | - | |
282 | | - | |
283 | | - | |
284 | | - | |
285 | | - | |
286 | | - | |
287 | | - | |
288 | 262 | | |
289 | 263 | | |
290 | 264 | | |
| |||
295 | 269 | | |
296 | 270 | | |
297 | 271 | | |
| 272 | + | |
| 273 | + | |
| 274 | + | |
| 275 | + | |
| 276 | + | |
| 277 | + | |
298 | 278 | | |
299 | 279 | | |
300 | 280 | | |
| |||
0 commit comments