Skip to content

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

Closed

Description

@objectstack-fleet

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

Direction (not a ruling)

Dedupe

MCP search_issues on this repo, closed included:

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

  1. objectstack-fleet commented on Oct 12, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: first grade, bug · priority:p3 · domain:spec · area:devpath, into pm: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 diverges

    Triage seat (objectstack-wide, seat post #6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-12T04:57Z. ⛔ Not a claim, ⛔ not a dispatch.

  2. added
    area:devpathThe road — create, dev, verify, publish/install, connect an agent, iterate
    bugSomething isn't working
    and removed on Oct 12, 2026
  3. objectstack-fleet commented on Oct 12, 2026

    @objectstack-fleet
    ContributorAuthor

    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_019G5ZnRrTMiwcHG6iZM19zf
    Account: os-warren (the seat's linked user as GET /user answers it; the card's assignee from this act)
    Branch: claude/issue-22934-s0-duplicate-name-census
    Worktree: objectstack-issue-22934
    Domain: domain:spec
    Seat: 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 is Clause-②: 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 today
    Thread-read: 6118030391
    Serial constraints cleared:

    This act moves the card pm:queue → pm:dispatched and assigns os-warren.

  4. objectstack-fleet commented on Oct 12, 2026

    @objectstack-fleet
    ContributorAuthor

    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 name for 25 keys; extend for 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

  5. objectstack-fleet commented on Oct 12, 2026

    @objectstack-fleet
    ContributorAuthor

    ✅ ACCEPT, stage 0 (report 6120008386): 27 of 30 named collections measurably lose or diverge on a same-stack duplicate. The build follows PR #22931; transition pm:dispatched → pm:blocked on #22902

    domain:spec seat 1 (#6017) · os-warren · session session_019G5ZnRrTMiwcHG6iZM19zf · 2026-10-12T07:53Z · holder of claim 6119405831, released by this act. Thread-read: the report.

    Checked against GitHub:

    • The report is on this card.
    • claude/issue-22934-s0-duplicate-name-census on the remote is the base 246edf5b3 with no commit, and no PR exists for it. Stage 0 committed nothing, as ordered.
    • The code anchors the plan names exist on origin/main: collectDuplicateActionKeyErrors (stack.zod.ts:2196, pushed at :2947), deriveViewContainerObject (packages/metadata/src/view-container.ts:77), and the metadata service's EndpointMatcher over listForIndex('api').

    The readings, as the report gives them (os serve from source and os build → artifact boot, at 246edf5b3):

    • Lose (last wins on both paths), 23 keys: 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) and apps.
    • A census miss that loses too: objectExtensions. Two extensions of one target in one stack keep only the last.
    • Diverge, 3 keys:
      • 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 validate and os build refuse it (docs/duplicate-name), while os serve from source keeps the last.
    • Out of the batch, each with its reading:
    • Assumptions: A1 holds, with the docs and objectExtensions corrections. A2 is falsified: a same-stack duplicate object is not composeStacks' merge. Its default objectConflict: '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):

    • One frozen identity table plus one collector, collectDuplicateEntryErrors, in the cross-reference aggregate of packages/spec/src/stack.zod.ts, beside the action collector and PR feat(spec)!: refuse a duplicate name inside one stack’s functions array (#22902) #22931's.
    • One finding per collision, naming the key, the identity and every index; STACK_CROSS_REFERENCE_INVALID, 422.
    • deriveViewContainerObject moves into spec and is re-exported from @objectstack/metadata under the same name: one copy, not a second. That is a domain:engine file, 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:

    1. The config boot serves no declared apis endpoint (404 on os serve from source, matched on the artifact boot): filed serve (config boot): a stack's declared apis endpoints answer 404 ENDPOINT_NOT_FOUND on os serve from source, while an artifact boot of the same stack matches them — the metadata service holds no api items on config boots #22989.
    2. composeStacks misattributes 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.
    3. 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:blocked and not pm:queue: the build edits the lines of validateCrossReferences that PR #22931 (#22902) edits, and that collector is its template. Triage put it as "ordering, not a block". Blocked-by: #22902 is the mechanical way to hold that order: the unlock scan returns this card to pm:queue when #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:blocked with Blocked-by: #22902, unassigned. Resume-from: none (stage 0 pushed no commit). This act moves the card pm:dispatched → pm:blocked and removes the assignee os-warren.

  6. 4 remaining items

  7. objectstack-fleet commented on Oct 12, 2026

    @objectstack-fleet
    ContributorAuthor

    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 6120008386 build_plan and ACCEPT 6120079730) · 2026-10-12T09:52Z
    Session: session_019G5ZnRrTMiwcHG6iZM19zf
    Account: os-warren (the seat's linked user as GET /user answers it; the card's assignee from this act)
    Branch: claude/issue-22934-duplicate-entry-batch
    Worktree: objectstack-issue-22934
    Domain: domain:spec
    Seat: domain:spec#1 (seat post #6017)
    File surface (at origin/main 8e4a2302a or later; stop on breach and explain in the report):

    This act moves the card pm:queue → pm:dispatched and assigns os-warren.

  8. objectstack-fleet commented on Oct 12, 2026

    @objectstack-fleet
    ContributorAuthor

    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 'cli unit 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 the unit tier ran 369 passed and 1 failed, the fixture below, whose file passes 10 / 10 after the change; the 25 in the integration tier ran 190 / 190; the 26 *.e2e.test.ts files 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

  9. objectstack-fleet commented on Oct 12, 2026

    @objectstack-fleet
    ContributorAuthor

    ✅ ACCEPT: PR #23031 at 9442d4b7f3 (Fixes #22934). Next: the contract-tier review, then the queue

    domain:spec seat 1 (#6017) · os-warren · session session_019G5ZnRrTMiwcHG6iZM19zf · 2026-10-12T12:14Z · holder of claim 6121863164. 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 in validateCrossReferences beside the action-key and functions checks, 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.
    • The identities:
      • name for 24 collections.
      • name@locale for emailTemplates, with an untagged entry counting as the floor tag.
      • extend for objectExtensions.
      • For views: an aggregated container uses the object it binds to; any other entry uses its name, falling back to the same derivation.
      • An entry with no readable identity is not counted.
      • Out of the table, each with its reason in the docblock: plugins, agents, picklistExtensions, actions, functions.
    • deriveViewContainerObject now has one declaration, packages/spec/src/ui/view-container-object.ts, with no imports. The body is byte-for-byte the removed one from packages/metadata/src/view-container.ts. Metadata re-exports it under the same name from its root and its /view-container subpath. The cost is stated in that header: the subpath now loads the spec root entry, and check:lean-entry-closure's admitted set for @objectstack/objectql/core is unchanged.
    • The pin, stack-duplicate-entry.test.ts, covers:
      • every row, refused with the code, the status and the message;
      • the controls: distinct identities, the email-template i18n bundle, the map form, and the out-of-table collections;
      • the cross-collection aggregate and the object-less stack.
    • The CLI fixture in permission-set-name-collisions.test.ts moves to strict: false. Its comment says why: the door it pins must still not misreport a non-strict or pre-refusal artifact.

    Checklist:

    • Body: first line Fixes #22934, correct for the whole card. Clause-②: no (narrowing), as the build sheet set it.
    • Changeset: @objectstack/spec minor, marked BREAKING under the launch-window convention, with a FROM → TO table. @objectstack/metadata patch. The ADR-0087 marker names the D3 semantic entry stack-collection-duplicate-entry-refused (protocol 18), which has no D2 conversion.
    • Scope: no content/docs/releases/, no governed path. The two domain:engine files (packages/metadata/**) are the cross-lane surface declared in the claim.
    • Size: 934 changed lines, under the human-merge threshold.
    • mcp_calls 0. 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_state clean, draft.

    Report items, one line each:

    • The dev's PR body correction (the Consumer radius bullet: the cli unit 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.
    • The emailTemplates identity is (name, locale), not name. This is a measured correction of the build sheet: the spec's own i18n-bundle pins and ITEM_KEY_DISCRIMINATORS key 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.
    • The main merge 9442d4b7f was made before the PR opened. The contract review reads this head.
    • Open question for the contract review: ADR-0048 §3.4. That section retires the cross-package throw. It keeps same-package re-registration as an overwrite (an idempotent reload), and it says an author shipping two page/home in one package "stays covered by" the os lint advisory. This PR refuses that authored case at the contract. The registry's overwrite on reload is untouched.
      • The seat's provisional reading is the dev's A: a reading, not a reversal. §3.4 decides runtime behaviour, which is unchanged. Its lint sentence says where hygiene lived; it does not make the contract accept duplicates. The action-key refusal is the standing precedent.
      • The contract reviewer rules on it. If the reviewer reads it as a reversal (B, a status line on a Tier H file), the PR does not queue until the maintainer decides.
    • Out-of-scope findings, as the report carries them:
      1. composeStacks misattributes a same-stack duplicate object: in Acceptance notes, carrier this card. The batch removes the authored path to it.
      2. 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.
      3. os lint's intra-package duplicate advisory (duplicateNameIssues) can no longer fire on a strict top-level stack: in Acceptance notes. It stays reachable for strict: false stacks and hand-built package bodies, so it is not dead, and nothing is filed.
      4. The "LEAF subpath" comment at objectql 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:dispatched with its assignee until MERGED.

  10. objectstack-fleet commented on Oct 12, 2026

    @objectstack-fleet
    ContributorAuthor

    Queued: PR #23031 at 9442d4b7f3 (Fixes #22934). The contract-tier review PASSed

    domain:spec seat 1 (#6017) · os-warren · session session_019G5ZnRrTMiwcHG6iZM19zf · 2026-10-12T13:07Z · holder of claim 6121863164.

    • Contract review: 6124849864 on the PR, at CONTRACT_REVIEW_TIER, on this head, with Local-runs: none. VERDICT: PASS.
      • ADR-0048 §3.4 was ruled a READING (option A). No amended status line and no Tier H landing are owed. §3.4 governs the registry door, and this diff touches no registry file. Cross-package coexistence cannot reach the collector at all: it has one call site inside defineStack, and a manifest: 'preserve' artifact keeps each package in packages[i], which the collector never reads. The action-key and functions refusals on main are the precedent. This seat's provisional reading in the ACCEPT stands.
      • A declaration finding, not a FAIL: Clause-②: no (narrowing) is correct on both its arm and its level. It understates one fact: deriveViewContainerObject is a new export of @objectstack/spec, the shape clause2-line.mjs spells yes (narrowing). Both spellings grade the changeset identically. The correction owed is Clause-②: 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.
      • A comment nit: the lastWinsByName docblock says 23 collections, but 21 rows use it. Carrier: the next edit to that table.
      • An observation outside this PR: §3.4 names the intra-package duplicate advisory as naming/namespace-prefix, but it is duplicateNameIssues. Carrier: a docs-only ADR touch. Not filed.
    • Pre-queue checks on 9442d4b7f3:
      • mergeable_state clean;
      • 37 success and 5 skipped, every skip in the roster (check-expected-skips --pr 23031 exit 0);
      • check-governed-merges --pr 23031: NOT governed, 934 changed lines.
    • Queue: pr_ready and automerge_enable in one relay run. added_to_merge_queue at 2026-10-12T13:06Z.
    • Then: the landing record at MERGED, with a content check (patch-id) and a scan for wrongly closed issues. The assignee stays until then.
  11. objectstack-fleet commented on Oct 12, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed: PR #23031 → 1959aacd15 (Fixes #22934). The card is closed, and pm:dispatched comes off in this act

    domain:spec seat 1 (#6017) · os-warren · session session_019G5ZnRrTMiwcHG6iZM19zf · 2026-10-12T13:36Z · holder of claim 6121863164.

    • Landed through the merge queue at 2026-10-12T13:35Z as 1959aacd15. It has one parent and is an ancestor of origin/main.
    • Content: the reviewed head 9442d4b7f3's diff and the landed commit's diff have the same stable patch-id, 8fb0cab10138.
    • The chain: stage-0 ACCEPT 6120079730, build claim 6121863164, ACCEPT 6124068670, contract review 6124849864 (PASS), queued 6124892083.
    • What now holds: strict defineStack refuses two entries of one of 27 named collections under one identity. The refusal is one STACK_CROSS_REFERENCE_INVALID finding (422) per collision, naming the collection, the identity and every index. deriveViewContainerObject has one declaration, in @objectstack/spec, and @objectstack/metadata re-exports it under the same name.
    • Carried forward, from the contract review:
      • 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.
      • The lastWinsByName docblock count is 23, but 21 rows use it. Carrier: the next edit to that table.
      • ADR-0048 §3.4 names naming/namespace-prefix where the advisory is duplicateNameIssues. Carrier: a docs-only ADR touch.
    • Mis-close scan: the body's only closing keyword is Fixes #22934. The only issue closed in the merge window is this card.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area:devpathThe road — create, dev, verify, publish/install, connect an agent, iteratebugSomething isn't workingdomain:specpriority:p3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions