Repository navigation
fix(spec): a stack whose mapping authors connectorSource validates and lints again — the live ledger row carries no author warning (#21127) - #21176
Conversation
…`authorWarn` The author-side lint's describe() throws on a warned `live` row by design, which turns `os validate` / `os lint` into exit 1 for every stack that authors the key. The graded walk never reads a container row that drills into `children`, so the gate now walks the raw rows at every depth, refuses the combination, and prints the warned-row census every run. Red at this commit on purpose: the shipped `mapping.connectorSource` row is the one offender, re-graded in the next commit. Claude-Session: https://claude.ai/code/session_017VaLJnYwhPsanVCe9dMCJU Co-authored-by: Claude <noreply@anthropic.com>
…arning, so `os validate` / `os lint` judge a stack that authors it The liveness lint has no verdict for a warned `live` row and throws its ledger-integrity error, which made both commands exit 1 on every stack whose mapping authors the binding. The row drops `authorWarn` / `authorHint`; the caveat they carried (nothing schedules a pull until a job can drive one) moves to the key's description. The retired `connector.syncConfig` prescription, the `connector-sync-keys-retired` upgrade entry (registry regenerated), SYNC_ARCHITECTURE.md, the ledger README, the pending connector-source changeset and two lint comments no longer say authoring the binding warns. Pins: the spec ledger pin now asserts no row of the binding warns; the lint suite and the runtime door judge the card's binding silent, with a warned `planned` row still warning as the control; a CLI integration test runs the fixture through `os validate --json` / `os lint --json` (exit 0). Claude-Session: https://claude.ai/code/session_017VaLJnYwhPsanVCe9dMCJU Co-authored-by: Claude <noreply@anthropic.com>
… the connectorSource description Claude-Session: https://claude.ai/code/session_017VaLJnYwhPsanVCe9dMCJU Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017VaLJnYwhPsanVCe9dMCJU Co-authored-by: Claude <noreply@anthropic.com>
…nnector-source-live-row
📓 Docs Drift CheckThis PR changes 2 package(s): 1 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
What this run could not see
Coarse fallback — 137 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin b6ed1851f47fad8414e834a77d9f453750c82426 && git checkout b6ed1851f47fad8414e834a77d9f453750c82426
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin d34aa58a2affc87ded426dc6a326edb03534cb62 b6d58dd04427126553d781fc78e05778642bee65 && git checkout -B drift-repro d34aa58a2affc87ded426dc6a326edb03534cb62 && git merge --no-ff b6d58dd04427126553d781fc78e05778642bee65
node scripts/docs-audit/affected-docs.mjs --json d34aa58a2affc87ded426dc6a326edb03534cb62
|
Contract reviewServed-tier: Rendered at 2026-10-01T13:11Z by an isolated reviewer subagent of the ① Derived judgmentsAccept set and public surface, item by item:
Check-runs on the head, read at 2026-10-01T13:09Z: 33 runs. 20 success, 2 skipped as expected ( ② Semver level
③ Boundary flags
The seat's question 4: the Nothing escalated: the diff forks no product semantics and no public contract shape; correcting a pending note is the ruled in-seat path. Implemented-by: VERDICT: PASS Generated by Claude Code |
|
Designed red, recorded before enqueue ·
Generated by Claude Code |
…erdict model the lint ships (objectstack-ai#21450) Fixes objectstack-ai#21135 Clause-②: no ## What this changes `packages/spec/liveness/README.md`, section "Author warnings — closing the loop", now describes the model `packages/lint/src/lint-liveness-properties.ts` ships on `main`: - A `dead`, `live-elsewhere` or `experimental` verdict warns on its own. `authorWarn` opts in a `planned` row, and nothing else. - Every warning shows the row's `authorHint`, or else the verdict's default hint. The ledger `note` is never shown. - Rule 1 is about the verdict, not the marker. Rule 2 (booleans) covers any materialized default. - The coverage paragraph states the walk's real reach. Two sentences outside the section restated the old opt-in model, so they are fixed too (dispatch Zone 2, item 4). There is also one `@objectstack/spec` `patch` changeset. ⛔ No ledger row changes. ⛔ No lint code change. The paragraph "And one the gate enforces for you: never on a `live` row" was re-checked against the code and is unchanged (see Premise check, item 1). ## Premise check (dispatch Zone 2, measured at `535d1d25ab`) 1. **The hint chain had moved again since the card's table. The section is written to the code, not to the card or the triage.** - `shouldWarn` (`lint-liveness-properties.ts:173-176`) admits a row whose status is `dead`, `live-elsewhere` or `experimental`, or whose `authorWarn` is `true`. - `checkItem` (`:407-410`) builds the hint as `entry.authorHint ?? defaultHint`, for every row. Commit `327391c3af` (PR objectstack-ai#21169, landed for objectstack-ai#21096) removed the `note` fallback that opted-in and `experimental` rows still had. So triage's chain ("`note` only for opted-in and `experimental` rows") no longer holds. The README now says the `note` is never shown, on any row. - `live` + `authorWarn`: `describe()` throws its integrity sentinel (`:283`). `check:liveness` refuses the combination at every depth (`scanAuthorWarnRows` in `check-liveness.mts`, from commit `9bdc6d3faa`, PR objectstack-ai#21176, landed for objectstack-ai#21127). The README paragraph objectstack-ai#21176 added says exactly that, so it stays as it is. - **Probe** (tsx over `src`, against the shipped ledgers): - A view container `name` / `label` → `liveness-dead-property`, hint "Remove it — it is declared in the spec but not consumed at runtime." - `rowLevelSecurity[].label` / `.description` → the same. - `object.externalSharingModel` (`planned` + `authorWarn`, no `authorHint`) → `liveness-planned-property`, default "Keep it — …" hint. - `action.outcomeMessages` (`planned` + `authorWarn` + `authorHint`) → its `authorHint`. - `agent.lifecycle` / `tool.outputSchema` (`experimental`) → the default hint, not the `note`. - `manifest.runtime` (`live-elsewhere`) is admitted to the warn map, but `lintLivenessProperties({ manifest })` gives 0 findings: no walk visits `manifest`. - `dashboard`'s warn map holds no depth-2 key (`widgets.chartConfig.*` are `dead`), so the lint reads one level of `children`. - Synthetic: a `dead` row gives byte-identical findings with and without `authorWarn`. A `live` + `authorWarn` row throws. 2. **Stale sentences re-located.** At `535d1d25ab` they sit at `README.md:560` ("opt-in per entry"), `:564` (the `experimental`-only default), `:565` ("falls back to `note`"), `:569-572` (rule 1), `:573-577` (rule 2) and `:587-591` (coverage). The same section also had two false statements the card did not list: - "(see `enable.searchable`'s `_authorWarnSkipped`)": no ledger row at any depth carries `_authorWarnSkipped` (census over all 41 ledgers, 940 rows), and `enable.searchable` is `live`. - "It covers **every governed type**": 11 governed types with warning rows are not walked (`manifest`, `connector`, `analytics_cube`, `query`, `realtime_subscription`, `api` and the five `RestServerConfig` families). 3. **No generated file lifts this section.** `pnpm --filter @objectstack/spec run check:generated` passes with "All 15 generated artifacts are up to date", `check:docs` included. The README's only programmatic readers are `check-liveness.mts`, `build-state-counts.mts` and `readme-table.mts`, and all three read the *Current state* table only. `git grep` for the section's phrases over `content/`, `apps/` and `packages/spec/scripts` gives 0 hits. 4. **The old model elsewhere.** `git grep -n "opt-in per entry\|authorWarn"` over `packages/spec/liveness/` and `content/docs/`: - `README.md:379` ("the honest status is `dead` + `authorWarn`") and `:966-969` ("every misleading entry carries `authorWarn`", plus "must also be registered in `TYPE_COLLECTIONS`") state the old model as current. Both are fixed here. - `:348` / `:360` (the dated 2026-07 tally), `:434`, and the *Current state* Notes cells are dated history, not the current model. They are unchanged. - `content/docs/`: the only hits are `releases/v14.mdx:245` and `releases/v17/index.mdx:175`. Both are release-owned history, so there is no owner to notify. `permissions/authorization.mdx:524` and `ui/translations.mdx:390` describe warnings in terms that still hold. ## Every sentence changed, before → after **Section heading** - Before: `## Author warnings — closing the loop (`authorWarn`)` - After: `## Author warnings — closing the loop (verdicts, and `authorWarn`)` **Intro paragraph** - Before: "Classification is also fed back to the *author* at build time. The CLI `compile` lint (`packages/lint/src/lint-liveness-properties.ts`) reads these ledgers and emits an advisory **warning** when an authored object/field sets a property that is misleading — "you set this expecting it to do something; at runtime it does nothing / isn't enforced" — with a corrective hint. Never fails the build." - After: "Classification is also fed back to the *author*. The liveness lint (`packages/lint/src/lint-liveness-properties.ts`) reads these ledgers and emits a **warning** when an authored item sets a property whose row warns — "you set this expecting it to do something; at runtime it does nothing / isn't enforced / isn't read yet" — under a rule id per verdict, with a corrective hint. `os lint`, `os validate` and `os build` / `os compile` run it, and so does the runtime metadata write door for `email_template`, `mapping` and `datasource` items. It is never an error and never fails `os build`, but `os lint --strict` and `os validate --strict` turn every warning into exit 1." - Sources: `AUTHORING_COMMANDS` and `runtimeTypes` in `authoring-rules.ts`, and `failing = errors.length + (strict ? warnings.length : 0)` in `lint.ts`. objectstack-ai#21092 measured the `--strict` 0 → 1 flip at both doors. **The opt-in line** - Before: "Signal over noise is the whole point, so warnings are **opt-in per entry**:" - After: "**The verdict decides which rows warn.** `authorWarn` only opts in a row whose verdict does not warn on its own:" It is followed by a new five-row table: `dead`, `live-elsewhere` and `experimental` always warn, `planned` only with `authorWarn`, and `live` never. Each row names its rule id. **`authorWarn` field row** - Before: "warn when this property is authored (in addition, any `experimental` entry warns by default — it's a declared-but-unenforced guarantee)." - After: "makes a `planned` row warn. On a `dead`, `live-elsewhere` or `experimental` row it changes nothing, because the verdict already warns. So leaving it off never keeps such a row quiet." **`authorHint` field row** - Before: "the corrective one-liner shown under the warning (falls back to `note`)." - After: "the corrective one-liner shown under the warning, on every row that warns. Without one, the verdict's default hint is shown: `dead`'s says "Remove it", `planned`'s and `live-elsewhere`'s say "Keep it", and `experimental`'s says the guarantee is not yet enforced. The row's `note` is never shown to an author, on any row: it is written for this ledger's maintainers." **Rules intro** - Before: "Two rules keep it false-positive-free, **both of which the marker author must respect**:" - After: "Two rules keep the warnings truthful. **Both bind whoever grades a row, not only whoever marks one**:" **Rule 1** - Before: "**Only mark genuinely *misleading* dead props** — ones that imply a capability/behavior that doesn't exist (`versioning`, `field.columnName`, `softDelete`). Benign display/doc metadata that's "dead" (no runtime reader) — `description`, `tags`, `icon` — must NOT be marked; an author isn't misled by them." - After: "**Grading a row `dead`, `live-elsewhere` or `experimental` is an author-facing act.** The verdict warns every author who sets the key, and fails their `--strict` run. No marker keeps it quiet. That includes benign display or doc metadata with no runtime reader (`description`, `tags`, `icon`): graded `dead`, it warns "Remove it" like any other dead key." It is followed by three bullets: - Measure a display key against *Designer previews count as consumers* before writing `dead`. A key that something shows to a person is `live`, and that measurement, not a missing marker, is what keeps it quiet. - A display key still `dead` after that warns, and the warning is the measurement speaking. ⛔ Never grade a row `live` or `planned` to silence it. - When the verdict's default hint is the wrong corrective, give the row an `authorHint`. The `note` cannot do that job. **Rule 2** - Before: "**Booleans: only mark `default(false)` flags.** The lint warns on a boolean only when set `true`, and it can't tell author-set-`true` from a schema default. A `default(true)` flag (`enable.searchable`) would then warn on *every* object that has an `enable` block — so leave those unmarked (see `enable.searchable`'s `_authorWarnSkipped`). Object/string/array props warn when merely present, so this caveat is boolean-only." - After: "**Booleans and materialized defaults.** The lint warns on a boolean only when it is `true`, and on any other value when it is present at all. Where the stack it reads has been parsed (`os validate`, `os build`, any `defineStack` config), schema defaults have already filled in, so it cannot tell an authored value from a default. A `default(true)` flag, or any key whose default materializes, would warn on every item that carries the default, whoever wrote it. So `authorWarn` stays off such a row, and a warning verdict on one warns every such item. `enable.searchable` (`default(true)`) is the shape; it is graded `live`, so the question does not arise for it. `mapping.errorPolicy` and `batchSize` were dead keys of this kind, and no warning could reach their authors truthfully, so they were retired instead (the `mapping` row below)." - The old "boolean-only" sentence contradicted the README's own `mapping` row, which calls those two keys "the non-boolean instance of the default(true) rule". On parsing: the rule's input tier is `parsed`, and `defineStack` runs `ObjectStackDefinitionSchema.safeParse` by default. `os lint`'s own comment says its `parsed` tier fills no defaults for a raw config, which is why the sentence names the doors where defaults do fill. **Coverage paragraph** - Before: "The lint is ledger-driven: coverage grows by marking more entries `authorWarn`, not by touching the lint code. It covers **every governed type**: objects (incl. `enable.*`) and their fields walk bespoke nesting; flows/actions/agents/tools/skills/datasets/permissions/hooks/pages are checked as flat stack collections, and container properties fan out over arrays (each flow node, each dataset measure)." - After: "The lint is ledger-driven: coverage grows with the ledger's verdicts (grading a row `dead`, `live-elsewhere` or `experimental`) and with `authorWarn` opt-ins on `planned` rows, not by touching the lint code. That holds only inside the types its walk visits: objects (incl. `enable.*`) and their fields walk bespoke nesting, translation bundles walk their locale entries, and every type listed in the lint's `TYPE_COLLECTIONS` is checked as a flat stack collection, with container properties fanning out over arrays (each flow node, each dataset measure). It reads a row and its direct `children`, no deeper. A governed type the walk does not visit (`manifest`, `connector` and `realtime_subscription` are three) warns no author through this lint, whatever its rows say." **Outside the section, `README.md:379`** - Before: "When in doubt, the honest status is `dead` + `authorWarn`: an author who gets a" - After: "When in doubt, the honest status is `dead`, which warns its authors with no `authorWarn` marker (see *Author warnings* below): an author who gets a" **Outside the section, below the *Current state* table** - Before: "The `dead` set across types is the enforce-or-remove worklist (ADR-0049); every misleading entry carries `authorWarn` so authors hear about it at compile time (governed types with warn entries must also be registered in the CLI lint's `TYPE_COLLECTIONS` — see lint-liveness-properties.ts)." - After: "The `dead` set across types is the enforce-or-remove worklist (ADR-0049); a `dead` row warns its authors at compile time by its verdict alone, with no `authorWarn` marker (see *Author warnings* above). That reaches an author only in a type the lint walks: a governed type's warning rows are heard only once the lint visits the type, which for a flat stack collection means registering it in the lint's `TYPE_COLLECTIONS` (see lint-liveness-properties.ts)." - Measured: 0 `dead` rows carry `authorWarn` today. The only `authorWarn` rows are 3 `planned` ones: `action.outcomeMessages`, `object.externalSharingModel` and `translation.flows`. The old "must also be registered" did not hold either: `manifest` is deliberately unwalked, and objectstack-ai#21092 pinned that. ## Changeset: measured, not assumed - `npm pack --dry-run --json --ignore-scripts` in `packages/spec` lists 2077 files, and `liveness/README.md` is one of them (control: `liveness/view.json`, also listed). The README ships, so it gets a `patch` changeset for `@objectstack/spec`: `.changeset/21135-liveness-readme-author-warnings.md`, carrying `Clause-②: no`. - The `Check Changeset` job (`pr-automation.yml`) has no path exemption. It is skipped only by the `skip-changeset` label or the release PR. ## Verification (all at head `a3aed7da52`) | What | Command | Result | |---|---|---| | spec build (empty closure) | `pnpm --filter @objectstack/spec build` (under the verify lock) | exit 0 | | generated artifacts | `pnpm --filter @objectstack/spec run check:generated` | exit 0, "All 15 generated artifacts are up to date" | | spec tests (the card's done-when) | `pnpm --filter @objectstack/spec exec vitest run --project local --maxWorkers=2` (lock) | exit 0: 600 files, 17684 passed, 1 todo | | the lint the section documents (not touched; evidence that the model described is the pinned one) | `pnpm --filter @objectstack/lint exec vitest run --maxWorkers=2 src/lint-liveness-properties.test.ts` (lock) | exit 0, 95 passed | | derived gates | `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands` (no paths; 2 paths vs merge base `535d1d25a`), each command run, exit captured before any pipe | 60 derived: 58 exit 0, 2 exit 3 (below) | | reconciliation | `dispatch-gates.mjs --ran ran.list` | "60 derived, 58 run, 2 NOT-MEASURED, 0 UNRUN" | | roster gates whose roster sits in a directory this diff touches | `check-changeset-fixed`, `check:spec-changes`, `check:authz-resolver`, `check:error-code-casing`, `check:filter-alias-parity` | all exit 0 | | liveness gates | `check:liveness`, `check:empty-state`, `check:strictness-ledger`, `check:variant-docs` (in the derived set) | all exit 0 | | nul bytes, plus a hand scan for control bytes | `pnpm check:nul-bytes`, and a `grep -P` control-byte scan over both files | exit 0; 0 hits | **NOT MEASURED (declared to CI).** Both report `PREREQUISITE NOT MET`: they load built `dist/` entry points this worktree does not have. Neither reads anything this diff changes, since a markdown file and a changeset are emitted into no `dist/`. The `Lint & Repo Gates` job measures both on a fresh full build. - `pnpm check:dual-build-cjs-loads` needs every package's `dist`. - `pnpm check:lean-entry-closure` needs the `@objectstack/objectql` closure's `dist`. **Typecheck not run (declared).** The diff has no TypeScript, and neither `.md` file is in any tsc program. **eslint, narrowed and measured.** `pnpm exec eslint --no-inline-config --format json` was run over both changed paths. - Population, from eslint's own answer: 2 files, and both report "File ignored because no matching configuration was supplied". The config's file globs are code extensions only (`**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}`). - Count, from the JSON output: 2 results, 0 errors, 0 lint messages. - Invariance: `eslint.config.mjs` enables no type-aware linting (no `parserOptions.project` / `projectService`), so this diff cannot move the verdict on any file it does not touch. ## Acceptance notes These are observations, not filed. - **Stale `_authorWarnSkipped` mentions elsewhere.** No ledger carries the field any more. It is still named in spec source comments (`data/mapping.zod.ts:30`, `ui/app.zod.ts:1153`, `conversions/registry.ts:3013`) and in two dated *Current state* Notes cells (`mapping`, `qa`). These are comments and history, with no runtime or author-facing effect. Carrier: none. - **Ledger notes on `view.label`, `rowLevelSecurity.label` and `.description`** end with "Not authorWarn'd" / "Benign, not authorWarn'd". That is true of the marker, but no longer means silence: the verdict warns. This card touches no ledger JSON, so they are unchanged. The rows themselves were measured under the designer-previews ruling (objectui `db11afd4967`), so the restated rule 1 implies no re-grade. Carrier: none. - **Stale reasoning in code comments.** `kernel/metadata-plugin.zod.ts:947` reasons from "`datasource.json` carries 0 `authorWarn` rows". Its conclusion still holds, because `datasource` has no warning-verdict row, but the reason is the old model. The lint's own docblock (`lint-liveness-properties.ts:720`) says "Covers every governed metadata type", which the coverage measurement above contradicts. Both are comments, and lint code is out of this card's scope. Carrier: none. - **Upstream since `535d1d25ab`.** `origin/main` moved 2 commits, touching `liveness/analytics_cube.json` and `liveness/dataset.json` only. Neither touches a sentence here or a file in this diff. --- _Generated by [Claude Code](https://claude.ai/code/session_01UtnxvdiN376GF3sgXwAw4d)_ Co-authored-by: Claude <noreply@anthropic.com>
Fixes #21127
Clause-②: no
What this changes
os validate --jsonandos lint --jsonexited 1 on any stack whosemappings[]entry authorsconnectorSource. The whole answer was the liveness lint's integrity sentinel:lintLivenessProperties: ledger entry has unrecognised status "live". The ledger rowmapping.connectorSourcehad been re-gradedliveand keptauthorWarn: true.describe()inpackages/lint/src/lint-liveness-properties.tsthrows on a warnedliverow by design, and that throw stays as it is.packages/spec/liveness/mapping.json):connectorSourcestaysliveand dropsauthorWarn/authorHint. Itsnoterecords why.packages/spec/src/data/mapping.zod.ts). TheconnectorSource.describe()now reads: "Pulled when a job drives it; nothing schedules it yet, so the binding alone moves no rows — schedule the pull with ajobonce a job can drive one". The docblock no longer says the ledger keepsauthorWarn.check:liveness,packages/spec/scripts/liveness/check-liveness.mts). No new gate or script was added. The graded walk never reads a container row that drills intochildren, andconnectorSourceis such a row, so the new rule walks the raw ledger rows at every depth. It fails onstatus: "live"withauthorWarn: true, prints a prescription, and prints a census line on every run:author warnings: 2 ledger row(s) opt into authorWarn, at any depth (planned 2); 0 on a live row.content/docs/references/data/mapping.mdxandintegration/connector.mdx(gen:docs), andpackages/spec/src/migrations/registry.ts(gen:migration-registry).Files beyond the claim's listed surface (each one is text this change made false)
The claim lists the ledger row, the
connectorSourcedescribe, the existing integrity check, the regenerated artefacts, the pins and one changeset. These files are outside that list. Each one said that authoring the binding warns, and that stopped being true with the row fix:packages/spec/src/integration/connector.zod.ts: the retiredconnector.syncConfigprescription, which an author sees in the refusal.packages/spec/src/migrations/entries/semantic/18.connector-sync-keys-retired.ts: the upgrade entry's acceptance text.registry.tsis regenerated from it.packages/spec/src/integration/connector-sync-retirement.test.ts: a one-line pin on that prescription text.packages/spec/docs/SYNC_ARCHITECTURE.md: "authoringconnectorSourcestill warns".packages/spec/liveness/README.md: the mapping row's note, plus a thirdauthorWarnrule (aliverow carries none).packages/lint/src/authoring-rules.ts: comments only.packages/lint/src/runtime-gate.inert-type-writes.test.ts: a comment, and a pin. Amappingwrite that authorsconnectorSourceused to get anauthoring-rule-threwadvisory at the runtime door, and now gets none.packages/cli/test/validate-lint-mapping-connector-source.test.ts: the door pin, a new file..changeset/20919-spec-connector-source-live.md: see the next section..changeset/20919-spec-connector-source-live.mdbelongs to PR #21084. It has not been released yet, and it said "connectorSourcerows areliveand keepauthorWarn". This PR makes that false, so the sentence now reads "rows arelive, with no author warning: that nothing schedules a pull yet is said on the key's description".node scripts/check-empty-changeset.mjs --base origin/mainis therefore red by design. It says: "This PR changes a changeset it did not add … DELIBERATE CORRECTION -- … do NOT restore it -- say so on the PR and get it confirmed". This section is that statement, andskip-changesetis not applied..changeset/20281-connector-sync-moved-to-mapping.mdcarried a similar claim. PR #21150 has since rewritten it onmainwithout the warn claim, and this PR does not touch it.Premise check
665cab338f, which contains PR feat(lint): a ledger-dead or live-elsewhere key warns without an authorWarn opt-in, and never shows the ledger note (#16094) #21092b616c0a63d). The card's fixture is one objectfx_accountwithsharingModel: 'private', plus one mapping withsourceFormat: 'json',targetObject: 'fx_account',mode: 'upsert', afieldMappingandconnectorSource: { connector: 'crm_api', action: 'request' }.os validate --jsonandos lint --jsonboth exited 1, and the only error was the sentinel.b6d58dd04:validateexits 0 with"valid": true.lintexits 0 with"passed": trueand one unrelated warning (protocol/missing-engines-range). Neither output namesconnectorSourceor contains the sentinel.sharingModel, the same object getssecurity-owd-unset, so the fixture declares it.check:liveness. Run against the unfixed ledger, the gate exits 1 with exactly one ✗ block: "1liveledger row(s) opt intoauthorWarn— the author-side lint throws on them: mapping/connectorSource".authorWarn: true, bothplanned(object.externalSharingModel,translation.flows). None islive. Before the fix,mapping.connectorSourcewas the onlyliveone, so no other row has this defect.Pins
packages/spec/src/data/mapping-connector-source.test.ts: every key of the binding islive, and no row of it, at any depth, carriesauthorWarn/authorHint. The description still says nothing schedules a pull. The old assertionauthorWarn === truepinned the defect.packages/spec/scripts/liveness/check-liveness.test.tsruns the real gate via--ledger-root:plannedrows and none on aliverow (the control);livecontainer row withchildrenopts in (the regression's shape);livechild opts in.packages/lint/src/lint-liveness-properties.test.tsruns against the real ledger. The card's fixture lints without throwing and draws noconnectorSourcefinding. Control:object.externalSharingModel(planned+authorWarn) still warns asliveness-planned-property.packages/lint/src/runtime-gate.inert-type-writes.test.ts: amappingwrite that authorsconnectorSourcegets no errors and no advisories at the runtime door.packages/cli/test/validate-lint-mapping-connector-source.test.tsis in the integration tier and spawns the CLI:os validate --jsonexits 0 withvalid: true, andos lint --jsonexits 0 withpassed: true;externalSharingModelproduces aliveness-planned-propertywarning, still at exit 0.Reverse verification (one-time, not a permanent file)
This was run twice on the committed fix, with identical results: once at
7bc7c5c26, and again after themainmerge atb6d58dd04.node scripts/ablation-replace.mjsput"authorWarn": trueback on theconnectorSourcerow. The anchor count went 1 → 0, and the blob changed49cc72e394b8→65665b90b21d. With the defect back, every leg went red:validateexit 1 andlintexit 1, with the sentinel in both;check:livenessexit 1 (1 on a live row — FORBIDDEN);The tool restored the file and proved it: the blob equals HEAD and
git diff HEADis empty. The ledger is read frompackages/spec/liveness/at runtime, not fromdist/, so no rebuild was involved.Verification (all at
b6d58dd04, the PR head)origin/mainfde553c50was merged in withbash scripts/pm/os-regen-merge.sh. The merge was clean.registry.tsauto-merged as text, andcheck:migration-registrythen confirmed it. Main had not touched the two reference pages, so they kept the branch's bytes. No regeneration commit was needed: after a fresh spec build,check:generatedreports all 15 generated artefacts up to date. The delta againstmainis unchanged: 19 files, +538 / −42.pnpm --filter @objectstack/spec buildpnpm --filter @objectstack/spec run check:generatedpnpm exec turbo run build --filter=@objectstack/cli... --concurrency=2os validate --json/os lint --jsonconnectorSource0 timespnpm --filter @objectstack/spec run check:livenessliverow"pnpm --filter @objectstack/spec run check:strictness-ledgervitest run scripts/liveness src/data src/integration/connector-sync-retirement.test.ts src/migrationspnpm --filter @objectstack/spec typecheckpnpm --filter @objectstack/lint exec vitest run+typecheckvitest run --project unit test/lint test/validate src/commands/validate src/commands/lintvitest run --project integration test/validate-lint-mapping-connector-source.test.tspnpm exec eslint --no-inline-config --format jsonon all 19 changed paths.md/.mdx/.json) as "no matching configuration"; 0 errors, 0 warningsdispatch-gates.mjs --commands(117), each run, then--rancheck-empty-changeset --base origin/main, the deliberate correction above.--ran: "117 run, 0 NOT-MEASURED, 0 UNRUN".check:pm-dispatch-gateswas killed by a timeout at 400s and then at 540s. It was then run to completion and exited 0; its battery took 1432s on this boxThe eslint narrowing is a measurement, not a skip:
eslint.config.mjshas no configuration for.--format jsonoutput.parserOptions.project), so this diff cannot move the verdict on any file it did not touch.Acceptance notes
AGENTS.mdbefore the merge. It appended its managedturborepo-agent-rulesblock on every AI-attributed turbo run in this worktree: 3 builds, and once duringcheck:type-check-debt. Each time the file was restored withgit checkout HEAD -- AGENTS.md, andgit diff HEADwas confirmed empty. Files were staged by path only, so the block was never staged and is not in this PR. PR chore(turbo): opt out of the agent-guidance block in the root turbo.json #21151 (agentGuidance: falseinturbo.json) came in with the merge. Since then,AGENTS.mdstayed unmodified across the 59-task CLI closure build and all 117 gates.@objectstack/lintgets no changeset. Its source change is comments only. Measured: tsup keeps comments, so the comment text does reachdist/*.jsbytes. The seat can override this.os lint/os validatecrash on any stack whose mapping authorsconnectorSource: the ledger row islivewithauthorWarn: true, and the liveness rule throws its integrity sentinel #21127 is cited only where agents and reviewers read: theauthoring-rules.tscomment, and the ledger'snoteand README prose. Author-facing text (.describe(), the retired-key prescription, the upgrade entry) cites no tracker.Generated by Claude Code