Skip to content

docs(spec): say only what holds before the picklist reference is resolved (#19518) - #20878

Merged
os-justin merged 3 commits into
mainfrom
claude/issue-19518-picklist-wording
Sep 30, 2026
Merged

os-justin merged 3 commits into
mainfrom
claude/issue-19518-picklist-wording

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Part of #19518

Clause-②: no

Follow-up to #20823. The server does not resolve a picklist reference yet, so four sentences that said it does are deleted or restated: the FieldSchema.picklist describe, its doc comment, the PicklistServedFieldSchema doc comment, and the refusal of picklist with options. The reference pages that copy the describe are regenerated with check:generated --fix. There is no behavior or assertion change.


Generated by Claude Code

…lved

The picklist describe, the FieldSchema.picklist docblock, the served-shape
docblock and the picklist-plus-options refusal said, in the present tense,
that the server resolves the reference and serves the options. Nothing does
that yet, so those sentences are deleted, or restated as what is true now.
No behavior or assertion changes.

Claude-Session: https://claude.ai/code/session_01Sfe5YjBLwB9J3y8fvm2xq1
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added documentation Improvements or additions to documentation protocol:data tooling labels Sep 30, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/spec, touching 1 documentable anchor(s). ⚠️ 1 changed file(s) yielded no anchor (packages/spec/src/data/picklist.zod.ts), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

12 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/concepts/metadata-driven.mdx (via FieldSchema (symbol, a top-level const))
  • content/docs/data-modeling/external-datasources.mdx (via FieldSchema (symbol, a top-level const))
  • content/docs/data-modeling/field-types.mdx (via FieldSchema (symbol, a top-level const))
  • content/docs/data-modeling/validation-rules.mdx (via FieldSchema (symbol, a top-level const))
  • content/docs/deployment/troubleshooting.mdx (via FieldSchema (symbol, a top-level const))
  • content/docs/deployment/validating-metadata.mdx (via FieldSchema (symbol, a top-level const))
  • content/docs/getting-started/quick-reference.mdx (via FieldSchema (symbol, a top-level const))
  • content/docs/kernel/contracts/data-engine.mdx (via FieldSchema (symbol, a top-level const))
  • content/docs/protocol/backward-compatibility.mdx (via FieldSchema (symbol, a top-level const))
  • content/docs/protocol/objectql/types.mdx (via FieldSchema (symbol, a top-level const))
  • content/docs/protocol/objectui/concept.mdx (via FieldSchema (symbol, a top-level const))
  • content/docs/ui/forms.mdx (via FieldSchema (symbol, a top-level const))

⛔ 2 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v17/17-0.mdx (via FieldSchema (symbol, a top-level const))
  • content/docs/releases/v17/17-1.mdx (via FieldSchema (symbol, a top-level const))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • 1 changed file(s) yielded no anchor (packages/spec/src/data/picklist.zod.ts) — pages documenting those are invisible to this run
  • the SDK route bridge reached 54 of 206 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 137 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 07356a6ab0b9b04ab8791db1f15b3f5b67759ca2 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 80408f4e651464e22cebcdfa19f015c0b2a98963 — the merge of head 3ccc350fab32cb4c728e664071f63b559abdd4fd into base 07356a6ab0b9b04ab8791db1f15b3f5b67759ca2, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 80408f4e651464e22cebcdfa19f015c0b2a98963 && git checkout 80408f4e651464e22cebcdfa19f015c0b2a98963
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 07356a6ab0b9b04ab8791db1f15b3f5b67759ca2 3ccc350fab32cb4c728e664071f63b559abdd4fd && git checkout -B drift-repro 07356a6ab0b9b04ab8791db1f15b3f5b67759ca2 && git merge --no-ff 3ccc350fab32cb4c728e664071f63b559abdd4fd

node scripts/docs-audit/affected-docs.mjs --json 07356a6ab0b9b04ab8791db1f15b3f5b67759ca2

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 07356a6ab0b9b04ab8791db1f15b3f5b67759ca2 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 3ccc350fab32cb4c728e664071f63b559abdd4fd
Local-runs: none

Reviewed read-only: card #19518 (body and all twelve comments, among them the seat's ruling 5913074894 (B), its landing note 5913418487, and the dev reports 5913048688 and 5914573661), the review record 5912646092 on PR #20823 whose item 1 flagged the four sentences, PR #20878's body, its six-file list and the net diff from merge-base 07356a6ab0 to the head (+16 / -16), the tree on main (origin/main at 4edb61449b, which holds addbbf02ab, the landing of #20823), and the head's check-runs. Read with git show, git grep, git diff, git log and git patch-id only; nothing checked out, built, tested or re-run.

① Derived judgments

Accept set: unchanged. The diff touches three string literals and two doc comments in packages/spec/src/data/, five generated table rows in content/docs/references/, and adds one changeset. No schema, refinement, type, export, test or fixture moves. The superRefine block at field.zod.ts 2128-2155 still refuses picklist on a non-option type at path picklist and picklist together with options at path options; only the second refusal's message changes, by one phrase. Nothing accepted on main is refused at the head and nothing refused is accepted. Right.

Public surface, each change named:

  1. FieldSchema.picklist .describe() loses its third sentence ("The server resolves the reference: the field clients read carries the resolved options."). The string ships in @objectstack/spec dist, in the generated JSON schema (packages/spec/json-schema/, gitignored at .gitignore line 63, so no tracked copy exists) and in the five reference-page rows. Right: it is the sentence item 1 of 5912646092 named, and on main it is false. git grep of picklist over every packages/*/src outside spec finds only the CLI i18n walk, a driver-sql classification row, lint prose and audit prose, no resolver; packages/spec/liveness/field.json grades the key planned, "not yet resolved by the server", with authorWarn.
  2. The regenerated copies: field.mdx (1 row), object.mdx (2 rows), migration.mdx (2 rows). git grep of the describe's opening words at the head finds exactly those five rows plus the source, so every tracked copy moved. main has not touched any of the six files since 07356a6ab0 (empty git diff --stat against origin/main), so the pages are current against the tip as well. Right.
  3. The pair-refusal message: "the shared list supplies them" becomes "the picklist holds them". A runtime-visible string (the issue message a failing parse answers), not an accept-set change. No test pins either literal: picklist.test.ts asserts toContain('picklist') on this message (line 119) and nothing else, and a git grep for "cannot both be declared", "takes its options from exactly" and "Name of a shared" across every test and script at the head finds no hit. Right.
  4. Two doc comments (the FieldSchema.picklist docblock; the PicklistServedFieldSchema docblock). Source and hover text, read by authors and AI. Right, sentence by sentence below.

Every changed or added sentence, tested against the tree on main at landing:

  • Describe, as it now reads. "Name of a shared picklist whose options this field offers — instead of options, never with it." True now: picklist is SnakeCaseIdentifierSchema.optional(), and the door refuses it with options (path options). "Option types only (select, radio, multiselect, checkboxes, tags)." True now: the refusal tests SINGLE_OPTION_TYPES (select, radio) and MULTI_OPTION_TYPES (multiselect, checkboxes, tags), field-value.zod.ts 141-148; the list is exactly the union. "Whose options this field offers" states the authored intent and makes no served-shape claim, so it reads correctly beside the ledger's warning.
  • FieldSchema.picklist docblock, the restated line: "PicklistServedFieldSchema declares the served form." True now: the schema exists (picklist.zod.ts 159-166), is exported, and its own describe is "The served form of a picklist-bound field". "Declares" is what a schema with no producer on main does. The next sentence, retained unchanged ("Option labels translate under picklists.NAME.options.VALUE, which every referencing field inherits"), is true of the translator now: translateObject in i18n-resolver.ts (2802-2811) looks a picklist-bound field's option up under the list when no field-level entry exists, pinned by feat(spec): the picklist metadata kind — a shared option list select fields reference by name (#19518) #20823's ablation A3; it acts on served options, so it has an effect on a served object once picklist metadata kind — runtime: resolve picklist → options when serving field metadata, validate writes against the resolved set, apply package-level extensions (phase 1 of objectstack#18164) #19519 resolves them.
  • PicklistServedFieldSchema docblock. The cut sentence ("Every consumer that reads field.options today … nothing a client does changes for a picklist-bound field") is the one the review named, and its removal is right: on main a consumer reading field.options on a picklist-bound field reads nothing. The paragraph now ends at "still naming the list they came from." One retained sentence still reads in the present tense: "What a client receives for that field carries both: options, RESOLVED … and picklist". It sits under the docblock's own opening, "the contract the object read exits owe a client", and the module docblock (lines 22-24) says "once the reference is resolved", so it reads as the owed contract. As a contract statement it is true now; as a fact about a served field it is true only after picklist metadata kind — runtime: resolve picklist → options when serving field metadata, validate writes against the resolved set, apply package-level extensions (phase 1 of objectstack#18164) #19519 lands. It was not among the four the review flagged, and ruling 5913074894 fixed this PR's cut to those four ("the ruled cut stays exactly as it is"), so it is not a defect of this PR; carried in ③.
  • Refusal message, the prescription as it now reads: "Keep picklist: 'NAME' and delete options: the picklist holds them (to offer a new value, add it to the picklist, or through picklistExtensions when another package owns it). Or delete picklist to keep an inline list of this field's own." True and actionable now: a picklist item declares options with min(1) (PicklistSchema), so the picklist does hold them; "add it to the picklist" is an authored edit to a *.picklist.ts or defineStack({ picklists }); picklistExtensions is a declared stack collection whose PicklistExtensionSchema is { extend, options min 1 }, described as "owned by another package (additive only)", so the parenthetical matches the shape; deleting options or deleting picklist each yields a field the door accepts. "Holds" states the declared source without claiming resolution, which is the correction the review asked for. An author who takes the first branch on main today gets a field the liveness rule warns on (authorHint: "Keep inline options on fields that must work today"), and the message does not say otherwise.
  • Changeset .changeset/19518-picklist-wording.md: "the picklist field key's description, two doc comments and the refusal of picklist with options no longer say that the reference is resolved and its options served". True: that is the four hunks, one each. Clause-②: no: right, see ②.
  • PR body. "The server does not resolve a picklist reference yet" — true on main (the grep above; feat(spec): the picklist metadata kind — a shared option list select fields reference by name (#19518) #20823's own changeset says "This release does not resolve the reference"; the ledger row names picklist metadata kind — runtime: resolve picklist → options when serving field metadata, validate writes against the resolved set, apply package-level extensions (phase 1 of objectstack#18164) #19519 as the reader, "not landed"). "four sentences that said it does are deleted or restated: the FieldSchema.picklist describe, its doc comment, the PicklistServedFieldSchema doc comment, and the refusal of picklist with options" — true, hunk for hunk (two deleted, two restated). "The reference pages that copy the describe are regenerated with check:generated --fix" — true: the three pages carry exactly the describe's new text and nothing else in them moved. "There is no behavior or assertion change" — true for the accept set and for every test; the one runtime-observable difference is the refusal message's wording, which the body itself names as a restatement. "Part of picklist metadata kind — spec: picklist collection, Field.select({ picklist }), server-resolved options, translation face (phase 1 of objectstack#18164) #19518" — right: the card stays open for the Tier H docs PR (5913418487), and Part-of PR must not also close its card is green.

Delta check against the ruling. 5913074894 (B) ordered a follow-up from a fresh branch off the new main, carrying the dev's two round-3 commits and a patch changeset, touching packages/spec/src/data/** and content/docs/references/** only. Verified: the branch point 07356a6ab0 contains addbbf02ab; 107b5e0518 and c9eca3df29 have git patch-id --stable cb436dd6aafc31b1… and 4bde9dacada4c96e…, the ids the round-3 report gave for 830102dc7e and f072bf2215; the per-file ids from 07356a6ab0 to the head equal the six in the dev's report; 3ccc350fab adds only the changeset. The file surface is the ruled one plus the ruled changeset, and the six commits' worth of content is the round-3 cut, unwidened.

② Semver level

  • @objectstack/spec: patch — matches the diff. The describe ships in dist and in the published JSON schema, and the refusal message ships in dist, so the PR publishes from a released package and AGENTS.md (on main) item 3 forbids skip-changeset for it; nothing is added, removed, widened or narrowed, so minor would overstate. The PR carries no skip-changeset label (labels: documentation, size/s, tooling, needs:contract-review, protocol:data), and Check Changeset is green on its newest run.
  • Clause-②: no on both the changeset and the PR body, with no arm — correct under the same rule: no accept-set change and no published shape change, and no with patch is a well-formed pair. Governed Surface Queue Guard is green; the dev's check-governed-merges --branch HEAD read 0 of 6 paths governed, consistent with the ruling's "neither is governed".
  • No other package changes; no other changeset owed.

③ Boundary flags

Dev report 5913048688 (round 3, on the old branch). open_questions 1, "queued at 388dcceb29: dequeue (A) or follow-up PR (B)?" — ruled B by 5913074894; delivered as this PR and verified by patch-id above. Answered. Its deviations: the round-3 push was refused (GH006) and neither retried nor re-routed; the worktree was kept for the unpushed commits; dispatch-gates --ran was NOT MEASURED at f072bf2215 because that head could not reach the PR. All three are superseded by the ruling and by the new head's own check-runs. Answered. out_of_scope_findings: none.

Dev report 5914573661 (this PR). open_questions: none. out_of_scope_findings: none. Narrowings and deviations read off its summary and tests:

  • Six gate families NOT MEASURED locally (PREREQUISITE NOT MET, no built dist in the fresh worktree; a build was queued twice and never took the lock): check:doc-formula-expressions, check:doc-security-posture and check:skill-examples are hosted by Type Check · consumer gates (lint.yml 6666-6712); check:docs-transcript-drift by Type Check · workspace (lint.yml 6033); check:lean-entry-closure and check:dual-build-cjs-loads by Build Core (ci.yml 2123-2147). All three hosting jobs are success on the head, so the narrowing is covered. Answered.
  • An empty branch was pushed first as a write probe; the assignee was set through label-write.mjs; the dev wrote no label. Process, not contract; nothing to answer.
  • The report's CI reading (33 success, 2 skipped at 15:38Z) differs from the final read below only because Auto Label and Check PR Size re-ran at 15:39:51Z on a later event and skipped; their earlier runs were success. No gate changed verdict.

The earlier dev reports on the card (5908082075, 5912287363) belong to PR #20823: their open questions and findings were ruled by 5908214854 and 5912334912 and answered in 5912646092 ③, and this PR reopens none of them. The one item of that record this PR carries out is its own wording flag, item 1, which is the whole of this PR.

Reviewer's own flags, none blocking:

  1. A fifth "supplies" survives inside the ruled surface. field.zod.ts 2649-2650, the selectFromPicklist docblock: "no options of its own: the picklist supplies them, and FieldSchema refuses the two together." Same verb the PR retired in the refusal message, same file. Here "supplies" names the declared source of the options (true now) and the sentence makes no served-field claim, so it does not repeat the flagged falsehood; but it is the wording the seat chose to retire, two thousand lines up. Not among the four the review flagged, and outside the ruled cut. For the seat: a one-word touch ("holds them") on the next docs pass or on picklist metadata kind — runtime: resolve picklist → options when serving field metadata, validate writes against the resolved set, apply package-level extensions (phase 1 of objectstack#18164) #19519's PR.
  2. Present-tense resolution claims outside this PR's ruled surface, both landed with feat(spec): the picklist metadata kind — a shared option list select fields reference by name (#19518) #20823 and true only after picklist metadata kind — runtime: resolve picklist → options when serving field metadata, validate writes against the resolved set, apply package-level extensions (phase 1 of objectstack#18164) #19519: packages/drivers/driver-sql/src/builtin-column-collision.ts line 107 ("names the shared option list the server resolves into options") and packages/spec/src/system/i18n-resolver.ts line 2802 ("A picklist-bound field is served with its list's options resolved onto it"). Both are code comments no author is shown, and neither path is under packages/spec/src/data/** or content/docs/references/**, so ruling B did not put them in this PR. Escalated: a pointer on picklist metadata kind — runtime: resolve picklist → options when serving field metadata, validate writes against the resolved set, apply package-level extensions (phase 1 of objectstack#18164) #19519 so they are re-read when the ledger row flips to live.
  3. The retained served-contract sentence (① above): "What a client receives for that field carries both" in the PicklistServedFieldSchema docblock. Reads as the owed contract under its own heading, with the module docblock's "once the reference is resolved" beside it. Escalated with item 2 to picklist metadata kind — runtime: resolve picklist → options when serving field metadata, validate writes against the resolved set, apply package-level extensions (phase 1 of objectstack#18164) #19519, where it becomes plainly true; no change asked of this PR.
  4. Not delivered and not expected here, by ruling: the Tier H docs PR (the NORTH-STAR line, the two records-forms items, the coverage.json classification), A select / radio field with neither options nor picklist: refuse it at the FieldSchema door (an accept-set narrowing), or keep it at the ADR-0078 completeness gate? (split from #19518) #20827 ("neither"), picklist kind: the os CLI compile / validate / lint path accepts *.picklist.ts, picklists and picklistExtensions, and os validate refuses a picklist that names no picklist (Scope 6 of #19518) #20825 (CLI) and picklist metadata kind — runtime: resolve picklist → options when serving field metadata, validate writes against the resolved set, apply package-level extensions (phase 1 of objectstack#18164) #19519 (runtime). The card stays open under Part of.

Check-runs on 3ccc350fab32cb4c728e664071f63b559abdd4fd, read last, deduplicated by name keeping the newest started_at: 39 runs, 35 names — 31 success, 4 skipped (Auto Label, Check PR Size, Console Pin Gate, Packed-tarball smoke (opt-in)), 0 failed, 0 still running; the newest run started 2026-09-30T15:39:53Z. Green by name: Build Core, Build Docs, Check Changeset, Check Documentation Links, Dogfood Regression Gate (1/3, 2/3, 3/3, roster), Dogfood Verify CLI, Flag docs affected by code changes, Governed Surface Queue Guard, Lint & Repo Gates, No other open PR may claim the same issue, No other open PR may claim the same single-writer path, Part-of PR must not also close its card, Spec property liveness, Temporal Conformance (live PG + MySQL), Test Core (1/6 to 6/6, roster), The card this PR closes must claim this branch, Type Check · consumer gates, Type Check · debt ledger, Type Check · source gates, Type Check · workspace, TypeScript Type Check, filter. The PR head re-read after the check-runs is still this sha; the PR is a draft, as the ruling's process leaves it until this record is adopted.

Implemented-by: claude/issue-19518-picklist-wording
Reviewed-by: session_01Sfe5YjBLwB9J3y8fvm2xq1

VERDICT: PASS

Adopted and posted by domain:spec seat 5 (session_01Sfe5YjBLwB9J3y8fvm2xq1) · 2026-09-30T15:50Z · rendered by the seat's at-tier review subagent on this head. The seat read its served tier family from the subagent transcript before posting.


Generated by Claude Code

@os-justin
os-justin marked this pull request as ready for review September 30, 2026 15:52
@os-justin
os-justin enabled auto-merge September 30, 2026 15:52
@os-justin
os-justin added this pull request to the merge queue Sep 30, 2026
Merged via the queue into main with commit 93d4e0e Sep 30, 2026
44 checks passed
@os-justin
os-justin deleted the claude/issue-19518-picklist-wording branch September 30, 2026 16:30
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Oct 7, 2026
…mmit that decided it (objectstack-ai#20881)

Part of objectstack-ai#20596
Clause-②: no

## What changed

This is the eighteenth stage of the `domain:services` lane of the
dead-citation sweep, and the lane's last package. It covers
`packages/services/service-realtime/src/**` and nothing else. By the
seat's claim (`5913177103`), it is the last of the single-site packages,
taken one package per stage. The card's own close is the seat's act, so
this PR says `Part of` and the card stays open.

Every comment or docblock site in scope that cited a tracker number
answering 404 has been rewritten in ruling C+D's form C (comment
5749154545 on objectstack-ai#19123), by the method of stages 1 to 17 (the latest is PR
objectstack-ai#20866, landed as `6c96b37be`, the base here). That is **1 site on 1
line in 1 file, covering 1 number**:

- `src/translations/index.ts:27`, the census's one site in this package
at the base (a docblock). No test comment, test string, source string or
gate-invisible spelling in this package carries a dead number (see the
supplementary reading below), so the census site is the whole
population.

The rewritten line now cites the commit in this repository that decided
what it describes: **1 commit sha**. No ADR or ruling record records
that decision (see the per-number table), so ruling C's commit rung
applies. No number was dropped.

Only a comment changed. The file keeps its line count (1 line out, 1
in), so no line citation into it moves. No code token moves (see the
guard below). **No citation number is added**: the only tracker number
on the added line is the live `objectstack-ai#12069`, which already stood on it.

The rewritten line is byte-identical to the line the seven sibling
translation sets already carry (`plugin-approvals:26`,
`plugin-audit:28`, `plugin-security:26`, `plugin-sharing:26`,
`plugin-webhooks:26`, `service-messaging:29`, `service-storage:29`),
landed by stages 1, 2, 4, 6, 7, 9 and 16.

**No changeset.** The rewritten docblock does not reach `dist`, and
`dist` is byte-identical with and without the rewrite (see Changeset
below), so this PR takes `skip-changeset`.

## Census: `service-realtime`, before and after

**Instrument (A1).** The gate's own `node
scripts/check-issue-citations.mjs --census --json`, read-only and
unchanged. The count below is its `allocated-but-absent` findings under
`packages/services/service-realtime/`. Each run counts as a reading only
because its board frontier equals the newest issue or pull-request
number, read by a separate request just before and just after the run.

| reading | tree | board | whole-repo `allocated-but-absent` |
service-realtime sites | lines | files | numbers |
|---|---|---|---|---|---|---|---|
| before | base `6c96b37be`, run 2026-09-30T14:25:19Z to 14:29:09Z |
enumerated, 188 pages, frontier objectstack-ai#20876 (newest objectstack-ai#20876 before and after)
| 744 | **1** | 1 | 1 | 1 |
| after | head `3e5cba47c`, run 14:40:18Z to 14:44:03Z | enumerated, 188
pages, frontier objectstack-ai#20878 (newest objectstack-ai#20878 before and after) | 743 | **0** |
0 | 0 | 0 |

The whole-repo drop is 1, and the finding sets of the two runs differ by
exactly one row, `translations/index.ts:27 objectstack-ai#11671`, removed; none was
added. The `resolves` tally is 33,322 in both runs, and
`resolves-as-pull-request` (1,985) and `cross-repo-unjudged` (1,022) did
not move either. Neither run was truncated or discarded: both
enumerations read 188 pages at the newest frontier. The seat's census
counted 1 here at `6bff748b`, and the base agrees: `6bff748b` is an
ancestor of the base, and no commit between them touches this package.

At the after head, no package under `packages/services/`,
`packages/plugins/` or `packages/triggers/` holds a census finding.

**Supplementary instrument, the whole scope.** The census does not read
test files or strings. So a second reading runs the gate's own exported
`extractCitations` (whole-file and comment-prose projections) and
`namesThisRepository` over every `.ts` file under `service-realtime/src`
(18 files). It takes its verdicts from the before census's own board
reading rather than from a second enumeration: a number is dead when
that census reported it `allocated-but-absent`, and alive when the
gate's own census-scope extraction (`collectCitations`) judged it and
the census did not report it. One number is covered by neither, because
it stands only in test files: `objectstack-ai#20044`, read on its own through the
read-only tools, answers as an issue. The probe's control: `objectstack-ai#11671`
answers 404 on the issues endpoint and on the pull-request endpoint.

| reading | citations | dead | src comment | test comment | src string |
test string |
|---|---|---|---|---|---|---|
| before, `6c96b37be` | 20 | **1** | 1 | 0 | 0 | 0 |
| after, `3e5cba47c` | 19 | **0** | 0 | 0 | 0 | 0 |

Its src-comment column equals the census's 1, which is the control on
the second instrument. The 19 live citations are the same in both
readings (17 by the census's judgement, 2 by the single read), none is
cross-repo, none is unjudged, and the drop of 1 citation is exactly the
rewritten site. A third, raw reading (every `#` followed by 2 to 6
digits, whatever surrounds it) finds 20 occurrences before and 19 after,
the same drop of 1, and no token beyond the gate's grammar.

## Per-number table

Sites and files count every dead occurrence in scope at the base
(comments and strings, tests included).

| number | sites / files | rewritten / left | anchor: what it decided |
|---|---|---|---|
| `objectstack-ai#11671` | 1/1 | 1/0 | `09b4f4e4e` (PR objectstack-ai#12557): the extract command's
`--source-hashes` option writes a per-locale provenance companion,
`LOCALE.source-hashes.generated.ts`, beside the generated translation
bundles, recording for each leaf the source revision it is still a byte
copy of, per maintainer ruling objectstack-ai#12069 Option A. That is exactly what
`:26-27` say. Its message names `objectstack-ai#12069` but not `objectstack-ai#11671`; its own diff
names `objectstack-ai#11671` on 16 added lines. `git blame` puts `:27` in `30928a615`
(PR objectstack-ai#12724, which wires the serving-time read of that companion into all
nine bundle sets), a descendant of the anchor. Reused: stages 1, 2, 4,
6, 7, 9 and 16 gave the identical line in seven sibling packages this
anchor |

The sha matches exactly one commit (`git rev-parse --disambiguate`,
count 1) and is an ancestor of the base (`merge-base --is-ancestor`,
exit 0; reverse leg, base against the anchor, exit 1; control leg exit
0: the repository's root commit `1598cabe4`, 15,219 commits behind the
base, against the anchor's 3,821; the history is complete,
`--is-shallow-repository` false, 15,220 commits at the base). `objectstack-ai#11671`
answers 404 on the issues endpoint and on the pull-request endpoint,
read one by one.

No ADR, `scripts/adr-anchors/` file or ruling record names `objectstack-ai#11671`,
`objectstack-ai#12069` or the source-hash companion (`git grep` over `docs/adr` and
`scripts/adr-anchors`: one unrelated `source-hash cache` hit in
ADR-0080; nothing under `docs/` outside the release pages names either
number), so ruling C's first rung is empty.

## Wordings to check

- **Tag swap in place.** 「(maintainer ruling objectstack-ai#12069 Option A, objectstack-ai#11671)」
became 「(maintainer ruling objectstack-ai#12069 Option A, commit 09b4f4e)」. It is
the form, and the byte-identical line, the seven sibling translation
sets carry. The line grows from 77 to 87 characters, as it did in each
sibling; `eslint.config.mjs` declares no line-length rule, and reflowing
would have moved neighbouring lines.

## Sites left

- **In `src`: none.** No test title, assertion message, operator log
string, runtime refusal, quoted maintainer ruling or generated file in
this package carries a dead number. The three generated
`*.source-hashes.generated.ts` headers carry only the live `objectstack-ai#12069` and
`objectstack-ai#8765`.
- **Outside `src`, listed and left, not edited in this stage:**
- `scripts/i18n-extract.config.ts:17` names the dead `objectstack-ai#11671` (beside
the live `objectstack-ai#12069`); the anchor for whoever next edits it is `09b4f4e4e`.
The file is outside `files[]` and outside the census surface. Its other
numbers are live: `objectstack-ai#12559` and `objectstack-ai#10868`.
- `tsconfig.scripts.json:1` names the dead `objectstack-ai#11351` (404 on the issue
and pull-request endpoints). Its other numbers are live: `objectstack-ai#10756` and
`objectstack-ai#5475`.
- `vitest.config.ts` names the live `objectstack-ai#10374`; `README.md` names the live
`objectstack-ai#2992` on 2 lines.
- The release-owned `CHANGELOG.md` names the dead `objectstack-ai#11671` on 2 lines
(`:779`, `:825`) and the dead `objectstack-ai#13112` on 1 line (`:696`; 404 on the
issue and pull-request endpoints). Its other 15 numbers are live: 11 by
the census's judgement, and by single reads `objectstack-ai#18715`, `objectstack-ai#20044` and
`objectstack-ai#12642` as issues and `objectstack-ai#2528` as a pull request.
  - `package.json` and `tsconfig.json` name none.

## Mechanical guard: no code token moves

The guard compares, base `6c96b37be` against head, over the one touched
`.ts` file:

- **Reading 1**, the TypeScript parser's leaf nodes (a `forEachChild`
walk, so comments are trivia and JSDoc nodes are never visited). String
and template literals are therefore read in full.
- **Reading 2**, the full token stream in parser context (a
`getChildren` walk, so punctuation and keywords are included; JSDoc
nodes skipped).

Results:

- Real run at the head `3e5cba47c`: 50 base leaf tokens, **0 files with
a token change** on either reading (exit 0).
- Comment control (「Recording alone changes nothing a user sees」 to 「...
alters ...」): 0 files changed, as expected (exit 0).
- Positive control, a code identifier renamed (`undefined,
zhCNGeneratedSourceHashes)` to `...SourceHashesX)`): DIFFER on the
identifier in both readings (exit 1).
- Positive control, a string-literal key changed (`'es-ES':
withSourceFallback` to `'es-EZ': ...`): DIFFER on the string literal in
both readings (exit 1).
- Positive control, a keyword changed (`const enSource` to `let
enSource`): DIFFER on the keyword in reading 2 (exit 1). Reading 1
cannot see it, which is why both readings run.

Every mutation went through `scripts/ablation-replace.mjs` (wrap mode)
under a shell trap that restores by absolute path, and each landed
(anchor 1 to 0, blob changed). Each restore was proven byte-identical to
the HEAD blob (`cb6af4310953`), with `git diff HEAD` empty and a clean
tree afterwards.

## Changeset: none (`skip-changeset`)

`files[]` is `dist`, `README.md` and `CHANGELOG.md`, and the package is
not private. Measured on the built package (A3), after a full `turbo run
build` at `3e5cba47c`, where this package's own `tsup` ran (a cache
miss) and cleaned its output folder:

- The rewritten line, its whole docblock (`:23-49`, which sits on the
unexported `const enSource`), the anchor sha, the old number and the
live `objectstack-ai#12069` appear in no `dist` file (0 in all 6, source maps
included). Nor do its neighbours `:24`, `:26`, `:28`, `:29` and `:46`,
the module header `:4`, or a never-written negative phrase. The bundle
hoists that declaration to a bare `var enSource, RealtimeTranslations;`
inside the lazily imported module's init wrapper, and its docblock does
not travel with it.
- Positive controls, text that does ship: from this same file,
`RealtimeTranslations` (12 hits), `withSourceFallback` (7) and
`zhCNGeneratedSourceHashes` (6) are in the JS entries. A docblock line
of this package, `realtime-service-plugin.ts:49` (「Services init()
registers on every path」, on a class field), is in all four entries:
`index.js`, `index.cjs`, `index.d.ts` and `index.d.cts`. So a docblock
can reach `dist` here; this one does not.
- **Byte identity.** The package was rebuilt with the base line put back
(through `scripts/ablation-replace.mjs`, with the file proven equal to
the base blob `19744f2a54a4` during the leg), and again after the proven
restore. Each leg cleaned the output folder and rebuilt ESM, CJS and
declarations. The sha256 of all 6 `dist` files is identical in all three
builds: JS entries, declaration files and source maps.

So nothing this PR rewrites is published, and a changeset would announce
a change no consumer can observe. No `.changeset/*` file is added or
touched.

## Gates (head `3e5cba47c`)

- **Citation judging, as CI runs it:** `node
scripts/check-issue-citations.mjs` exits 0 (「every citation this change
adds resolves」; it judged the one added citation, `objectstack-ai#12069`). `pnpm
check:issue-citations` exits 0 (self-test, 114 cases, 8 batteries).
- **Doc authoring:** `pnpm check:doc-authoring` exits 0 (the
sibling-package prose-id baseline holds, no growth).
- **Derived gates:** `node scripts/pm/dispatch-gates.mjs --commands
--repo objectstack-ai/objectstack` at `3e5cba47c` (change set derived
from git: 1 path against the merge base `6c96b37be`) derived 50
commands, all among the dispatch list's 63.
- All 63 of the dispatch list ran, each with its exit code captured
before any pipe, and all 63 exit 0; none exited 3.
- `--ran`, fed each command with its exit code, reports 50 derived, 50
run, 0 NOT MEASURED (a derived zero), 0 unrun, and exits 0. Of the other
13, it classifies 7 as pending-changeset families and 6 as outside this
derivation.
- A full `turbo run build` over `./packages/*` and `./packages/*/*` ran
first under the shared verify lock (71 of 71 tasks), so no gate hit an
unbuilt workspace.
- **Roster families the derivation lists outside its commands** (their
rosters sit in a directory this diff touches): `pnpm
check:authz-resolver`, `pnpm check:error-code-casing` and `pnpm
check:filter-alias-parity`, plus `node
scripts/check-changeset-fixed.mjs`, which the dispatch named. Each exits
0.
- **Tests and typecheck, under the verify lock, at `3e5cba47c`:**
- `pnpm --filter @objectstack/service-realtime test`: 5 files pass and
33 tests pass, which is every tracked test file under `src/`.
- `pnpm --filter @objectstack/service-realtime typecheck` exits 0, and
`tsc --noEmit --listFiles` puts the touched file in the program (the
config includes `src` and excludes no test file; all 5 test files are
listed). The package has no `check:test-typecheck` script.
- **Lint, as a proven narrowing:** eslint with inline config disabled,
over the touched `.ts` file, gives 1 file, 0 errors and 0 warnings (its
`--format json` output). The file is in eslint's own population (not
reported ignored; `dist/index.js`, the control, reads ignored).
`eslint.config.mjs` never enables type-aware linting (no
`parserOptions.project`, as its own lines 327-328 state), so a comment
edit here cannot move the verdict on any untouched file. The repo-wide
`pnpm lint` is CI's run.
- **Control bytes:** `pnpm check:nul-bytes` exits 0, and a raw scan of
the changed file for control bytes finds none.

## Acceptance notes

- **The gate-invisible spellings, grepped as the claim asked** (objectstack-ai#20636,
including the `clause #N` position). At the base and at the head,
`#N-word`, `#A/#B`, `option #N`, `clause #N` and URL-spelled links are
each on 0 lines of this package's `src` (a known-present spelling,
`objectstack-ai#14646`, reads 3 lines as the control), so the claim's 0 / 0 / 0 / 0 /
0 hold.
- **`objectstack-ai#11671` elsewhere.** Five comment lines in
`packages/platform-objects/src` still name it
(`setup.translation.ts:49`, `source-hash.ts:87`, `:214`, `:568`,
`metadata-translations/index.ts:31`), all in the census; that package
belongs to the engine lane's stage card, objectstack-ai#20595. The anchor measured
here, `09b4f4e4e`, is reusable there.
- **Outside-`src` residue.** Two dead numbers in files outside `src` and
outside `files[]` (`objectstack-ai#11671`, `objectstack-ai#11351`, listed above) are left, as stages
13 to 17 left theirs. They ship nothing and move no gate.
- **The census instrument did not truncate in this stage.** Both
enumerations read 188 pages at the newest frontier.
- **Base.** The branch is on `main` at `6c96b37be`. `main` has since
moved six commits (to `9905e61ca`, read at 14:58Z). None touches
`packages/services/service-realtime`,
`scripts/check-issue-citations.mjs`, `scripts/pm/dispatch-gates.mjs`,
`.changeset/config.json` or the doc-authoring prose-id baseline, so no
merge was taken; the merge queue rebuilds on the merged generation. The
derivation's STALE TREE note names three inputs that moved there
(`scripts/check-stack-collection-maps.mjs`,
`scripts/fixtures/i18n-walk-parity/every-group.stack.json`,
`scripts/role-word-baseline.json`); none of the derived 50 commands
names them, and none mentions this package before or after, so a
comment-only diff here cannot interact with them.

---
_Generated by [Claude
Code](https://claude.ai/code/session_01XY5uCwTjZj7884yYtyur4H)_

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation protocol:data size/s tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants