Repository navigation
Commit 05077d4
fix(spec): shard the generated liveness and strictness counts so PRs moving different units stop conflicting on a committed total (#20532)
Fixes #20361
Clause-②: no
## What changed
- **Liveness counts are sharded.** `gen:liveness-counts` writes
`packages/spec/liveness/state-counts/TYPE.md`, one shard per governed
type (40). Each shard carries only its own row.
`packages/spec/liveness/state-counts.md` is deleted. No total is
committed anywhere. `check:liveness` sums the shards at read time,
prints the sum on its success line, and carries it in `--json` as
`countsTotal`. The generator prints the sum too.
- **Strictness-ledger counts are sharded the same way**, because a
measurement showed the same shared-total shape (see below).
`gen:strictness-ledger` writes
`docs/audits/2026-07-unknown-key-strictness-ledger.counts/DIR.md`, one
shard per `packages/spec/src` directory with object sites (5 triaged, 9
untriaged). The global section, the posture total row and the global
bucket split are summed at read time by `check:strictness-ledger` and
`gen:strictness-ledger`, and committed nowhere. The single `….counts.md`
is deleted.
- **Both gates reconcile shard by shard.** They name a MISSING directory
or shard, a STALE shard (with the shard's path and its first differing
line), a STRAY file that no unit renders, and the RETIRED single file if
a merge brings it back. Both generators rewrite only the shards whose
bytes moved, prune strays, and delete the retired file. These are new
legs inside the two existing gates; ⛔ no new gate or `check:` script was
added. The text-shard read/write/reconcile helpers live in
`packages/spec/scripts/lib/sharded-artifacts.ts` and serve both
artifacts.
- **Routing.** `.gitattributes`, `scripts/regen-artifacts.mjs` and
`packages/spec/scripts/check-generated.ts` route the two directories
(`…/**` with `merge=os-regen`), so the local driver still owns a
same-unit collision.
- **Readers of the retired names.** Updated: the liveness README (the
two paragraphs that linked the file, and the `connector` Notes cell that
named it), the `_note` of `liveness/book.json` and of
`liveness/translation.json`, and seven links in the strictness ledger.
Those links now point at the shards; the per-directory anchors
(`#ui--open` and the rest) keep working inside each shard.
## Measured before the change
Dispatch base `2b24b8b823`. Probe: a bare `--shared` clone with no merge
driver registered (`git config --get merge.os-regen.driver` exits 1).
Each side was regenerated with the real generator and committed, then
`git merge-tree --write-tree --name-only` was run on the pair.
| pair | result |
|---|---|
| liveness: `field.useGrouping` planned→dead vs `sharing_rule.type`
planned→live | exit 1, CONFLICT (content) in `state-counts.md`. The two
rows are 36 lines apart; the only overlap is the total row. |
| liveness: `field.useGrouping` planned→dead vs `sharing_rule.type`
planned→dead (equal delta) | exit 0 and WRONG. The merged total reads
`dead 149 / planned 8`; the two moves make 150 / 7. |
| strictness: 1 strict site added in `ui/` vs 2 added in `data/` | exit
1, CONFLICT (content) in `….counts.md`. Both `.zod.ts` sources merged
clean. |
## Measured after the change
The same probe on branch head `1be2134dc1`, with the real generators.
| pair | result |
|---|---|
| liveness: planned→dead vs planned→live (the conflicting pair above) |
exit 0. Each side touched only its own shard. |
| liveness: equal delta (the clean-but-wrong pair above) | exit 0. The
merged tree was materialised: `check:liveness` exits 0 on it, and its
read-time total is `150 dead · 7 planned`, which is correct. |
| liveness: two moves of `field` | exit 1, conflict on
`state-counts/field.md` only. This residue is expected and the local
driver owns it. |
| strictness: `ui/` vs `data/` | exit 0 |
| strictness: two moves in `ui/` | exit 1 on `….counts/ui.md` (plus the
shared source file) |
**The pin** is `packages/spec/scripts/count-shards-merge.test.ts`. It
builds a throwaway repository with the real attribute line and no
driver, commits each real renderer's output, and asks `git merge-tree`:
- pairs that move different units (different deltas, equal deltas,
adjacent liveness rows, adjacent untriaged directories) merge clean, and
the merged tree equals the regeneration of both moves;
- a same-unit pair conflicts on that unit's shard and nowhere else. This
is the lit control.
## Parity
- **Liveness.** The sum of the 40 shards equals the total the single
file committed: 940/5/1/148/9/1103 at the dispatch base. This branch
then merged main twice, taking in main's `qa` move and its `rest_api`
move. At main `fb386074f5` both sides read 949/5/1/139/9/1103, and every
per-type row is byte-equal to main's last single-file table.
- **Strictness.** All 96 per-directory rows are byte-equal to main's
`….counts.md`. The only rows dropped are the cross-directory ones:
- the posture total 461/331/4/1/125;
- the global buckets 1/0/120/3/0/1;
- the global measures: 5 dirs, 461 sites, 125 strip, 22 files.
The gate's read-time sums print exactly those numbers.
- **Pinned.** `check-liveness.test.ts` asserts that the printed total
equals the sum of the shards on disk, read back row by row.
`strictness-ledger-doc.test.ts` asserts that the read-time totals equal
the sums of the shard rows on disk.
## Verification
**The full union ran at `ac64401fec`.** That commit is the head after
the first main merge that met the retired file, plus its regeneration.
- **Gates.** `node scripts/pm/dispatch-gates.mjs --repo
objectstack-ai/objectstack --commands` derived 93 families, and I ran
each one with its exit code captured before any pipe. `--ran`
reconciliation: 93 derived, 91 run, 2 NOT-MEASURED, 0 UNRUN.
- **90 exit 0.** This includes `check:liveness`,
`check:strictness-ledger`, `check:generated` (15/15 up to date),
`check:merge-driver`, `check:cross-package-test-inputs`,
`check:nul-bytes`, `check:adr-0087-registration --base origin/main`,
`check:api-surface` and `check:pm-dispatch-gates` (1976/1976).
- **1 red, already red on main:** `check:platform-checklist`. It reports
one ✗, the anchor
`packages/plugins/plugin-auth/src/auth-plugin.ts#twoFactor` in
`docs/qa/platform-checklist/areas/identity-auth.json` (ABSENT SYMBOL).
The same single ✗ appears on origin/main `fb194c70e5` and `4a1df19656`,
and on the dispatch base `2b24b8b823`. This diff does not touch it.
- **NOT MEASURED:** `check:dual-build-cjs-loads` and
`check:type-check-debt`. Both exit 3 (PREREQUISITE NOT MET) because they
need a whole-workspace build. They are declared to CI.
- **Spec tests** (`vitest run --project local --maxWorkers=2`, then
`--project repo`): local 573 files, 16820 passed and 1 todo; repo 40
files, 709 passed.
- **Spec typecheck** (`pnpm --filter @objectstack/spec typecheck`: tsc,
the scripts program and the test program): exit 0. `tsc -p
tsconfig.scripts.json --listFiles` includes all 10 changed script and
test files.
- **Narrowed lint.** `eslint --no-inline-config --format json` over the
13 changed `.ts`/`.mts`/`.mjs` files: 13 files linted, 0 errors, 0
warnings. `--print-config` resolves a config for each of the 13 files.
`eslint.config.mjs` never enables type-aware linting (no
`parserOptions.project`, no typed rules), so this diff cannot move the
verdict on any untouched file. The repo-wide `pnpm lint` is CI's.
- **One-shot reverse verification of the strictness gate's new legs.** I
put the retired file back, dropped in a stray shard, and skewed one
shard's posture row through `scripts/ablation-replace.mjs`. Each run
exited 1 with exactly one drift, named RETIRED, STRAY and STALE
respectively. Restoration was proven by blob equals HEAD, `git diff
HEAD` empty and `git status --porcelain` empty. The liveness legs are
pinned permanently in `check-liveness.test.ts` (missing, skewed,
retired, row-set, hand-count, and the verbatim-copy control).
**Re-run at the final head `0190e0e55f`**, after the second main merge
(main's `rest_api` move) and its regeneration:
- the ratchet family, all exit 0: `check:liveness`,
`check:strictness-ledger`, `check:generated` (after a fresh spec build,
15/15), `check:merge-driver`, `check:nul-bytes`,
`check:cross-package-test-inputs`;
- the liveness and strictness tests: local 11 files, 271 passed; repo 4
files, 80 passed.
A driver-free `merge-tree` of `0190e0e55f` against origin/main
`0368a336db` exits 0.
## Acceptance notes
- **The one-time transition for other in-flight PRs.** A PR that edits
`state-counts.md` or `….counts.md` meets a modify/delete on its next
base merge. The settlement is to keep the deletion (`git rm`) and run
the generator. Both gates refuse the retired file while it is present,
and both generators delete it. This branch met exactly that twice, with
main's `qa` and `rest_api` moves, and settled it that way.
- **An adjacent-line README overlap with PR 20458.** The `connector`
Notes cell named the retired file, so this branch updates it. That cell
sits directly above the `analytics_cube` row, which PR 20458 edits. A
driver-free `merge-tree` of this head against that PR's head
`f639af5f3d` conflicts on `packages/spec/liveness/README.md` only.
Whichever of the two lands second keeps both lines. The file is
hand-written and not driver-managed.
- **Historical prose left alone** (carrier: none). The
`scripts/pm/dispatch-gates.mjs` docblock names
`liveness/state-counts.md` inside a measurement pinned at `ad54eb342`.
`packages/spec/CHANGELOG.md` and earlier `.changeset` entries are
release-owned. The strictness ledger's merge-queue narrative (it says
`counts.md` was recorded as pending) is past tense.
- **AGENTS.md is still accurate.** Its sentence that the three hottest
artifacts are sharded stays true: five directories are sharded now. It
is governed and is not edited here.
- **Changeset.** `@objectstack/spec` gets a patch changeset. `npm pack
--dry-run` measured 40 `liveness/state-counts/*` files shipped,
`liveness/state-counts.md` absent (control: `liveness/README.md` and
`liveness/book.json` present), and nothing under `docs/audits` shipped.
- **A second hot spot, reported to the seat and not changed here:** the
hand-written `step18.rationale` tail in
`packages/spec/src/migrations/registry.ts`. Two synthetic appends at the
dispatch base still conflict driver-free. 16 of 101 registry-touching
first-parent commits in the 14 days to `2b24b8b823` rewrote that closing
line.
---
_Generated by [Claude
Code](https://claude.ai/code/session_014EJ1ED8X4MMrT18BhVx4tx)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 03b19d9 commit 05077d4
76 files changed
Lines changed: 2419 additions & 703 deletions
File tree
- .changeset
- docs/audits
- 2026-07-unknown-key-strictness-ledger.counts
- packages/spec
- liveness
- state-counts
- scripts
- lib
- liveness
- scripts
Some content is hidden
Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
67 | 67 | | |
68 | 68 | | |
69 | 69 | | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
70 | 73 | | |
71 | 74 | | |
72 | 75 | | |
73 | | - | |
74 | | - | |
| 76 | + | |
| 77 | + | |
75 | 78 | | |
76 | 79 | | |
77 | 80 | | |
78 | 81 | | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
79 | 89 | | |
80 | 90 | | |
81 | 91 | | |
| |||
138 | 148 | | |
139 | 149 | | |
140 | 150 | | |
141 | | - | |
| 151 | + | |
142 | 152 | | |
143 | 153 | | |
144 | 154 | | |
| |||
149 | 159 | | |
150 | 160 | | |
151 | 161 | | |
152 | | - | |
| 162 | + | |
153 | 163 | | |
154 | 164 | | |
155 | 165 | | |
| |||
This file was deleted.
| 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 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 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 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
0 commit comments