Skip to content

fix(cli): os build refuses a view container whose name disagrees with its object, as boot does (#20393) - #20459

Merged
objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/issue-20393-build-view-container-name
Sep 28, 2026
Merged

objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/issue-20393-build-view-container-name

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #20393
Clause-②: no

os build / os compile now runs the check os validate has run since #20331: a views: container whose own name disagrees with the object key it binds to is refused. The build exits 1 with the message the boot registrar prints, and it writes no artifact. Before this change the build exited 0 and wrote dist/objectstack.json, and os serve then refused that artifact at boot. This follows triage's grade on the card (comment 5865077508): the build calls the same function over the same view set, the validate-build-gate-parity row moves from VALIDATE_ONLY_GATES to SHARED_NON_REGISTRY_GATES, and there is no second rule.

Premise, measured on origin/main 7fa3e3e07 before any edit (H0)

The setup: the CLI's dependency closure was built (turbo, 59 tasks). Then os init my-app -t app --no-install, os g object order_line and os g view order_line were run, and the view's name was hand-edited to 'order_line', bound to my_app_order_line.

step exit what it did
os validate 1 "The server would refuse this stack at boot (1 view container)" (the #20331 control)
os build 0 Build complete, and wrote dist/objectstack.json carrying [{"name":"order_line","object":"my_app_order_line"}]
os serve in a directory holding only that artifact (no config) 1 "Invalid views: container from manifest 'com.example.my-app': the container's own name is 'order_line', which disagrees with the object key it binds to, 'my_app_order_line' …"

The card's moot condition does not hold: the container body name still parses on main (#20357 retired list.tabs only).

What changed

  1. packages/cli/src/commands/compile.ts, new step 3a, right after the schema parse and before the rule table and any artifact write. It makes the same call validate.ts step 2c makes, findViewContainerNameRefusals(result.data). That is the walk over @objectstack/objectql's viewContainerNameRefusal, the function the boot registrar throws the answer of. There is no second implementation of the check or of the walk. The message is the runtime's own, unchanged.
    • --json: { success: false, errors, warnings: warningsSoFar(), conversions }. This is build's schema-exit envelope, with the refusal rows in errors exactly as os validate --json carries them: { path, code: 'VALIDATION_ERROR', httpStatus: 400, message }. The exit carries warnings and conversions like every other exit, so the build-json-failure-warnings contract holds.
    • Text face: os validate's header ("The server would refuse this stack at boot (N view container(s))") and the bullet list. No step line is printed, so a passing build prints what it printed before (the docs transcripts stay true).
  2. packages/cli/test/validate-build-gate-parity.test.ts: findViewContainerNameRefusals moves to SHARED_NON_REGISTRY_GATES, so both commands run findViewContainerNameRefusals now holds both doors to it. VALIDATE_ONLY_GATES stays, empty, with a note that empty is its steady state. Its two-way pruning test is unchanged.
  3. packages/cli/src/commands/validate.ts: one comment sentence in step 2c said "os build does not run it", which this change makes false. It now points at build's step 3a. Code is unchanged.
  4. New packages/cli/test/build-view-container-name.test.ts (integration tier: it spawns the CLI and constructs ObjectQL). It has 6 tests:
    • a premise case: boot refuses both divergent payloads and accepts both controls;
    • THE PIN: build --json exits 1, success: false, and errors[0] equals what ObjectQL.registerApp throws for the same payload (message, code, httpStatus, path: 'views[0]'). No artifact is written;
    • the text face: exit 1, the same words, no Build complete, no artifact;
    • a packages[] stack: the divergent container in the second body is refused as packages[1].manifest.views[0], under that package's id and in the words boot throws for that body. The matching container in the first body is not reported;
    • two controls, a matching name and no name: each exits 0. The matching control's written artifact is registered by boot without a throw.
  5. Changesets.
    • New .changeset/20393-build-view-container-name.md: @objectstack/cli patch, Clause-②: no.
    • The pending .changeset/20331-validate-view-container-name.md was also edited. See the gate note below: this is a deliberate correction and needs your confirmation.

Which shape build judges, and why the verdict is boot's (H1)

Build judges result.data, the output of ObjectStackDefinitionSchema.safeParse(lowerCallables(normalized).lowered). That is the same expression, over the same pipeline, as the input to validate.ts step 2c. It is also the object build serializes. Step 4 adds only docs, packages[i].manifest.docs (via attachPackageDocs, docs only) and runtimeModule, never a view. So every views: entry the artifact carries is judged here, at the top level or in each packages[i].manifest body. The walker mirrors the load path over that shape: { ...manifest, ...stack } under artifactPackageId, or each package body under its own id. Measured after the fix on the repro project: os build --json's errors[0].message is byte-equal to the line os serve printed when booting the pre-fix artifact (cmp: identical, 568 bytes).

After the fix, on the same project (CLI from source)

  • os build exits 1 with the refusal, and no dist/ directory is created. os build --json exits 1 with success: false, and errors[0] is views[0] / VALIDATION_ERROR / 400.
  • Controls: name: 'my_app_order_line' gives exit 0 and an artifact. Deleting name gives exit 0 and an artifact.
  • examples/: os build --json exits 0 with success: true on each of app-crm, app-multi-package, app-showcase and app-todo, with no refusals. The output was written outside the tree.

Fixture census (H2): no build-door fixture turned red

A structural scan read 7,909 tracked .ts/.js/.json files under packages/, examples/, apps/ and scripts/, including the 260 string and template literals that carry config source (the configs tests write to disk). It found 936 containers. It read each object literal carrying a container arm (list/form/listViews/formViews) and no viewKind, and derived the key as boot does. It found 21 divergent containers, all outside every build door: packages/lint rule unit tests (13), packages/objectql (3, including the refusal's own fixtures), packages/metadata-protocol (2) and packages/spec (2). None of these runs os build. packages/cli, packages/qa and examples/ have none; #20331's patch round had already fixed that population. There were 9 non-literal-name containers, none in a build-door suite. The behavioural half agrees: all 15 build-door .e2e suites (the nightly tier) and the full unit tier are green at the head.

Ablation (H3), with the fix committed first

The mutation went through scripts/ablation-replace.mjs on packages/cli/src/commands/compile.ts. It replaced the call with const containerNameRefusals: ReturnType of typeof findViewContainerNameRefusals = [] plus the marker __ABLATION_20393_NO_CALL. The tool reported anchor x1 to x0, replacement x0 to x1, and blob 6b4b8871 to b7f532cb. On disk, the marker count was 1 and = findViewContainerNameRefusals( counted 0.

  • Both pins read source: the parity test reads src/commands/compile.ts as text, and the build-door test runs bin/run-dev.js, which is src/ through tsx. So no dist/ leg applies.
  • The result was 4 of 30 red: both commands run findViewContainerNameRefusals (unit), THE PIN, the text face, and the packages[] case. The premise, both controls and the rest of the parity file stayed green. The direction is red, as expected.
  • Restore: by the tool's trap, git checkout HEAD -- on the absolute path. The blob after the restore equals HEAD 6b4b8871, git diff HEAD is empty, git status --porcelain is empty, and the marker count is 0. The re-run was 30 of 30 green.

Tests, at head 0c4e9c3724 (after merging origin/main 3cf644938)

  • @objectstack/cli vitest run --project unit --maxWorkers=2, two shards: 117 + 116 files, 1789 + 1547 tests, all passed.
  • --project integration, run locally for the two view-container files only: 11 of 11 passed. The rest of the integration tier is declared to CI.
  • Nightly .e2e build-door suites (OS_TEST_TIERS=nightly), 15 files: 297 of 297 passed.
  • pnpm --filter @objectstack/cli typecheck: exit 0. tsc --listFilesOnly puts the new test in the tsconfig.test.json program.
  • Host note (macOS): three suites compare paths under the default TMPDIR, which macOS resolves from /var to /private/var: published-subpath-console.pin, published-subpath-hook-body.pin and config-miss-stdout-purity.e2e. Under the default TMPDIR they fail on that prefix alone, for commands this PR does not touch (os diff, os info, os verify, …). With TMPDIR set to its realpath they pass (29 of 29 and 174 of 174), so the counts above were taken that way. See the Acceptance notes.

Gates, at 0c4e9c3724

  • node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands gave 63 commands. --ran reconciled them as 63 run, 0 NOT-MEASURED and 0 UNRUN. Every command exited 0 except the one below.
    • check:dual-build-cjs-loads and check:i18n-coverage first exited 3 (PREREQUISITE NOT MET, unbuilt packages). Each exited 0 after the named packages were built.
  • pnpm lint (full eslint . --no-inline-config): exit 0 in 45 s.
  • node scripts/check-issue-citations.mjs --base origin/main, run after merging origin/main: exit 0, 3 citations resolve.
  • Two families take their argv from the workflow and are CI-only: check-issue-citations.mjs --census and the dogfood shard attestation.

⚠ check-empty-changeset --base origin/main exits 1 by design: a pending release note is corrected here

.changeset/20331-validate-view-container-name.md is #20331's pending entry. It closes with "Not changed: os build does not run this check, so it still writes an artifact carrying such a container …". This PR makes that sentence false. Both entries ship in the same release while that file is pending, and a published CHANGELOG.md sentence is corrected only in the entry that carries it. So that paragraph now reads "os build runs the same check as well (#20393, its own entry), so it no longer writes an artifact carrying such a container." The gate classifies this as the DELIBERATE CORRECTION class: it stays red, and its remedy is to confirm the correction on the PR, ⛔ not to restore the file.

Declared narrowing: verification ran UNLOCKED

Every build and test run above went through scripts/pm/os-verify-lock.sh, and each run printed this disclosure (quoted verbatim from the first one):

**Declared narrowing — verification ran UNLOCKED.** `scripts/pm/os-verify-lock.sh`
could not take the shared verify lock on this host: no usable `flock`. The shared
verify lock is declared Linux-only (`flock` is util-linux, and a stock macOS does
not ship it), so the command below was run directly, without the lock —
a declared narrowing, not a silent one. No serialization guarantee held for this
run, nor for any sibling agent in this container while it ran.

    pnpm turbo run build --filter='@objectstack/cli...' --concurrency=2

Acceptance notes

  • File surface beyond the claim, declared: the claim named compile.ts, the parity test, packages/cli tests and one new changeset. Two more files were edited, each because this change made a sentence in it false. One is a comment sentence in validate.ts step 2c ("os build does not run it"). The other is the closing paragraph of the pending .changeset/20331-validate-view-container-name.md (above).
  • Headers now incomplete, not false, and left alone:
    • packages/cli/src/utils/view-container-names.ts opens "os validate's author-time half of …". Both doors call it now.
    • packages/objectql/src/view-container-name-refusal.ts says "called by the boot registrar and by os validate". It is read-only for this card, and os build reaches it through the walker.
    • Carrier: none.
  • Bound carried over, unchanged: the walker does not walk a nested plugins[] entry's views, which boot also registers. Its header states why: the stack schema types plugins as unknown[]. Build inherits that bound. It does not widen it.
  • macOS test portability (not a product defect, not filed): three suites fail under macOS's default TMPDIR on a /var vs /private/var prefix: test/published-subpath-console.pin.test.ts, test/published-subpath-hook-body.pin.test.ts and test/config-miss-stdout-purity.e2e.test.ts. They pass with a realpath TMPDIR, and CI (Linux) is unaffected. There is no public-door reach. Carrier: none.

Generated by Claude Code

hotlong and others added 3 commits September 28, 2026 21:56
… its object, as boot does

`os build` / `os compile` now runs the walk `os validate` runs at its
step 2c, over the same judge the boot registrar throws the answer of,
right after the schema parse and before any artifact write. A stack the
server would refuse at boot no longer ships as an artifact.

The gate-parity ledger row moves from VALIDATE_ONLY_GATES to
SHARED_NON_REGISTRY_GATES, where both doors are held to the call.

Claude-Session: https://claude.ai/code/session_local_1d2a197c-c20e-4e90-9be8-413d4d432289
Co-authored-by: Claude <noreply@anthropic.com>
…ner name refusal

The pending #20331 entry's closing paragraph said `os build` does not run
the check; it does now, and both entries ship in the same release, so that
paragraph is corrected to point at this one.

Claude-Session: https://claude.ai/code/session_local_1d2a197c-c20e-4e90-9be8-413d4d432289
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation tests tooling labels Sep 28, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/cli, touching 3 documentable anchor(s).

18 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 b285508188ebe9cf14cd1ee621ea857f688a938b.

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

What this run could not see
  • 1 anchor(s) matched too much of the corpus to be a work list: os validate (command, 52 pages)
  • 2 name(s) were too generic to anchor anything (single lowercase words)
  • 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 — 25 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 b285508188ebe9cf14cd1ee621ea857f688a938b → packageMentionDocs.

Which tree this was computed on

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

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

⚠️ 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 b285508188ebe9cf14cd1ee621ea857f688a938b → 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: 0c4e9c37248551ad36a9261603eea191a225e7f1
Local-runs: none

PR #20459 (Fixes #20393), draft, base main at b285508188. Inputs read: card #20393 body and all 4 comments (triage grade 5865077508, unlock 5869790235, the claim 5871106299 in its amended text of 14:50 UTC, the os-dev-report 5872378755); the PR body, its 6-file list and the net diff against main; the five touched sources fetched at this head; the check-runs on this head. The card carries no maintainer ruling; the binding direction is triage's grade: os build calls the same function over the same view set, and no second rule.

Check-runs on this head, read at 14:57 UTC: 32 check-runs, newest per name. 23 success; 3 skipped (Build Docs, Console Pin Gate, Packed-tarball smoke (opt-in)); 1 failure (Check Changeset, judged in the sub-section below); 5 in_progress (Lint & Repo Gates, Test Core (3/6), Test Core (4/6), Test Core (6/6), Type Check · workspace). No verdict is inferred for the five still running. Green among the completed: Build Core, Dogfood Regression Gate and its three shards, Dogfood Verify CLI, Test Core (1/6) (2/6) (5/6), Type Check · source gates / consumer gates / debt ledger, Temporal Conformance, Governed Surface Queue Guard, both single-writer guards, the branch-claim guard, Check PR Size, Flag docs affected by code changes, Check Documentation Links.

① Derived judgments

  1. Accept set of os build / os compile narrows to boot's, on one axis. packages/cli/src/commands/build.ts is class Build extends Compile, so the diff's one call covers both spellings. New step 3a (compile.ts 384-424) runs findViewContainerNameRefusals(result.data), the same call validate.ts step 2c makes (line 355), over the same parse: both commands compute result as ObjectStackDefinitionSchema.safeParse(lowerCallables(normalized).lowered) (compile.ts 294/360, validate.ts 297/298). The walker (packages/cli/src/utils/view-container-names.ts) owns only the walk and hands each entry to @objectstack/objectql's viewContainerNameRefusal, the function the boot registrar throws the answer of. A stack carrying at least one views: container whose own name is set and disagrees with the derived object key now exits 1 at this door and writes nothing; every other stack builds as before. Right — this is triage's grade executed literally: same function, same view set, no second rule, no second walk.

  2. Same view set as the artifact boot loads. The walker judges the top-level views under artifactPackageId({ ...manifest, ...stack }) when the parsed stack has no packages, and each packages[i].manifest.views under that package's id otherwise, which is the load path's own branch. Step 4 builds finalBundle = { ...result.data } and adds only docs, per-package docs via attachPackageDocs, and runtimeModule (compile.ts 905-922, 967) — never a view. So the set judged at 3a is the set the artifact carries. Right.

  3. The refusal precedes every write; no flag bypasses it. 3a sits after the schema exit (step 3) and before the rule table (3b), preflight (3c), unknown keys (3d), the access-matrix snapshot (3e — the only pre-artifact writeFileSync, line 794), docs (3f) and artifact generation (4: mkdirSync 902, writeFileSync 982, runtime bundle 4b). this.exit(1) fires at 414 (--json) and 423 (text). The flag set is output, json, strict-body, runtime-bundle, no-runtime-bundle, update-access-matrix; none gates 3a. Right — the card's defect (exit 0 plus an artifact the server refuses) is closed at the door, not papered over.

  4. --json failure envelope. { success: false, errors, warnings: warningsSoFar(), conversions: conversionNotices } is byte-for-byte the shape of build's own schema exit (line 364), and each errors row is { path, code: 'VALIDATION_ERROR', httpStatus: 400, message }, the row os validate --json has carried since [finding] os validate passes a view container whose own name disagrees with the object key it binds to, and os serve then refuses that stack at boot #20331 (validate.ts 358-366, which wraps them in its own valid: / duration envelope). Build's errors key previously carried Zod issues only; it may now also carry these rows. Judged: a consumer-visible addition inside an existing key, in the shape the sibling door already publishes; no packages/spec/src/** schema and no package export is involved, and no doc pins build's failure-row shape (content/docs/deployment/cli.mdx documents only the invocation; content/docs/releases/v17/17-3.mdx line 573 says every failure exit carries the advisory lists and conversions, which this exit does). Right, and patch-level (see ②).

  5. Text face. os validate's header ("The server would refuse this stack at boot (N view container(s))") plus printBulletList with JSON_FULL_LIST_REMEDY; no printStep line, so a passing build prints exactly what it printed. A sweep of content/docs, apps/docs and the cli README at the merge base found no sentence claiming os build lacks the check, so no doc becomes false and the documentation face carries nothing this diff must correct. Right.

  6. Parity ledger (packages/cli/test/validate-build-gate-parity.test.ts). findViewContainerNameRefusals moves from VALIDATE_ONLY_GATES to SHARED_NON_REGISTRY_GATES; VALIDATE_ONLY_GATES becomes {} with a comment that empty is its steady state. it.each(SHARED_NON_REGISTRY_GATES)('both commands run %s') (line 802) now reads both compile.ts and validate.ts source for the call, so both doors are held; the two-way pruning test (1045-1058) iterates Object.keys({}) and passes vacuously; "every call site in compile.ts and validate.ts is classified" (829) sees the new call classified. The new row's comment quotes the deleted row verbatim ("os build still emits an artifact carrying such a container, which the runtime refuses when it loads it") and cites the runPerPackageAuthoringRules row's reason for deleting rather than rewording, which that row (lines 108-117) does give. The dev's ablation (call replaced, 4 of 30 red including this roster test, restore verified) shows the roster is live, not decorative. Right.

  7. The new pin, packages/cli/test/build-view-container-name.test.ts. Six tests: a premise (boot refuses both divergent payloads and accepts both controls, via new ObjectQL().registerApp), THE PIN (build --json exits 1, success: false, errors[0] equals the boot registrar's thrown message / code / httpStatus, path: 'views[0]', and no dist/objectstack.json), the text face, a packages[] stack refused as packages[1].manifest.views[0] under the second package's id with the first body's matching container not reported, and two exit-0 controls of which the matching one's artifact boots without a throw. The one-package payload is driven as { ...manifest, ...stack } and each packages[] body directly, which is the load path's shape per the walker header. It spawns bin/run-dev.js (source, via tsx) and constructs ObjectQL, so the derived integration tier collects it exactly as its [finding] os validate passes a view container whose own name disagrees with the object key it binds to, and os serve then refuses that stack at boot #20331 sibling validate-view-container-name.test.ts. Asserting equality with the runtime's thrown error rather than a copied string is the right pin for a "one judge" fix. Right. (Test Core shards 3/6, 4/6, 6/6 were still running at my read; 1/6, 2/6, 5/6 green.)

  8. Bound carried over, not widened. The walker does not walk a nested plugins[] entry's views, which boot also registers; its header states why (the stack schema types plugins as unknown[]). Build inherits the same bound os validate shipped with in [finding] os validate passes a view container whose own name disagrees with the object key it binds to, and os serve then refuses that stack at boot #20331. Not a regression, and outside triage's grade. Right, and correctly declared.

  9. Public surface. No export added or removed in any published package; packages/objectql/**, packages/spec/**, packages/metadata/** (the claim's read-only surface) are untouched by the diff. The change lives entirely inside @objectstack/cli command code. Right.

  10. validate.ts step 2c comment rewrite. Old: "os build does not run it (see the ledger row in test/validate-build-gate-parity.test.ts)" — false at this head (compile.ts calls it; the ledger row is gone). New: "os build runs the same call at its step 3a ([finding] os build / os compile writes an artifact with a view container whose name disagrees with its object key, and os serve then refuses that artifact at boot #20393); test/validate-build-gate-parity.test.ts holds both doors to it" — true at this head (step 3a exists at compile.ts 384; the both commands run %s roster includes the gate). Right.

  11. Headers left incomplete, not false. view-container-names.ts opens "os validate's author-time half of …" and the objectql judge's header says "called by the boot registrar and by os validate". Both are now incomplete; neither is false, and the walker's own closing lines already say it "reads the PARSED stack — what os build serializes" and names both doors as readers of artifactPackages. The cli header is inside the dev's writable surface and is a one-line follow-up the next touch of that file can take; the objectql one is outside this card's surface by the claim. Not a defect of this change; non-blocking.

DELIBERATE CORRECTION — .changeset/20331-validate-view-container-name.md

The gate's reading. Check Changeset (job 108981804594, pr-automation.yml changeset-check) printed "No empty-frontmatter changeset introduced by this diff (2 declaring changeset(s) added)" and then exactly one refusal: .changeset/20331-validate-view-container-name.md is "present on the merge base and CHANGED by this PR", with the two-class remedy text. The diff's edit is the DELIBERATE CORRECTION class the script names (scripts/check-empty-changeset.mjs, the foreign-changeset scan, ruling D on #17712): the remedy is to confirm on the PR, never to restore, and the gate stays red by design. The red is that rule alone; no other changeset finding is in the log.

Scope of the edit. Exactly two lines of the file changed, the closing paragraph. I fetched the file at main and at this head: the frontmatter (@objectstack/cli: patch, @objectstack/objectql: minor), the title, the Clause-②: yes line, the os validate paragraphs, the Fix line and the objectql widening paragraph are byte-identical.

Every rewritten sentence, old versus new, against this head's code:

Reach. The rewrite drops the "Not changed:" label (the paragraph no longer describes an unchanged thing) and the clause whose referent is gone; it adds a pointer to the entry that carries the change. It says nothing new about os validate, the objectql widening, the Fix line or the frontmatter. It does not reach beyond what this change made false.

Judgment: the correction is CONFIRMED. This record names the corrected note by path and judges each rewritten sentence, which is what landing-operations.md requires of a same-head at-tier record for this class. The two entries are both pending and ship in one release, so @objectstack/cli's CHANGELOG will carry a true 20331 entry followed by the 20393 entry. The dev's open_questions entry is answered option A: keep the correction; option B would republish a sentence the release itself makes false, and the gate names restoring as the wrong remedy for this class.

For the seat's queue mechanics (not this record's verdict): the real scan runs only in pr-automation.yml (on: pull_request, types opened / synchronize / reopened / labeled / unlabeled / edited); lint.yml's merge_group run carries the --self-test halves only (its own comment at the step says the real scans stay in changeset-check), so this red does not recur on a queue build; the PR body's "check-empty-changeset exits 1 by design" section records gate and reason. If a release consumes the 20331 file before this lands, the edit becomes a modify/delete conflict and dropping it is right, as the PR body already says.

② Semver level

  • Changeset .changeset/20393-build-view-container-name.md: @objectstack/cli: patch, body carries Clause-②: no, no (widening) / (narrowing) arm. The diff publishes one behaviour change inside @objectstack/cli command code and nothing from any other package; no export is added or removed; no packages/spec/src/** schema is touched. AGENTS.md: a bug fix in a released package takes patch. patch is right.
  • The narrowing at the author-time door is to the runtime's already-published accept set, not a narrowing of a published contract; the precedent [finding] os validate passes a view container whose own name disagrees with the object key it binds to, and os serve then refuses that stack at boot #20331 took patch for @objectstack/cli on the identical narrowing at os validate and declared yes / minor only for the objectql exports it added. This PR adds none. Clause-②: no is right, and it agrees across the claim (Clause-②: no), the PR body and the changeset body; the claim's stated reason ("pulls the author-time door back to the declared contract") is the reason.
  • The 20331 edit changes no frontmatter, so the release's bump set is unchanged by it. The Check Changeset red is the foreign-changeset rule alone (the empty-frontmatter half passed, "2 declaring changeset(s) added"); no check-changeset-no-major or ADR-0087 registration question arises for a patch.
  • Clause-②: no (confirmed against the diff).

③ Boundary flags

Dev deviations (6), each answered:

  1. File surface beyond the claim (validate.ts step 2c comment; the 20331 changeset paragraph) — the claim 5871106299 was amended in place (14:50 UTC) to name both as sentences this change made false; both are judged true in ①.10 and the sub-section. Answered; inside the claim.
  2. check-empty-changeset --base origin/main exit 1, recorded as 1 in gates — by design; the sub-section confirms it. Answered.
  3. Verification ran UNLOCKED (no flock on macOS), disclosure quoted verbatim in the PR — a declared narrowing; the head's check-runs are the gate verdicts this record reads, not the local runs. Answered; nothing to escalate.
  4. Three suites need a realpath TMPDIR on macOS (published-subpath-console.pin, published-subpath-hook-body.pin, config-miss-stdout-purity.e2e) — host portability of tests this diff does not touch; CI is Linux; Test Core 1/6, 2/6, 5/6 are green at read. Answered; noted-not-filed with carrier none is acceptable; a card is optional and not this PR's.
  5. check:dual-build-cjs-loads / check:i18n-coverage exit 3 then 0 after building the named packages — prerequisite-not-met, then measured; ran.list carries the re-runs. Answered.
  6. A transient /tmp/ov.bak outside the scratchpad, deleted in the same command — a process slip with no residue. Noted; nothing to escalate.

open_questions (1): keep the correction (A) or restore the file (B) — A, per the sub-section.

out_of_scope_findings (2), carrier none on both: the macOS TMPDIR portability (item 4 above); the two incomplete headers (①.11) — not false, non-blocking, the cli one a one-line follow-up inside the dev's own surface, the objectql one outside this card's surface. Neither meets the escalation bar in SKILL.md (no product-semantics fork, no breaking or hard-to-reverse action).

The "bound carried over" note (nested plugins[] views not walked) is acknowledged as the same bound #20331 shipped with; not widened, not this card's.

Nothing escalates to the decision box. The read-only surface named in the claim was honoured: the diff touches nothing under packages/objectql, packages/spec or packages/metadata.

Implemented-by: claude/issue-20393-build-view-container-name
Reviewed-by: local_1d2a197c-c20e-4e90-9be8-413d4d432289

VERDICT: PASS

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review September 28, 2026 15:05
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Sep 28, 2026
Merged via the queue into main with commit acd0095 Sep 28, 2026
35 of 36 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-20393-build-view-container-name branch September 28, 2026 15:26
os-tesla pushed a commit that referenced this pull request Sep 28, 2026
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

1 participant