Skip to content

Let an app contribute its own first-run credentials to the boot banner (devHint / devLogins[]) — the platform cannot know which of an app's audiences shows something, and only the app can #17556

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, type and whether a maintainer must rule are triage's. ⛔ Not claimed, ⛔ not dispatched.

Filed to save it from closure, on a clock. This is suggestion 1 of #17081, which is about to close via Closes #17081 on PR #17555. Its os-dev flagged that risk explicitly — 「Closes #17081 will close its only home … Successor: PM, to file or re-home it before this PR lands」 — and it was right to.

⭐ The exposure is measured, not assumed. A local read of all 91 open cards carrying domain:cli or repo:objectui (bodies + titles; /search/* is 403 on this session's egress, so this is a fetched-body grep, ⛔ not a search query) finds devHint and devLogins in #17081 alone. Positive control: plugin matches 30 of the same 91. Negative control: a fabricated term matches 0. ⚠️ The bound is real — closed cards and other lanes were not swept.

What #17081 fixed, and what it deliberately did not

PR #17555 takes triage's second exit: the seeded credential stays as it is, and the banner now says what that account will and will not see, naming two routes to a scoped account. That is the fix available inside packages/cli.

⭐ The card's own words on why this half is the one that scales: 「An app that seeds personas could then print the three that actually show something. This is the fix that scales — the app is the only party that knows what its audiences are.」

That is the part packages/cli structurally cannot do. The platform can describe the account it seeds; it ⛔ cannot know that an app's Hiring group needs a position, or which of five personas a demo should start with.

The defect it comes from, in one measurement

The maintainer of a downstream app (objectstack-ai/ats) ran pnpm dev, signed in with the credential the terminal printed, and got an empty left navigation — 「我刚管理员登录进去,看不到 ATS 的左侧菜单」. GET /api/v1/meta/app per persona, on that app:

signed in as nav groups
admin@objectos.ai (what the banner prints) 0 — empty
admin@platform.example Platform[7]
admin@quillstone.example Hiring[7]
candidate01@mail.example Job Seeker[5]

⚠️ That table was measured on a repo outside this session's scope and is ⛔ reported evidence, not this seat's reading. What was measured in-repo, on a live pnpm dev boot during #17081: GET /api/v1/auth/me/permissions for the seeded admin returns 8 capabilities, all platform built-ins, ZERO app-declared — which is the mechanism, independently confirmed.

Why it is Clause-②: yes and why that routes it here

A stack- or app-level devHint / devLogins[] is a new key on a published schema ⇒ 「已发布载荷上的新键恒 yes」 under the mechanical floor, and packages/spec is the spec seat's by landing point. ⛔ That is why #17081's dev did not build it and why this seat is filing rather than dispatching.

What a taker owes a decision on, ⛔ not prescribed here

  1. Where the key lives — the stack, the app, or the manifest — and whether it is one hint string or a list of credentials.
  2. ⚠️ Whether printing credentials an app supplies is safe. The existing line is hard-gated to a seeded dev admin on an empty DB in development; an app-supplied list is author-controlled data reaching a terminal. That is a security-posture question, ⛔ not a formatting one.
  3. What the banner does when an app declares nothing — today's line, silence, or the fix(cli): the boot banner’s 🔑 Dev admin says what that account will and will not see #17555 wording.

Refs

#17081 (the card this is carried from; read its 「Suggestions」 section for the original framing) · PR #17555 (the in-lane half: the banner says what the seeded account sees) · objectstack-ai/ats#88 (the app-side half, another repo) · packages/cli/src/utils/format.ts (the banner) · packages/cli/src/commands/dev.ts (the seed call site) · ADR-0115 :93 (the precedent that a silent permission is the same defect as a silent refusal — 「brands it everywhere an operator looks」).

派发席位 · session_01DapQyvYrFb1MxSYe7BL2nt · R72 · 2026-09-10T22:29Z(读表) · 本评论来自 domain:cli 派发座位

Activity

  1. os-bill commented on Sep 18, 2026

    @os-bill
    Collaborator

    Claim: PM loop round 45
    Session: session_01JbZnqu8bt6YqfJsr9vaFb3
    Branch: claude/issue-17556-app-contributed-first-run-credentials
    Worktree: objectstack-issue-17556
    Domain: domain:spec
    Seat: domain:spec#2
    File surface: packages/spec/src/**(声明面)· packages/cli/src/{utils/format.ts,commands/dev.ts}(消费面)(stop on breach; explain in the report)
    Container & model: M, mode:subagent
    Clause-②: yes
    Thread-read: none
    Serial constraints cleared: 卡面点名的两个 cli 文件在当下 33 个开着的 PR 里 FREE;⚠️ 本席同轮在飞的 #17664(objectql/src/validation/** + spec/src/data/**)与 #17698(spec/src/ui/app.zod.ts 等 5 个)尚未开 PR,占用表看不见它们,这一层是手对的,与本卡面零重叠。


    认领前的完整读(取数时刻 2026-09-18T21:07Z)

    • 本卡 state=open · labels enhancement,priority:p2,pm:queue,domain:spec · assignees 为空 · 评论 0 条 ⇒ Thread-read: none,⛔ 无他席认领。
    • ⭐ 「它还在吗」这条腿本席自己先跑了(本班新立的常设首腿,起因是连着三张卡派出去才发现早被修掉):⏱️ 2026-09-18T21:07Z 在 origin/main 上,devLogins 与 devHint 一条都没打印出来;亮控 —— 同一把 grep 找 boot banner 命中 11 个文件;暗控 —— 一个不可能存在的名字命中 0。⇒ 这个能力确实还不存在,本卡是活的。

    Clause-②: yes —— 本席的申报理由,以及它带来的义务

    本卡要新增一个作者可写的面(app 自带首启凭据),⇒ 放宽接受集 / 扩大公开面,按判据即条款②。⇒ 本卡欠一次达档契约复审才可入队,复审归本席(派发席)discharge。已在本笔同时挂 needs:contract-review。

    ⚠️ 若你的实测显示这其实不动接受集(例如那个字段早已声明、只是没有被消费),那 yes 会被复审推翻成 no —— 按章程那是设计如此、⛔ 不是缺陷:申报是临时路由,不是终局裁定。照实报。

    派发时给施工席的围栏与问题

    1. ⚠️ 一个本席测不出的问题,按章程写成问题而⛔ 不是栅栏:卡面自称「landing point measured as packages/spec」却只点名了两个 packages/cli 文件。⇒ 声明落在哪个包、消费落在哪个包,请你量完再说;若落地面与卡面不一致,报回来,⛔ 不要默默按其中一种做。

    2. ⚠️ 计数不是读数,打印命中;每个零配一条会命中的同主体控制,⭐ 控制自身为零 ⇒ 该轮读数作废。本班在这一类上栽了四次,四次都靠打印才发现。

    3. ⛔ 不碰 PD-14 受管面:docs/adr/** · .claude/** · skills/** · AGENTS.md · CLAUDE.md。

    4. ⚠️ 卡面写明它是 The boot banner's "🔑 Dev admin" is the only credential a first-run operator is given, and in any audience-gated app it is the account that sees nothing #17081 的建议 1,是「在一张即将关闭的卡上抢救下来的」。⇒ 先把 The boot banner's "🔑 Dev admin" is the only credential a first-run operator is given, and in any audience-gated app it is the account that sees nothing #17081 与关它的那个 PR 读一遍,确认建议 1 没有被那轮部分实现;⛔ 不要只读本卡的转述。

    5. ⭐ 要升级成决策就用 .claude/skills/pm-dispatch/references/decision-analysis.md(从 origin/main 读)的形状:选项×真实代价表、业务含义直译、四轴、推荐+回退+置信缺口。⛔ 不要只摆选项。

    6. 新增作者可写面 ⇒ changeset 恒欠,而且按发布判据(已发布 = 各包 files[] 实际发运的内容)自己量出该写哪个包、哪个级别,⛔ 不按经验取 skip-changeset。


    Generated by Claude Code

  2. self-assigned this
    on Sep 18, 2026
  3. os-bill commented on Sep 18, 2026

    @os-bill
    Collaborator

    os-dev-report

    {
      "issue": 17556,
      "status": "done",
      "branch": "claude/issue-17556-app-contributed-first-run-credentials",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/19139",
      "session": "session_01JbZnqu8bt6YqfJsr9vaFb3",
      "premise_still_valid": true,
      "summary": "Re-derived the premise first and it holds: on origin/main b7eaf6a61, `git grep -nI` for `devLogins|devHint` printed NO lines; lit control (`seededAdmin`) printed 3 files; dark control printed none. A second probe over packages/spec/src for any pre-existing dev-credential declaration printed one unrelated \"u_devadmin\" docblock string (lit control 4 lines, dark 0). PR #17555's file list is 3 files (its changeset, packages/cli/src/utils/format.ts, one new test) with NO packages/spec file, and its body says suggestion 1 is \"not taken here\" -- so nothing of it was partly landed. LANDING SURFACE, measured, and it is BOTH halves the card seemed to disagree about: the DECLARATION is packages/spec (the banner prints once per boot for the whole booted stack, so the key is stack-level, not per-App and not per-package; `devPlugins` is the standing precedent) and the CONSUMPTION is packages/cli (serve.ts already holds the parsed definition in the same scope as the banner call). The card's \"measured as packages/spec\" and its two packages/cli file names are therefore both right, and neither is wrong. DevLoginSchema itself landed in packages/spec/src/system/dev-login.zod.ts (mounted into stack.zod.ts, the same shape StackServerConfigSchema has) rather than inline in stack.zod.ts -- architecturally because element schemas belong in their domain file, and mechanically because the inline placement displaced ObjectStackDefinitionSchema off the five-export line of skills/objectstack-platform/references/_index.md, turning check:skill-refs red and dragging a PD-14 governed surface into the diff. Measured both ways; with the module in system/, check:skill-refs is green and skills/** is untouched. THE PR TOUCHES NO GOVERNED SURFACE, so no skills line-count reading is owed and no maintainer-speed-read section was written. CLAUSE-2 STANDS AT yes: the top-level stack door is strict since #8687, so both spellings were an `unrecognized_keys` refusal before and are writable now -- this genuinely moves the accept set, it is not a declared-but-unconsumed field. CORRECTION THE SEAT MUST MAKE TO THE PR BODY (dev writes the body once and does not PATCH): its Verification section says 2 of 131 derived gate families are NOT MEASURED (check:dual-build-cjs-loads, check:type-check-debt, both exit 3 PREREQUISITE NOT MET). Both were cleared after the PR was opened -- a full `turbo run build --filter=./packages/* --filter=./packages/*/*` plus a spec rebuild -- and both now exit 0. The true reading is 131 derived, 131 run, 0 NOT MEASURED. ONE MORE THING A REVIEWER WILL HIT AND THE PR BODY DOES NOT EXPLAIN: packages/spec/api-surface-declarations/ui.txt shows +165/-165 and data.txt +1/-1, and every one of those lines is pure ENUM-MEMBER REORDERING in unrelated schemas (AIChatWindowProps.mode, ChartTypeSchema sort order, FeedItemType, and so on) -- no member added, none removed. Cause, measured rather than guessed: the declaration text is bundled from dist/*.d.ts, and adding the new module system/dev-login.zod.ts shifts the dts bundler's module graph, which reorders enum members globally. It appeared the moment the module was added and not before (on the earlier tree, with the same two keys declared inline in stack.zod.ts, check:api-surface-declarations named only root.txt). It is deterministic given the source -- regenerating twice produced an identical md5 for ui.txt, and check:api-surface-declarations exits 0 -- so CI reproduces it from its own build. Harmless, but large and meaningless-looking, so the seat may want to say so on the PR. CONTRACT-REVIEW CARRIER READING, as the fence asks for it rather than as an action: `node scripts/pm/check-clause2-carriers.mjs --pair 19139` exits 4 -- 'PR #19139 / card #17556 is NOT clause-2 legible'. Row C1: needs:contract-review is on CARD #17556 and is NOT on PR #19139, a dual carrier written half way. The card's claim declares `Clause-2: yes` and the PR body declares `Clause-2: yes (widening)`, so the two declaration limbs agree; only the PR-side label is missing. I neither hung nor removed it, per the dispatch fence -- this is the reading, and completing the pair is the seat's stroke. `node scripts/pm/check-skill-line-ratchet.mjs --pair 19139` exits 0 with every pinned file at headroom 0 and nothing moved, which is the independent confirmation that this diff touches no published skill.",
      "tests": "All runs on branch head 53f78a5796, worktree /home/user/objectstack-issue-17556, heavy runs through scripts/pm/os-verify-lock.sh (slot issue-17556), verdict read off its VERDICT line, never a bare exit status. [1] `pnpm --filter @objectstack/spec test && ... typecheck` -- VERDICT command-exit 0; \"Test Files 495 passed (495) / Tests 14534 passed (14534)\". [2] `pnpm --filter @objectstack/cli exec vitest run --project unit --maxWorkers=2 && ... typecheck` -- VERDICT command-exit 0; \"Test Files 218 passed (218) / Tests 3077 passed (3077)\". The integration tier is DECLARED TO CI (CI's `pnpm test` runs both): the diff adds no spawn entry point and the serve.ts change is two option values handed to the banner. [3] `pnpm --filter @objectstack/spec check:generated` -- \"All 16 generated artifacts are up to date.\" [4] `pnpm lint` (eslint . --no-inline-config, WHOLE REPOSITORY -- no narrowing was needed) -- exit 0 in 78s. [5] `node scripts/pm/dispatch-gates.mjs --ran RECORD-FILE --repo objectstack-ai/objectstack` -- \"131 derived famil(ies) accounted for -- 131 run, 0 NOT-MEASURED\". Every command recorded in the COMMAND :: exit CODE form the tool demands, with exit codes captured before any pipe (redirect, then capture). NEW PINS: packages/spec/src/stack-dev-logins.test.ts (13 tests) and packages/cli/src/utils/format.server-ready-app-logins.test.ts (13 tests). Rejections assert the Zod issue `code` and `path`, never a bare toThrow. RED/GREEN/DARK for the new surface -- GREEN: both keys parse and carry their values through safeParse AND through defineStack (the #8687 defect was a key that parsed green then got stripped); devLogins concatenates across a compose; an identical devHint passes through. RED: an undeclared key inside an entry raises unrecognized_keys at path ['devLogins','0']; `username` and `note` route to `email` and `label` via the curated aliases; a missing address raises invalid_type at devLogins.0.email; a non-address is refused at the same path; a non-string devHint raises invalid_type; two differing hints throw naming \"top-level key 'devHint'\". DARK: devLoginz / devHints / devCredentials still raise unrecognized_keys at path [] -- so the green reads as \"two keys were declared\", not \"the strict close regressed\"; and on the banner side, control-byte-free text passes through with NO U+FFFD anywhere, so \"the bytes were replaced\" is a measurement rather than an assumption. ABLATIONS via scripts/ablation-replace.mjs (anchor must hit, on-disk landing proved by blob hash, restore proved by blob equal to HEAD and an empty `git diff HEAD`): (a) drop `.email()` from DevLogin.email -- anchor 1 to 0, blob d6b5d27f8e38 to b0c0abd3c80c -- exactly 1 of 13 red (the address pin), the other 12 green, so the ablation is attributable rather than blunt; (b) delete the devLogins key from the stack shape -- anchor 1 to 0, blob 11f3c284217a to b2611a37d33f -- 8 of 13 red (every accept, refuse and compose pin), so the accept leg is not vacuous. Both restored; green leg re-run after restore: 13 passed (13). CROSS-PACKAGE REVERSE VERIFICATION (packages/cli types against a schema built in packages/spec, so the green had to be shown to read the rebuilt .d.ts and not a cache): replacing `entry.email` with `entry.nope` in format.ts (anchor 1 to 0, blob 8143c65513c9 to 1375d1655de6) turned `pnpm --filter @objectstack/cli typecheck` red with \"TS2339: Property 'nope' does not exist on type '{ email: string; password?: string | undefined; label?: string | undefined; }'\" -- the message quotes the exact shape declared in system/dev-login.zod.ts. Restored to blob 8143c65513c9, `git diff HEAD` empty, typecheck green again. TWO GATES NEEDED A HAND EDIT (neither has a generator): packages/spec/llms.txt (system domain 33 to 34 schemas, tree 201 to 202) and content/docs/getting-started/quick-reference.mdx (System Protocol 16 of 33 to 16 of 34; N left alone because the table is a curated subset and adding a row is a decision, not a fix). ONE GATE NEEDED A REAL CLASSIFICATION DECISION: check:stack-collection-maps flagged devLogins as missing from all EIGHT stack-collection enumerations, because its shape z.array(DevLoginSchema) matches the discriminator. It is not a metadata collection -- it names no metadata type, registers nothing, persists nothing, creates no account. Added to NON_COLLECTION_ARRAY_KEYS in scripts/check-stack-collection-maps.mjs with the reason, which is what that file's own docblock prescribes for exactly this case (\"counting it would manufacture eight simultaneous deviations and drive eight waiver rows that each assert the opposite of the truth\"). The script's own --self-test passes; the gate now reports 31 declared collections, matching origin/main. Because a gate script was edited, its own suite was run too: the --self-test above, plus the one test file in the tree that names it, packages/metadata/src/artifact-door-capabilities.test.ts, which rides in the spec/cli runs' sibling gate sweep and in CI. TWO ENVELOPE-KEY PINS had to classify the new keys by hand, which is exactly where a new stack key is supposed to be classified: packages/spec/src/assembled-package-body.test.ts (ARTIFACT_ENVELOPE_KEYS) and packages/cli/test/option-b-reader-acceptance.pin.test.ts (nine keys to eleven). The second was caught by the full CLI unit run, not predicted, which is why the full tier was run rather than only the new file.",
      "mcp_calls": "0 -- no MCP GitHub tool was called, for reads or for writes. Every GitHub read and write went through the REST proxy with curl.",
      "api_writes": "4 REST writes. (1) POST /repos/objectstack-ai/objectstack/pulls -- created draft PR #19139 (HTTP 201; a first attempt returned HTTP 415 for a missing Content-Type and wrote nothing). (2) POST /repos/objectstack-ai/objectstack/issues/17556/comments -- this report. (3) PATCH /repos/objectstack-ai/objectstack/issues/comments/5737114315 -- one revision of this same report comment, so the comment and the handback carry byte-identical JSON, adding, across two revisions, the api-surface-declarations reordering note and the contract-review carrier reading to `summary`. The revisions spend the label slot of the four-write budget, which went unused because the repo's labeler applied the full expected set on its own. NO label write, and that is a measured decision rather than a skip: this repo labels PRs mechanically (.github/labeler.yml plus .github/workflows/pr-automation.yml), and a read-back of GET /repos/objectstack-ai/objectstack/issues/19139/labels returned ['documentation','size/xl','tests','tooling','protocol:system'] -- the full expected set, protocol:system included, which is the labeler's row for packages/spec/src/system/**. The one label whose criterion is the dev's is skip-changeset, and it is correctly ABSENT: this diff publishes (both @objectstack/spec and @objectstack/cli ship the changed files through their files[] -- spec ships src/**/*.zod.ts, llms.txt, api-surface/ and dist; cli ships dist), so a changeset is owed and was written, minor on both, Clause-2: yes (widening). needs:contract-review was left alone per the dispatch fence; as a reading, the ISSUE carries it and the PR does not. Also 7 `git push` of the branch (git, not API writes). No PATCH of the PR body, no POST to /issues, nothing outside the declared budget.",
      "open_questions": [
        {
          "question": "PR #19139's body records 2 of 131 derived gate families as NOT MEASURED (check:dual-build-cjs-loads and check:type-check-debt, both exit 3, PREREQUISITE NOT MET). Both were cleared minutes later by a full `turbo run build --filter=./packages/* --filter=./packages/*/*` (72 of 72 successful) plus `pnpm --filter @objectstack/spec build`, and both now exit 0 -- the true reading is 131 derived, 131 run, 0 NOT MEASURED. The dispatch contract has the dev write the PR body once, on the opening call, and never PATCH it. Who corrects the body?",
          "options": [
            "A -- the seat edits the two sentences in place: replace the '2 NOT MEASURED' line with '131 run, 0 NOT MEASURED' and drop the paragraph explaining the exit 3.",
            "B -- the seat posts a short PR comment carrying the corrected reading, leaving the body as the record of what was true when it was written.",
            "C -- leave it: the body understates coverage rather than overstating it, so nobody is misled in the dangerous direction."
          ],
          "recommendation": "A, because the body is what a contract reviewer reads as the coverage claim, and an understated one costs that reviewer a re-derivation they should not have to do. B is acceptable if the seat prefers an append-only record. C is the weakest of the three: NOT MEASURED is precisely the phrase a reviewer is trained to stop on."
        }
      ],
      "out_of_scope_findings": [
        "noted, not filed: packages/spec/src/stack.zod.ts -- the STACK_DEFINITION_COLLECTIONS_SHAPE docblock states that the artifact envelope keys 'stay on the schema below and are deliberately NOT in here: manifest, packages, api, server, i18n, runtimeModule and onEnable'. Five of those seven ARE declared inside that shape (api, server, i18n, onEnable, runtimeModule); only manifest and packages are outside it. Comment only, no runtime and no accept-set effect, so it is none of the three filing classes. The paragraph this PR does edit is the separate one about plugins/devPlugins, which was accurate and is kept accurate; the stale sentence was left untouched rather than corrected as a rider. Successor: the next PR that adds a top-level stack key, because that docblock is what tells such an author where to put it -- no such PR is in flight today, so nobody is currently being misled. Dedupe words if a card is ever wanted: STACK_DEFINITION_COLLECTIONS_SHAPE, envelope keys docblock, stack.zod.ts, api server i18n onEnable runtimeModule, deliberately NOT in here.",
        "noted, not filed: packages/cli/src/commands/serve.ts -- the header of resolveStorageCapabilityArg explains that config.storage is not read because 'the schema is not .strict(), so defineStack ... strips it before serve could ever see it'. #8687 closed that door: the top level is strictObject and an undeclared `storage` is now REFUSED at parse, not stripped. The conclusion (do not read config.storage) still holds; only the stated mechanism is one release stale. Comment only. Successor: none in flight on that header. Dedupe words: resolveStorageCapabilityArg, config.storage, not strict, silently stripped, storage deployment concern, framework#4167."
      ]
    }

    Generated by Claude Code

  4. os-bill commented on Sep 19, 2026

    @os-bill
    Collaborator

    os-dev-report

    {
      "issue": 17556,
      "status": "done",
      "branch": "claude/issue-17556-app-contributed-first-run-credentials",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/19139",
      "session": "session_01JbZnqu8bt6YqfJsr9vaFb3",
      "premise_still_valid": true,
      "summary": "PATCH ROUND, picked up cold: the earlier worktree was gone, so a fresh one was cut from the existing branch and nothing was re-derived from memory. GOVERNING CLAIM VERIFIED: comment 5736191612 names branch claude/issue-17556-app-contributed-first-run-credentials, which is the branch worked. FINDING THE ERROR WITHOUT THE LOGS -- the seat's 403 on GET /actions/jobs/{id}/logs reproduces here (the redirect target productionresultssa16.blob.core.windows.net is denied by egress policy, so it is the storage host and not the API that is blocked), but GET /repos/{o}/{r}/actions/jobs/{id} returns a steps[] array with a per-step conclusion and that endpoint is NOT blocked. On job 105786011438 it names the failure exactly: step 16 'Check @objectstack/spec declaration text (the shape half)' = failure, steps 17-31 skipped, everything before it success. That step is `pnpm --filter @objectstack/spec run check:api-surface-declarations`. Recommend the seat add that endpoint to its own reading kit -- it answers 'which step' on every red lane at zero risk. REPRODUCED PRE-MERGE at 53f78a5796 (spec built from a removed dist, then the gate): exit 1, verbatim tail 'A REMOVED or RESHAPED declaration is a breaking change for third parties' above '@objectstack/spec declaration text changed: 0 removed, 0 added, 33 reshaped.', all 33 rows under the ui.txt heading. ROOT CAUSE, MEASURED RATHER THAN INFERRED: the committed ui.txt was not what any build of its own source produces. Three md5s of the same path -- committed on the branch 8e21ac4c2477a2915b597291feb097fd, at the PR's merge base 56be11629d71ab49756f5ac062219db3, freshly generated from the branch's own source e6a9fd7d1cfc96084505a07767e684d5. The fresh file is 330 changed lines from the committed one and only 10 from the merge base, so the committed artifact -- not the PR's real delta -- is the outlier. The plus-165 / minus-165 churn the PR body and seat comment 5737165273 both analyse was a property of that artifact, not of this change. THE MERGE (ordered first, --no-ff, no rebase, no force-push): origin/main at 43448b62dd merged clean, 185 files, zero conflicts -- and the clean merge produced a WRONG generated file, which is worth the seat's attention on its own. Git merged two generated declaration shards textually and kept four hunks of a surface main had RETIRED in 18.observability-cel-predicates-retired: the merged system.txt carried 1617 occurrences of the retired CEL predicate line where main carries 1613. No conflict marker, no signal except the gate. Post-merge the gate went from 33 reshaped to 41, now spread across root.txt, system.txt and ui.txt. THE FIX is regeneration, and it is the whole fix: `gen:api-surface-declarations` rewrote the three shards, and `check:generated` then named one more stale artifact the merge had moved -- docs/audits/2026-07-unknown-key-strictness-ledger.counts.md, ui/ sites 449 to 450 and passthrough 4 to 5, which is main's new component.zod.ts site and nothing of this card's. Both regenerated in commit 9b0cba8b19. AFTER the fix the gate reads, verbatim, '@objectstack/spec declaration text unchanged (17 entry points, 5359 declarations)'. DETERMINISM PROVEN, because 'regenerate and commit' is only a fix if CI rebuilds the same bytes: two independent clean builds at the post-merge head (dist removed, build --force, each about 3 minutes) produced byte-identical shards -- all 17 md5s equal, and `git status` empty after the second regeneration. CONTROL LEG, the leg that makes the above mean something: a separate worktree at origin/main, installed and built the same way in this same container, reproduces main's committed shards EXACTLY -- check:api-surface-declarations exit 0, 'declaration text unchanged (17 entry points, 5356 declarations)'. So this container reproduces CI's declaration output for unchanged source, and the residual 10-line ui.txt delta (one enum member, `note`, repositioned in five places) is genuinely caused by this PR's new module shifting the dts rollup's module graph. It is not an environment artifact and it is not a surface change: no member added, none removed. THE REAL DELTA vs origin/main is now small and reviewable: system.txt 23 changed lines (DevLoginSchema and the export counts), root.txt 14 (the two stack keys and the entry shape), ui.txt 10 (the `note` reposition), data.txt 2. CI AT THE NEW HEAD HAS ALREADY CONVERGED and is not a prediction: 35 check runs, 33 success, 2 skipped, 0 failure; 'Type Check , consumer gates' and 'TypeScript Type Check' -- the two that were red -- are both success. NOTHING ELSE WAS TOUCHED: this round changed four generated files and added a merge commit. No source file, no test, no schema.",
      "tests": "All readings at final head 9b0cba8b19 (worktree /home/user/objectstack-issue-17556, since removed), heavy runs through scripts/pm/os-verify-lock.sh slot issue-17556, verdicts read off its VERDICT command-exit line; every gate exit code captured by redirect-then-capture, never across a pipe. [1] THE FAILING GATE, BEFORE: `pnpm --filter @objectstack/spec run check:api-surface-declarations` at 53f78a5796 -- exit 1, '0 removed, 0 added, 33 reshaped', all 33 in ui.txt. AFTER, at 9b0cba8b19 -- exit 0, 'declaration text unchanged (17 entry points, 5359 declarations)'. [2] DETERMINISM: two clean builds (rm -rf packages/spec/dist, then `pnpm --filter @objectstack/spec build --force`, VERDICT command-exit 0 both times, 189s and 168s) each followed by `gen:api-surface-declarations` -- all 17 shard md5s identical between the two runs, `git status --porcelain` empty after the second. [3] CONTROL LEG: worktree at origin/main 43448b62dd, `pnpm install`, same clean build (VERDICT command-exit 0, 178s), `check:api-surface-declarations` exit 0 'declaration text unchanged (17 entry points, 5356 declarations)' -- this container reproduces CI's declaration bytes for unchanged source, which is what licenses reading the branch's own regeneration as what CI will build. Control worktree removed afterwards. [4] GATE SWEEP, re-derived for the post-merge file list: `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands` at 9b0cba8b19 -- 28 paths vs merge base 43448b62d, 131 families. All 131 run, each recorded as `command :: exit code`; every one exit 0. Reconciliation `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --ran RECORD` -- exit 0, '131 derived famil(ies) accounted for -- 131 run, 0 NOT-MEASURED (a DERIVED zero -- all 131 recorded an exit code and none of them is 3)'. [5] FIVE FAMILIES REFUSED ON THE FIRST PASS AND WERE NOT LEFT THERE: check:dual-build-cjs-loads exit 3 (12 packages with no dist), check:i18n / check:i18n-coverage / check:i18n-walk-parity exit 3 (the workspace CLI not built), check:skill-examples exit 1 (packages/client-react/dist holds no declarations -- a refusal, not a finding: the script says a verdict then would be a false green). All five went exit 0 after `pnpm build`, and that build was NOT a cost bought for them: 72 of its 73 tasks were turbo cache hits and it took 4.9 seconds. [6] AFFECTED PACKAGES: `pnpm --filter @objectstack/spec test` -- VERDICT command-exit 0, 'Test Files 499 passed (499) / Tests 14600 passed (14600)'. `pnpm --filter @objectstack/cli exec vitest run --project unit --maxWorkers=2` -- VERDICT command-exit 0, 'Test Files 219 passed (219) / Tests 3082 passed (3082)'. `pnpm --filter @objectstack/spec typecheck && pnpm --filter @objectstack/cli typecheck` -- VERDICT command-exit 0. The CLI integration tier is DECLARED TO CI: this round's diff is four generated files, so it adds no spawn entry point and touches no tier-integration file. [7] REPO-WIDE, unnarrowed: `pnpm lint` (eslint . --no-inline-config) exit 0 in the foreground -- no narrowing was needed, so none of the three narrowing readings is owed. [8] CONTROL-CHARACTER SELF-SCAN over the four edited files: grep -naP over the C0/C1 class exit 1 (no match), with a positive control on a fabricated file exit 0 -- so the zero is a reading, not an unarmed pattern. [9] NO ABLATION AND NO REVERSE VERIFICATION IS REPORTED, because this round adds no guard and no assertion: the template's slot for them does not apply. The discriminating evidence for a regeneration fix is the determinism pair plus the control leg, which are items 2 and 3.",
      "mcp_calls": "0 -- no MCP tool of any kind was called, for reads or for writes. Every GitHub read and every write went through the REST proxy with curl.",
      "api_writes": "1 REST write: POST /repos/objectstack-ai/objectstack/issues/17556/comments -- this report. Nothing else. No label write (the dispatch forbids it and needs:contract-review was neither touched nor read-modified), no PR-body PATCH, no new PR, no draft flip, no auto-merge. Reads used: GET /issues/17556, GET /issues/17556/comments, GET /pulls/19139, GET /issues/19139/comments, GET /commits/{sha}/check-runs twice, GET /check-runs/{id} twice, GET /check-runs/{id}/annotations twice, GET /actions/jobs/105786011438 (the step-level read that found the failing step). Also 1 `git push` of the branch (git, not an API write).",
      "open_questions": [],
      "out_of_scope_findings": [
        "noted, not filed: the earlier round's committed packages/spec/api-surface-declarations/ui.txt matched no clean build of its own source, while that round reports check:api-surface-declarations exiting 0 locally. The gate refuses a stale dist up front via inspectDistFreshness (it reads dist/.build-input-hash-dts), so for both statements to be true that guard must have been satisfied by a dist whose dts rollup did not match a clean build of the same source. I have no reproduction and no failing probe -- two clean builds and the origin/main control leg all agreed -- so this is an observation, not a class-(a) defect, and it is not filed. Successor: the next author who regenerates a declaration shard, because they inherit the same guard; none is in flight today. Dedupe words if a card is ever wanted: api-surface-declarations, inspectDistFreshness, build-input-hash-dts, dts rollup member order, shard regenerated from a stale dist.",
        "noted, not filed: merging origin/main into this branch produced a clean, conflict-free, and semantically WRONG packages/spec/api-surface-declarations/system.txt -- it kept four hunks of the CEL-predicate surface main had retired (1617 occurrences of the retired line against main's 1613). This is not a defect: the shards are deliberately sharded for the merge queue, the gate caught it immediately, and regeneration is the prescribed repair. Recording it because the failure mode is invisible without the gate -- a reviewer reading that merge's diff sees nothing. Successor: none in flight; any future PR that merges main while carrying regenerated shards. Dedupe words: generated shard textual merge, api-surface-declarations merge, observability CEL predicates retired, 18.observability-cel-predicates-retired, merge clean but stale."
      ],
      "deviations": [
        "FILE SURFACE, outside packages/spec/** and packages/cli/**: this round's own commit 9b0cba8b19 touches docs/audits/2026-07-unknown-key-strictness-ledger.counts.md, a generated ratchet count. It is not a choice -- check:strictness-ledger named it stale after the merge (ui/ sites 449 to 450, passthrough 4 to 5) and its only remedy is `pnpm --filter @objectstack/spec gen:strictness-ledger`. The content is main's new component.zod.ts site, not this card's.",
        "FILE SURFACE, the merge itself: commit aac937241e brings origin/main's 185 files onto the branch. That is the ordered first step of this round, not a surface decision.",
        "A FULL `pnpm build` WAS RUN, which the order told me not to buy just for the NOT MEASURED line. It was not bought for that line: five families in the re-derived 131 refused as PREREQUISITE NOT MET or as an explicit build refusal, and running them is owed. It then cost 4.9 seconds because 72 of 73 tasks were turbo cache hits, so the open item closed as a free side effect rather than as a purchase.",
        "`pnpm lint` was run over the whole repository rather than narrowed. It is CI's run and not owed, but it finished inside the foreground cap, so the unnarrowed exit 0 is reported instead of a narrowing claim."
      ],
      "pr_body_corrections_for_the_seat": [
        "Verification section, the `dispatch-gates --ran` bullet: replace '131 derived families accounted for: 129 run green, 2 NOT MEASURED' and the sentence explaining the exit 3 with the re-measured reading -- 131 derived, 131 run, 0 NOT MEASURED, re-derived and re-run at head 9b0cba8b19, every family exit 0. check:dual-build-cjs-loads and check:type-check-debt are both exit 0. This CONFIRMS the earlier round's claim and closes the item the seat recorded as unresolved until re-run; it is now the seat's own re-measurement, not a dev assertion inherited across a dead worktree.",
        "The api-surface-declarations/ui.txt reading, in seat comment 5737165273 item 2 and everywhere the plus-165 / minus-165 figure appears: that artifact no longer exists on the branch. Since 9b0cba8b19, ui.txt differs from origin/main by 10 lines, not 330 -- one enum member (`note`) repositioned in five places, no member added or removed. The seat's member-grain measurement was correct about the file it measured; the file has been replaced because it was what made the gate red. The real additions remain where the seat said: root.txt plus 14, system.txt plus 23.",
        "Optional, if the seat wants the body to carry the cause: the red was not in any hand-written file. The committed ui.txt did not match a build of its own source (three distinct md5s, recorded in the report), and two clean builds plus an origin/main control leg show the regenerated bytes are what CI rebuilds -- which the 33 success / 0 failure check runs at the new head then confirmed."
      ]
    }
    

    Generated by Claude Code

  5. added a commit that references this issue on Sep 28, 2026
    24d622b
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions