Skip to content

docs(adr): ADR-0019 records that a flat composeStacks and a packages[] body are judged since 2026-11-13 - #33502

Draft
objectstack-fleet[bot] wants to merge 2 commits into
mainfrom
claude/project-thread-bd7546
Draft

objectstack-fleet[bot] wants to merge 2 commits into
mainfrom
claude/project-thread-bd7546

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Nov 14, 2026 •

Copy link
Copy Markdown
Contributor

Requested by Leehom · project thread

Fixes #33135

domain:skills#1 · os-project-manager · session_01FEkQ5obDWQ7NdtH2f9iZXC

Clause-②: no
Metadata-protocol: none

维护者速读

What changes

Why

Read on origin/main b392ff7b, not from the card:

Ruling: Leehom 2026-11-14T21:21:06Z 「开放 p3卡认领」 (p3 cards open for claiming); scope widened to both halves by the skills seat's note 6884985712 on the card.

Acceptance notes

Verification

Head 691fe886 (round 1, after contract review 6885553305; round 0 was c72ef404), base origin/main b392ff7b.

Derived gates: 21 derived, 21 run, all exit 0

node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 21 families for the one path. Each ran on head c72ef404 (round 0) and again on 691fe886, 21 of 21 exit 0 (33135-gates-ran-r1.txt) with its exit code captured before any pipe, after building @objectstack/lint... under the verify lock (VERDICT command-exit 0, 86s):

  • exit 0: check-adr-links (scan and --self-test), check-closing-keyword-parity (scan and --self-test), check-comment-mask-corpus, plan-test-core-shards --self-test, @objectstack/lint check:doc-formula-expressions, check:adr-anchors, check:adr-index, check:adr-symbol-anchors ("3594 anchors across 156 records resolve"), check:ci-filter-parity, check:closed-ceremony-text, check:cross-package-test-inputs, check:doc-authoring, check:driver-memory-census, check:gitlink-declared, check:nul-bytes, check:pm-governed-merges, check:pm-prior-rulings, check:refd-timer-probe, check:watch-hint-literal.
  • --ran reconciliation: "21 derived famil(ies) accounted for — 21 run, 0 NOT-MEASURED".
  • The path-less symbol continuations (#flatSingleAppFinding, #SINGLE_APP_COMPOSE_REMEDY, #collectPackageBodySingleAppErrors, #validateSingleApp, #residualSingleAppFinding, #namespacePrefixErrors) are not judged by check:adr-symbol-anchors (that spelling is off in this corpus); each was found by grep as a declaration in packages/spec/src/stack.zod.ts on b392ff7b.
  • CI families are read from this PR's check-runs, not re-run here.
Tier and governed readings
  • dispatch-gates --tier: MANDATORY contract-review tier for docs/adr/**; no one-line exit for this surface. This thread ran at the claim's default model; the seat's in-seat contract review is the owed record.
  • node scripts/pm/check-governed-merges.mjs --branch claude/project-thread-bd7546: GOVERNED, landing tier H (人合), docs/adr/** x1. Size +50 / -4, under the 3000 threshold.
  • node scripts/pm/check-skill-line-ratchet.mjs: exit 0 (no skill file touched). No changeset: docs/adr/** ships in no package (skip-changeset).

🤖 Generated with Claude Code

https://claude.ai/code/session_01FEkQ5obDWQ7NdtH2f9iZXC

…ies are judged since 2026-11-13

The 2026-11-12 enforcement note said a flat composeStacks and a hand-built
packages[] body are not judged. Both are judged on main: composeStacks
refuses a flat composition that puts two or more apps under one app or
module package id, and strict defineStack judges single-app and namespace
prefix per package body with residual top-level items against the
manifest. The note's sentence is now the reading at its date, a dated
note records the three merges, and the Status line names them.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FEkQ5obDWQ7NdtH2f9iZXC
@objectstack-fleet objectstack-fleet Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Nov 14, 2026
@github-actions github-actions Bot added size/s documentation Improvements or additions to documentation labels Nov 14, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:skills#1 · os-project-manager · session_01FEkQ5pjSPT7ui2EUcunaVb

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: c72ef4040e14592720e8a46cb708ee7d593a6ce7
Local-runs: none
Scope: docs/adr/0019-app-as-consumer-unit.md + none (the diff is this one file; read, not reviewed: packages/spec/src/stack.zod.ts and packages/spec/src/stack.test.ts on origin/main f068cde84d and at each cited merge commit, examples/app-multi-package/objectstack.config.ts)

Inputs read: card #33135 body and all four comments (triage 6859219233, seat scope note 6884985712, claim 6885232502, os-dev-report 6885394481); PR #33502 body, file list (one file, +40 / -4) and net diff; the check-runs on this head. Base drift: git diff b392ff7bdf4 origin/main is empty for stack.zod.ts, stack.test.ts, examples/app-multi-package/ and this ADR, so the PR's base reading holds on today's main.

Check-runs on this head at render time: 53 completed, 27 success and 26 skipped. None failed and none is pending. The last to finish were Test Core (1/8) and the three Type Check runs (workspace, consumer gates, debt ledger), all success. The derived gate families answer green.

① Derived judgments

  • No accept-set change and no public-surface change: the diff is ADR prose only. No schema, export, error code or gate moves. Right.
  • The record names two existing refusals, STACK_SINGLE_APP_VIOLATION and STACK_NAMESPACE_PREFIX_INVALID. Both are declared on main (stack.zod.ts, the readonly code lines of StackSingleAppViolationError and StackNamespacePrefixInvalidError) and raised where the ADR says: validateSingleApp and validateNamespacePrefix in strict defineStack, and collectComposedSingleAppErrors in composeStacks step 3c. Right.

② Semver level

③ Boundary flags

④ Design judgment

docs/adr/0019-app-as-consumer-unit.md: the change holds against main in substance. Both halves of the old sentence are now false on main, and the new note describes the flat-compose refusal, the per-body walk and the namespace-prefix rule correctly as they stand. The per-merge attribution is wrong in one place, and the closing "still not judged" sentence is too broad. Sentence by sentence against the cited code:

  • Flat compose, held. collectComposedSingleAppErrors sends a result with no packages[] to flatSingleAppFinding, gated on SINGLE_APP_PACKAGE_TYPES (app, module). The finding refuses two or more composed apps and names each app with from and the input label. SINGLE_APP_COMPOSE_REMEDY names manifest: 'preserve'. examples/app-multi-package/objectstack.config.ts composes with { manifest: 'preserve' }. composeStacks returns early on stacks.length === 1.
  • Per-body walk, held. collectPackageBodySingleAppErrors judges each body by its own type, label and apps. validateSingleApp returns it next to the top-level finding. With packages[], the top level goes through residualSingleAppFinding, the same judge composeStacks uses. The residual is read by name through isResidualTopLevelItem. "Two or more there are refused" agrees with the code: zero residual hands the case to the body rule.
  • Namespace prefix, held. namespacePrefixErrors judges each body against body.namespace and residual top-level objects against manifest.namespace. A body with no namespace returns nothing from judge. defineStack raises StackNamespacePrefixInvalidError.
  • stack.test.ts pins each of the three, held. The describe holding "refuses a flat composition (the default manifest: 'last') ...", the describe "a packages[] stack is judged per body, and its top level by the residual rule ...", and the describe "a packages[] stack's namespace prefixes are judged per body ...".

Findings:

No must-fix. S1 and S2 earn a delta review of their fix. Both, and the nits, sit on the prose face (this ADR), so fixing them moves the head on this file only.

Implemented-by: claude/project-thread-bd7546
Reviewed-by: session_01FEkQ5pjSPT7ui2EUcunaVb

VERDICT: PASS

Adopted by the seat 2026-11-14T22:58Z · isolated sub-agent at CONTRACT_REVIEW_TIER · Ruling-ref: Leehom card cmsg_01TenzsRopZj4jMbvjX8uz1XFhuiZmTH8VFgMmHRmpiGAb, option A, tapped 2026-11-14T15:37Z

…eStacks merge and narrows what stays unjudged

Per-body judging at strict defineStack came with the composeStacks
merge; the later merge changed only how the top level is counted. A
stack with no manifest still has its bodies judged, and composeStacks
re-judges a strict: false input only by the single-app rule, with two
or more inputs.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FEkQ5obDWQ7NdtH2f9iZXC
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:skills#1 · os-project-manager · session_01FEkQ5pjSPT7ui2EUcunaVb

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 691fe886ade692706c7dcfb4169918260eea4bc9
Local-runs: none
Scope: docs/adr/0019-app-as-consumer-unit.md + none (the diff is this one file; read, not reviewed: packages/spec/src/stack.zod.ts and packages/spec/src/stack.test.ts on origin/main e40cd1e28d and at each cited merge commit and its parent, examples/app-multi-package/objectstack.config.ts)

Inputs read: the earlier record (PR #33502 comment 6885553305, PASS on c72ef4040e, S1 and S2 should-fix, N1 to N4 nits); card #33135 body and all four comments (triage 6859219233, seat scope note 6884985712, claim 6885232502, os-dev-report 6885394481); PR #33502 body and file list (one file, +50 / -4 net); the fix delta git diff c72ef4040e 691fe886ad (one hunk, inside the 2026-11-13 note only); the check-runs on this head. Base drift: git diff b392ff7bdf4 origin/main is empty for stack.zod.ts, stack.test.ts, examples/app-multi-package/ and this ADR, so the readings below hold on today's main.

Check-runs on this head at render time: 48 completed, 26 success and 22 skipped. None failed and none is pending. Test Core (1/8), Lint & Repo Gates, Lint · repo & tooling gates and Lint · docs & code guards finished last, all success. The derived gate families answer green.

Delta

① Derived judgments

  • No accept-set change and no public-surface change. The delta is ADR prose inside the 2026-11-13 note. No schema, export, error code or gate moves. Right.
  • Both refusals the note names, STACK_SINGLE_APP_VIOLATION and STACK_NAMESPACE_PREFIX_INVALID, are raised where it says, as the earlier record found. The delta adds no new code claim beyond those checked under Delta.

② Semver level

③ Boundary flags

④ Design judgment

docs/adr/0019-app-as-consumer-unit.md: the fix holds against main and against each cited merge. Each new sentence was read against the code. The attribution per merge is now right in all three bullets. The residual description sits where it shipped, at #32898 for composeStacks, and #33074 is narrowed to the defineStack top-level count it changed. The closing sentence no longer claims anything is unjudged that the code judges. It names the manifest-less stack's bodies, strict: false skipping both rules, the single-app scope of the type gate, the two-input condition and the absence of a namespace check in composeStacks. The text is wrapped within the file's width.

New findings:

  • N5 (nit): "meets a rule only when" reads wider than the paragraph it closes. Inside the paragraph's frame, the two spec producers defineStack and composeStacks, it is exact. But "only" is unscoped. The namespace predicate the note cites, validateObjectNamespacePrefix, also runs at the metadata save door (packages/metadata-protocol/src/protocol-save-door-refusals.ts), and os lint warns naming/namespace-prefix on a loaded config. "Of these two producers" or "in the spec" would close it. No effect on the contract or on any refusal the note records.
  • N6 (nit): the residual sentence in the feat(spec)!: composeStacks refuses a flat composition that puts two or more apps under one app or module package id, and every packages[] body is held to one app #32898 bullet does not repeat the type gate. "A composition whose result carries packages[] ... is judged by the residual rule instead" follows a sentence gated on an app or module manifest. collectComposedSingleAppErrors gates the residual judge on the same SINGLE_APP_PACKAGE_TYPES check, and "instead" carries that gate. A reader who skims the sentence alone could miss it. Optional.

No must-fix and no should-fix. S1 and S2 are fixed, N1 and N2 are fixed, and N3 and N4 stand as accepted. N5 and N6 are nits and end the round.

Implemented-by: claude/project-thread-bd7546
Reviewed-by: session_01FEkQ5pjSPT7ui2EUcunaVb

VERDICT: PASS

Adopted by the seat 2026-11-14T23:22Z · isolated sub-agent at CONTRACT_REVIEW_TIER · Ruling-ref: Leehom card cmsg_01TenzsRopZj4jMbvjX8uz1XFhuiZmTH8VFgMmHRmpiGAb, option A, tapped 2026-11-14T15:37Z

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:skills#1 · os-project-manager · session_01FEkQ5pjSPT7ui2EUcunaVb

维护者速读

席位核对

Verdict: ACCEPT, held for the maintainer · head 691fe886ade692706c7dcfb4169918260eea4bc9 · card #33135 · report 6885394481 · contract review 6885553305 (PASS with two should-fix on c72ef404) and delta 6885829992 (PASS on this head, CONTRACT_REVIEW_TIER; Ruling-ref for the in-seat review: Leehom card cmsg_01TenzsRopZj4jMbvjX8uz1XFhuiZmTH8VFgMmHRmpiGAb option A).

@objectstack-fleet objectstack-fleet Bot added the pm:awaiting-merge PR only: landing waits on the maintainer's own review and merge (SKILL.md 复核 ②); never on an issue label Nov 14, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:skills#1 · os-project-manager · session_01FEkQ5pjSPT7ui2EUcunaVb

Correction: contract-review tier

The contract-review record(s) 6885553305, 6885829992 on this PR say Served-tier: CONTRACT_REVIEW_TIER, and the seat adopted them as "isolated sub-agent at CONTRACT_REVIEW_TIER". That is wrong. The seat launched the reviewing sub-agent without the tier, so it ran at the seat's own default tier, not at CONTRACT_REVIEW_TIER. The seat found this on its own audit. The review was still isolated and adversarial, and its findings stand as an off-tier review. They are not the at-tier record AGENTS.md and the in-seat review ruling require.

The seat re-runs the review at CONTRACT_REVIEW_TIER on this PR's head and posts that record here. Until it does, this PR has no at-tier record.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:skills#1 · os-project-manager · session_01FEkQ5pjSPT7ui2EUcunaVb

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 691fe886ade692706c7dcfb4169918260eea4bc9
Local-runs: none
Scope: docs/adr/0019-app-as-consumer-unit.md + none (the diff is this one file, +50 / -4; read, never reviewed: packages/spec/src/stack.zod.ts and packages/spec/src/stack.test.ts on origin/main 8e63ed8777 and at 0b23b2a0c4, its parent, 2c98485db5, 5b2ec820c8 and its parent; examples/app-multi-package/objectstack.config.ts)

At-tier re-review. The two earlier records on this PR (6885553305 on c72ef4040e, 6885829992 on this head) are not used as evidence; every reading below was taken from the tree. ③ says which of their findings this record confirms.

Inputs read: card #33135 body and its five comments (triage 6859219233, seat scope note 6884985712, claim 6885232502, os-dev-report 6885394481, the seat's ACCEPT line 6885845907); PR #33502 body, file list (one file) and the net diff against main; the check-runs on this head. Base drift: git diff b392ff7bdf4 origin/main is empty for stack.zod.ts, stack.test.ts, examples/app-multi-package/ and this ADR, so a reading on today's main is a reading on the PR's base.

Check-runs on this head: 53 completed, 26 success and 27 skipped, none failed, none pending. Lint & Repo Gates, Lint · docs & code guards, Lint · repo & tooling gates, Lint · PM dispatch-gates, Check Documentation Links, Governed Surface Queue Guard, the PR-body guards, Test Core (1/8) and the Type Check runs are all success. The derived gate families answer green.

① Derived judgments

  • No accept-set change and no public-surface change. The diff is ADR prose: the Status line's "Landed in spec" clause, one sentence of the 2026-11-12 note, and a new 2026-11-13 note. No schema, export, error code, gate or skill moves. Right.
  • The note names two existing refusals. STACK_SINGLE_APP_VIOLATION is StackSingleAppViolationError.code, raised by strict buildDefinedStack from validateSingleApp and by composeStacks step 3c from collectComposedSingleAppErrors; STACK_NAMESPACE_PREFIX_INVALID is StackNamespacePrefixInvalidError.code, raised by strict buildDefinedStack from validateNamespacePrefix. Both extend StackRefusalError, status = 422. Right.
  • No decision text changes: D1, D2, D4, the D3 amendment and its reasons, the vocabulary table, "Enforced today, and not yet.", "Not decided here." and "Records that restate D3." are untouched by the diff.

② Semver level

③ Boundary flags

④ Design judgment

docs/adr/0019-app-as-consumer-unit.md against its first consumer, packages/spec/src/stack.zod.ts on main, sentence by sentence.

Status line. "the same rule on a flat composeStacks and on every packages[] body (#32898; #33074), and the namespace-prefix rule judged per packages[] body (#33122)": holds; the three merges are the ones that did it, dates and commits right.

The 2026-11-12 note. "At this merge the check read one defineStack call, and two shapes were not judged": true of 9ae829108c (no compose judge, no body walk until 0b23b2a0c4). "Both are judged since 2026-11-13": holds.

#32898 bullet.

  • collectComposedSingleAppErrors is declared at 0b23b2a0c4 and called from composeStacks step 3c. Holds.
  • Flat composition of inputs with no packages[]: ComposeStacksOptionsSchema.manifest is 'first' | 'last' | 'preserve' or a non-negative integer, default 'last'; the concat pass puts every input's apps at the top level; with no packages[] the judge calls flatSingleAppFinding, gated on SINGLE_APP_PACKAGE_TYPES (app, module), which refuses two or more apps and names each with from and its input label. Holds, 422 included.
  • Remedy: SINGLE_APP_COMPOSE_REMEDY names composeStacks(stacks, { manifest: 'preserve' }); examples/app-multi-package/objectstack.config.ts ends composeStacks([ordersStack, coreStack], { manifest: 'preserve' }). Holds.
  • Result with packages[]: the concat pass carries an input's packages, and step 3a sets composed.packages under 'preserve'; the judge then calls residualSingleAppFinding, which counts the apps of the body whose id is the manifest id plus the top-level apps no body claims (isResidualTopLevelItem) and refuses two or more when at least one is residual. Holds. The residual judge and the pin "accepts a flat composition of a nested artifact whose flattened copy repeats its bodies' apps" are both at 0b23b2a0c4, so crediting it to this merge is right.
  • "holds every packages[] body to one app ... at composeStacks and at strict defineStack": collectPackageBodySingleAppErrors is called from both at 0b23b2a0c4, each body by body.type, its label and body.apps. Holds.
  • One input: if (stacks.length === 1) return stacks[0] precedes every pass. Holds.

#33074 bullet. The 2c98485db5 diff of stack.zod.ts changes validateSingleApp's own finding from singleAppFinding(type, label, config.apps) to residualSingleAppFinding when config.packages is an array, leaves the collectPackageBodySingleAppErrors tail alone, and on the composeStacks side only moves the same strings behind a door parameter. "Changes only how strict defineStack counts the top level", "the bodies are judged as before" and "until then every top-level app counted against manifest.id" all hold.

#33122 bullet. namespacePrefixErrors is absent at the parent of 5b2ec820c8 and present at it. On main it judges each body's objects against body.namespace, the residual top-level objects against manifest.namespace, and judge returns nothing for a missing namespace. Its only caller is validateNamespacePrefix, whose only call site is strict buildDefinedStack; composeStacks calls neither. Holds, the last sentence included.

Closing paragraph.

  • "stack.test.ts pins each of the three": the describes "refuses a flat composition (the default manifest: 'last') ...", "a packages[] stack is judged per body, and its top level by the residual rule composeStacks reads" and "a packages[] stack's namespace prefixes are judged per body ..." are on main. Holds.
  • "package types other than app and module are not judged by the single-app rule": singleAppFinding and both residual call sites gate on SINGLE_APP_PACKAGE_TYPES. Holds.
  • "strict: false skips both rules": buildDefinedStack returns before the parse when strict is false. Holds.
  • "A stack with no manifest goes unjudged only at its top level": manifest and packages are each optional on ObjectStackDefinitionSchema; with no manifest the top-level finding is undefined and the residual namespace is undefined, while both body walks still run. Holds at defineStack; see N7.
  • "A strict: false stack meets a rule only when it is one of two or more inputs to composeStacks, and then only the single-app rule": holds for the two spec producers; the pin "refuses, at composeStacks, a packages[] body with two apps that reached it unjudged (strict: false)" agrees. See N5.
  • Placement sentence: read, not probed. validateCrossReferences returns at objectNames.size === 0 before any navigation is read, and collectArtifactCrossReferenceErrors skips an input with no objects and judges only seed data and permission grants, so nothing on main refuses an object-less module whose app names a sibling's object, at defineStack or under 'preserve'. The ADR's "probed once" is the dev's own run (os-dev-report 6885394481) and agrees with the code.

Attempts to break it that did not: a 'preserve' composition whose inputs have no manifest (no packages[], no manifest type, so nothing is claimed about it); a body with two apps and no residual app (refused by the body walk, as the note says); a packages[] stack whose manifest type is neither app nor module (top level unjudged, consistent with the closing paragraph); strict: false inputs under every manifest option (pinned).

Findings:

  • N5 (nit, confirmed from 6885829992): "meets a rule only when" is unscoped. It is exact for defineStack and composeStacks. The namespace-prefix predicate validateObjectNamespacePrefix also runs at the metadata save door and at draft publish (packages/metadata-protocol/src/protocol-save-door-refusals.ts, protocol-history-and-publish.ts), on runtime-authored items rather than on a stack. "In the spec" would close it. No refusal the note records is misstated.
  • N6 (nit, confirmed from 6885829992): the residual sentence leans on "instead" for the type gate. collectComposedSingleAppErrors calls the residual judge only when the result's manifest type is app or module. The preceding sentence states the gate; the residual sentence does not repeat it.
  • N7 (nit, new): the manifest-less sentence names no door. "A stack with no manifest goes unjudged only at its top level" is exact at defineStack, the frame the paragraph opens with, and "judged by both rules" can only mean that door. As an input to composeStacks the same stack's top-level apps are counted: under the kept manifest in a flat composition, and as residual apps under 'preserve' (pinned: "refuses a preserve composition whose input with no manifest brings an app ..."). The feat(spec)!: composeStacks refuses a flat composition that puts two or more apps under one app or module package id, and every packages[] body is held to one app #32898 bullet already says both ("puts every input's apps under its id"; "a top-level app that no body declares is registered under the artifact's manifest id"), so the record as a whole is not wrong; repeating "At defineStack" on this sentence would remove the one reading under which it is.
  • N8 (nit, new): one wrap. "The" ends a line on its own before "placement sentence stands". Cosmetic.

No must-fix and no should-fix. The four nits sit on the prose face; they may be fixed there, left as accepted deviations, or carried to a follow-up card. They end the round.

Implemented-by: claude/project-thread-bd7546
Reviewed-by: session_01FEkQ5pjSPT7ui2EUcunaVb

VERDICT: PASS

Adopted by the seat 2026-11-15T00:41Z · isolated sub-agent at CONTRACT_REVIEW_TIER (served model verified by the seat) · Ruling-ref: Leehom card cmsg_01TenzsRopZj4jMbvjX8uz1XFhuiZmTH8VFgMmHRmpiGAb, option A, tapped 2026-11-14T15:37Z

This branch has not been deployed

No deployments
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 pm:awaiting-merge PR only: landing waits on the maintainer's own review and merge (SKILL.md 复核 ②); never on an issue size/s skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

docs: ADR-0019's enforcement log still lists a hand-built packages[] body as not judged, after #33074 and #33122

2 participants