Skip to content

fix(automation, audit): runtime strings state each decision in words instead of a tracker number (stage 4) - #21472

Merged
objectstack-fleet[bot] merged 6 commits into
mainfrom
claude/issue-20751-services-strings-stage4
Oct 2, 2026
Merged

objectstack-fleet[bot] merged 6 commits into
mainfrom
claude/issue-20751-services-strings-stage4

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Part of #20751
Clause-②: no

Stage 4 of the domain:services lane under the maintainer's A / A ruling (5902360492): the service-automation and plugin-audit strings. The card stays open for the later stages, so this PR carries no closing keyword. Text only: no status, error code, field, route, export or control flow moves (AST skeleton reads SAME for 10 of 10 changed sources, below).

What this does

The two packages' refusals, prescriptions, warnings, log lines, the sys_automation_run / sys_flow_dispatch field help and description, and the sys_activity type help (with its es-ES, ja-JP and zh-CN leaves) sent the reader to a tracker number for the reason behind them. In form D, as stages 1 to 3 applied it, the number goes. Where the sentence already said what was decided, only the citation goes. Where it leaned on the number, it now says the decision in words.

All 38 ledgered occurrences in this stage's surface (claim 5959898258), re-derived from the ledger on origin/main at 9b7a0ef3: service-automation 33 (25 pairs, 9 files), plugin-audit 5 (5 pairs, 5 files: objects/sys-activity.object.ts and its four translations/*.objects.generated.ts). They sit in 37 string sites. The sixth plugin-audit ledger entry, audit-writers.ts (one occurrence), is in a file excluded at file level (open PR #21436) and stays for a later stage. None of the three excluded files (audit-writers.ts, stored-metadata-body-migration.ts, stored-metadata-hash-migration.test.ts) is touched.

Rewritten in words

Author- and administrator-visible text first, log lines last. Line numbers are at the head.

Where Cited The text now says Decision read from
engine.ts:196-205, the fieldValues refusal prescriptions on create_record and update_record 2419 "a runtime alias for it was rejected by design: the node keeps one strict fields key rather than two spellings" 2419 is a pull request that proposed a cfg.fields ?? cfg.fieldValues alias in the executor; the design review declined it: correct the metadata at the source and keep a single strict config.fields contract
engine.ts:210-213, the screen field visibleIf refusal prescription 3528 "A predicate under any other key is never read, so the field always shows — and a required field meant to stay hidden then blocks the screen from ever being submitted" the card's root cause: a field visibility predicate never reached the client, so a required field the author meant to hide rendered unconditionally and the screen flow could not be submitted; visibleWhen went into the contract and is forwarded verbatim
engine.ts:10272-10274, the undeclared-config-key refusal 4045 "the built-in node types were reconciled so every key their executors read is declared, and a read-but-undeclared key is exactly the drift that reconciliation removed" 4045's closing: every built-in node publishing a configSchema got a Zod written from its executor and a two-way key-set reconciliation (CRUD, screen and map surfaced seven read-but-undeclared keys, all declared)
builtin/template.ts:192, the unknown value-expression function error 11060 "An unknown function is refused here rather than evaluated to null, which would write the field as undefined" the maintainer's ruling (option A): six functions mirrored 1:1 from CEL, and the silent null rewrite of an unknown function name became a loud, named error
sys-automation-run.object.ts:169, node_type help 3801 "Keys the resume authorization gate, which decides from the paused node's type who may continue the run (an approval pause only through its owning service)" the card's requirement, as refuseGatedResume enforces it: the gate is keyed on what the run is parked on; an approval pause continues only through ApprovalService, while a screen pause stays open to its flow runner
sys-automation-run.object.ts:234 trigger_type and :288 trigger_record_id help 7533 "rows written before run history recorded its trigger (they were not backfilled)" the card's ACCEPT: trigger columns written on terminal and paused rows; pre-existing rows carry none and rehydrate as "not recorded"; no backfill
sys-automation-run.object.ts:324, steps_json help 2585 "the bounded per-node step log, so a finished run's per-node detail survives a restart" item 3 of the card: durable single-run detail, a bounded step log persisted on terminal rows so a past run's detail survives a restart
plugin-audit objects/sys-activity.object.ts:116-121, type help, and its four locale leaves 11507 "Consumers must render an unknown value instead of assuming this list is exhaustive: the vocabulary is open by decision, not a gap awaiting enforcement." the maintainer's ruling (direction 4): the column is an open, author-extensible vocabulary, the declaration must say so, and every closed map over it is the bug
plugin.ts:1680-1689, the inert-connector warning 2612, 2977 "entries without a provider are catalog descriptors (descriptor-only contract) ... An entry that names a provider is a connector instance instead: that provider's installed executor materializes it into a live connector (ADR-0097)" 2612's resolution (descriptor-only contract, boot audit warning, enabled: false marks a catalog-only entry) and 2977's (ADR-0097 implemented: a provider-bound entry is materialized at boot by a generic executor). The old sentence said provider-bound instances were "tracked in" 2977, which has since landed; findInertDeclaredConnectors already skips provider-bound entries, which the new wording now says
plugin.ts:1143-1146, debug 1928 "(flow conditions are checked against object fields at registration)" 1928's closing: schema-aware condition validation at registerFlow through an opt-in object-schema resolver
plugin.ts:1184-1187, debug 3356 "(a user-mode run carries the triggering user's positions and permission sets)" 3356's resolution: runAs:'user' resolves the triggering user's positions and permission sets at run setup
plugin.ts:1214-1217, debug 3475 "(a lookup the start node declares in expand is read with the run's own identity)" 3475's plan as landed: opt-in start-node config.expand, expanded engine-side as the run's own identity so the referenced object's RLS/FLS holds

Citation only (the sentence already stated the decision)

  • builtin/connector-nodes.ts:72 (3017, "Dispatch is unavailable until its upstream recovers; the platform retries automatically." — ADR-0097 quotes "the platform retries automatically", which stays verbatim).
  • builtin/parse-config.ts:112 (4277, "config does not satisfy the X contract ... The declared contract is the node type's configSchema (the Studio form) and the X config Zod").
  • builtin/screen-nodes.ts:336 (1870, "no function named 'X' is registered. Register it via defineStack({ functions }), or fix the name").
  • engine.ts:181, :184, :187, :191, the bulk-intent guidance (5393, "Bulk intent is multi: true ... so the concept keeps one name from node config to driver call ... a predicate write is refused by the engine rather than silently widened"; "Translating that declaration into options.multi on the engine call is the executor's job").
  • engine.ts:4026 (3017, "the materializer retries with backoff").
  • engine.ts:6069, :6073 (3760, 1888: "its data operations will be REFUSED ... Declare runAs:'system' ... a record-change flow fired by a system write carries no user either (ADR-0049)").
  • engine.ts:10268 (4277, "Flow 'X' rejected: N undeclared config key(s)").
  • engine.ts:11326 (4414, "The branch selection is IGNORED and every out-edge is evaluated instead ... mark the fallback edge isDefault: true").
  • runtime-identity.ts:113, the AUTOMATION_UNSCOPED_RUN_DATA_ACCESS refusal (1888, 3760: "refusing a data operation ... would execute UNSCOPED ... Declare runAs: 'system'"; it keeps "(ADR-0049)"). Its code is unchanged.
  • plugin.ts:1831, :2074, :2086, :2143 (3017: "pending retry cancelled", "until a retry succeeds", "retrying with backoff, attempt N", "next retry in Nms").
  • sys-flow-dispatch.object.ts:86 (10220, "one row per claimed dispatch key ... so a re-scan, a rebuilt kernel or an operator replay never re-launches a flow for a window it already delivered").

Every cited card (21: 1870, 1888, 1928, 2419, 2585, 2612, 2977, 3017, 3356, 3475, 3528, 3760, 3801, 4045, 4277, 4414, 5393, 7533, 10220, 11060, 11507) was read through REST, body and every comment, before its string was rewritten. 2419 is a pull request; its closing comment carries the decision. 11060 and 11507 answer 404 on the issue endpoint while their comment threads read normally; the rulings and dev reports in those threads carry the decisions.

Translations

  • The es-ES, ja-JP and zh-CN sys_activity.fields.type.help leaves were byte copies of the English source, recorded as such in each locale's *.source-hashes.generated.ts. No translated text was written by hand: the three leaf values were emptied (key kept) and node scripts/check-i18n-bundles.mjs --write --filter=plugin-audit (the extract config's documented command, --fill=default) refilled them from the revised source, rewrote en, and recorded the three new digests. The same route the original ruling's PR took for these leaves.
  • All four leaves are byte-equal to the revised source description (440 characters each, checked against the string reassembled from sys-activity.object.ts). Tracker numbers left in the package's locale bundles: 0.

Ledger (scripts/doc-authoring-prose-id.baseline.json)

At dispatch no open PR held this file. While this branch was being built, PR #21462 (domain:spec stage 1 of #20749) opened and took it, so this PR waited: the branch was finished and green, and it was opened only after #21462 merged (7e7e64b1, about 40 minutes of waiting). Then origin/main at 7e7e64b1 was merged in (no rebase), and the ledger was recomputed on that tree with node scripts/check-doc-authoring.mjs --census-ledger (exit 0, no growth refusal) into a scratch file. The recomputed file is byte-identical to git's textual merge of the two sides. Against main the diff deletes 58 lines and adds none: exactly the 14 file blocks of this stage. Every other row is unchanged (a scripted comparison: 14 keys moved, all of them this stage's, each to absent). At PR-open time no other open PR touches the file.

before after
service-automation 33 occurrences, 25 pairs, 9 files 0
plugin-audit 6 occurrences, 6 pairs, 6 files 1 occurrence, 1 pair, 1 file (audit-writers.ts, excluded)
whole ledger, on the tree first derived (9b7a0ef3) 227 occurrences, 157 pairs, 62 files 189, 127, 48
whole ledger, after #21462 (7e7e64b1) 174 occurrences, 127 pairs, 53 files 136, 97, 39

pnpm check:doc-authoring at the head: "sibling-package prose ids hold the baseline — 118 pinned site(s) across 39 file(s), 86309 string(s) read in 1253 parsed source(s), no growth, no burn-down unrecorded". No gate is added or loosened; scripts/check-doc-authoring.mjs is untouched.

Changeset

.changeset/20751-services-strings-stage4-state-the-decision.md: patch for @objectstack/service-automation and @objectstack/plugin-audit. Measured after building: the new sentences are in each package's built output (service-automation dist/index.js and dist/index.cjs; plugin-audit dist/index.js and dist/index.mjs). A TypeScript scan of every string literal and template text in the built output finds 0 tracker ids in service-automation, and 2 in plugin-audit, both the excluded audit-writers.ts occurrence (once per build format). Control: the same scan reads 86 in plugin-security's built output.

Text-only proof

A TypeScript-AST skeleton of each changed non-test .ts file, where every string literal and template text is a placeholder, a run of adjacent string operands of a + chain is one string (only its embedded expressions are kept), identifiers and numbers keep their text, and comments are never read. 9b7a0ef3 against the head: 10 of 10 SAME (the two merges from origin/main touched neither package). Controls on scratch copies of plugin.ts, each mutation's marker counted once on disk first: a one-identifier rename reads DIFF; a text-only change reads SAME; a re-split of one template into two concatenated pieces reads SAME.

Pins

Assertions that found a message by its tracker number now find it by what it says. No code or status assertion was touched; none of these strings is a coded refusal.

  • builtin/config-unknown-keys.test.ts:97: the visibleIf prescription by "blocks the screen from ever being submitted" instead of the id.
  • builtin/decision-branch-routing.test.ts:210: the unclaimed-branch warning by "The branch selection is IGNORED" instead of the id.
  • connector-degrade-cause.test.ts:185 and :274: the degrade error by "retrying with backoff, attempt 1." and the husk warning by "stays absent from the connector registry until a retry succeeds"; the three hand-built fixture messages in the same file (:376, :396, :407), which mirror the production text, drop the id too.
  • connector-descriptor-audit.test.ts:134: the inert-connector warning by "catalog descriptors (descriptor-only contract)".
  • degraded-register-cause.test.ts:212: the degraded-registration warning by "the materializer retries with backoff"; its fixture message (:407) drops the id.
  • plugin-audit objects/sys-activity-type-open-vocabulary.test.ts asserts the type help by three markers (built-in, open vocabulary, ADR-0052), all kept; no edit needed.

The five edited test files were run with the verbose reporter (5 files, 50 tests passed) and every re-pinned case is listed as passed.

Tests

All through scripts/pm/os-verify-lock.sh, every verdict VERDICT command-exit 0, at the head f300a255:

  • Build: turbo run build --filter=./packages/* --filter=./packages/*/* (71/71).
  • @objectstack/service-automation: 166 files, 2053 tests passed; @objectstack/plugin-audit: 38 files, 618 tests passed.
  • typecheck for both, including check:test-typecheck: OK for each.

Gates

  • node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands (no paths) at f300a255 (24 paths vs merge base 7e7e64b1, 195 changed lines): 74 commands, run one at a time from the worktree, each exit code recorded before any pipe; 74 exit 0. --ran: "74 derived famil(ies) accounted for — 74 run, 0 NOT-MEASURED (a DERIVED zero — all 74 recorded an exit code and none of them is 3)". The same 74 also ran green at 1af6bed2, before the last merge.
  • check:i18n: "OK (9 package(s) — all bundles in sync, no undeclared authoring keys)"; check:i18n-stale-fill: "OK (10 bundle set(s) — no new stale fills, 0 baselined)"; check-issue-citations: "no issue citations added against 7e7e64b (17 file(s) read)"; check:nul-bytes: OK, 9807 files; check:type-check-debt: "none above its recorded number"; check:dual-build-cjs-loads: 105 require entry points across 66 packages load; check:dts-closure: 71 built packages, 167/167; check:sourcemap-no-sources-content: 68 packages, 524 maps; check:published-files: 69 publishable packages.
  • Outside the derived set, all exit 0 at f300a255: the eleven declared wide-population families (check:init-service-contract, check:live-db-isolation, check:meta-type-normalized, check:optional-error-sink, check:resume-authority-declared, check:route-envelope, check:runner-env-posture, check:settings-bind-window, check:startup-registry-verdict, check:verify-stand-in, check:wildcard-fallthrough) and the artifact-roster families whose roster sits under one of this PR's directories (check-changeset-fixed, check-published-list-mirrors and its self-test, check:authz-resolver, check:console-injection, check:error-code-casing, check:filter-alias-parity, check:published-readme-exports, check-dts-references --self-test; check:engine-double-contract and check:i18n-stale-fill are in the derived set). The path-scheduled CI jobs and the type-check lanes are CI's.
  • pnpm lint (eslint . --no-inline-config, repo-wide, not narrowed) at f300a255: exit 0.
  • origin/main moved by two commits after the ledger recompute (49524f69, packages/rest; aa463223, packages/spec and object-grid surfaces). Neither touches the ledger, service-automation or plugin-audit; the PR's CI merge ref and the queue carry them.

Acceptance notes

Noted, not filed:

  • packages/runtime/src/domains/automation-put-post-error-parity.test.ts (lines 57-70, assertion at line 247) and automation-register-error-class.test.ts (lines 87-100, assertion at line 308) each rebuild the undeclared-config-key refusal in a test fake, copied from engine.ts with both old ids, and assert toContain of the 4277 id on their own fake's message. They stay green (the fake is self-consistent) but no longer mirror the engine's text. packages/runtime is outside this stage's surface. Carrier: none.
  • plugin-audit/src/audit-writers.ts keeps its one ledgered occurrence (excluded file, open PR fix(metadata-protocol)!: a metadata body's stored content hash is served and compared only in keyed form, never copied, never evaluated (#21207) #21436); it is left for a later stage.
  • Two plugin-audit test files carry the 11507 id in failure-message text; test files are outside the ledger.

Generated by Claude Code

claude added 6 commits October 2, 2026 19:39
…instead of a tracker number (stage 4)

The service-automation refusals, prescriptions, log lines and run-object
field help, and the sys_activity type help, drop their tracker numbers;
where the sentence leaned on the number it now says what was decided.

Claude-Session: https://claude.ai/code/session_01DiCSbmJrkzNhuEAier4VoJ
Co-authored-by: Claude <noreply@anthropic.com>
…sed source text

The es-ES, ja-JP and zh-CN leaves were byte copies of the English source;
they were emptied and refilled by the extract config's own documented
command (scripts/check-i18n-bundles.mjs --write --filter=plugin-audit),
which also rewrote en and recorded the new source digests.

Claude-Session: https://claude.ai/code/session_01DiCSbmJrkzNhuEAier4VoJ
Co-authored-by: Claude <noreply@anthropic.com>
…age 4 (service-automation, plugin-audit)

Recomputed with `node scripts/check-doc-authoring.mjs --census-ledger` on
the tree with origin/main merged in: 58 lines deleted, 0 added; the
service-automation entries and the plugin-audit entries other than
audit-writers.ts go to zero, no other row moves. Adds the stage-4
changeset.

Claude-Session: https://claude.ai/code/session_01DiCSbmJrkzNhuEAier4VoJ
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation tests tooling labels Oct 2, 2026
@github-actions

github-actions Bot commented Oct 2, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/plugin-audit, @objectstack/service-automation, touching 21 documentable anchor(s).

22 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: node scripts/docs-audit/affected-docs.mjs --json aa4632235ba571ef800b95e6bc18d00a30aa1d57.

⛔ 7 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails.

What this run could not see
  • 7 name(s) were too generic to anchor anything (single lowercase words)
  • 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 — 12 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 aa4632235ba571ef800b95e6bc18d00a30aa1d57 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from ab498c8c92e369a84c5e9b06dd2d8c58278bdff8 — the merge of head f300a255b0e20023e6f6e0e131fc5b7b261ac4d8 into base aa4632235ba571ef800b95e6bc18d00a30aa1d57, 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 ab498c8c92e369a84c5e9b06dd2d8c58278bdff8 && git checkout ab498c8c92e369a84c5e9b06dd2d8c58278bdff8
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin aa4632235ba571ef800b95e6bc18d00a30aa1d57 f300a255b0e20023e6f6e0e131fc5b7b261ac4d8 && git checkout -B drift-repro aa4632235ba571ef800b95e6bc18d00a30aa1d57 && git merge --no-ff f300a255b0e20023e6f6e0e131fc5b7b261ac4d8

node scripts/docs-audit/affected-docs.mjs --json aa4632235ba571ef800b95e6bc18d00a30aa1d57

⚠️ 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 aa4632235ba571ef800b95e6bc18d00a30aa1d57 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 2, 2026 22:40
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 2, 2026 22:41
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 2, 2026
Merged via the queue into main with commit cc07862 Oct 2, 2026
36 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-20751-services-strings-stage4 branch October 2, 2026 23:14
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Oct 7, 2026
…registry findings state each decision in words instead of a tracker number (stage 2) (objectstack-ai#21491)

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

**Stage 2 of the `domain:spec` lane's share under the maintainer's A / A
ruling (5902360492): the last 51 ledgered tracker-number occurrences in
`packages/lint`, in 18 `packages/lint/src` files.** After this,
`packages/lint` has no row left in
`scripts/doc-authoring-prose-id.baseline.json`. The card stays open for
the later stages, so this PR carries no closing keyword. Text only: no
rule, condition, code path, rule id, severity or registry field moves
(AST skeleton proof below, 18 of 18 SAME).

## What this does

These 18 files print findings to authors through `os validate`, `os
lint` and `os build` (and, for the rules on the runtime publish gate,
through the Studio, REST and MCP publish door). `authoring-rules.ts`
also carries the `surfaceReason` texts of the exported `AUTHORING_RULES`
registry, and `lint-liveness-properties.ts` one thrown integrity error.
51 tracker numbers in that text sent the reader to a card for the reason
behind it. In form D, as stage 1 (PR objectstack-ai#21462) and the sibling lane's
stages applied it, the number goes. Where the sentence already said what
was decided, only the citation goes. Where it leaned on the number, it
now says the decision in words.

The surface, re-derived from the ledger and from
`check-doc-authoring.mjs --census` on `origin/main` at `7e7e64b13d` (the
claim's numbers hold exactly): `authoring-rules.ts` 9,
`validate-empty-combinators.ts` 6, `validate-predicate-path-refs.ts` 6,
`validate-searchable-fields.ts` 5, `validate-sortable-fields.ts` 5,
`validate-null-guards.ts` 4, `validate-visibility-predicates.ts` 3,
`data-model-rules.ts` 2, `validate-security-posture.ts` 2, and one each
in `lint-liveness-properties.ts`, `validate-component-props.ts`,
`validate-dashboard-action-refs.ts`,
`validate-nav-object-servability.ts`, `validate-react-page-props.ts`,
`validate-rule-schema-formats.ts`, `validate-seed-state-machine.ts`,
`validate-view-containers.ts` and `validate-widget-bindings.ts`. They
sit in 46 census sites (40 string groups once a `+` chain is read as one
string). By class: 37 occurrences in finding envelopes (a `message` or
`hint`), 13 in prose (9 in `AUTHORING_RULES` `surfaceReason` texts, 4 in
the null-guard outcome clauses a finding carries) and 1 thrown. None is
logged.

Every cited card (32 objectstack cards, plus `objectui#4049`,
`objectui#2348` and `objectui#4051`, where objectstack-ai#5149 was migrated) was read
through REST, body and every comment, before its string was rewritten.
Where a card closed with no ruling comment, its landing commit was read.

### Rewritten in words

Lines are the census lines at `7e7e64b13d`.

| Where | Cited | The text now says | Decision read from |
|---|---|---|---|
| `authoring-rules.ts:440`, `:1177`, `:1974`, the full-snapshot,
capability-reference and sharing-rule `surfaceReason` texts | 4463 | "P2
of the runtime publish gate (the Studio, REST and MCP door that runs
this registry):" in place of "P2 (objectstack-ai#4463):" | 5156970572 (the
maintainer-delegated ruling: one shared rule core, run at the metadata
write boundary so Studio, REST and MCP are all covered), claim
5160065194 (P1 flow first, P2 the full snapshot), ACCEPT 5160403203 |
| `authoring-rules.ts:507`, the advisory-volume reason | 4717, 4716 |
"each carried back in the save response and rendered by Studio's
designer ... The object door opened to the gating object rules alone;
crossing an advisory one is a separate UX decision." | 4717: maintainer
ruling 5237206591 (`advisories` rides the save response; Studio renders
it); 4716: adjudication 5328603673 (the five gating object rules cross,
the six advisory-only rules do not ride, advisory UX is separate work) |
| `authoring-rules.ts:1111`, the component-types reason | 4716 |
"pending the crossing discipline the gating object rules went through: a
measured false-refusal budget, here over stored tenant page rows" |
5328603673 (crossing priced on a measured false-refusal budget) |
| `authoring-rules.ts:1976`, the sharing-rule reason | 9698 | "The
anchor arm, which refuses a rule anchored on a public-OWD object or a
master-detail detail (no share row could widen either), FALSIFIED that"
| triage promotion 5332890862 (a statically decidable inert sharing
declaration is a loud authoring error, as objectstack-ai#7503 ruled) and the landing
dev report 5338330042 |
| `data-model-rules.ts:344`, bare declared `unique: true` | 5082 |
"Protocol 18 rejects this spelling, and stored metadata that still
carries it converts to `unique: 'global'`, which builds the same
physical index." | the card body (ADR-0120 D2 conversion `true` to
`'global'`, bare `true` rejected at protocol 18) and the hold records
5225701234 and 5481784294 |
| `lint-liveness-properties.ts:288`, unrecognised ledger status (thrown)
| 11384 | "An unrecognised status fails loudly here rather than being
graded `dead`." | claim 5389729619 and dev report 5390065066 (an
unrecognised status throws instead of silently grading `dead`) |
| `validate-component-props.ts:326`, props hint | 5068 | "props are
judged here, at the authoring door, as a warning before they become an
error" | maintainer ruling 5196404940 (direction A: gate at publish/lint
by `type`, warning first then error; B, reshaping `properties`, not
done) |
| `validate-empty-combinators.ts:231`, `:235`, `:271`, the `$and: []`,
`$or: []` and empty-node messages | 5322 | "Every backend reduces an
empty combinator (an empty node) to its boolean identity, so it ..." |
maintainer ruling 5185589944 (boolean identity reduction everywhere; the
analytics compilers align with the five backends) |
| `validate-empty-combinators.ts:237`, `$or: []` on a read scope | 5134
| "(fail-closed by design: an empty disjunction never opens a read scope
to the whole table)" | the card body and ACCEPT 5178806144 (an empty
`$or` compiles to FALSE; the SQL driver had answered the whole table) |
| `validate-null-guards.ts:597`, the fail-closed outcome | 4649, 4763 |
"(a predicate that cannot evaluate refuses the write rather than being
skipped)" | 4649: PM ruling 5161512629 and ACCEPT 5161923543 (fail
closed on a predicate that cannot evaluate); 4763: closing record
5164195111 (the `has()` shape refused at build/publish) |
| `validate-predicate-path-refs.ts:652`, `:694`;
`validate-visibility-predicates.ts:1001`, `:1023`, `:1126`, the
fall-open consequence | 5149 (and 6254 at `:694`) | "(failing open is
the console's settled behaviour)" | 5149: ruling 5202273981 (keep
fail-open, make it loud, lint at build time) and, on the migrated
`objectui#4051`, the delegated ruling 5235422835 (fail-open kept); 6254:
landing `643b7c76bc` (a repeater still spells its root `data`, which the
`:694` hint already states) |
| `validate-predicate-path-refs.ts:731`, dotted right-hand side |
`objectui#4049` | "the form evaluator keeps its right-hand side a
literal by design, and says why only in a development build" | claim and
ruling 5251302658 (B: a dev-mode diagnostic, no semantic change; A
rejected) and ACCEPT 5251653400 |
| `validate-rule-schema-formats.ts:326`, format hint | 5029 | "the one
`rule-validator.ts` registers so that a `format` is enforced on every
write" | PM ruling 5175865103 (option 1: register `ajv-formats`) and
ACCEPT 5176328955 |
| `validate-security-posture.ts:464`, unset OWD | `objectui#2348` | "the
leave_request incident, where an object with no sharingModel let an
ordinary read/write grant read and edit every other user's records" |
the PR body (merged) and ADR-0090 §Context and D1, which record the
incident |
| `validate-security-posture.ts:526`, `controlled_by_parent` with no
relation | 7474 | "refused with 422 INVALID_METADATA, as a metadata
defect rather than a permission denial" | maintainer ruling 5248468777
(split the legs by true semantics) and ACCEPT 5248935610 |
| `validate-seed-state-machine.ts:146`, seed outside the state machine |
3433 | "(a seed records established facts rather than walking the
lifecycle)" | landing commit `0c302a7790` (seed writes exempt from
`state_machine`: a curated snapshot of established facts) |
| `validate-view-containers.ts:133`, a ViewItem in `views:` | 5320 |
"The registration loop refuses this entry too: the stack schema, this
rule and the loop hold `views:` to one container-only contract." |
ruling 5265947108 (tighten the loop to the container-only contract) and
fork ruling 5271008099 (the `viewItems:` home first, then the tighten
with lint alignment) |
| `validate-react-page-props.ts:458`, absent `groupBy` | 5583 | "An
ungrouped single-value chart is NOT a supported ObjectChart shape —
groupBy stays required, ..." (the ruling stated directly, its citation
dropped) | landing commit `e0f300ba5d` (`groupBy` stays required; the
single-value need is served by the object-metric block) |

### Citation only (the sentence already stated the decision)

- `authoring-rules.ts`: `:1978` (8309, "`objects` IS carried by the
per-write snapshot"; audit 5279528993); `:1982` (7220, "a rule that has
never run at a door should not claim it"; maintainer ruling 5237225765).
- `data-model-rules.ts:521` (5030, "SQL UNIQUE is NULL-distinct ... it
enforces nothing", the fix naming the NULL-safe organization key part;
ADR-0120 and landing record 5178627933).
- `validate-dashboard-action-refs.ts:299` (`objectstack#6739`, "a modal
target names a PAGE, only", with the hint naming `actionType: 'form'`;
maintainer ruling 5229995836).
- `validate-empty-combinators.ts:256` (5330, the hint already says a
programmatic producer keeps the runtime identity and only authored
metadata is judged; ACCEPT 5226245518); `:295` (5297, "which is why the
runtime absorbs rather than filters").
- `validate-nav-object-servability.ts:167` (7912, "The server prunes
this entry from the served `/meta` payload"; maintainer ruling
5271014830).
- `validate-null-guards.ts:601` (4811, the clause already describes the
skipped `requiredWhen`; ACCEPT 5169421708).
- `validate-predicate-path-refs.ts:737` (`objectui#4049`, "it stops
working when this surface moves to the real CEL evaluator"); `:739`
(7696, "this one finding carries BOTH readings"; dev report 5255289068).
- `validate-searchable-fields.ts:356`, `:419`, `:443`, `:476` (4254,
"400 INVALID_FIELD"; landing `af2a0958c8`); `:414` (6674, the hint
already says the ingress gate now refuses the entry; triage promotion
5230241805 and landing `4ac12ef4cf`).
- `validate-sortable-fields.ts:415`, `:416`, `:435`, `:471` (6994 and
7095, "the REST ingress and the engine" refuse a formula sort with 400
INVALID_SORT; landings `9f7a7c25ab` and `6908830573`, maintainer ruling
5236143229 on 7095).
- `validate-widget-bindings.ts:982` (2501, "The filter is ANDed into
this widget's analytics query", the same treatment this file's two
neighbouring messages already had).

No occurrence was left in place: no cited decision was unclear, and each
fit the string's space.

## Ledger (`scripts/doc-authoring-prose-id.baseline.json`)

Recomputed with `node scripts/check-doc-authoring.mjs --census-ledger`
(exit 0, no growth refusal) into a scratch file, then copied into place;
recomputed again on the merged tree (below), where it is byte-identical
to the file in this branch. Against `main` the diff deletes 71 lines and
adds none: exactly the 18 file blocks (36 brace lines and 35 pair
lines). A scripted comparison of every other key: 0 moved, 0 added.

| | before | after |
|---|---|---|
| the 18 files | 51 occurrences, 35 pairs, 18 files | 0 |
| `packages/lint` | 51 occurrences, 18 files | 0 |
| whole ledger, at the branch point `7e7e64b13d` | 174 occurrences, 127
pairs, 53 files | 123 occurrences, 92 pairs, 35 files |
| whole ledger, on `main` at `cba4297176` (merged in) | 136 occurrences,
97 pairs, 39 files | 85 occurrences, 62 pairs, 21 files |

`pnpm check:doc-authoring` at the merged head `dfb89505f5`:
"sibling-package prose ids hold the baseline — 72 pinned site(s) across
21 file(s), 86353 string(s) read in 1253 parsed source(s), no growth, no
burn-down unrecorded". No gate is added or loosened;
`scripts/check-doc-authoring.mjs` is untouched.

Ledger serial: while this stage was built, the sibling lane's stage 4
(PR objectstack-ai#21472) held the ledger. This PR opened only after it merged
(`cc0786223b`), with `origin/main` at `cba4297176` merged in (a merge
commit, no rebase) and the ledger recomputed on that tree; git's text
merge of the two deletion-only diffs and the recomputation agree byte
for byte. At opening, no open PR touches the ledger or any of the 18
files (every open PR's file list read). PR objectstack-ai#21480 (objectstack-ai#21457) landed in the
same queue run; its edits to `validate-predicate-path-refs.test.ts` and
`lazy-deps.test.ts` are kept as they landed (both files equal `main`
here), and this PR touches neither.

## Quoted elsewhere

- `content/docs/data-modeling/schema-design.mdx:179` quoted the
stale-entry hint verbatim, `(objectstack-ai#4254)` included; the quote is updated to
the new text in this PR.
- `skills/**`: no quote of a changed message.
- The other docs hits for these numbers are the docs' own prose with
their own citations, not quotes of lint text; untouched.

## Changeset

`.changeset/20749-lint-strings-stage2-state-the-decision.md`: `patch`
for `@objectstack/lint`, carrying `Clause-②: no`. Measured after
building: the new sentences are in `packages/lint/dist/index.js`,
`index.cjs`, `runtime.js` and `runtime.cjs`, and none of the replaced
id-bearing fragments is (the two `(objectstack-ai#6994)` / `(objectstack-ai#5082)` hits left in
`dist` are source comments tsup keeps).

## Text-only proof

The stage-1 tool, unchanged: a TypeScript-AST skeleton of each changed
source in which every string literal and template text is a placeholder,
a `+` chain is flattened and a run of adjacent string operands is one
string (only its embedded expressions are kept, in order), identifiers,
numbers and regex literals keep their text, every child is visited, and
comments are never read. A second leg compares the TEXT of every string
group in order: each group that changed must have carried a tracker id
before and carry none after, and every other group must be
byte-identical. `7e7e64b13d` against the head: 18 of 18 SAME on both
legs, token counts identical per file, 40 groups changed, all of them
id-bearing before and id-free after, parse diagnostics 0/0.

Controls on a scratch copy of `validate-empty-combinators.ts` (head
version), each mutation counted on disk first (1 anchor hit, replacement
present, anchor gone): one function renamed reads DIFF; `===` flipped to
`!==` reads DIFF; one literal re-split into two `+` operands reads SAME
with no extra group changed; a text change in a string that never
carried an id reads SAME on the skeleton and VIOLATION on the text leg.
No repo file was mutated for the controls.

## Pins

Every assertion that found a finding by its tracker number, or by the
full old text, now finds it by the words that replaced it, at the same
strength (`toContain` stays `toContain`, the exact `toBe` stays exact).
No rule-id, severity or path assertion was touched.

- `validate-visibility-predicates.test.ts:968`, `:1418`: "failing open
is the console's settled behaviour" for `'objectstack-ai#5149'`.
- `validate-react-page-props.test.ts:1202`: "groupBy stays required, and
a single number belongs in an object-metric block instead" for `'5583'`.
- `validate-searchable-fields.test.ts:760`: the exact
outside-the-declared-set message, now ending "returns 400
INVALID_FIELD."
- `packages/cli/test/data-model-rules.test.ts:622`: "on every row whose
'organization_id' is NULL it enforces nothing" for `'objectstack-ai#5030'`.

## Tests

All builds and tests through `scripts/pm/os-verify-lock.sh`, every
verdict `VERDICT command-exit 0` on the runs quoted. These ran at
`d5137b469b`, the head before `main` was merged in; the merge brought
nine `main` commits that touch none of this PR's files apart from other
rows of the ledger, and the build, the lint suite and the gate union are
re-run on the merged head, with their readings in this stage's dev
report on objectstack-ai#20749.
- Build: `pnpm --workspace-concurrency=2 --filter
'@objectstack/lint^...' build` (the closure), then `pnpm turbo run build
--concurrency=2 --filter=./packages/* --filter=./packages/*/*` ("Tasks:
71 successful, 71 total") for the dist-reading gates and the CLI test.
- `@objectstack/lint`: `vitest run --maxWorkers=2`: "Test Files 119
passed (119) / Tests 5614 passed | 5 skipped (5619)". The first run,
before the re-pins, read 4 failed in 3 files, exactly the four pins
listed above. `typecheck`: `tsc --noEmit` exit 0 and
"check:test-typecheck: OK — @objectstack/lint's test layer compiles ...
2 file(s) / 6 error(s) / 2 pinned signature(s) held".
- `@objectstack/cli`, unit layer, `test/data-model-rules.test.ts`: "Test
Files 1 passed (1) / Tests 56 passed (56)". No `packages/cli` source,
`bin/` or integration-layer file is touched, so the integration layer is
CI's.

## Gates

- `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack
--commands` (no paths) at `d5137b469b`: 99 commands, run one at a time
from the worktree, each exit code recorded before any pipe. `--ran`: "99
derived famil(ies) accounted for — 99 run, 0 NOT-MEASURED (a DERIVED
zero — all 99 recorded an exit code and none of them is 3)". All 99 exit
0, the dist-reading ones (`check:docs-transcript-drift`,
`check:dts-closure`, `check:dual-build-cjs-loads`,
`check:lean-entry-closure`, `check:published-files`,
`check:sourcemap-no-sources-content`) after the full build. Outside the
derived set, also all exit 0: the 11 declared wide-population families
and the 11 artifact-roster families whose roster sits under one of this
PR's directories (`check-changeset-fixed`,
`check-published-list-mirrors` and its self-test,
`check:authz-resolver`, `check:console-injection`,
`check:engine-double-contract`, `check:error-code-casing`,
`check:filter-alias-parity`, `check:i18n-stale-fill`,
`check:published-readme-exports`, `check-dts-references --self-test`).
- Named by the dispatch: `check:doc-authoring` (self-test and run) exit
0; `check:nul-bytes` exit 0; `check-adr-0087-registration` exit 0 ("this
PR adds no declared-breaking changeset (1 non-breaking changeset(s)
seen)"); `check:empty-changeset` exit 0; `check-changeset-fixed` exit 0
(""fixed" group is in sync with 69 public workspace packages");
`check-changeset-no-major` exit 0 on the plain run ("This diff
introduces no `major` bump."), and exit 0 when fed this body as a
`pull_request` event (the `Clause-②: no` line read, `patch` judged
against it). At the merged head `dfb89505f5`, `check:doc-authoring`,
`check:nul-bytes`, `check-changeset-no-major` and
`check-adr-0087-registration` were re-run before opening: all exit 0.
- `check-issue-citations`: "no issue citations added against 7e7e64b
(18 file(s) read)"; `check:partof-closing-keyword` on this body: exit 0.
- Repo-wide `pnpm lint` is CI's.

## Acceptance notes

Noted, not filed:
- Test titles and test-only strings in these files' tests still cite
numbers, outside stage 2: 228 ids in test titles and 21 in other test
strings across `lint-liveness-properties.test.ts` (35 / 4),
`validate-component-props.test.ts` (19 / 0),
`validate-dashboard-action-refs.test.ts` (8 / 0),
`validate-empty-combinators.test.ts` (3 / 1),
`validate-null-guards.test.ts` (3 / 0),
`validate-predicate-path-refs.test.ts` (11 / 1),
`validate-react-page-props.test.ts` (28 / 0),
`validate-rule-schema-formats.test.ts` (4 / 0),
`validate-searchable-fields.test.ts` (13 / 0),
`validate-security-posture.test.ts` (7 / 1) and its
`.runtime-surface.test.ts` (27 / 2),
`validate-seed-state-machine.test.ts` (1 / 0),
`validate-sortable-fields.test.ts` (12 / 0),
`validate-view-containers.test.ts` (2 / 0),
`validate-visibility-predicates.test.ts` (23 / 0),
`validate-widget-bindings.test.ts` (27 / 0), plus
`authoring-rule-wiring.test.ts` (1 / 12) for `authoring-rules.ts` and
`reference-integrity-suite.test.ts` (4 / 0) for
`validate-nav-object-servability.ts`, which have no same-named test
file; `data-model-rules`' two same-named test files carry none, and
`packages/cli/test/data-model-rules.test.ts` carries 3 in titles. The
ledger does not read test files.
- `validate-empty-combinators.test.ts` selects its identity cases with
`(c.note ?? '').includes('objectstack-ai#5322')` over `FILTER_LOGIC_CASES` notes in
`@objectstack/spec`: that test-only id is load-bearing (it is a
selector, not prose), so the later stage that sweeps test strings cannot
just strip it.
- `authoring-rules.ts:1111`'s reason holds the component-types rule to a
budget measured "over stored tenant page rows"; the object-door crossing
it cites was adjudicated on a measured lower bound from authored
config-file metadata, with a post-launch replay as the audit, because no
tenant rows exist yet. The reason's wording is unchanged in substance
here; whether that bar still describes the page crossing is the seat's
to judge. Carrier: none.
- The two `.mjs` gate scripts stage 1 named
(`packages/lint/scripts/check-doc-formula-expressions.mjs`,
`scripts/check-startup-registry-verdict.mjs`) are untouched, as staged.
- Comments in the 18 files keep their ids: a comment is the sanctioned
home for an internal anchor.

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Oct 7, 2026
…n in words instead of a tracker number (stage 6) (objectstack-ai#21533)

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

**Stage 6 of the `domain:services` lane under the maintainer's A / A
ruling (5902360492): the `plugin-sharing` strings and the one
`plugin-audit` string left over from stage 4.** The card stays open for
the later stages, so this PR carries no closing keyword. Text only: no
status, error `code`, field, route, export or control flow moves (the
AST skeleton reads SAME for 6 of 6 changed `.ts` files, below).

## What this does

Eight strings in these two packages sent the reader to a tracker number
for the reason behind them. In form D, as stages 1 to 5 applied it, the
number goes. Where the sentence already said what was decided, only the
citation goes. Where it leaned on the number, it now says the decision
in words.

All 8 ledgered occurrences in this stage's surface (claim `5962584533`),
re-derived from the ledger on `origin/main` at `aa463223` and read
again, the same eight, on `ad7c3518` before merging: `plugin-sharing` 7
(7 pairs, 3 files: `share-link-service.ts`, `sharing-rule-service.ts`,
`sharing-service.ts`) and `plugin-audit` 1 (`audit-writers.ts`, the
entry stage 4 left for a later stage). The file excluded at dispatch,
`plugin-sharing/src/translations/serving-seam.test.ts` (PR objectstack-ai#21480, since
merged), carries no ledger entry and is not touched by this diff.

### Rewritten in words

Author- and administrator-visible text first, log lines last. Line
numbers are at the head.

| Where | Cited | The text now says | Decision read from |
|---|---|---|---|
| `plugin-audit` `audit-writers.ts:972-976`, the missing-table fix in
the audit write-failure line | 5226 | "so on a fresh `os dev` boot the
table exists in that sibling file and not in the primary one; look there
before concluding it was never created" | 5226's dev measurement and the
PM review: the "table never created" premise was falsified; on a fresh
`os dev` boot `sys_audit_log` was in the sibling `dev.telemetry.db`
(with its rows) and absent from `dev.db`, because ADR-0057 section 3.6
routes the audit lifecycle class to the telemetry datasource |
| `sharing-service.ts:312`, the reason the record-share orphan sweep
prints in its "revoked N rows" line | 5103 | "every share on a deleted
record goes, whatever its source, so a reused record id cannot inherit
it" | the ruling on 5103 (option A): a record's deletion revokes every
`sys_record_share` row on it, whatever the source, plus a boot sweep by
record existence; the card's hazard is a reused record id handing the
old record's shares to the new one |
| `share-link-service.ts:67`, the same reason for the share-link sweep |
5190 | "a share link is a bearer token, so a reused record id must not
inherit it" | 5190's case and ACCEPT: a share link is an identity-less
capability token, so on a reused id whoever holds it wins; a deleted
record's links are revoked and resolution fails closed |
| `sharing-service.ts:735-741`, the write-gate failure log line
(`error`) | 6428 | "a failed lookup is a refusal, never an abstention,
because an abstention would hand the row to the other write authorities,
which may admit it" | 6428's tri-state write verdict (allow / abstain /
deny), added because a two-state answer that merged "allow" with "no
opinion" was measured failing open, and its fail-closed rule: a query
failure is a deny, never an abstain (an abstain hands the row to another
authority, a deny ends it) |
| `sharing-service.ts:955-961`, the authored-row-write probe log line
(`warn`) | 5493 | "(fail-closed: only an app-authored row-level policy
that positively admits this row may lift the sharing refusal)" | the
maintainer's ruling on 5493 (Q1 = A, Q2 = A1): the by-id write gate
defers only on an `admit` from an app-authored, non-floor policy;
abstain, a missing method or a throw leaves the refusal byte-for-byte |
| `sharing-service.ts:1835-1842`, the hierarchy-scope log line (`warn`)
| 5973 | "\"No org\" is not \"every org\": the
IHierarchyScopeResolver.resolveOwnerIds contract makes a resolver fail
closed on a missing organization" | 5973 is a pull request (merged as
`abeb3751f`; its REST endpoints answer 404, its timeline reads): it made
`organizationId` the authoritative, required field and wrote the
fail-closed rule into `IHierarchyScopeResolver.resolveOwnerIds` |

### Citation only (the sentence already stated the decision)

- `sharing-rule-service.ts:424-429`, the no-active-organization refusal
(8158: "manage_sharing is an ORG-scoped capability ... answering
unscoped would expose every tenant's rules. Select an active
organization and retry. Platform operators ... and system contexts are
unaffected"). Its `PERMISSION_DENIED` prefix is unchanged.
- `sharing-rule-service.ts:529-533`, the platform-global delete refusal
(7795: "requires platform authority ... Org-scoped manage_sharing does
not authorize it, because this rule belongs to no organization and
deleting it revokes every tenant's grants under it. It remains listable,
readable and evaluable"). Its `PERMISSION_DENIED` prefix is unchanged.

Every cited card (8: 5103, 5190, 5226, 5493, 5973, 6428, 7795, 8158) was
read through REST, body and every comment, before its string was
rewritten. 5973 answers 404 on both the issue and the pull endpoint; its
timeline and its merge commit `abeb3751f` carry the decision.

### The `OrphanSweepSubject.issue` member

The two sweep reasons above are the values of
`OrphanSweepSubject.issue`, an exported member that
`sweepOrphanedRowsByRecordExistence` appends to its "revoked N rows"
warning. The member keeps its name and type (a rename is an export
change, outside a text-only stage); only its doc comment changes, from
"Issue reference appended to ..." to "Why the rows go, appended to ...
Runtime text carries no tracker number, so this states the decision in
words", so the published declaration no longer asks a caller for a
tracker number.

### Translations

None of these strings has a locale variant: they are refusal and log
text, not translation keys.

## Ledger (`scripts/doc-authoring-prose-id.baseline.json`)

The ledger is serial across every lane's stages, so this PR opened only
after stage 5's PR objectstack-ai#21518 merged (`e3ad4922`). `origin/main` at
`ad7c3518` (which also carries stage 4, PR objectstack-ai#21472, and another lane's
stage, PR objectstack-ai#21521) was then merged in (no rebase). The ledger conflicted;
main's copy was taken and regenerated on the merged tree with `node
scripts/check-doc-authoring.mjs --census-ledger` (exit 0, no growth
refusal). Against `main` the diff deletes 16 lines and adds none:
exactly the four file blocks of this stage. A scripted key-by-key
comparison of main's copy against the regenerated one reads 4 keys
moved, all of them this stage's, each to absent; every other row is
unchanged. No other open PR touches the file.

| | before (`ad7c3518`) | after |
|---|---|---|
| `plugin-sharing` | 7 occurrences, 7 pairs, 3 files | 0 |
| `plugin-audit` (`audit-writers.ts`) | 1 occurrence, 1 pair, 1 file | 0
|
| whole ledger | 42 occurrences, 35 pairs, 10 files | 34, 27, 6 (all
`service-analytics`) |

`pnpm check:doc-authoring` at the head: "sibling-package prose ids hold
the baseline — 26 pinned site(s) across 6 file(s), 86200 string(s) read
in 1252 parsed source(s), no growth, no burn-down unrecorded". No gate
is added or loosened; `scripts/check-doc-authoring.mjs` is untouched.

## Changeset

`.changeset/20751-services-strings-stage6-state-the-decision.md`:
`patch` for `@objectstack/plugin-sharing` and
`@objectstack/plugin-audit`. Measured after building the merged tree:
the new sentences are in each package's built output (`dist/index.js`
and `dist/index.mjs` of both). A TypeScript scan of every string literal
and template text in the built output finds 0 tracker ids in either
package. Control: the same scan reads 34 in `service-analytics`' built
output, the count its ledger entries carry.

## Text-only proof

A TypeScript-AST skeleton of each changed `.ts` file, where every string
literal and template text is a placeholder, a run of adjacent string
operands of a `+` chain is one string (only its embedded expressions are
kept), identifiers and numbers keep their text, and comments are never
read. `aa463223` (where the branch was cut) against the head: 6 of 6
SAME, and the same against `ad7c3518` (main did not touch any of the six
files). Controls on scratch copies of `sharing-service.ts`, each
mutation's marker counted once on disk first: a one-identifier rename
reads DIFF; a text-only change reads SAME; a re-split of one string into
two concatenated pieces reads SAME.

## Pins

- `sharing-service.test.ts:1616`: the write-gate failure log line is
found by "an abstention would hand the row to the other write
authorities" instead of the id; the `fail-closed` assertion beside it
and the `deny` verdict assertions are unchanged. Reverse check, at the
committed `90db7d4e` (the merge did not touch these files), through
`scripts/ablation-replace.mjs` under the lock: the new clause put back
to the citation form (anchor hit once, blob moved) turned exactly that
case red (predicted 1, measured 1 of 131); restored byte-identical to
`HEAD` with an empty `git diff HEAD`.
- No other test asserts any of the eight strings by their ids. The two
dogfood tests that read the no-active-organization refusal match "active
organization", which stays.

## Tests

All through `scripts/pm/os-verify-lock.sh`, every verdict `VERDICT
command-exit 0`, at the head `2f545642` (the merged tree; `pnpm install
--frozen-lockfile` first):
- Build: `turbo run build --filter=./packages/* --filter=./packages/*/*`
(71/71).
- `@objectstack/plugin-sharing`: 38 files, 954 tests passed;
`@objectstack/plugin-audit`: 38 files, 618 tests passed.
- `typecheck` for both, including `check:test-typecheck: OK` for each.
- The same suites and typecheck were green at `90db7d4e`, before the
merge.

## Gates

- `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack
--commands` (no paths) at `2f545642` (8 paths vs merge base `ad7c3518`,
62 changed lines): 74 commands, run one at a time from the worktree
after the full build, each exit code recorded before any pipe; 74 exit
0. `--ran`: "74 derived famil(ies) accounted for — 74 run, 0
NOT-MEASURED (a DERIVED zero — all 74 recorded an exit code and none of
them is 3)". The same 74 also ran green at `90db7d4e`, before the merge.
- `check-issue-citations`: "no issue citations added against ad7c351
(5 file(s) read)"; `check:i18n`: "OK (9 package(s) — all bundles in
sync, no undeclared authoring keys)"; `check:nul-bytes`: OK, 9876 files;
`check:type-check-debt`: "none above its recorded number";
`check:dual-build-cjs-loads`: 106 require entry points across 66
packages load; `check:dts-closure`: 71 built packages, 169/169;
`check:sourcemap-no-sources-content`: 68 packages, 532 maps;
`check:published-files`: 69 publishable packages;
`check:engine-double-contract`: OK.
- Outside the derived set, all exit 0 at `2f545642`: the eleven declared
wide-population families (`check:init-service-contract`,
`check:live-db-isolation`, `check:meta-type-normalized`,
`check:optional-error-sink`, `check:resume-authority-declared`,
`check:route-envelope`, `check:runner-env-posture`,
`check:settings-bind-window`, `check:startup-registry-verdict`,
`check:verify-stand-in`, `check:wildcard-fallthrough`), plus
`check:durability-log-level` and `check:error-code-casing` (log and
refusal text moved; no level or code did).
- Lint, narrowed as a measurement: `eslint --no-inline-config --format
json` over the 6 changed `.ts` files at `2f545642`: 6 files linted, 0
errors, 0 warnings. `eslint.config.mjs` enables no type-aware linting
(no `parserOptions.project`, no typed rules), so this diff cannot move
any untouched file's verdict. Repo-wide `pnpm lint` is CI's.
- `origin/main` was still `ad7c3518` when this PR opened.

## Acceptance notes

Noted, not filed:
- `OrphanSweepSubject.issue` now carries a reason in words; its name
still says "issue". Renaming it is an export change and belongs to no
text-only stage. Carrier: none.
- `plugin-sharing` test titles and comments still carry ids (`[objectstack-ai#6428]`,
`objectstack-ai#5103`, `objectstack-ai#5190`, ...); test bodies and comments are outside the ledger.
Carrier: none.

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

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 size/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants