Repository navigation
finding: one stack can author two entries under one name in 20+ named collections (objects, pages, flows, permissions, webhooks …), and os validate exits 0; only actions and the functions array (#22902) refuse it #22934
Description
Filing gate ①, class (c) candidate: metadata an author or AI can write twice under one name, accepted at a public door with no finding. reach: os validate --json exits 0 with valid: true and no finding naming the duplicate, measured on 20 keys (below). The downstream loss per key is NOT MEASURED, and that is this card's first work item.
Filed by domain:spec seat 1 (#6017) · os-warren · session session_019G5ZnRrTMiwcHG6iZM19zf, from the census in PR #22931's body (#22902's dev, report 6117331386). Triage's grade of #22902 (6114801402) asked for exactly this: "Siblings that show the same silent loss go to one batch card, filed at claim, ⛔ not one card per key." ⛔ Not a claim.
Reader who acts: triage grades it and names the lane. The refusals would sit in packages/spec/src/stack.zod.ts, beside collectDuplicateActionKeyErrors and PR #22931's collectDuplicateFunctionNameErrors, so it reads as domain:spec unless triage routes it elsewhere.
What is measured (PR #22931's census, at 70c36c1151)
Method: take examples/app-showcase's defineStack output, duplicate one key's first entry, re-run defineStack, and run os validate --json on a fixture that does the same. Keys the examples do not author were measured with hand-written entries through defineStack only.
Accepted by defineStack and by os validate (exit 0, valid: true, no finding naming the duplicate): objects (showcase_account; the only new findings are field-no-consumers on the copy), datasources, pages, dashboards, reports, datasets, flows, jobs, emailTemplates, books, positions, permissions, capabilities, sharingRules, webhooks, hooks, mappings, analyticsCubes, connectors, plugins.
Accepted by defineStack (hand-written entries, os validate not run): picklists, docs, agents, tools, skills, a named views entry; apis sharing a name on different paths (refused only by METHOD + path); apps on a manifest.type: 'module' package (refused only by the single-app count on an app package).
Already refused in one stack: actions (collectDuplicateActionKeyErrors, scope-qualified key) and the functions array (PR #22931, in review).
Not measured, and why it decides the batch
- What each key does with the second entry downstream: last wins, merged, both kept, or refused later at boot. For
functionsthe two paths diverged (the build kept the first underkc_a; a source boot kept the second), which is the defect finding: a stack'sfunctionsarray accepts two entries authoring onename, andobjectstack buildexits 0 shipping the second under a suffixed name (onmain, not callable after load), wherecomposeStacksalready refuses a duplicate function name #22902 fixes. ⚠️ Some keys may merge duplicates by design.composeStacksmergesobjectsby name across stacks (COMPOSE_KEY_DISPOSITIONS, the'objects'disposition), so a same-stack duplicate object may be intended extension semantics, not a loss. A key whose duplicate is merged on purpose leaves the batch, with the reading that shows it.
Direction (not a ruling)
- First, measure each key's downstream behaviour on a source boot and through
objectstack build→loadArtifactBundle, as finding: a stack'sfunctionsarray accepts two entries authoring onename, andobjectstack buildexits 0 shipping the second under a suffixed name (onmain, not callable after load), wherecomposeStacksalready refuses a duplicate function name #22902's dev did forfunctions. - Refuse at the contract only the keys that measurably lose or diverge, one sibling collector (or one shared helper) in the cross-reference aggregate, each finding naming the key, the name and every index, as
collectDuplicateFunctionNameErrorsdoes. - One batch, ⛔ not one card per key. A narrowing, so
Clause-②: no (narrowing)and FROM → TO.
Dedupe
MCP search_issues on this repo, closed included:
duplicate name same stack collection accepted defineStack uniqueness refusal objects pages flows: 28 hits, none on this subject. The nearest are finding: a stack'sfunctionsarray accepts two entries authoring onename, andobjectstack buildexits 0 shipping the second under a suffixed name (onmain, not callable after load), wherecomposeStacksalready refuses a duplicate function name #22902 (thefunctionsmember), composeStacks re-merges bound standalone actions that defineStack already copied into their objects — every bound action appears twice in the composed object's actions #14847 and composeStacks objectConflict: 'merge' — a fixed-shape config object (enable, access, protection, tenancy, lifecycle, userActions, publicSharing, external) the later object declares still replaces the earlier one wholesale (residue of the #14848 collection-only ruling) #16075 (composeStacksmerging), spec(flow): FlowSchema accepts edges whose source/target names no node and repeated (source,target) pairs; the engine then runs the target once per incoming edge #22088 (flow edges).array of named entries no uniqueness check two entries one name stack metadata silently last wins: 3 hits, none on this subject (finding: a stack'sfunctionsarray accepts two entries authoring onename, andobjectstack buildexits 0 shipping the second under a suffixed name (onmain, not callable after load), wherecomposeStacksalready refuses a duplicate function name #22902, metadata-protocol: the twoOS_METADATA_WRITABLEreaders disagree on the legacyOBJECTSTACK_METADATA_WRITABLEspelling, so the listing advertises the hatch for a type the save door then refuses #22411, Semantic-entry id uniqueness in the migration registry is guarded only by the shard FILENAME rule — no runtime assertion would see a duplicate #19732).- CONTROL: both queries return finding: a stack's
functionsarray accepts two entries authoring onename, andobjectstack buildexits 0 shipping the second under a suffixed name (onmain, not callable after load), wherecomposeStacksalready refuses a duplicate function name #22902, the family's own card, so the instrument reaches it.
Dedupe words: duplicate name same stack collection; array of named entries no uniqueness refusal; two objects one name accepted defineStack; same-stack duplicate metadata name siblings.
Generated by Claude Code
Activity
objectstack-fleet commentedon Oct 12, 2026
Triage: first grade,
bug·priority:p3·domain:spec·area:devpath, intopm:queue. This is the batch card #22902's grade asked for: measure each key's downstream loss first, then refuse only where it measurably loses or divergesTriage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-12T04:57Z. ⛔ Not a claim, ⛔ not a dispatch.packages/spec/src/stack.zod.ts, besidecollectDuplicateActionKeyErrorsand PR feat(spec)!: refuse a duplicate name inside one stack’sfunctionsarray (#22902) #22931's collector, sodomain:spec.objectstack build→loadArtifactBundle, the way finding: a stack'sfunctionsarray accepts two entries authoring onename, andobjectstack buildexits 0 shipping the second under a suffixed name (onmain, not callable after load), wherecomposeStacksalready refuses a duplicate function name #22902's dev measuredfunctions.objectsas the first suspect:composeStacksmergesobjectsby name across stacks.functionsarray (#22902) #22931 (finding: a stack'sfunctionsarray accepts two entries authoring onename, andobjectstack buildexits 0 shipping the second under a suffixed name (onmain, not callable after load), wherecomposeStacksalready refuses a duplicate function name #22902), whose collector is the template. This is ordering, not a block.Clause-②: no(narrowing). A narrowing of the published accept set owes the contract-tier review before the queue (execution-duties.md).objectstack-fleet commentedon Oct 12, 2026
Claim: PM loop round 1 (#22934 stage 0 only: measure what each named collection does downstream with a same-stack duplicate entry, and plan the one refusal batch from the readings; no code change in this stage) · 2026-10-12T07:03Z
Session:
session_019G5ZnRrTMiwcHG6iZM19zfAccount:
os-warren(the seat's linked user asGET /useranswers it; the card's assignee from this act)Branch:
claude/issue-22934-s0-duplicate-name-censusWorktree:
objectstack-issue-22934Domain:
domain:specSeat:
domain:spec#1(seat post #6017)File surface: none committed in stage 0. Probe fixtures and scripts stay in the dev's scratch space; no PR, no changeset. Any commit is a breach: stop and explain in the report.
Container & model:
M(a measurement over about 28 keys on two load paths, with judgment on what counts as by-design merging),mode:subagent,model: default tier(judgment, not mechanical). The build stage that follows isClause-②: no (narrowing)and owes a contract-tier review before the queue.Clause-②: no (narrowing) — the card's eventual build; stage 0 changes nothing.
Responsibility:
the cross-reference aggregate in packages/spec/src/stack.zod.ts accepts a second same-named entry for about 28 named collections, and downstream loaders then pick one silently or diverge | none: os validate exits 0 with no finding; only actions and the functions array are refused (functions in PR #22931, held) | any author or AI writing a stack, measured through os validate on 20 keys; no named user is known to depend on duplicates todayThread-read: 6118030391
Serial constraints cleared:
functionsarray (#22902) #22931 (finding: a stack'sfunctionsarray accepts two entries authoring onename, andobjectstack buildexits 0 shipping the second under a suffixed name (onmain, not callable after load), wherecomposeStacksalready refuses a duplicate function name #22902, this seat, held on ci: merge-queueTest Corereds on the new suite-duration ceiling —@objectstack/cloud-connectionmeasured 177.5 s and 200.3 s vs its 163.89 s ceiling, in two groups of PRs that do not touch it (live since1bd612bd6) #22949), whosecollectDuplicateFunctionNameErrorsis the template (triage's sequence in6118030391: "ordering, not a block"). PR feat(spec,cloud-connection,cli): the installModes manifest key and the install door's unmanaged mode (ADR-0131 D6, stage S2) #22916 (feat(spec,objectql,cli): the template install mode — a package copied once into the environment ledger, fully editable, refused on shared-database multi-tenant postures (ADR-0131 D6) #15213 S2, held) also editsstack.zod.ts.This act moves the card
pm:queue→pm:dispatchedand assignsos-warren.objectstack-fleet commentedon Oct 12, 2026
os-dev-report
{
"issue": 22934,
"status": "done",
"branch": "claude/issue-22934-s0-duplicate-name-census",
"pr": null,
"session": "session_019G5ZnRrTMiwcHG6iZM19zf (mode:subagent, the parent PM session id from the claim)",
"premise_still_valid": true,
"base": "246edf5b398a937126bb310cddbef9d3669bd89d (origin/main at dispatch); origin/main later moved to a2240c4 with 3 commits, none touching a measured file (only http-dispatcher.ts in runtime)",
"summary": "Stage 0 measured, at 246edf5, what each named collection does with a same-stack duplicate on a source boot (os serve CONFIG, no dist) and on os build followed by an artifact-backed boot (createStandaloneStack → loadArtifactBundle), using an in-process kernel:ready probe (metadata service, ObjectQL registry, and a scan of every kernel service's stores) plus HTTP for apis. Of 30 keys examined (the census's 28 plus objectExtensions and picklistExtensions), 27 measurably lose or diverge: 23 named collections are silent last-wins on both paths; datasources (metadata describes the last entry while the driver connects the first) and hooks (metadata shows one, both run) diverge within each path; docs diverges between paths (os validate and os build refuse, os serve from source keeps the last); and objectExtensions, a census miss, is last-wins too (two extensions of one target keep only the last). Out of the batch: plugins (declared last-one-wins contract, ruling #9864 B), agents (withdrawn, ADR-0063 section 2), picklistExtensions (merged by design). A1 holds (with the docs and objectExtensions corrections), A2 is falsified (composeStacks refuses, not merges, by default; the boot replaces), A3 is mostly falsified (the paths agree on the served entry for every key but docs; the real divergences are inside each path). No repository file changed, no commit, no PR.",
"assumptions": {
"A1": "HOLDS at 246edf5: the showcase fixture with 21 showcase-authored keys duplicated at once (the census's 20 corpus rows plus apis) gives os validate --json exit 0, valid true, 0 errors; against the no-duplicate control the 6 extra warnings all concern the copies' properties (field-no-consumers, liveness) and none names a duplicate. The minimal fixture (picklists, agents, tools, skills, apps, apis, views): exit 0, valid true. Corrections: docs is refused by os validate and os build (docs/duplicate-name), though defineStack accepts it; objectExtensions loses too (census excluded it).",
"A2": "FALSIFIED: a same-stack duplicate object is not composeStacks' intended merge. composeStacks([dupStack, otherStack]) with the default objectConflict 'error' refuses with STACK_COMPOSE_OBJECT_CONFLICT 422 ('defined in multiple stacks', misattributed); 'merge' (opt-in, cross-stack) keeps both fields; 'override' keeps the second; a single input returns untouched. The boot replaces: only the second's field survives on both paths.",
"A3": "MOSTLY FALSIFIED for these keys: both paths serve the same entry (the last) for every key except docs (build refuses, source serves) and flow version history (2 vs 1). The divergences that matter are within each path: datasources (metadata vs driver), hooks (metadata vs engine), jobs (the boot report counts 2 for one name)."
},
"census_table": [
{
"key": "objects",
"built": "showcase_account copied; FIRST adds field probe_first_only, SECOND adds probe_second_only and label PROBE_SECOND",
"source_boot": "last wins: registry, engine schema and metadata service carry probe_second_only only; the second own-registration splices the first (logged at debug only)",
"build_then_artifact_boot": "artifact ships both (objects[0], objects[24]); boot: metadata service serves raw SECOND, registry serves SECOND merged with the showcase's own objectExtension; probe_second_only only",
"paths_diverge": "no",
"class": "loses",
"by_design_check": "A2 falsified: composeStacks' default objectConflict 'error' REFUSES this stack beside any other input (STACK_COMPOSE_OBJECT_CONFLICT 422, misattributed as 'defined in multiple stacks'); 'merge' is opt-in and cross-stack (keeps both fields); a single input returns untouched; the boot never merges (first's unique field gone)"
},
{
"key": "datasources",
"built": "showcase_external copied; FIRST config.filename *_probe_first.db, SECOND *_probe_second.db and label PROBE_SECOND",
"source_boot": "metadata service serves SECOND (label, config.filename *_probe_second.db); the engine's registered driver carries FIRST's config and showcase_external_probe_first.db is the only file created; no warn",
"build_then_artifact_boot": "artifact ships both; boot: same split (metadata SECOND config, driver FIRST, only _probe_first.db on disk)",
"paths_diverge": "no between paths; YES within each path: metadata describes the second entry's database, queries run against the first's",
"class": "diverges"
},
{
"key": "pages",
"built": "showcase_capability_map copied, label marker",
"source_boot": "last wins (registry SECOND)",
"build_then_artifact_boot": "last wins (metadata service and registry SECOND)",
"paths_diverge": "no",
"class": "loses"
},
{
"key": "dashboards",
"built": "showcase_chart_gallery copied",
"source_boot": "last wins (registry SECOND)",
"build_then_artifact_boot": "last wins (metadata and registry SECOND)",
"paths_diverge": "no",
"class": "loses"
},
{
"key": "reports",
"built": "showcase_hours_by_status copied",
"source_boot": "last wins (registry SECOND)",
"build_then_artifact_boot": "last wins (metadata and registry SECOND)",
"paths_diverge": "no",
"class": "loses"
},
{
"key": "datasets",
"built": "showcase_task_metrics copied",
"source_boot": "last wins (registry SECOND; metadata service holds no dataset)",
"build_then_artifact_boot": "last wins (registry SECOND; metadata service holds no dataset)",
"paths_diverge": "no",
"class": "loses"
},
{
"key": "flows",
"built": "showcase_task_completed copied",
"source_boot": "last wins (registry SECOND; automation engine flows map SECOND; flowVersionHistory 2 entries)",
"build_then_artifact_boot": "last wins (metadata, registry and automation SECOND; flowVersionHistory 1 entry)",
"paths_diverge": "served flow no; version history yes (2 vs 1)",
"class": "loses"
},
{
"key": "jobs",
"built": "showcase_health_sweep copied; FIRST cron 11 1 * * , SECOND 22 2 * * ",
"source_boot": "registry SECOND; '[Registry] Overwriting job: com.example.showcase:showcase_health_sweep' prints only at info level; with OS_AUTOMATION_SCHEDULED_WORK_ENABLED=1 the binder logs 'Scheduled background jobs' count 2, handlers 2 for one name",
"build_then_artifact_boot": "artifact ships both schedules; registry SECOND",
"paths_diverge": "no",
"class": "loses",
"note": "scheduler state NOT read back (cron and interval adapter maps empty at ready+15 s on both paths); code reading: CronJobAdapter.schedule and IntervalJobAdapter.store cancel by name before registering, so last wins"
},
{
"key": "emailTemplates",
"built": "showcase_task_done_email copied",
"source_boot": "last wins (registry SECOND)",
"build_then_artifact_boot": "last wins (metadata and registry SECOND)",
"paths_diverge": "no",
"class": "loses"
},
{
"key": "books",
"built": "showcase_manual copied",
"source_boot": "last wins (registry SECOND)",
"build_then_artifact_boot": "last wins (metadata and registry SECOND)",
"paths_diverge": "no",
"class": "loses"
},
{
"key": "positions",
"built": "contributor copied",
"source_boot": "last wins (metadata service and registry SECOND)",
"build_then_artifact_boot": "last wins (metadata and registry SECOND)",
"paths_diverge": "no",
"class": "loses"
},
{
"key": "permissions",
"built": "showcase_contributor copied",
"source_boot": "last wins (metadata and registry SECOND): the first permission set's grants are gone",
"build_then_artifact_boot": "last wins (metadata and registry SECOND)",
"paths_diverge": "no",
"class": "loses"
},
{
"key": "capabilities",
"built": "showcase.export_data copied",
"source_boot": "last wins (metadata and registry SECOND)",
"build_then_artifact_boot": "last wins (metadata and registry SECOND)",
"paths_diverge": "no",
"class": "loses"
},
{
"key": "sharingRules",
"built": "share_new_inquiries_with_field_ops copied",
"source_boot": "last wins (metadata and registry SECOND)",
"build_then_artifact_boot": "last wins (metadata and registry SECOND)",
"paths_diverge": "no",
"class": "loses"
},
{
"key": "webhooks",
"built": "showcase_task_changed copied",
"source_boot": "last wins (registry SECOND; bootstrapDeclaredWebhooks seeds sys_webhook from registry.listItems('webhook'))",
"build_then_artifact_boot": "last wins (metadata and registry SECOND)",
"paths_diverge": "no",
"class": "loses"
},
{
"key": "hooks",
"built": "showcase_normalize_task_title copied",
"source_boot": "registry shows ONE hook (SECOND); the engine binds BOTH (beforeInsert x2, beforeUpdate x2, meta FIRST and SECOND): both run",
"build_then_artifact_boot": "same: metadata and registry SECOND, engine binds both",
"paths_diverge": "no between paths; YES within each path: metadata shows one hook, two execute (collectBundleHooks dedupes by reference only; registerHook appends)",
"class": "diverges"
},
{
"key": "mappings",
"built": "showcase_inquiry_feed copied",
"source_boot": "last wins (registry SECOND)",
"build_then_artifact_boot": "last wins (metadata and registry SECOND)",
"paths_diverge": "no",
"class": "loses"
},
{
"key": "analyticsCubes",
"built": "showcase_delivery copied (description marker)",
"source_boot": "last wins (registry and analytics cubeRegistry SECOND)",
"build_then_artifact_boot": "last wins (metadata and registry SECOND)",
"paths_diverge": "no",
"class": "loses"
},
{
"key": "connectors",
"built": "showcase_status_api copied",
"source_boot": "last wins (registry and automation connectors SECOND)",
"build_then_artifact_boot": "last wins (metadata, registry and automation connectors SECOND)",
"paths_diverge": "no",
"class": "loses"
},
{
"key": "plugins",
"built": "first entry (com.objectstack.connector.openapi instance) listed twice, as the census did",
"source_boot": "kernel last-one-wins with one warn 'Plugin superseded: com.objectstack.connector.openapi ... REPLACED the earlier one'",
"build_then_artifact_boot": "artifact ships both as inert JSON; the artifact boot never kernel.use()s artifact plugins",
"paths_diverge": "n/a (host assembly, not artifact metadata)",
"class": "merged/resolved by design - OUT",
"by_design_reading": "packages/core/src/plugin-registration.ts: duplicate registration by name is last-one-wins with one warn by DECLARED contract (maintainer ruling #9864 option B), pinned by plugin-registration.contract.test.ts and load-bearing (an app plugins entry supersedes a CLI auto-registered one); plugins are runtime assembly, not metadata (ASSEMBLED_PACKAGE_BODY_ENVELOPE_KEYS docblock, #15219 ruling A)"
},
{
"key": "picklists",
"built": "hand-written cen_pick x2 (minimal fixture, module package)",
"source_boot": "last wins (registry SECOND)",
"build_then_artifact_boot": "last wins (metadata and registry SECOND)",
"paths_diverge": "no",
"class": "loses"
},
{
"key": "docs",
"built": "hand-written cen_doc x2 (inline)",
"source_boot": "os serve accepts; registry SECOND (serve's byName dedupe runs only when src/docs/.md exist)",
"build_then_artifact_boot": "REFUSED: os validate exit 1 and os build exit 1, rule docs/duplicate-name, no artifact written",
"paths_diverge": "YES (build and validate refuse, source boot serves the last)",
"class": "diverges",
"note": "census row said 'defineStack only'; defineStack accepts, the CLI doors refuse"
},
{
"key": "agents",
"built": "hand-written cen_agent x2",
"source_boot": "registry SECOND",
"build_then_artifact_boot": "metadata and registry SECOND",
"paths_diverge": "no",
"class": "OUT (withdrawn surface)",
"by_design_reading": "app-package agents are withdrawn (ADR-0063 section 2): agent-authoring-withdrawn warns per entry that 'the runtime filters it out of the agent catalog and refuses to load it, so it never runs'; no served loss to refuse over (agent catalog itself NOT MEASURED)"
},
{
"key": "tools",
"built": "hand-written cen_tool x2",
"source_boot": "last wins (registry SECOND)",
"build_then_artifact_boot": "last wins (metadata and registry SECOND)",
"paths_diverge": "no",
"class": "loses"
},
{
"key": "skills",
"built": "hand-written cen_skill x2",
"source_boot": "last wins (registry SECOND)",
"build_then_artifact_boot": "last wins (metadata and registry SECOND)",
"paths_diverge": "no",
"class": "loses"
},
{
"key": "views",
"built": "hand-written named container cen_thing x2 (name equals its object)",
"source_boot": "last wins (registry SECOND)",
"build_then_artifact_boot": "last wins (metadata and registry SECOND)",
"paths_diverge": "no",
"class": "loses",
"note": "registrars key a container by its derived object (deriveViewContainerObject), so two UNNAMED containers for one object collide the same way (code reading, not booted)"
},
{
"key": "apis",
"built": "showcase_task_feed copied with path /api/v1/apps/showcase/tasks/probe-second (same METHOD+path is already refused)",
"source_boot": "registry SECOND; HTTP both paths 404 ENDPOINT_NOT_FOUND, and 404 on the no-duplicate control too (separate finding below)",
"build_then_artifact_boot": "metadata and registry SECOND; HTTP GET /api/v1/apps/showcase/tasks answers 404 ENDPOINT_NOT_FOUND (the first endpoint is unroutable), GET .../tasks/probe-second answers 401 UNAUTHENTICATED (matched, authRequired), control path 404",
"paths_diverge": "no",
"class": "loses (public door)",
"note": "EndpointMatcher indexes metadata.list('api'), which is name-keyed"
},
{
"key": "apps",
"built": "hand-written cen_app x2 on a manifest.type module package",
"source_boot": "last wins (registry SECOND)",
"build_then_artifact_boot": "last wins (metadata and registry SECOND)",
"paths_diverge": "no",
"class": "loses"
},
{
"key": "objectExtensions (census miss)",
"built": "two extensions of cen_thing in one stack, fields probe_ext_first / probe_ext_second",
"source_boot": "os validate exit 0 valid true; only probe_ext_second survives (registerObject 'extend' splices the same package's earlier extension of the target)",
"build_then_artifact_boot": "artifact ships both; only probe_ext_second survives",
"paths_diverge": "no",
"class": "loses",
"note": "census excluded it as 'keyed by extend, additive'; within one package it is not additive"
},
{
"key": "picklistExtensions",
"built": "not booted",
"source_boot": "NOT MEASURED",
"build_then_artifact_boot": "NOT MEASURED",
"paths_diverge": "n/a",
"class": "merged by design - OUT",
"by_design_reading": "registry.registerPicklistExtension merges the same package's contributions by option value and refuses a duplicate value (packages/objectql/src/registry.ts); code reading"
},
{
"key": "translations, data, datasourceMapping, devLogins, devPlugins, requires, tiers, packages, viewItems",
"class": "NOT MEASURED",
"why": "not named collections (keyed by locale / object / email, string lists, envelope, or z.never); the census exclusion stands and they are outside this card's subject"
},
{
"key": "actions",
"class": "already refused",
"why": "collectDuplicateActionKeyErrors at 246edf5 (not re-measured)"
},
{
"key": "functions (array form)",
"class": "in PR #22931 (open, not on main at 246edf5)",
"why": "collectDuplicateFunctionNameErrors absent from stack.zod.ts at 246edf5"
}
],
"build_plan": {
"sequence": "after PR #22931 (#22902) lands: it is the template and edits the adjacent lines of validateCrossReferences; PR #22916 (#15213 S2, held) also edits stack.zod.ts. Ordering, not a block.",
"keys_in_batch": [
"objects",
"datasources",
"pages",
"dashboards",
"reports",
"datasets",
"flows",
"jobs",
"emailTemplates",
"books",
"positions",
"permissions",
"capabilities",
"sharingRules",
"webhooks",
"hooks",
"mappings",
"analyticsCubes",
"connectors",
"picklists",
"docs",
"tools",
"skills",
"views",
"apis",
"apps",
"objectExtensions"
],
"keys_out": {
"plugins": "declared last-one-wins contract (ruling #9864 B) and host assembly, not artifact metadata",
"agents": "withdrawn for app packages (ADR-0063 section 2), nothing served",
"picklistExtensions": "merged by design in the registry",
"actions": "already refused",
"functions": "PR #22931"
},
"helper": "packages/spec/src/stack.zod.ts, cross-reference aggregate: one frozen table SAME_STACK_ENTRY_IDENTITY (key → identity reader and its noun) and one collector collectDuplicateEntryErrors(config), pushed from validateCrossReferences BEFORE the object-count early return, beside collectDuplicateActionKeyErrors and #22931's collector. Identity is
namefor 25 keys;extendfor objectExtensions; for views the container's derived object (object, else list.data.object, else form.data.object, else name). That derivation lives in packages/metadata/src/view-container.ts (deriveViewContainerObject), which spec cannot import: move the pure function into spec and re-export it from @objectstack/metadata under the same name, so it stays one copy, never a fourth hand-copy. Every entry counts, byte-identical twins included; strict:false skips it like every cross-reference check.","finding_shape": "one finding per (key, identity) collision, naming the key, the identity and EVERY index, in #22931's wording family: "Datasource 'showcase_external' is declared twice in
datasources: stack.datasources[0] and stack.datasources[1]. The runtime registers datasources by name, so only one of these entries survives (or both run, or the metadata describes one while the runtime uses the other). Rename one of them, or merge them into a single entry." Envelope: StackCrossReferenceError, code STACK_CROSS_REFERENCE_INVALID, status 422, one issues entry per finding. Reach: loadConfig runs the author's defineStack, so os validate, os build and os serve refuse at config load (measured for functions on #22902).","from_to": {
"common": "FROM: two entries of stack.KEY under one identity are accepted by defineStack and os validate (exit 0, valid true); the boot keeps the last. TO: refused by defineStack with STACK_CROSS_REFERENCE_INVALID 422 naming the key, the identity and every index. One-line fix: rename one entry, or merge the two into one.",
"datasources": "FROM: metadata describes the last entry, the engine driver connects the first entry's config. TO: refused.",
"hooks": "FROM: metadata shows one hook, both handlers are bound and run. TO: refused.",
"docs": "FROM: os validate / os build refuse (docs/duplicate-name) but os serve from source keeps the last. TO: refused at the contract on every door; the CLI rule keeps the inline-vs-src/docs collision.",
"apis": "FROM: same name on two paths accepted; the first endpoint is unroutable (404). TO: refused; give each endpoint its own name.",
"apps": "FROM: on a module package two apps sharing a name accepted, last wins. TO: refused (the app-package single-app count rule is unchanged).",
"views": "FROM: two containers for one object accepted, last wins. TO: refused; one container per object, merge listViews/formViews into it.",
"objectExtensions": "FROM: two extensions of one target in one stack accepted, only the last one's fields register. TO: refused; merge their fields into one extension."
},
"size_estimate": "about +450 lines over 6 files, no runtime package change: stack.zod.ts about +70/-2; view-container derivation moved to spec about +25 (metadata about -10/+3 re-export); one table-driven pin file about +250 (one refusal case per batch key asserting code, status, issues count, header and named subjects; controls: distinct names per key, map form for map-supported keys, the cross-scope action pair untouched, strict:false kept); changeset about +50; D3 semantic entry about +40",
"clause_2": "Clause-②: no (narrowing)",
"adr_0087_disposition": "registered, with one D3 semantic entry (for example 18.stack-collection-duplicate-entry-refused) and no D2 conversion (which entry to keep is the author's decision; nothing converts mechanically). Precedent: #22902, where check:adr-0087-registration refused not-required because the changeset body carries a migration prescription.",
"fixture_triage": "measured at 246edf5: examples/app-crm, app-todo, app-showcase and app-multi-package carry no duplicate under the identity table (22, 15, 142 and 3 entries scanned, top level and packages[i].manifest). Package test fixtures NOT MEASURED: the build greps the consumer radius of defineStack across packages' tests.",
"gates_the_build_will_owe": "spec test and typecheck, spec build then check:generated (check:api-surface moves if the view derivation is exported from spec), metadata test, check:adr-0087-registration, check:changeset-no-major, check:nul-bytes, the dispatch-gates derivation and --ran reconciliation; contract-tier review before the queue (triage)."
},
"tests": "Builds through the lock at 246edf5:
bash scripts/pm/os-verify-lock.sh -c 'NODE_OPTIONS=--max-old-space-size=4096 pnpm exec turbo run build --concurrency=2 --filter=@objectstack/cli...'VERDICT command-exit 0 (59 tasks, 1 cached, 8m06s);... --filter=@objectstack/example-showcase^...VERDICT command-exit 0 (60 tasks, 59 cached). CLI runs (node packages/cli/bin/run.js), each exit captured before any pipe: validate showcase fixture all keys exit 0 valid true / no-dup control exit 0 valid true; validate minimal fixture (7 keys) exit 0 valid true; validate minimal with docs exit 1 (docs/duplicate-name); validate objectExtensions pair exit 0 valid true. build: minimal exit 0 (both entries shipped per key), showcase exit 0 (both entries shipped per key, indices first and last), minimal docs exit 1 (no artifact), objectExtensions exit 0 (both shipped), jobs-only exit 0. Boots (serve --no-server, probe self-terminates after kernel:ready): minimal source exit 0, minimal artifact exit 0 (banner 'Artifact: dist/objectstack.json'), showcase no-dup control exit 0, showcase source exit 0 (twice), showcase artifact via probe-only non-host config exit 0 (banner 'Artifact: dist/objectstack.json'), docs source exit 0, objectExtensions source and artifact exit 0, jobs (OS_AUTOMATION_SCHEDULED_WORK_ENABLED=1) source x5 and artifact x1, all exit 0. Environmental failures, recorded and not counted as readings: the first showcase source boot exited 1 before the fixture dir carried the showcase's package.json and src (connector spec path resolved against cwd); the first pure-artifact showcase boot exited 1 (no connector providers, see deviations); the first minimal validate exited 1 on security-owd-unset on the probe's own object (fixed by sharingModel). HTTP (server on): showcase artifact boot /tasks 404, /tasks/probe-second 401, control 404; showcase source dup /tasks 404 and /tasks/probe-second 404; showcase source no-dup control /tasks 404; REST /api/v1/meta/ answered 401 (no auth configured) so the in-process reads stand in. Pure node: composeStacks on a same-stack duplicate (results under A2). Examples scan (node --import tsx): no duplicates under the identity table. No ablation in stage 0 (no code).","gates": [
{
"cmd": "bash scripts/pm/os-verify-lock.sh -c 'NODE_OPTIONS=--max-old-space-size=4096 pnpm exec turbo run build --concurrency=2 --filter=@objectstack/cli...'",
"exit": 0,
"verdict": "VERDICT command-exit 0"
},
{
"cmd": "bash scripts/pm/os-verify-lock.sh -c "NODE_OPTIONS=--max-old-space-size=4096 pnpm exec turbo run build --concurrency=2 '--filter=@objectstack/example-showcase^...'"",
"exit": 0,
"verdict": "VERDICT command-exit 0"
},
{
"cmd": "node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack",
"exit": 2,
"verdict": "STALE TREE (origin/main 3 commits ahead; none touches a measured file); 0 commands derived for an empty diff"
},
{
"cmd": "pnpm check:type-check-debt",
"exit": null,
"verdict": "NOT MEASURED, reason: excluded by the dispatch order (unlocked whole-workspace re-measure)"
},
{
"cmd": "check: families",
"exit": null,
"verdict": "none owed: no repository file changed (git status --porcelain empty, 0 commits past base)"
}
],
"mcp_calls": "0 - no MCP GitHub tool called",
"api_writes": "1 - this report comment via scripts/pm/post-stamped.mjs over the fleet-write relay (POST /repos/objectstack-ai/objectstack/dispatches executing POST /repos//issues/22934/comments as objectstack-fleet[bot]). Plus one git push of the empty branch (not REST). REST reads only otherwise: GET issue 22934 and its comments, GET comment 6117331386, GET pull 22931.",
"files_changed": [],
"deviations": [
"Pushed the empty branch claude/issue-22934-s0-duplicate-name-census before anything else, per role-file rule 1 (landing marker and write-route probe). It carries no commit: remote head == base 246edf5. The order's 'commit nothing; no PR' holds.",
"examples/app-showcase is a host config (live plugin instances in plugins), so os serve CONFIG never loads its compiled artifact. The artifact-backed showcase boot therefore used a separate dir holding the copied dist/ and a probe-only NON-host config (its own stack registers nothing; its onEnable is grafted onto the artifact's AppPlugin), with the showcase's four connector-provider instances merged as a devPlugins named export and its requires, under --dev. A pure artifact boot (no config) of the showcase fails before readiness: automation cannot materialize connector showcase_status_api without its provider plugin, with or without duplicates.",
"All 21 showcase-authored keys were duplicated in ONE fixture and each key's fate was read by its own name in one boot; isolated per-key runs were done only for jobs, docs and objectExtensions. Fixture dirs carry the showcase package.json and symlinked src, scripts and node_modules (relative connector spec paths and host-declared packages need them).",
"REST meta reads answered 401 (no auth configured); the in-process metadata service and registry reads stand in for that door. Jobs: the scheduler's state could not be read back (adapter maps empty at ready+15 s), so its last-wins half rests on code reading.",
"The census rows for picklists, agents, tools, skills, a named view and apps (module package) used a minimal module-package fixture rather than the showcase (the showcase is an app package, which the single-app count rule refuses for apps).",
"The harness attribution reminder asks for a model-named Co-Authored-By trailer; stage 0 made no commit, so nothing carries it."
],
"open_questions": [],
"out_of_scope_findings": [
"class: a (candidate; cause not isolated) · reach: public door - on a source boot (os serve CONFIG, no compiled artifact) of examples/app-showcase WITHOUT any duplicate, GET /api/v1/apps/showcase/tasks (declared apis entry showcase_task_feed, authRequired) answers 404 ENDPOINT_NOT_FOUND, while the artifact-backed boot of the same stack answers 401 UNAUTHENTICATED (matched). Probe: on config boots the metadata service holds no 'api' items (metadata.list('api') empty on both the minimal and showcase source boots, present on both artifact-backed boots), and EndpointMatcher builds its index from that list (packages/metadata/src/metadata-manager.ts). os dev not measured. · evidence: issue-22934 stage-0 probe runs at 246edf5 · dedupe words: source boot declared api endpoint 404; config boot metadata service missing api items; EndpointMatcher empty os serve config; declarative apis not served without build",
"carrier: this card's build stage · noted, not filed · composeStacks misattributes a same-stack duplicate object as 'defined in multiple stacks' (STACK_COMPOSE_OBJECT_CONFLICT) because mergeObjects keeps one name map across all inputs; the batch refusal in defineStack removes the authored path to it.",
"carrier: this card's build stage · noted, not filed · os build's summary counts duplicates as distinct entries ('2 Datasources', '3 APIs', '2 Picklists'), and the registry's overwrite notices print only above warn, so a default-level boot says nothing; both disappear once the batch refuses."
]
}
Generated by Claude Code
objectstack-fleet commentedon Oct 12, 2026
✅ ACCEPT, stage 0 (report
6120008386): 27 of 30 named collections measurably lose or diverge on a same-stack duplicate. The build follows PR #22931; transitionpm:dispatched→pm:blockedon #22902domain:specseat 1 (#6017) ·os-warren· sessionsession_019G5ZnRrTMiwcHG6iZM19zf· 2026-10-12T07:53Z · holder of claim6119405831, released by this act. Thread-read: the report.Checked against GitHub:
claude/issue-22934-s0-duplicate-name-censuson the remote is the base246edf5b3with no commit, and no PR exists for it. Stage 0 committed nothing, as ordered.origin/main:collectDuplicateActionKeyErrors(stack.zod.ts:2196, pushed at:2947),deriveViewContainerObject(packages/metadata/src/view-container.ts:77), and the metadata service'sEndpointMatcheroverlistForIndex('api').The readings, as the report gives them (
os servefrom source andos build→ artifact boot, at246edf5b3):objects,pages,dashboards,reports,datasets,flows,jobs,emailTemplates,books,positions,permissions,capabilities,sharingRules,webhooks,mappings,analyticsCubes,connectors,picklists,tools,skills,views,apis(the first endpoint becomes unroutable) andapps.objectExtensions. Two extensions of one target in one stack keep only the last.datasources: the metadata service describes the second entry's database, while the driver connects the first's.hooks: the metadata service shows one hook, and both bind and run.docs:os validateandos buildrefuse it (docs/duplicate-name), whileos servefrom source keeps the last.plugins: last-one-wins with a warning is a declared contract (ruling core(finding): duplicate plugin registration throws onLiteKerneland silently overwrites onObjectKernel— a fourth instance of the two-kernel semantic split #9864 B,plugin-registration.contract.test.ts), and plugins are host assembly, not artifact metadata.agents: withdrawn for app packages (ADR-0063 §2).picklistExtensions: merged by option value in the registry, and a duplicate value is refused. This one rests on a code reading only, NOT MEASURED on a boot.docsandobjectExtensionscorrections. A2 is falsified: a same-stack duplicate object is notcomposeStacks' merge. Its defaultobjectConflict: 'error'refuses (misattributed), and the boot replaces. A3 is mostly falsified: the real divergences are within each path.The build plan is accepted as the build sheet (report
build_plan):collectDuplicateEntryErrors, in the cross-reference aggregate ofpackages/spec/src/stack.zod.ts, beside the action collector and PR feat(spec)!: refuse a duplicate name inside one stack’sfunctionsarray (#22902) #22931's.STACK_CROSS_REFERENCE_INVALID,422.deriveViewContainerObjectmoves into spec and is re-exported from@objectstack/metadataunder the same name: one copy, not a second. That is adomain:enginefile, declared cross-lane at the build's claim.Clause-②: no (narrowing), registered with one ADR-0087 D3 semantic entry, and a contract-tier review before the queue. About +450 lines over 6 files.Out-of-scope findings:
apisendpoint (404 onos servefrom source, matched on the artifact boot): filed serve (config boot): a stack's declaredapisendpoints answer 404 ENDPOINT_NOT_FOUND onos servefrom source, while an artifact boot of the same stack matches them — the metadata service holds noapiitems on config boots #22989.composeStacksmisattributes a same-stack duplicate object as "defined in multiple stacks": Acceptance notes of the build, carrier this card. The batch removes the authored path to it.os build's summary counts duplicates as distinct entries, and the registry's overwrite notices print below warn: Acceptance notes of the build, carrier this card. The batch removes both.Why
pm:blockedand notpm:queue: the build edits the lines ofvalidateCrossReferencesthat PR #22931 (#22902) edits, and that collector is its template. Triage put it as "ordering, not a block".Blocked-by: #22902is the mechanical way to hold that order: the unlock scan returns this card topm:queuewhen #22902 closes, which happens when PR #22931 merges (Fixes #22902). PR #22931 is itself held only by the timing gate (#22949, fix PR #22973 accepted).Release:
session_019G5ZnRrTMiwcHG6iZM19zf· why: stage 0 is complete, and the build waits for PR #22931 by triage's sequence · to:pm:blockedwithBlocked-by: #22902, unassigned. Resume-from: none (stage 0 pushed no commit). This act moves the cardpm:dispatched→pm:blockedand removes the assigneeos-warren.4 remaining items
objectstack-fleet commentedon Oct 12, 2026
Claim: PM loop round 1 (#22934 build: the same-stack duplicate-entry refusal for 27 named collections, built from stage 0's accepted plan, report
6120008386build_planand ACCEPT6120079730) · 2026-10-12T09:52ZSession:
session_019G5ZnRrTMiwcHG6iZM19zfAccount:
os-warren(the seat's linked user asGET /useranswers it; the card's assignee from this act)Branch:
claude/issue-22934-duplicate-entry-batchWorktree:
objectstack-issue-22934Domain:
domain:specSeat:
domain:spec#1(seat post #6017)File surface (at
origin/main8e4a2302aor later; stop on breach and explain in the report):packages/spec/src/stack.zod.ts: one frozen identity table and one collector in the cross-reference aggregate, pushed fromvalidateCrossReferencesbesidecollectDuplicateActionKeyErrorsandcollectDuplicateFunctionNameErrors.deriveViewContainerObjectmoves frompackages/metadata/src/view-container.ts(domain:engine, declared cross-lane in this act) into a spec module, and@objectstack/metadatare-exports it under the same name. One copy, never a second.packages/spec, and the existing pins and fixtures the refusal reaches, found by a consumer-radius grep.check:generatednames, and one.changeset/22934-*.md(Clause-②: no (narrowing); the level followscheck-changeset-no-majorand finding: a stack'sfunctionsarray accepts two entries authoring onename, andobjectstack buildexits 0 shipping the second under a suffixed name (onmain, not callable after load), wherecomposeStacksalready refuses a duplicate function name #22902's precedent).Container & model:
M(about +450 lines over 6 files, stage 0's estimate),mode:subagent,model: default tier(dispatch-gates --tier --repo objectstack-ai/objectstack: "no path-derived mandate";Clause-②suspect onstack.zod.ts). A narrowing owes a review atCONTRACT_REVIEW_TIERbefore the queue, by an isolated at-tier subagent.Clause-②: no (narrowing)
Responsibility:
the cross-reference aggregate in packages/spec/src/stack.zod.ts accepts a second same-identity entry for 27 named collections, and the boot keeps the last, or diverges (datasources, hooks, docs) | none: os validate exits 0; only actions and the functions array are refused | any author or AI writing a stack, measured through os validate and two boot doors in stage 0Thread-read: 6121316349
Serial constraints cleared (all open PRs' file lists read at 2026-10-12T09:51Z):
stack.zod.ts. That is ordinary concurrency, and the later lander mergesmain.packages/metadata/src/view-container.ts. Neither file is aSINGLE_CLAIM_PATHSentry.This act moves the card
pm:queue→pm:dispatchedand assignsos-warren.feat(spec)!: refuse a duplicate identity inside one stack's named col…
feat(spec): key emailTemplates by (name, locale); D3 entry, changeset…
chore(spec): regenerate api-surface for deriveViewContainerObject (#2…
test(cli): build the same-package permission-set twin through strict:…
objectstack-fleet commentedon Oct 12, 2026
os-dev-report
{
"issue": 22934,
"status": "done",
"branch": "claude/issue-22934-duplicate-entry-batch",
"pr": "#23031",
"session": "session_019G5ZnRrTMiwcHG6iZM19zf (mode:subagent, the parent PM session id from claim 6121863164)",
"premise_still_valid": true,
"base": "4e6b2709991e4238a55e159bef7f9c102e2edb3b (origin/main at worktree creation); origin/main 9432bac merged in as 9442d4b (disjoint files: flows / notify-node). PR head 9442d4b.",
"summary": "Built stage 0's accepted plan. packages/spec/src/stack.zod.ts gains one frozen identity table SAME_STACK_ENTRY_IDENTITY (27 keys: identity reader, subject, measured consequence, remedy) and one collector collectDuplicateEntryErrors, pushed from validateCrossReferences before the object-count early return beside the action-key and function-name collectors: one finding per (key, identity) collision naming the key, the identity and every index, envelope StackCrossReferenceError / STACK_CROSS_REFERENCE_INVALID / 422, twins included, skipped under strict:false. deriveViewContainerObject moved into packages/spec/src/ui/view-container-object.ts (exported from the spec root and ./ui) and @objectstack/metadata re-exports that one declaration under the same name at its root and /view-container. Two measured deviations from the plan: emailTemplates is keyed by (name, locale) because an i18n bundle is one name across several locales (the spec's own stack-email-template-locale-floor pins went red under name-only, and the registry's ITEM_KEY_DISCRIMINATORS keys email_template by that pair), so 24 keys, not 25, are keyed by name; and a views entry that is not an aggregated container is keyed by its name, the slot every registrar files it under. One fixture outside the spec needed a change (cli permission-set-name-collisions, built through strict:false). Changeset level minor for spec plus patch for metadata, one D3 semantic entry, Clause-②: no (narrowing). The PR is not Tier H/S governed; it owes the contract-tier review before the queue.",
"assumptions": {
"A1": "HOLDS at 4e6b270: a probe through defineStack from source accepted a same-identity pair for all 27 keys (27/27 ACCEPTED, before any edit). The ablation leg re-shows it through the pin file: with the push line removed, 60 refusal cases go red.",
"A2": "HOLDS for the four examples (configs load through the rebuilt spec at 9442d4b; os validate --json exit 0 on all four and os build exit 0 on crm, showcase, multi-package at e9e921b). Package test fixtures: 1 changed. packages/cli/src/utils/permission-set-name-collisions.test.ts, case 'the SAME package declaring its own set name twice is NOT a collision', authored a byte-identical permission-set twin through strict defineStack on purpose; it now builds that package through { strict: false }, assertion unchanged. Radius = every test file outside the spec calling defineStack( (186 files, 22 packages); 160 ran green against the rebuilt spec dist, 26 cli .e2e.test.ts (nightly tier, in neither PR project) NOT MEASURED. A static AST scan of 8483 tracked .ts files for array literals under the 27 keys repeating a literal identity found 19, none reaching strict defineStack (lintConfig raw inputs, core position rows, a dogfood declaredPositionNames input). A second fixture class the plan did not foresee was caught in the spec suite itself: 4 email-template i18n-bundle pins (deviation 1), fixed in the collector, not in the fixtures.",
"A3": "HOLDS and widens: check:api-surface moved by one export on . and ./ui (regenerated with gen:api-surface), and export-origins/ moved too (regenerated with gen:export-origins; the spec suite's export-origins freshness pins caught it). Neither hand-edited; check:generated exit 0 at 9442d4b."
},
"tests": "All exits captured before any pipe; heavy runs through os-verify-lock. (1) Pin file packages/spec/src/stack-duplicate-entry.test.ts: 138 passed (per key a refusal asserting name, code, status, issue count, header, subjects and every index; per key a twin case; cross-collection 3+2 indices; object-less; map keys spelling one explicit name; named+unnamed container; same-named apps on an app package; joined envelope with action-key and function findings; controls: 27 distinct-identity, 19 map-form, email i18n bundle, cross-scope action pair, plugins/agents/picklistExtensions repeated, 27 strict:false). (2) Ablation of the push line at committed e9e921b via scripts/ablation-replace.mjs, anchor 'errors.push(...collectDuplicateEntryErrors(config));' hit 1 -> 0, blob 6dd315756e28 -> 48864d7b1e4d; RED 60 failed | 78 passed (138); restore: blob == HEAD 6dd315756e28, git diff HEAD empty, anchor back to 1; GREEN 138 passed. Subject imported from source (./stack.zod), no dist leg owed. (3) @objectstack/spec full suite at 9442d4b: 652 files, 19606 passed, 1 todo (VERDICT command-exit 0); pre-merge run at c1c13cb was 6 failed (4 email-template pins, 2 export-origins freshness) - both causes fixed. spec typecheck exit 0, check:generated exit 0 (14 artifacts up to date). (4) @objectstack/metadata full suite 58 files 871 passed; typecheck exit 0. @objectstack/cli typecheck exit 0. (5) Consumer radius against spec dist built at 21d95f9 (cli closure build, 59 tasks, VERDICT command-exit 0): cli unit tier 27 files 369 passed + 1 failed (the fixture above; its file 10/10 after the change), cli integration tier 25 files 190 passed (lock held 17m41s, the wrapper printed a holder-side starvation advisory), qa/dogfood 47 files 347, runtime 11/171, verify 14/135, objectql 4/127, plugin-security 5/103, plugin-webhooks 1/46, lint 3/76, create-objectstack 3/73, plugin-auth 2/37, service-analytics 3/35, service-datasource 2/29, app-todo 2/27, plugin-dev 1/20, metadata-protocol 1/19, cloud-connection 1/17, plugin-email 1/17, metadata 2/16, core 1/12, app-crm 1/7, client 1/2 - all exit 0; app-showcase 2 files first failed to resolve unbuilt @objectstack/connector-slack (environmental), after building it 45 passed. (6) Gates at 9442d4b: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 97; 96 run, every one exit 0 (incl. check:api-surface, check:generated, check:lean-entry-closure 15 packages admitted set held, check:dual-build-cjs-loads 107 entries / 66 packages after building 8 unrelated packages whose missing dist made the first sweep exit 3 PREREQUISITE NOT MET, check:nul-bytes, check:adr-0087-registration --base origin/main, check:changeset-no-major --base origin/main, check:empty-changeset); pnpm check:type-check-debt NOT MEASURED (excluded by the dispatch). Reconciled: dispatch-gates --ran with an exit-annotated record -> '97 derived famil(ies) accounted for - 96 run, 1 NOT-MEASURED', exit 0. (7) Lint, a declared narrowing: pnpm exec eslint --no-inline-config --format json over the 11 changed .ts files at 9442d4b, exit 0, 11 files linted, 0 errors, 0 warnings; population from eslint.config.mjs (files **/.{ts,tsx,mts,cts,js,jsx,mjs,cjs} minus NEVER_LINTED; all 11 inside it); count from the JSON output; invariance: the config never enables type-aware linting (no parserOptions.project, eslint.config.mjs lines 326-328), so the diff cannot move an untouched file's verdict. Full pnpm lint is CI's. (8) Control-byte self-scan of changed files: no match.",
"size": "+846 / -88 = 934 changed lines over 16 files (generated: 4 lines, api-surface/root.json, api-surface/ui.json, export-origins/root.json, export-origins/ui.json). The plan estimated about +450: the pin file is 417 lines (138 cases) and stack.zod.ts +214/-1 (table and docblock). Under the 3000-line human-merge threshold.",
"mcp_calls": "0 - no MCP GitHub tool called",
"api_writes": "2 - both through the fleet-write relay (POST /repos/objectstack-ai/objectstack/dispatches executed as objectstack-fleet[bot]): (a) pr_create -> POST /repos/objectstack-ai/objectstack/pulls (draft) + POST /repos//issues/23031/assignees [os-warren], run 38241750623, read-back 13911 bytes sent, 13911 stored, identical; (b) this os-dev-report comment -> POST /repos//issues/22934/comments via scripts/pm/post-stamped.mjs. Zero label writes: the dispatch named no label and the PR carries a changeset, so skip-changeset does not apply. Zero PR body edits. git pushes are not REST. REST reads only otherwise (issue 22934 and its comments, pull 23031).",
"deviations": [
"emailTemplates identity is (name, locale), not name: measured by the spec suite's own i18n-bundle pins and declared by ITEM_KEY_DISCRIMINATORS. The ruling's intent holds (a same-locale pair, stage 0's measured case, is refused).",
"views: a non-container entry is keyed by its name (registrar rule), an aggregated container by its derived object as planned.",
"Role file over dispatch: the dispatch allowed up to 2 PR body edits; the role file says the dev writes the PR body once and names any correction for the seat. One correction is owed (next item); no PATCH was sent.",
"PR body correction for the seat to apply (Verification record, Consumer radius bullet): replace '
cliunit tier: 78 files, 369 passed and 1 failed, the fixture below, whose file passes 10 / 10 after the change.' with 'cli: of its 78 files, the 27 in theunittier ran 369 passed and 1 failed, the fixture below, whose file passes 10 / 10 after the change; the 25 in theintegrationtier ran 190 / 190; the 26*.e2e.test.tsfiles are in neither PR tier and are NOT MEASURED here.' and in the same bullet '(186 files, 22 packages) ran against' with '(186 files, 22 packages; 160 of them ran) against'. The body states 78 unit-tier files; the unit project ran 27 of them.","Merged origin/main (9432bac) into the branch before opening the PR (AGENTS.md multi-agent section 10); the spec was rebuilt and its full suite, typecheck and check:generated re-run at the merge, plus the final gate sweep. The consumer radius ran before the merge, against the pre-merge spec dist (the merged commits touch flows / notify-node only).",
"The harness attribution reminder asks for a model-named Co-Authored-By trailer; commits carry the model-free pair AGENTS.md requires (Claude-Session + Co-authored-by: Claude). The merge commit 9442d4b is git's default message with no trailer.",
"Pushed the empty branch before any edit (role rule 1)."
],
"open_questions": [
{
"question": "ADR-0048 section 3.4 leaves an author shipping two page/home in one package to authoring-time hygiene (os lint's naming/namespace-prefix advisory). This PR refuses that same-package case at the contract for 27 collections. Is that a reading of the ADR, or a reversal that needs an amended status line (Prime Directive 13)?",
"options": [
"A: a reading. 3.4's decisions (cross-package coexistence; same-package re-registration on reload) are untouched, and the authoring case it delegates is tightened from a warning to a refusal, as collectDuplicateActionKeyErrors already did for actions. Record it in the contract review; no ADR edit.",
"B: a reversal. Add a one-line amended status to ADR-0048 3.4 (a Tier H governed file, so a separate PR)."
],
"recommendation": "A. Business need: stage 0 measured silent loss on all 27 keys at two boot doors, and the advisory reaches only os lint, not validate, build or serve. Long-term: the contract is the one door every path shares (contract-first), and the actions precedent already stands. AI-proofing: a refusal at load is the stronger structural guard against an AI writing two entries under one name; a lint warning is skimmable. Startup focus: no new surface and no new gate, and a separate governed ADR edit would add a Tier H landing for no change in behaviour. Flagged in the PR's Acceptance notes so the contract reviewer rules on it."
}
],
"out_of_scope_findings": [
"carrier: this PR (stage 0's note, in Acceptance notes) · noted, not filed · composeStacks misattributes a same-stack duplicate object as 'defined in multiple stacks' (STACK_COMPOSE_OBJECT_CONFLICT); the batch removes the authored path to it.",
"carrier: this PR (stage 0's note, in Acceptance notes) · noted, not filed · os build's summary counts duplicates as distinct entries and the registry's overwrite notices print below warn; both disappear once the batch refuses.",
"carrier: none (承接者:无) · noted, not filed · os lint's intra-package duplicate advisory (duplicateNameIssues, packages/cli/src/commands/lint.ts: apps, pages, dashboards, flows, reports, datasets) can no longer fire on a top-level stack that strict defineStack built; reachable only for strict:false stacks and hand-built package bodies. Dead-code observation, in Acceptance notes.",
"carrier: the next edit to that import · noted, not filed · packages/objectql/src/engine.ts imports deriveViewContainerObject 'from the LEAF subpath'; @objectstack/metadata/view-container now loads the spec root (98 modules standalone, CJS) and is no longer a leaf, though the comment's reason (keep the manager and filesystem tier out of the lean entry) still holds and the lean closure is unchanged. Outside this claim's file surface, in Acceptance notes."
]
}
Generated by Claude Code
objectstack-fleet commentedon Oct 12, 2026
✅ ACCEPT: PR #23031 at
9442d4b7f3(Fixes #22934). Next: the contract-tier review, then the queuedomain:specseat 1 (#6017) ·os-warren· sessionsession_019G5ZnRrTMiwcHG6iZM19zf· 2026-10-12T12:14Z · holder of claim6121863164. Report:6123996316. Thread-read: the report.Read in the diff (merge base
9432bac077, 16 files, +846 / −88, 4 generated lines):packages/spec/src/stack.zod.ts: one frozen table,SAME_STACK_ENTRY_IDENTITY, with 27 rows, and one collector,collectDuplicateEntryErrors. The collector is pushed invalidateCrossReferencesbeside the action-key andfunctionschecks, outside the objects guard. It emits one line per (collection, identity) collision and names every index. That is the stage-0 build sheet as accepted.namefor 24 collections.name@localeforemailTemplates, with an untagged entry counting as the floor tag.extendforobjectExtensions.views: an aggregated container uses the object it binds to; any other entry uses itsname, falling back to the same derivation.plugins,agents,picklistExtensions,actions,functions.deriveViewContainerObjectnow has one declaration,packages/spec/src/ui/view-container-object.ts, with no imports. The body is byte-for-byte the removed one frompackages/metadata/src/view-container.ts. Metadata re-exports it under the same name from its root and its/view-containersubpath. The cost is stated in that header: the subpath now loads the spec root entry, andcheck:lean-entry-closure's admitted set for@objectstack/objectql/coreis unchanged.stack-duplicate-entry.test.ts, covers:permission-set-name-collisions.test.tsmoves tostrict: false. Its comment says why: the door it pins must still not misreport a non-strict or pre-refusal artifact.Checklist:
Fixes #22934, correct for the whole card.Clause-②: no (narrowing), as the build sheet set it.@objectstack/specminor, marked BREAKING under the launch-window convention, with a FROM → TO table.@objectstack/metadatapatch. The ADR-0087 marker names the D3 semantic entrystack-collection-duplicate-entry-refused(protocol 18), which has no D2 conversion.content/docs/releases/, no governed path. The twodomain:enginefiles (packages/metadata/**) are the cross-lane surface declared in the claim.mcp_calls0. Writes: the PR create and the report, both over the relay. No label write: the PR carries a changeset.CI at this read: 32 success, 3 skipped,
mergeable_stateclean, draft.Report items, one line each:
cliunit tier ran 27 of its 78 files, and 160 of the 186 files ran) is applied by this seat in the same act as this comment. Under the role file, the dev writes the body once.emailTemplatesidentity is(name, locale), notname. This is a measured correction of the build sheet: the spec's own i18n-bundle pins andITEM_KEY_DISCRIMINATORSkey by the pair. A same-locale pair, the stage-0 case, is refused. Accepted.views: a non-container entry is keyed by its name, the registrars' rule. Accepted.mainmerge9442d4b7fwas made before the PR opened. The contract review reads this head.page/homein one package "stays covered by" theos lintadvisory. This PR refuses that authored case at the contract. The registry's overwrite on reload is untouched.composeStacksmisattributes a same-stack duplicate object: in Acceptance notes, carrier this card. The batch removes the authored path to it.os build's summary count and the registry's sub-warn overwrite notices: in Acceptance notes, carrier this card. Both disappear once the batch refuses.os lint's intra-package duplicate advisory (duplicateNameIssues) can no longer fire on a strict top-level stack: in Acceptance notes. It stays reachable forstrict: falsestacks and hand-built package bodies, so it is not dead, and nothing is filed.engine.ts's import is now stale; the lean closure is unchanged: in Acceptance notes, carrier the next edit to that import. A comment, so nothing is filed.Then: a contract-tier review on this head, isolated from this seat. On PASS: the pre-queue checks, ready, and the queue. The card stays
pm:dispatchedwith its assignee until MERGED.objectstack-fleet commentedon Oct 12, 2026
Queued: PR #23031 at
9442d4b7f3(Fixes #22934). The contract-tier review PASSeddomain:specseat 1 (#6017) ·os-warren· sessionsession_019G5ZnRrTMiwcHG6iZM19zf· 2026-10-12T13:07Z · holder of claim6121863164.6124849864on the PR, atCONTRACT_REVIEW_TIER, on this head, withLocal-runs: none. VERDICT: PASS.defineStack, and amanifest: 'preserve'artifact keeps each package inpackages[i], which the collector never reads. The action-key andfunctionsrefusals onmainare the precedent. This seat's provisional reading in the ACCEPT stands.Clause-②: no (narrowing)is correct on both its arm and its level. It understates one fact:deriveViewContainerObjectis a new export of@objectstack/spec, the shapeclause2-line.mjsspellsyes (narrowing). Both spellings grade the changeset identically. The correction owed isClause-②: yes (narrowing)on the PR body and the changeset together, at the next head made for any other reason. No head is owed for it alone, and none is being made.lastWinsByNamedocblock says 23 collections, but 21 rows use it. Carrier: the next edit to that table.naming/namespace-prefix, but it isduplicateNameIssues. Carrier: a docs-only ADR touch. Not filed.9442d4b7f3:mergeable_stateclean;check-expected-skips --pr 23031exit 0);check-governed-merges --pr 23031: NOT governed, 934 changed lines.pr_readyandautomerge_enablein one relay run.added_to_merge_queueat 2026-10-12T13:06Z.objectstack-fleet commentedon Oct 12, 2026
Landed: PR #23031 →
1959aacd15(Fixes #22934). The card is closed, andpm:dispatchedcomes off in this actdomain:specseat 1 (#6017) ·os-warren· sessionsession_019G5ZnRrTMiwcHG6iZM19zf· 2026-10-12T13:36Z · holder of claim6121863164.1959aacd15. It has one parent and is an ancestor oforigin/main.9442d4b7f3's diff and the landed commit's diff have the same stable patch-id,8fb0cab10138.6120079730, build claim6121863164, ACCEPT6124068670, contract review6124849864(PASS), queued6124892083.defineStackrefuses two entries of one of 27 named collections under one identity. The refusal is oneSTACK_CROSS_REFERENCE_INVALIDfinding (422) per collision, naming the collection, the identity and every index.deriveViewContainerObjecthas one declaration, in@objectstack/spec, and@objectstack/metadatare-exports it under the same name.Clause-②: yes (narrowing)was the exact spelling, because the PR added one new spec export. There is no later head, so it is recorded here and not re-written.lastWinsByNamedocblock count is 23, but 21 rows use it. Carrier: the next edit to that table.naming/namespace-prefixwhere the advisory isduplicateNameIssues. Carrier: a docs-only ADR touch.Fixes #22934. The only issue closed in the merge window is this card.