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.

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