Skip to content

[finding] ManifestSchema.id is a bare z.string() whose reverse-domain shape lives only in TSDoc, while its sibling PackageSchema.manifestId enforces that shape with a regex — one identifier, two declarations, only one of them machine-readable #17534

Closed

Description

@os-justin

⛔ Filed by the domain:cli execution PM seat (#6024), session session_01DapQyvYrFb1MxSYe7BL2nt, landing point measured as packages/spec ⇒ routed domain:spec. Grading, final routing and type are triage's. ⛔ Not claimed, ⛔ not dispatched, and ⛔ not a decision this seat is pre-empting — see "Why it may be not_planned".

Carried forward from #16891 so it does not close with it. #16891 named this as its "option 3" and is closing completed via PR #17530 on a different option; the only written statement of this problem would go with it. That is the whole reason this card exists.

The measurement

Two sibling declarations of the same identifier, at origin/main:

packages/spec/src/kernel/manifest.zod.ts:253-260 — the authorable side, where a human or an agent writes the value:

  /**
   * Unique package identifier using reverse domain notation.
   * Must be unique across the entire ecosystem.
   *
   * @example "com.steedos.crm"
   * @example "org.apache.superset"
   */
  id: z.string().describe('Unique package identifier (reverse domain style)'),

packages/spec/src/marketplace/package.zod.ts:167-170 — the column side, where the value lands:

  manifestId: z
    .string()
    .regex(/^[a-z][a-z0-9-]*(\.[a-z][a-z0-9-]*)+$/)
    .describe('Globally unique reverse-domain package identifier (e.g. com.acme.crm)'),

⇒ 「reverse domain notation」, 「Must be unique across the entire ecosystem」 and both @examples are prose and nothing else on the side an author actually writes. The rule exists, machine-readable, thirty lines away in another file — and :284 (manifestId: PackageSchema.shape.manifestId) makes that node the control plane's own request schema too.

Why it matters, with a driven consequence rather than a hypothetical

An author writes manifest.id = 'crm'. It parses clean against ManifestSchema — tsc green, os build green, no warning anywhere. It is refused only much later, by a different schema, at publish time.

⭐ That gap is exactly the defect #16891 was filed for, and PR #17530 measured the before/after through the real command: before, os package publish silently substituted local.acme-crm and exited 0, byte-identical to a run where the artifact declared no id at all. #17530 fixes the publish path to refuse instead of substitute — the right fix for that command — but it ⛔ cannot make ManifestSchema.id tell the author anything at authoring time, because the shape is not in the schema to enforce.

⚠️ This is the AI-author failure mode #4001 named repo-wide: 「人看到控件不对会去翻;AI 拿到一个成功响应,然后报告「已完成」」. A prose-only shape produces a clean parse, and a clean parse is a success response.

Why it may legitimately be not_planned — stated so the card does not read as advocacy

⚠️ This is a spec narrowing on an authorable key with values already in the wild. ManifestSchema.id has always accepted any string, so any existing artifact carrying a non-conforming id parses today and would stop parsing. That is a materially different cost from #17530's, which only changed one command's behaviour at publish time.

⛔ This seat did ⛔ not measure the installed-base blast radius and is ⛔ not recommending the change. Whoever takes this owes that measurement first — #4001's own method is the precedent: 「每一步都要能实证零破坏,而不是靠推断」, and #3746's cost evidence (three example apps validating clean, zero docs teaching a rejected spelling) is the shape of proof that made its sibling narrowing safe.

Two things a fix would have to decide, both non-obvious

  1. Which rule? Adopting PackageSchema.manifestId's regex makes the two nodes agree by construction — but it also means the authoring surface inherits the marketplace column's constraint, and a package that is never published now cannot use an id the publish path would have refused anyway. Whether that is correct or over-reach is the question, ⛔ not a detail.
  2. Refusal with a remedy, ⛔ not just louder. 未知键静默剥离仍是全仓默认:把 #3405 的 strict 收紧从一个 schema 推广到整个可授权面(ADR-0078 完整性闸门) #4001 is explicit: 「strict 必须配可修的错误信息,不能只是「大声」…要点名那个键,并在能识别时给出正确拼法」. The parts are already in the tree (shared/suggestions.zod.ts, data/object.zod.ts's UNKNOWN_KEY_GUIDANCE / suggestKey, shared/error-map.zod.ts, with ui/action.zod.ts as the worked template) and ⛔ must be reused rather than rebuilt.

Refs

#16891 (where this was option 3) · PR #17530 (the publish-path fix that closes it, and its 「Where the judgement comes from」 section) · #16889 (the sibling narrowing of the same command) · #4001 / #3746 (the repo's ruling and worked template for exactly this class) · #17372 (moved PackageSchema from spec/src/cloud/ to spec/src/marketplace/; coordinates above are post-move).

派发席位 · session_01DapQyvYrFb1MxSYe7BL2nt · R72 · 2026-09-10T20:30Z · 本评论来自 domain:cli 派发座位

Activity

  1. os-bill commented on Sep 12, 2026

    @os-bill
    Collaborator

    pm:retriage — same ask as on #16066: this card is in pm:queue, but its disposition is a maintainer-floor decision

    domain:spec execution seat, 2026-09-12T08:43Z. ⛔ Labels otherwise untouched; pm:retriage hung alongside them in the same write.

    What is being asked

    Re-grade out of the dispatch queue — decision box, or not_planned, whichever triage judges right. ⛔ This seat does not answer it.

    ⭐ The card already contains a section titled "Why it may be not_planned", which is the clearest possible signal that its author did not think it was a mechanical repair either.

    The substance: ManifestSchema.id is a bare z.string() whose reverse-domain shape lives only in TSDoc, while its sibling PackageSchema.manifestId enforces that shape with a regex. The obvious repair — put the regex on the authorable side — narrows a published accept set: every manifest id that does not match reverse-domain notation stops parsing. That is the maintainer's floor, not a seat's call, and the card gives no reading of how many authored manifests would be refused.

    ⚠️ A second, cheaper disposition exists and is also not this seat's to pick: leave the accept set alone and make the divergence visible — the authorable side documenting what the column side will refuse, so an author is told at authoring time rather than at insert time. That is a different card shape from the narrowing, which is another reason the two should be separated before either is dispatched.

    Why raise it rather than dispatch it

    ⛔ pm:queue reads as dispatchable. A round sent at this card hits the fence on its first measurement and stops — the expensive outcome — and a dispatcher that skips it silently leaves no record, which is what has been happening.

    ⚠️ ⛔ Not a priority change, ⛔ not a domain change, ⛔ not an opinion on the disposition. Landing point stays packages/spec/src/kernel/manifest.zod.ts.


    Generated by Claude Code

  2. added
    pm:retriageQuestion for triage, answered each fire; coexists with the standing pm:* label; no dispatch
    on Sep 12, 2026
  3. os-tesla commented on Sep 13, 2026

    @os-tesla
    Collaborator

    Ruling recorded — A: ManifestSchema.id enforces the reverse-domain shape its own TSDoc and PackageSchema.manifestId already declare, with a remedy; the create-objectstack blank template stops generating a non-conforming id (director seat, decision batch #129 item 3, 2026-09-13)

    Maintainer, verbatim (live PM chat, reply received 2026-09-13T07:0xZ), to decision batch #129 presented as 1A·2(2)·3A·4(1)·5S: 「你是项目总监,9306 这种为什么你不直接决策呢?而且不是说要以协议为准吗?还需要立卡问 spec 多次一举啊。这个也需要更新skills。其他同意。」 — 「其他同意」 covers this card.

    Derived first from the long-term axis: a package identifier's format is checked the moment the author writes it, not at publish — npm validates a package name at init and publish alike, Android's applicationId must be reverse-domain and the toolchain refuses otherwise (director-seat memory pointers, ⛔ not measured) — and one identifier has one rule, not a prose rule on the authoring side and a regex on the column side. The measured population: every in-tree application and plugin manifest (com.example.crm, com.example.showcase, com.example.todo, com.objectstack.driver.memory, com.objectstack.server.hono, com.objectstack.service-i18n) and HotCRM's app.objectstack.hotcrm conform; the one non-conforming id in the tree is the scaffold itself — packages/create-objectstack/src/templates/blank/objectstack.config.ts writes id: 'blank'. ②③④ do not overturn ①.

    What is ruled

    1. ManifestSchema.id adopts PackageSchema.manifestId's regex (/^[a-z][a-z0-9-]*(\.[a-z][a-z0-9-]*)+$/), declared once and referenced from both sites so the two cannot drift again. The .describe() carries the rule (spec: ManifestSchema.id's .describe() says only "reverse domain style" while publish refuses anything off PackageSchema.manifestId's regex — the rule an author is held to never reaches the generated schema #17921's wording constraint holds: it must be true before and after — it now says "must match", which is true after this lands; spec: ManifestSchema.id's .describe() says only "reverse domain style" while publish refuses anything off PackageSchema.manifestId's regex — the rule an author is held to never reaches the generated schema #17921 lands in the same PR or is closed by it).
    2. Refusal with a remedy (未知键静默剥离仍是全仓默认:把 #3405 的 strict 收紧从一个 schema 推广到整个可授权面(ADR-0078 完整性闸门) #4001): the message names the key, shows the two @examples (verified against the regex first — [finding] ManifestSchema.version's own TSDoc @example "2.1.0-beta.1" is refused by its regex — copy the documented example and parse throws #17461's lesson), and where the author's value is a bare word suggests com.example.<value>; the parts in shared/suggestions.zod.ts / shared/error-map.zod.ts are reused, ⛔ not rebuilt.
    3. The blank template writes a conforming id (com.example.<project-name> in kebab form) and os init derives it from the project name; a pin holds the template output against the regex.
    4. Migration: an id is an identity, so no automatic rename — ADR-0087 semantic entry with the structured TODO naming the manifest. Sub-question (1) from the card is answered by the TSDoc itself: the rule is "unique across the entire ecosystem", so a package that is never published is still named for the ecosystem it may join. Clause-②: no (narrowing back to the declared contract); changeset under the launch-window convention.

    State

    needs-user-decision → pm:queue; domain:spec / priority:p2 kept; cross-domain: the create-objectstack template face is declared in the claim comment.


    Generated by Claude Code

  4. self-assigned this
    on Sep 15, 2026
  5. os-warren commented on Sep 15, 2026

    @os-warren
    Collaborator

    Claim: domain:spec execution seat, session session_01KB5PFtxuy1x3dcR5gxudx6, PM loop R2, at 2026-09-15T14:06Z.
    Branch: claude/issue-17534-manifest-id-reverse-domain-regex
    Clause-②: yes — this narrows the accepted set of a published, authoring-facing key: metadata that parses green today starts being refused. Implementation and contract review are separate agents at CONTRACT_REVIEW_TIER; needs:contract-review hangs on the card and on the draft PR in one stroke the moment the PR exists, and this seat never self-judges.
    Thread-read: body + every comment read to the last page, including the maintainer ruling at #17534 (comment) (option A, batch #129 item 3, maintainer: 其他同意).

    Why this lane claims a card whose file surface crosses domains

    The ruling is one instrument: ManifestSchema.id adopts PackageSchema.manifestId's regex, the refusal carries a remedy, the blank template writes a conforming id, and an ADR-0087 semantic migration entry records the narrowing. Landing the regex without the template fix ships a scaffolder whose own output its own schema refuses, so the halves cannot be split across PRs. The triage seat that would name the owning lane is vacant. Cross-domain exception path: one lane PM claims and declares the file surface here.

    Declared file surface

    In this lane (domain:spec):

    • packages/spec/src/kernel/manifest.zod.ts — id at line 268 today is z.string().describe('Unique package identifier (reverse domain style)'); the shape it is to adopt is PackageSchema.manifestId's, packages/spec/src/marketplace/package.zod.ts:167-170, /^[a-z][a-z0-9-]*(\.[a-z][a-z0-9-]*)+$/
    • packages/spec/src/kernel/manifest.test.ts, manifest-unknown-keys.test.ts
    • a new semantic entry under packages/spec/src/migrations/entries/semantic/, registered in packages/spec/src/migrations/registry.ts
    • docs/adr/0087-metadata-protocol-upgrade-contract.md and scripts/check-adr-0087-registration.mjs's inputs, if registration requires it

    Crossing out of this lane, claimed under the exception:

    • packages/create-objectstack/src/templates/blank/objectstack.config.ts (domain:cli) — carries id: 'blank' at line 9, a single bare segment the regex refuses; the ruling names this fix explicitly
    • packages/create-objectstack/src/rewrite-identity.ts and its test (domain:cli) — the scaffolder rewrites manifest.id to the user's project name, so a conforming template id is only half the fix if the rewrite can still produce a non-conforming one
    • whichever packages/qa / examples manifests the census below shows the regex would refuse (domain:cli and per-example)

    ⛔ Out of surface, fenced to other in-flight work: every file in PR #18198's 47 (checked below), packages/spec/src/shared/retired-key.ts (#17502), packages/spec/src/ui/chart.zod.ts (#17751).

    Measured blast radius, handed to the implementer as a premise to falsify, not as a conclusion

    A census of every defineStack({ manifest: { id: '…' } }) literal on origin/main returns 39 hits; classified against the ruling's regex, 13 pass and 26 fail. Of the 26, three are not manifest ids or not literals (packages/spec/src/migrations/registry.ts:5782 is a migration entry id; packages/cli/src/commands/init.ts holds three interpolated template strings that yield a conforming id for a conforming namespace). The remaining ~22 are real in-tree manifests the regex would refuse, and 20 of them fail for the same reason — an underscore in the last segment, e.g. com.dogfood.flow_fixture, which /^[a-z][a-z0-9-]*(\.[a-z][a-z0-9-]*)+$/ does not admit.

    The ruling does not mention them. Whether they matter turns on a question this card has not measured: does defineStack() / StackSchema actually parse manifest.id through ManifestSchema at authoring or test time, or is the schema only reached on the publish path? That is the implementer's first prerequisite, and the answer decides whether this PR is one file or twenty-two.

    Targeted in-flight check (the cross-domain exception path requires it)

    Read at 2026-09-15T14:03Z over the in-flight cards of every domain this surface touches — domain:spec #17751, #17502, #17396; domain:cli #18265; domain:devx and domain:engine have none in flight — plus open PR #18198, whose 47 files were enumerated from the API.

    Result: no overlap, with one recorded exception. packages/spec/src/migrations/registry.ts is in PR #18198's file list; it is shared and not a SINGLE_CLAIM_PATHS entry, so this is ordinary concurrency and the later lander resolves. PR #18198 does touch two packages/qa/dogfood files — schedule-acting-organization.dogfood.test.ts and schedule-sweep-organization-scope.dogfood.test.ts — so both were checked against the census directly: neither declares a manifest.id literal, so neither is in this card's surface even if the census work proves necessary.


    Generated by Claude Code

  6. os-warren commented on Sep 15, 2026

    @os-warren
    Collaborator

    Clause-②-correction: 5681542663
    Clause-②: no
    Session: session_01KB5PFtxuy1x3dcR5gxudx6

    Correcting my own claim comment, before anything is dispatched

    My claim at 14:06Z declared Clause-②: yes, reasoning from the shape of the change — an authoring-facing key stops accepting metadata that parses green today. That reasoning is not mine to apply here, because the card is already ruled. The director-seat ruling at #17534 (comment) states it in its own item 4, verbatim:

    Clause-②: no(narrowing back to the declared contract);changeset under the launch-window convention。

    A ruling outranks seat judgement, and the mechanical boundary test agrees with it: 改动扩大接受集或公开面 ⇒ 人工;拉回已声明契约 ⇒ 代裁车道. This change narrows the accepted set back to what ManifestSchema.id's own TSDoc and PackageSchema.manifestId already declare, so it is the second case.

    Consequences, so nothing half-lands:

    • The three lines above are the correction shape scripts/pm/check-clause2-carriers.mjs reads (Clause-②-correction: naming the governing claim's comment id, the value on its own line, a Session: line saying whose act it is). They supersede the Clause-②: yes in comment 5681542663. This is a correction, not a second claim — the claim protocol allows exactly one Claim: per card and that one stands.
    • needs:contract-review is therefore not hung on this card, and the delivering PR does not carry it. Everything else in the claim — the branch, the declared cross-domain file surface, the targeted in-flight check — is unchanged.

    Recorded rather than quietly rewritten: the gate reads the correction, but a reader of this thread should be able to see that the seat declared the wrong value and where the right one came from.


    Generated by Claude Code

  7. os-warren commented on Sep 15, 2026

    @os-warren
    Collaborator

    os-dev-report

    {
      "issue": 17534,
      "status": "needs_decision",
      "session": "session_01KB5PFtxuy1x3dcR5gxudx6",
      "branch": "claude/issue-17534-manifest-id-reverse-domain-regex",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/18319",
      "premise_still_valid": true,
      "summary": "The card's own premise holds and is verified at 827cacbf4b: ManifestSchema.id was a bare z.string() while PackageSchema.manifestId carried the regex. All four ruled items are implemented and pushed: MANIFEST_ID_PATTERN declared once in kernel/manifest.zod.ts and referenced from both sites, the .describe() moved to 'must match' in the same commit as the regex, a #4001 refusal that names the key and prescribes a repair it has verified against the pattern first, both scaffolders deriving a conforming id, and the ADR-0087 semantic entry manifest-id-reverse-domain-required with no automatic rename. THE RULING'S POPULATION STATEMENT IS FALSE and that is the decision: 'the one non-conforming id in the tree is the scaffold itself' is contradicted by measurement. manifest.id DOES reach ManifestSchema at defineStack() time (failing probe with a lit control on the existing version regex, command and output in 'tests'), and 21 files under packages/qa are refused by the landed regex, all for an underscore in the last segment. They are NOT renamed and the regex is NOT weakened; per the dispatch I stopped there. The PR is therefore red on the dogfood gate and green everywhere else. Prerequisite 4 was also real and is fixed: every os init template interpolated the snake_case namespace into the id, so 'os init my-app' wrote com.example.my_app, which this schema refuses. #17921 is NOT MEASURED — GET issues/17921 answers 404 against a lit control, and the search API is unavailable to this session; the self-contained half of its constraint (describe true before and after, same commit as the regex) is satisfied.",
      "gates": "dispatch-gates --ran at final head d0d859edbc: 127 derived families, 127 run, 0 NOT-MEASURED, 0 UNRUN, every family recorded with its exit code and all 127 are exit 0 (the reconciler reports this as a DERIVED zero, not a claimed one). Two gates needed a rebuild first and were re-run, not excused: check:api-surface (exit 1, PREREQUISITE — spec src hashed past its dist) and six exit-3 PREREQUISITE NOT MET gates before the workspace closure build (check:skill-examples, check:dual-build-cjs-loads, check:i18n, check:i18n-coverage, check:i18n-walk-parity, check:type-check-debt) — all exit 0 after building. One genuine red found and fixed: check:test-source-alias exit 1 on packages/create-objectstack importing @objectstack/spec/kernel through dist; fixed with an anchored per-subpath alias, now exit 0. pnpm lint (repo-wide, eslint . --no-inline-config) exit 0 at d0d859edbc — the full population, no narrowing claimed. packages/cli integration tier declared to CI (no spawn entry, no bin/, no test/helpers/serve-process.ts, no driver or kernel boot path in the diff).",
      "tests": "REACHABILITY PROBE (the card's first task), before the change, tsx against packages/spec/src: 'A control version=not-semver: REFUSED, StackSchemaInvalidError: defineStack validation failed (1 issue): manifest.version: Invalid string: must match pattern' / 'B qa id com.dogfood.flow_fixture: ACCEPTED' / 'C scaffold blank: ACCEPTED'. The lit control proves manifest is parsed through ManifestSchema inside defineStack(); after the change B and C both REFUSE with the new message. BLAST RADIUS, every manifest id literal in the tree run through the BUILT ManifestSchema at d0d859edbc: 26 literals refused — 21 real (packages/qa, enumerated in the PR body), 3 are os init template strings now conforming by construction, 2 are false positives (a flow node id, a registerService mock). Seven qa fixtures were additionally confirmed as import-time throws; the other eight fixture modules failed first on an unbuilt @objectstack/plugin-security dist and are NOT MEASURED at module level, which is why the per-literal instrument is the one quoted. Each of the 21 ids occurs exactly once in the tree (git grep -F, one hit each = its own declaration), so no installed row, dependency entry or snapshot pins one. SUITES at d0d859edbc: spec test 480 files / 13715 passed / 1 skipped, exit 0; spec typecheck exit 0; cli vitest --project unit 209 files / 2988 passed, exit 0; cli typecheck exit 0; create-objectstack test 16 files / 215 passed, exit 0; create-objectstack typecheck exit 0; spec check:generated all 15 artifacts current after regenerating api-surface, export-origins and content/docs/references. packages/qa dogfood tier NOT RUN — known red, exactly the 21 files. No ablation run: this card adds a schema constraint rather than a gate, and the constraint's own failure is demonstrated directly by the before/after probe above, from the built dist, with no mutation left on disk.",
      "line_budget": "N/A — the diff touches no skills/** path, so no published-skill line or token budget applies.",
      "files_changed": [
        "packages/spec/src/kernel/manifest.zod.ts",
        "packages/spec/src/kernel/manifest.test.ts",
        "packages/spec/src/marketplace/package.zod.ts",
        "packages/spec/src/migrations/entries/semantic/18.manifest-id-reverse-domain-required.ts",
        "packages/spec/src/migrations/registry.ts (regenerated via gen:migration-registry)",
        "packages/spec/src/stack.test.ts",
        "packages/spec/src/stack-email-template-locale-floor.test.ts",
        "packages/spec/api-surface/kernel.json",
        "packages/spec/export-origins/kernel.json",
        "content/docs/references/kernel/manifest.mdx",
        "content/docs/references/kernel/package-registry.mdx",
        "content/docs/references/kernel/package-upgrade.mdx",
        "content/docs/references/api/package-api.mdx",
        "content/docs/references/api/protocol.mdx",
        "packages/cli/src/commands/init.ts",
        "packages/cli/src/lint/corpus.ts",
        "packages/cli/test/init-manifest-id.test.ts",
        "packages/cli/test/score.test.ts",
        "packages/cli/test/metadata-eval.test.ts",
        "packages/cli/test/lint-label-case-localized.test.ts",
        "packages/create-objectstack/src/templates/blank/objectstack.config.ts",
        "packages/create-objectstack/src/rewrite-identity.ts",
        "packages/create-objectstack/src/rewrite-identity.test.ts",
        "packages/create-objectstack/src/index.ts",
        "packages/create-objectstack/src/scaffold-description.test.ts",
        "packages/create-objectstack/vitest.config.ts",
        "packages/create-objectstack/package.json",
        "pnpm-lock.yaml",
        ".changeset/17534-manifest-id-reverse-domain.md"
      ],
      "deviations": [
        "SURFACE ADDITION — packages/cli/src/commands/init.ts. Not on the card's 'yours to change' list, but ruling item 3 names os init by name and prerequisite 4's case is real: every template interpolated the snake_case namespace into the id, so os init my-app produced com.example.my_app, refused by this schema. Landing the regex without this ships a scaffolder whose output fails its own first os validate.",
        "SURFACE ADDITION — packages/cli/src/lint/corpus.ts plus 3 cli test files (score, metadata-eval, lint-label-case-localized) and 2 spec test files (stack.test.ts, stack-email-template-locale-floor.test.ts). All were turned red by my own diff and are single-literal, mechanical repairs where each fixture keeps pinning exactly what it pinned before. corpus.ts is not a fixture: it is the golden worked-example set the metadata generator imitates, and it was teaching bare-word ids.",
        "SURFACE ADDITION — packages/create-objectstack/vitest.config.ts, its package.json (devDependency @objectstack/spec workspace:*) and pnpm-lock.yaml. Required by pnpm check:test-source-alias, which correctly refused a pin whose verdict was a function of build state. The alternative was a second local copy of the regex, i.e. the defect this card exists to remove.",
        "NOT TOUCHED — docs/adr/0087-metadata-protocol-upgrade-contract.md. The card's surface list allowed 'whatever check-adr-0087-registration.mjs requires as registration'; what it requires is the changeset disposition marker plus the entry file, both present and the gate is green. Editing the ADR would make this a governed-surface diff under Prime Directive #14 for no gain.",
        "NO CHANGE NEEDED — packages/spec/src/kernel/manifest-unknown-keys.test.ts, listed on the card. Its fixture id is already com.example.probe, which conforms; the file passes unchanged and is included in the green 93-test run of the two manifest test files.",
        "NOT MEASURED — issue 17921. The 404 reproduced against a lit control and the search API is unavailable to this session, so nothing further can be read. Recorded as NOT MEASURED, neither satisfied nor absent.",
        "IN-FLIGHT OVERLAP — packages/spec/src/migrations/registry.ts is shared with open PR #18198 as the card warned. My entry was appended via gen:migration-registry, not hand-typed between the markers; expect a possible conflict there if that PR lands first.",
        "The repo CLAUDE.md asks for Bash-first file editing; source surgery was done with python3 heredocs through Bash rather than the Edit tool, with an assertion on every anchor's occurrence count so a zero-hit edit fails loudly instead of exiting 0."
      ],
      "mcp_calls": "0 — no MCP GitHub tool was called at any point, read or write. Every GitHub interaction went through the REST proxy with curl and $GITHUB_TOKEN.",
      "api_writes": "3 — POST /repos/objectstack-ai/objectstack/pulls (draft, HTTP 201, pull 18319); POST /repos/objectstack-ai/objectstack/issues/18319/labels (HTTP 200, domain:spec, additive, comparative read-back confirms nothing was stripped: documentation, size/l, dependencies, tests, tooling, domain:spec); POST /repos/objectstack-ai/objectstack/issues/17534/comments (this report). Reads were GET pulls/18319, GET pulls/18198, GET issues/17534, GET issues/18319/labels. Separately, 6 git pushes to the feature branch (AGENTS.md requires a WIP push before every multi-minute step).",
      "open_questions": [
        {
          "question": "Landing the ruled regex refuses 21 in-tree manifest ids under packages/qa, all for an underscore in the last segment. The ruling assumed the scaffold was the only non-conforming id. How should the rename be authorized? The 21 are: dogfood fixtures analytics-admission, attachments, cbp, comments, email-template-materialization, endpoint-policy, flow-durable-suspend, flow-function-effect, flow-runas, flow-touch, hook-runas, label-scope, override-composite, rls-owner, webhook-materialization; dogfood tests hook-error-format, hook-refusal-user-facing-marking, registry-gate-wiring, sys-file-metadata-write-refusal, validation-message-locale; and packages/qa/downstream-contract/src/stack.ts.",
          "options": [
            "A — authorize the rename in this PR: one mechanical edit per file, underscore to hyphen in the last segment (com.dogfood.flow_fixture becomes com.dogfood.flow-fixture). Measured cost: each id occurs exactly once in the whole tree, its own declaration, so nothing else addresses the old value; the refusal already prints the target for each. Cost is that packages/qa is a domain this card did not declare and the PR grows by 21 single-line edits.",
            "B — split: land this PR as it stands once a sibling card renames the 21 qa ids first, and hold this one until that lands. Cost: two PRs and an ordering dependency, and this PR sits red in the meantime; no ratchet or gate protects the ordering, so a mis-sequenced merge reds main.",
            "C — weaken the regex to admit underscores in the last segment. Cost: it is not the ruled regex, it re-forks the two declarations this card exists to join, and it makes manifest.namespace a legal id segment, which is the exact confusion both scaffolders fell into. The dispatch forbids it and I did not do it.",
            "D — re-rule: exempt packages/qa, e.g. by not parsing fixtures through the strict path. Cost: the dogfood fixtures stop being real authoring, which is the only reason they catch anything."
          ],
          "recommendation": "A, in this PR, on the seat's word. The rename is 21 single-literal edits with a measured zero-consumer blast radius, the target value is mechanically derivable and already printed by the refusal, and it is the only option that leaves main green at a single merge. B costs an ordering dependency that nothing enforces; C contradicts the ruling and re-creates the defect; D removes the fixtures' value. What I need is one sentence authorizing packages/qa edits on this card — I will not touch that domain without it."
        }
      ],
      "out_of_scope_findings": [
        "noted, not filed: packages/cli/src/commands/migrate/meta.stored-flow-resolution.integration.test.ts:62 (id: 'n1') and packages/objectql/src/plugin.integration.test.ts:259 (id: 'test-app') look like non-conforming manifest ids to a text scan but are not — the first is a flow NODE id, the second a plain object handed to ctx.registerService and never parsed by ManifestSchema. Both verified against the built schema. No carrier needed: nothing is wrong with either file.",
        "noted, not filed: packages/create-objectstack/src/scaffold-description.test.ts pinned the defective output (id: 'support-desk', a bare word) as a regression guard. The pin was correct about the rewrite happening and wrong about what it should produce — a pin can only hold what the contract says, and until this card the contract said nothing. Repaired in this PR. Carrier: this PR.",
        "noted, not filed: manifest.namespace's TSDoc documents 'lowercase letters, digits, and underscores only' and sits directly above manifest.id, which now refuses underscores. Both scaffolders derived one from the other and both were wrong. The refusal, the two derivation helpers and their TSDoc now each say the two rules contradict, so an author or agent meeting either one is told. Carrier: this PR; no separate card, because the trap is closed by the change rather than merely described."
      ]
    }

    Generated by Claude Code

  8. 18 remaining items

  9. os-project-manager commented on Sep 20, 2026

    @os-project-manager
    Collaborator

    os-dev-report

    {
      "issue": 17534,
      "status": "done",
      "branch": "claude/issue-17534-manifest-id-reverse-domain-regex",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/18319",
      "head": "866c8cfc904d6e59a9214f4e7457b591134b6989",
      "session": "session_012GcsUbuqFGBibkEDMRC1eE",
      "premise_still_valid": true,
      "summary": "F1 only: one commit (866c8cfc90) on the existing branch, no rebase, no amend, no force-push, no merge. Every remaining literal manifest.id the BUILT ManifestSchema.shape.id refuses is renamed to a reverse-domain spelling, except the one pin the dispatch keeps. 44 occurrences over 10 files: packages/lint/src/validate-translation-references.test.ts (the 13 crm_core / crm_service package ids -> com.example.crm-core / com.example.crm-service, plus the doc comment naming them; no assertion in that file reads an id string, so every case keeps its meaning); packages/spec/prompts/create-new-project.md (com.example.my_erp -> com.example.my-erp, in the spec tarball); content/docs/getting-started/your-first-project.mdx (my-app -> com.example.my-app, which is what create-objectstack now derives for a project named my-app; namespace: 'my_app' untouched); content/docs/api/declarative-endpoints.mdx and content/docs/protocol/kernel/http-protocol.mdx (acme-crm -> com.acme.crm, namespace: 'acme' untouched); content/docs/api/metadata-api.mdx (plugin-auth -> com.objectstack.plugin-auth on both request bodies and both echoed responses); packages/services/service-package/README.md (crm -> com.example.crm, including the get/delete calls that address the same package). TWO ITEMS BEYOND THE REVIEWER'S LIST, both found by my own sweep and both renamed: (a) packages/create-objectstack/src/rewrite-identity.test.ts:31 wrote a scaffolded config with id: 'x' while its own sibling fixture 12 lines down already used com.example.x -> now com.example.x; (b) the platform-core checklist item carries NINE qa_manifest_probe_* ids, not one, so the whole family moved to com.example.qa-manifest-probe-* (and qa_pkg_lifecycle_probe -> com.example.qa-pkg-lifecycle-probe in the api-backend item); both items bump revision 1 -> 2 and append the history entry docs/qa/platform-checklist/README.md requires of any field edit, and check:platform-checklist is green on that. DELIBERATELY KEPT, each with a stated reason and each on the sweep's declared-residue list: packages/runtime/src/domain-handler-registry.test.ts:591 'pkg-a' (the HTTP install door's residual pin, the only pin on that gap); the two FROM examples in this PR's own changeset (a FROM -> TO migration note must show the refused spelling; no .changeset file was touched); the three Not A Reverse Domain negative controls in packages/spec (check-template-manifests.ts and template-manifest-id.test.ts, refused on purpose); the 13 manifestId values in the local install-ledger fixtures (packages/cli/src/commands/doctor-ledger-*.test.ts, packages/cloud-connection/src/*.test.ts) -- that is a plain `manifestId: string` field on LocalManifestSource's entry interface (packages/cloud-connection/src/local-manifest-source.ts:40), judged by no schema; it is neither ManifestSchema.id nor PackageSchema.manifestId, the only two keys carrying MANIFEST_ID_PATTERN. MANIFEST_ID_PATTERN is untouched (the declaration and both call sites are byte-identical to the reviewed head). ⛔ No PR-body PATCH, no ready flip, no label write, no assignee write, no issue filed, no MCP GitHub write. OWED TO THE SEAT: the body sentence correction, and the needs:contract-review re-hang -- check-clause2-carriers --pair 18319 now reads exit 4 row C3 (the gate was cleared at 16:57:52Z and the head has since moved to 17:11:36Z, so the clear no longer covers what would land; the script itself says the re-hang is a seat's act). A precise replacement claim the tree now supports: zero literal manifest.id values the pattern refuses remain in the tree outside the domain-handler-registry install-door pin, this PR's own changeset FROM examples and the spec template-manifest negative controls.",
      "tests": "SWEEP (my own instrument, /tmp scratch, not committed; five legs over all 9070 tracked files, every literal run through the BUILT ManifestSchema.shape.id from packages/spec/dist/kernel/index.mjs after `pnpm --filter @objectstack/spec build`): leg A balanced-brace `manifest: {` -> the id at depth 1; leg B 3-line window; leg C manifestId / manifest_id; leg D a VARIABLE named manifest* (`const manifest = { id }` -- this is the leg that sees domain-handler-registry.test.ts:591, and its absence is why a brace-key-only sweep misses that pin); leg E an ALIASED body (`manifest: ownerBody` resolved to `const ownerBody = { id }`). BASE 6783718d6a: 560 structural literals, 41 refused (LITERAL class), plus 12 TEMPLATE (interpolated, conforming by construction), 3 EMPTY and 16 window-only hits, each adjudicated individually (nav/record/session ids, not manifest ids). FINAL 866c8cfc90: 560 structural literals, 19 refused, UNDECLARED = 0 -- every one of the 19 is on the declared-residue list quoted in the summary, and the instrument exits 1 if any hit falls outside it. LIT CONTROLS, both at the final commit: (1) --inject writes a file carrying `manifest: { id: 'control_bad_id' }` into the scanned set -> verdict exit 1, `UNDECLARED 1`, and the injected file is removed again (verified: the directory is gone and `git status --porcelain` is empty); (2) --control injects the direct literal AND an aliased body -> 2 structural hits on 2 distinct lines + 2 propagation hits, `CONTROL: LIT`. Instrument self-check on every run: the built schema must accept com.steedos.crm and refuse control_bad_id, or it throws before scanning. Known blind spot, declared: a manifest body produced by a FACTORY call (`const body = contributorBody({...}); { manifest: body }`) is not resolved by leg E -- validate-translation-references.test.ts:1100 is one, caught by the value-propagation grep instead, renamed, and `git grep` for the old values now returns zero hits tree-wide. GATE FAMILIES: `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` derived 138 families over the 104-path change set; all 138 were run and recorded as `command :: exit code`, and `--ran` reads back `138 derived, 138 run, 0 NOT-MEASURED, 0 UNRUN` WITH the codes present. Five of the 138 first returned an ENVIRONMENT verdict, not a diff verdict, and each was re-run to a real measurement after building the prerequisite it named: @objectstack/lint check:doc-formula-expressions and check:doc-security-posture (exit 3, formula+lint unbuilt) -> 0; check:skill-examples (exit 1, client-react dist held no .d.ts) -> 0; check:i18n (exit 3, cli+extract-config closure unbuilt) -> 0; check:dual-build-cjs-loads (exit 3, 14 packages with no dist, in two rounds) -> 0. Named greens among them: check:platform-checklist 0 (the two revision/history bumps), check:generated 0 (15/15 artifacts current, no regeneration owed by the prompts edit), check:docs 0, check:llms-txt 0, check:published-files 0, check:skill-examples 0 (this is the gate that type-checks the `os:check` fences in content/docs against the built spec, so both edited mdx samples still compile), check:authorable-surface 0, check:api-surface 0, check-adr-0087-registration --base origin/main 0, check-changeset-no-major --base origin/main 0, check-empty-changeset 0, check-changeset-fixed 0, check:nul-bytes 0. SUITES, each through scripts/pm/os-verify-lock.sh with OS_VERIFY_LOCK_SLOT=issue-17534-f1 and the verdict read from the wrapper's VERDICT line (never a bare $?): pnpm --filter @objectstack/lint test -> 0, 106 files / 4005 passed; the renamed file alone, pnpm --filter @objectstack/lint exec vitest run --maxWorkers=2 src/validate-translation-references.test.ts -> 0, 1 file / 105 passed; pnpm --filter create-objectstack test -> 0, 16 files / 215 passed; pnpm --filter @objectstack/spec test -> 0, 503 files / 14735 passed; pnpm --filter @objectstack/lint typecheck && pnpm --filter create-objectstack typecheck -> 0 (the lint leg's check:test-typecheck compiles the test layer). pnpm lint (repo-wide, eslint . --no-inline-config) -> exit 0, full population, no narrowing, run at the final commit 866c8cfc90. Every exit code captured by redirect-then-$?, never through a pipe. NOT MEASURED locally, by scope: the packages/qa dogfood tier and the cli integration shards (no diff touches an integration-tier file, a spawn entry or a driver/kernel boot path this round -- declared to CI); check:react-declaration-parity (needs the objectui manifest input, as check:generated itself reports). No ablation this round: nothing new is guarded, the change is a rename of literals plus two checklist history entries. One derivation caveat, reported not worked around: dispatch-gates printed a STALE TREE warning (HEAD is behind origin/main and 5 family-defining files moved there); I re-derived after `git fetch origin main` and the 138-command list came back byte-identical, and deriving from a tree AT origin/main would require a merge this round forbids.",
      "mcp_calls": "0 — no MCP GitHub tool was called at any point, read or write.",
      "api_writes": "2. 1 x git push origin claude/issue-17534-manifest-id-reverse-domain-regex (git over HTTPS, not REST): 6783718d6a..866c8cfc90, fast-forward, pre-push check:commit-card-trailers green (no card relation, no model identifier in the trailer pair). 1 x REST POST /repos/objectstack-ai/objectstack/issues/17534/comments (this report). No POST /pulls (the PR exists), no PATCH of the PR body, no POST /issues/18319/labels, no POST /issues. Reads were GET /issues/comments/{id} and GET /issues/17534/comments only.",
      "open_questions": [],
      "out_of_scope_findings": [
        "noted, not filed: the HTTP install door reads manifest.id positionally (packages/runtime/src/domains/packages.ts:755-760, :804 parses ManifestSchema.shape.version alone, PackageInstallBodySchema.safeParse is never called), so POST /packages answers 201 to ids defineStack, os build and the publish face all refuse. Confirmed again this round as the reason domain-handler-registry.test.ts:591 'pkg-a' must stay. Carrier: already escalated to triage by the seat with carrier #18058 — I filed nothing to avoid a duplicate.",
        "noted, not filed: the local install-ledger entry type (packages/cloud-connection/src/local-manifest-source.ts:40) declares `manifestId: string` with no schema behind it, and its fixtures carry ids the manifest pattern refuses (billing / broken / clean, 13 occurrences across 5 test files). Not the F1 population — a different key, judged by nothing — but the ledger stores what is, in production, a real manifest id. Carrier: this report; a follow-up would be a contract question (should the ledger entry be parsed at all?) rather than a rename.",
        "noted, not filed: packages/services/service-package/README.md ships in that package's tarball and this PR edits it, while the PR's changeset covers @objectstack/spec, @objectstack/cli and create-objectstack only. The dispatch forbids touching any .changeset file, and all four changeset gates are green, so no changeset was added; naming it here so the seat can decide. Carrier: this report."
      ]
    }

    Generated by Claude Code

  10. os-project-manager commented on Sep 20, 2026

    @os-project-manager
    Collaborator

    Contract review round 2 adopted — FAIL on head 866c8cfc90 (record 5751666135 on PR #18319), one residual item; one-line fix round dispatched

    Director seat (session_012GcsUbuqFGBibkEDMRC1eE), 2026-09-20T18:14Z. Adopted verbatim: Served-tier: CONTRACT_REVIEW_TIER (224/224 fable stamps in round 1's transcript; round 2's transcript re-read the same way before this adoption) · Head-sha: 866c8cfc904d6e59a9214f4e7457b591134b6989 · Implemented-by: claude/issue-17534-manifest-id-reverse-domain-regex / Reviewed-by: session_012GcsUbuqFGBibkEDMRC1eE.

    Owed (⛔ F1, residual): round 1's whole residue list is cleared (old values git grep = 0 tree-wide; the reviewer's seven-leg sweep over 9058 tracked files through the BUILT ManifestSchema.shape.id, five injected bad ids all caught), but one refused id reaches the schema through a constant the dev's instrument could not resolve: packages/metadata-protocol/src/protocol.package-publish-audit-rows.test.ts:372 const PKG = 'pkg_helpdesk', used at :1105 in manifest: { id: PKG, namespace: NS } — inside the 2026-09-15 rename population (5689818081). The reviewer measured the fix: com.example.helpdesk keeps that file 20/20 green. One line, then the body sentence stays true as written.

    Everything else RIGHT at this head: the three MANIFEST_ID_PATTERN sites byte-identical to round 1; refusal arms; MANIFEST_ID_EXAMPLES; both scaffolders; ADR-0087 entry; ADR-0130 D4 block vs the code's door order (three ADR gates 0); both merge-forced pins; merge faithfulness (104 paths = PR file list, check:generated 15/15); ② minor + BREAKING + FROM → TO + adr-0087 marker + fail-OPEN → fail-CLOSED section; --pair 18319 exit 0; CI seven required contexts green (42 success / 8 skipped).

    Two ③ items carried by this seat, ⛔ not this PR's blockers: (a) the install door's positional manifest.id read — its cited carrier #18058 closed on 2026-09-18 (#18752 covered the version leg only), so this seat files the gap as its own card (class b: MANIFEST_ID_PATTERN is the declared contract, the door does not enforce it); (b) .changeset/18490-nav-contribution-groups-imports-package-id-owner.md, another PR's pending note on the merge base, states that id: '' resolves — false at this head (refused at DOOR 1). This PR does not edit it (#17712); the written statement the changeset gate asks for is this line, and the correction rides a docs-only follow-up under the maintainer's confirmation, the way #18482 / #18867 landed today.

    Carriers stripped with this comment; re-hung on the fix push; round 3 on the new head.


    Generated by Claude Code

  11. os-project-manager commented on Sep 20, 2026

    @os-project-manager
    Collaborator

    os-dev-report

    {
      "issue": 17534,
      "status": "done",
      "branch": "claude/issue-17534-manifest-id-reverse-domain-regex",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/18319",
      "head": "1d4c99bcbdf082c937d06157d488d8ac191939b6",
      "session": "session_012GcsUbuqFGBibkEDMRC1eE",
      "premise_still_valid": true,
      "summary": "Round-2 F1 residual only: ONE commit (1d4c99bcbd) on the existing branch -- no rebase, no amend, no force-push, no merge; the diff vs the reviewed head 866c8cfc90 is 1 file / 1 insertion / 1 deletion. packages/metadata-protocol/src/protocol.package-publish-audit-rows.test.ts:372 `const PKG = 'pkg_helpdesk'` becomes 'com.example.helpdesk'. VERIFIED FIRST against the BUILT ManifestSchema.shape.id (after `pnpm --filter @objectstack/spec build`), with a lit control on the judge (accepts com.steedos.crm and org.apache.superset, refuses control_bad_id and the empty string, or the probe throws): 'pkg_helpdesk' REFUSED -- the message even suggests 'com.example.pkg-helpdesk' -- and 'com.example.helpdesk' ACCEPTED. ASSERTION REVIEW: all 30 `PKG` references read the constant, the three `toContain(PKG)` assertions (lines 488, 662, 1163) included, so every case keeps its meaning and no assertion needed an identical edit; the sibling assertion at :1164 `toContain(`${NS}_`)` still measures something real, because 'com.example.helpdesk' does not contain 'helpdesk_' any more than 'pkg_helpdesk' did. 'pkg_helpdesk' now reads 0 tree-wide. NOTHING ELSE WAS TOUCHED. Honest note on what the suite proves: that file was 20/20 green before and after -- its manifest.id is never parsed by ManifestSchema -- so the green is a no-regression reading, not evidence the rename mattered; the evidence for the rename is the sweep plus the 2026-09-15 rename population ruling.",
      "sweep_result": "NO OTHER constant-carried or factory-carried manifest id the pattern refuses. Two independent instruments, each lit before it was believed. INSTRUMENT 1 (forward, manifest-scoped): 8942 tracked text files / 169.8 MB, five legs -- A balanced-brace `manifest: {...}` including the JSON `\"manifest\": {` spelling, C `manifestId` / `manifest_id`, E an aliased body `manifest: IDENT`, F `id: IDENT` resolved to a string constant, G a factory `manifest: fn(...)` resolved to the object literal fn returns -- every value judged by the BUILT ManifestSchema.shape.id. 1203 id-sites, 853 judged (33 via a constant), 30 REFUSED. LIT CONTROL: an injected repo carrying all eight refused shapes (literal, JSON literal, aliased body, constant, arrow factory, function factory, manifestId literal, manifestId via constant) -- 8 of 8 reported on the right legs, and the two accepted controls (com.example.good, com.example.jsongood) were not flagged. That control caught a real blind spot before the real run: the first build skipped a quoted `\"id\"` key because the string-skip ran before the key check, so every JSON-shaped manifest body was invisible; the key check was moved ahead of it and the JSON leg then fired. ALL 30 REFUSED ARE DECLARED RESIDUE, none new: this PR's own changeset FROM examples my_app / com.acme.my_app (2); the foreign pending changeset 18490's empty id (1, the reviewer's own flag, which this PR may not edit); the 14 local install-ledger `manifestId` fixtures billing / broken / clean in doctor-ledger-*.test.ts and cloud-connection (13 literals + 1 interpolated -- a plain `manifestId: string` interface field carrying neither of the two keys that hold MANIFEST_ID_PATTERN); the three Not A Reverse Domain negative controls in packages/spec; the ruled-to-stay refusal pin '   ' at package-api.test.ts:914; and 10 template interpolations (cli/src/commands/init.ts x3 plus seven cli e2e fixtures) which my instrument judges on their RAW text and which conform BY CONSTRUCTION -- verified independently, not taken from the review: manifestIdSlug (init.ts:95-103) lowercases, folds every non-alphanumeric run to '-', trims hyphens and forces a leading letter, so it can only emit ^[a-z][a-z0-9-]*$. INSTRUMENT 2 (inverse + leg D): starts from every string constant in the tree (7505 found, 3061 refused by the pattern) and asks whether the identifier ever reaches an `id:` / `manifestId:` / `packageId:` position -- deliberately wider than 'inside a manifest body', because that is exactly the leg the five-leg sweep missed. Result: ZERO refused constants reach `manifestId:` / `manifest_id:` anywhere, and ZERO reach an `id:` position with a manifest in scope (measured as 'manifest' within the 6 preceding lines). BOTH ZEROS ARE LIT, by the strongest control available: the REAL pre-fix file, `git show HEAD:...` of the target test, was committed into the control repo and instrument 2 reported `const PKG = \"pkg_helpdesk\" @372 -> id: PKG @1105` -- the exact F1 site at the reviewer's exact line numbers -- and the 6-preceding-lines filter fired on it too. LEG D (a variable whose NAME says manifest holding an object with an id) had a blind spot I found and fixed: the name regex required at least one character before 'manifest', so a bare `const manifest = {...}` could not match; after the fix leg D reports 10 rows, and the live tree supplies its own lit control -- domain-handler-registry.test.ts:591 `const manifest = { id: 'pkg-a' ... }`, the install-door pin the dispatch explicitly keeps. The other 9 leg-D rows are all a DIFFERENT schema and were each read in context: six service-settings *.manifest.ts files are `SettingsManifest`, which declares no top-level id at all (its top key is `namespace`) -- the matched id is nested in `specifiers[]`; plugin-security-advanced.test.ts:270 is `PluginSecurityManifestSchema`, where the match is a permission id under permissions.permissions[0].id and the top-level key is `pluginId: 'com.acme.analytics'`; and protocol.ts:19257 `dupManifest` takes a runtime value, not a literal (see out_of_scope_findings).",
      "tests": "Every exit code captured by redirect-then-$?, never through a pipe; every build/suite through scripts/pm/os-verify-lock.sh with OS_VERIFY_LOCK_SLOT=issue-17534-f1b, the verdict read from its `VERDICT command-exit` line (the lock was free on every acquisition; waits 0-1s). BUILDS: `pnpm --filter @objectstack/spec build` VERDICT command-exit 0 (needed BEFORE judging anything -- a stale dist lies in both directions); `pnpm --filter '@objectstack/metadata-protocol^...' build` (dependency closure) VERDICT command-exit 0; later a full `pnpm exec turbo run build --concurrency=2 --filter='./packages/*' --filter='./packages/*/*'` VERDICT command-exit 0, 72 successful / 72 total. DISPATCH-NAMED SUITES: the changed file alone -- `pnpm --filter @objectstack/metadata-protocol exec vitest run --maxWorkers=2 src/protocol.package-publish-audit-rows.test.ts` VERDICT command-exit 0, 1 file passed / 20 tests passed (the reviewer's 20/20, reproduced); the whole package -- `pnpm --filter @objectstack/metadata-protocol test` VERDICT command-exit 0, 182 files passed + 3 skipped (185), 2596 tests passed + 19 skipped (2615); `pnpm --filter @objectstack/metadata-protocol typecheck` VERDICT command-exit 0. That typecheck is PROVEN to cover the changed file rather than excluding it: `tsc --noEmit --listFiles` names protocol.package-publish-audit-rows.test.ts once, among 185 *.test.ts in the program. REPO-WIDE LINT: `pnpm lint` (= `eslint . --no-inline-config`) VERDICT command-exit 0 over the FULL population -- not narrowed, so no narrowing evidence is owed. DERIVED GATE FAMILIES: `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` (no paths passed; the script took its own change set from the merge base 8271c8142 -- 105 paths, the PR's whole diff, 1319 changed lines, under the 5000 threshold) derives 138 runnable families for the PR as a whole. From its own --json attribution I took the families THIS commit implicates -- 45 whose `via` names the changed file, the 6 gates of the 'adds or edits a test file' change-KIND, and the 8 whole-tree always-runs -- 51 distinct commands, recorded byte-for-byte with the tool's own capture idiom as `command :: exit code`. ALL 51 EXIT 0. Three of them first returned exit 3 and each was an ENVIRONMENT verdict, not a diff verdict -- check:dual-build-cjs-loads and check:lean-entry-closure both printed PREREQUISITE NOT MET (no dist), and check:type-check-debt printed '--re-measure cannot run: 19 workspace dependencies have no built type entry point'; all three were re-run to a real measurement after the full build and all three exit 0. None is reported as a failed measurement. RECONCILIATION: `dispatch-gates.mjs --ran` over that record reads '51 of 138 run, 87 UNRUN' and names every unrun family. That 87 is honest and expected, not a gap I am hiding: each belongs to one of the PR's other 104 paths (docs, changeset, platform-checklist, spec artifacts, i18n, ADR anchors), which this commit does not touch; the prior round measured 138/138 at head 866c8cfc90 and CI's seven required contexts were green there. NOT MEASURED, declared: (a) the dispatch-gates derivation itself printed a STALE TREE warning -- it read gate definitions from a tree at least 18 commits behind origin/main with 7 of its derivation-source files changed in that range (lint.yml and cross-package-test-inputs.mjs among them), and its prescribed remedy is to derive from a tree at origin/main, which would require the merge this dispatch forbids; so families main added since the merge base may be absent from my 138 and that half belongs to CI and to the seat. (b) The 5 workflow-valued families, the 47 artifact-roster silences, the 10 wide-population families and the 6 path-scheduled CI jobs are outside the derived total by the tool's own declaration. (c) No ablation or reverse verification was run or owed: this commit adds no guard and changes no behaviour -- it edits one fixture string.",
      "mcp_calls": "0 -- no MCP GitHub tool was called, read or write.",
      "api_writes": "1 -- POST /repos/objectstack-ai/objectstack/issues/17534/comments (this report). Plus one `git push` to the existing branch (not a REST write). NO POST /pulls (the PR exists), NO PR-body PATCH, NO label write, NO assignee write, NO ready flip, NO auto-merge, NO issue filed, NO force-push, NO rebase, NO amend, NO merge.",
      "open_questions": [],
      "out_of_scope_findings": [
        "to file (class b, contract violation) -- dedupe words: duplicatePackage, targetPackageId, manifest id unenforced, MANIFEST_ID_PATTERN, package duplicate door. packages/metadata-protocol/src/protocol.ts:19259 builds `dupManifest` with `id: request.targetPackageId` and writes it through installPackage with NO ManifestSchema parse anywhere in that file (grep for ManifestSchema in protocol.ts returns only two comment lines). Sharper still: :19123 ALREADY ASSUMES the reverse-domain shape -- it defaults the namespace to `request.targetPackageId.split('.').pop()` -- so the code relies on the contract it does not enforce, and a duplicate created with an underscore id silently takes the whole id as its namespace. This is the SAME CLASS as the install-door gap the seat said it would file at round 2 (POST /packages reading manifest.id positionally); I did not file it -- dev files nothing -- and I recommend it ride that same card as a second seam rather than becoming a third one.",
        "noted, not filed: the two round-2 boundary items are unchanged at this head and neither is touched by this commit -- the install door (packages/runtime/src/domains/packages.ts:755-760) still answers 201 to ids the publish face refuses, and .changeset/18490-nav-contribution-groups-imports-package-id-owner.md still states as measured premise that `id: ''` parses green, which is false at this head. Both are the seat's, already carried in comment 5751673952. Successor for both: the seat's own follow-up; no PR or person other than that follow-up touches these files.",
        "noted, not filed: `needs:contract-review` is absent from PR #18319's labels at this head (stripped with the round-2 FAIL comment, as that comment states). Re-hanging it is the seat's act and the dispatch forbade me any label write, so I made none. Reported so the round-3 review is not taken on an unlabelled PR."
      ]
    }

    Generated by Claude Code

  12. os-project-manager commented on Sep 21, 2026

    @os-project-manager
    Collaborator

    Landing record — director seat, summon #25 (session_012GcsUbuqFGBibkEDMRC1eE), 2026-09-21T00:19Z

    Landed. PR #18319 merged through the merge queue 2026-09-21T00:08Z (merged_by os-zhuang, the queue's second group after the first was invalidated by a failed group ahead of it), squash 097d2685941169e1d4de87b6f8b293effb3e4156 on origin/main (tip at this read 8f6d83147b, first-parent chain includes the squash). Verified by content on the fetched ref, ⛔ not the working tree: export const MANIFEST_ID_PATTERN at packages/spec/src/kernel/manifest.zod.ts:262 with 14 reference sites across packages/spec/src (schema, marketplace package schema, tests, the semantic migration entry 18.manifest-id-reverse-domain-required.ts and the registry); the ADR-0130 D4 record-keeping block present (DOOR 1 — the schema … INVALID_ARTIFACT_PACKAGE_ENTRY / 422); changeset .changeset/17534-manifest-id-reverse-domain.md present; the refused-id control pkg_helpdesk reads 0 hits tree-wide.

    What this card asked for, and what landed — the ruled change on the authoring contract (one declaration referenced from both ManifestSchema.id and PackageSchema.manifestId), the refusal with a remedy, both scaffolders deriving a conforming id, the ADR-0087 semantic entry with its disposition marker and a minor changeset, the in-repo fixture renames the maintainer widened the scope to on 2026-09-15, the two merge-forced pin repairs and the ADR-0130 D4 block (ruling batch #200 item 3, Q3-A component 2). At-tier contract review rounds 5751237820 (FAIL), 5751666135 (FAIL), 5751970203 (PASS on the merged head 1d4c99bcbd); provenance 5751976120; APPROVED by os-zhuang 2026-09-20T23:07Z (governed diff, tier H by the docs/adr/** path).

    Carried, not closed here: (1) #19417 — the HTTP install door still reads manifest.id positionally (the residual pin packages/runtime/src/domain-handler-registry.test.ts:591 pkg-a stays on purpose); the duplicate-package door seam is its second item. (2) The foreign pending changeset .changeset/18490-*.md carrying an empty id — the correction is owed by that changeset's own PR, recorded at 5751673952; ⛔ not edited here. (3) The os init namespace / manifest-id derivation is fixed in this PR; anything a consumer repo authored with a bare id before this release migrates through the semantic entry.

    Closing this card completed and removing pm:dispatched in this stroke; assignee left as the closing record. Closed by the director seat under the maintainer's takeover instruction in chat 「18319 什么情况,帮我跟进到合并。」.


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions