fix(devx): the objectui pin guard tests walk completeness, not object presence - #9448
Merged
Merged
Conversation
… presence `bump-objectui.sh` decided whether the pin range could be walked with `git cat-file -e OLD_SHA` — "is the OLD endpoint present as an object". That is a different question from "is the walk between the endpoints complete", and the gap is measured rather than theoretical: on the bump that landed `.changeset/console-82a94170c405.md` the test PASSED against a history truncated at commit 110 of 191, so the degraded path never fired and the digest exited 0 having credited 36 of its 119 entries to one commit that adds exactly one changeset. A truncated history is worse than an absent endpoint precisely because it ANSWERS: git presents its oldest visible commit as parentless, diffs it against the empty tree, and reports every `.changeset/*.md` in that commit's tree as added by it. The dedup in `collectAddedChangesets` then hands it whatever no newer commit claimed, so one commit absorbs a batch. `findRangeTruncation` tests the harm directly — a parentless commit INSIDE the range — which catches a `--depth` clone, a hand-written `.git/shallow`, a graft, a `git replace` and unrelated histories without naming any of them, and which does not fire on a shallow clone whose boundary sits at or before `from` (that range walks completely, and refusing it would be a false positive). It lives in `classifyRange`, the single shared implementation, so `objectui-range.mjs` cannot derive from a truncated walk either. `bump-objectui.sh` now repairs the input before labelling a derivation of it: `git fetch --unshallow` costs ~6s and ~4MB on objectui and takes the walk from 110 commits to the true 191, while a degraded console changeset becomes permanent published CHANGELOG text. The fetch is additive, announced, opt-out-able with OBJECTUI_NO_DEEPEN=1, and — measured — can exit 0 without repairing anything, so the range is re-checked instead of the status trusted. The arithmetic check the card sketched is NOT implemented, because it was measured not to work: `git show --name-only` on the sink runs the same empty-tree diff, so the credited set is a subset of the attributed set by construction. It fires on neither known-bad run (36 credited vs 72 attributed; 12 vs 47) and cannot fire on any history. Pinned as a tautology in the self-test so it is not reintroduced as a cheap backstop. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XqDQYVU5smx29ts9pAErja
os-steve
marked this pull request as ready for review
August 18, 2026 01:37
os-steve
enabled auto-merge
August 18, 2026 01:38
os-steve
disabled auto-merge
August 18, 2026 01:39
os-steve
enabled auto-merge
August 18, 2026 01:39
os-steve
disabled auto-merge
August 18, 2026 01:39
This was referenced Aug 18, 2026
This was referenced Sep 14, 2026
This was referenced Sep 14, 2026
akarma-synetal
pushed a commit
to akarma-synetal/framework
that referenced
this pull request
Sep 17, 2026
…— REST-only content writes, the ACCEPT refuses MCP writes, a stale shared checkout re-seats (objectstack-ai#18205) (objectstack-ai#18216) Fixes objectstack-ai#18205 ## The maintainer's order (verbatim, ⛔ not translated) 「派发令硬性指定 REST 通道:建议改。 你应该修改skills吧?」 and 「不只是 objectui 仓库,其他第三方元数据app仓库怎么办」 — the maintainer, 2026-09-14, in the skills seat's chat (audit comment 5666103417 on the card). Lock 1 (PR objectstack-ai#18072, `7ef05f9973`) stays in force as the channel rule; this PR corrects its identity claims and does not weaken it. Landing is governed by ruling C (objectstack-ai#17971): 「C. approve 后不管后续改动都由席位落地:」 — this PR is a DRAFT and stays one; the seat does the four-piece after ACCEPT and lands only after an authorized approval. ## What changed — equal-line under every ratchet, every touched line at or under 120 bytes Line numbers are on this branch at `7103d0b09f`; B = bytes of the line as stored. Readings taken 2026-09-14T16:27Z. | file | line | after | B | |---|---|---|---| | `.claude/agents/os-dev.md` | :51 | GitHub 写一律走 REST 代理(`curl` 带 `GITHUB_TOKEN`);归属 = 文本里的 session ID,非 `user.login`。 | 116 | | `.claude/agents/os-dev.md` | :53 | ⛔ 不用 MCP GitHub 写工具;令牌按会话定:installation ⇒ `claude[bot]`,user-to-server ⇒ 用户。 | 115 | | `.claude/agents/os-dev.md` | :369 | `"mcp_calls": "N — MCP GitHub calls with tool names; a write tool in the list = this report is refused",` (the file spells the placeholder N inside angle brackets, as the template always has) | 109 | | `SKILL.md` | :91 (new) | 同读 harness 载入面 `.claude/{settings.json,agents/*.md,hooks/*}` 的最新触碰是否已在共享检出 HEAD。 | 120 | | `SKILL.md` | :92 (new) | 否 ⇒ 收班、换新会话再派,⛔ 不推进共享检出;读数走 `scripts/pm/check-harness-current.mjs`。 | 115 | | `SKILL.md` | :97 | 用户账号仅三用:assignee、授权批准、维护者亲手;批准账号永不跑席位或作其关联用户。 | 117 | | `SKILL.md` | :98 | 内容写只走 REST 代理,⛔ 无 MCP 写;`user.login` 记令牌不记席位,归属 = 文本里的 session ID。 | 116 | | `SKILL.md` | :195 | 新仓登记是一张清单:座位贴、标签、类别归属、门禁盘点、写身份锁移植(deny + hooks)。 | 116 | | `SKILL.md` | :537 (merged) | 终报要求随派发词带一句:只收机器可核字段(gates / line_budget / deviations / files_changed)。 | 113 | | `SKILL.md` | :538 (new) | 派发令恒带 `Writes:` 行:只走 REST 代理、写预算(端点清单)、`mcp_calls` 计数,dev 两数都报。 | 117 | | `SKILL.md` | :556 (merged) | `mode:cloud` 只保留给 L/XL、活过 PM 会话的工作、浏览器/dogfood 验证;build 重的 M 卡逐卡判。 | 118 | | `SKILL.md` | :567 (merged) | 标记两种拼写等效(HTML 注释形、首行 `os-dev-report`);⛔ 永不把没收到失败通知读作还在跑。 | 120 | | `SKILL.md` | :602 (new) | `mcp_calls` 点名写工具(`settings.json` deny 清单 + `update_pull_request`)⇒ 拒收,⛔ 不带注放行。 | 115 | | `SKILL.md` | :775 (merged) | 终报 JSON 的权威形状住 `.claude/agents/os-dev.md` 终报消息节,⛔ 本文不抄第二份。 | 104 | | `references/core-rules.md` | :25 | 用户账号仅三用:assignee、授权批准、维护者亲手;写只走 REST 代理,署名随令牌非席位。 | 115 | | `references/platform-readings.md` | :129 | 容器 curl 的 REST 通道令牌按会话定:installation(`claude[bot]`)或 user-to-server(用户),core 15,000/时。 | 120 | | `references/rest-channel.md` | :54 | 直合仓 `PUT .../pulls/{n}/merge`;actor 记通道令牌:REST 按会话为 `claude[bot]` 或用户,MCP 恒用户。 | 118 | `SKILL.md` and `references/` are `.claude/skills/pm-dispatch/`. The two os-dev.md rule lines carry their 3-space list indent inside the count. - (a) facts and invariants: content writes go only through the REST proxy; ⛔ no MCP content write; `user.login` on a write names the channel's token — installation ⇒ `claude[bot]`, user-to-server ⇒ the bound user — per session, not the seat's to choose, never the actor; attribution is the session ID in the text carrier. os-dev.md :51/:53, SKILL.md :97–:98, core-rules :25, platform-readings :129, rest-channel :54. - (b) dispatch order and acceptance: SKILL.md :538 — every dispatch order carries a `Writes:` line (REST proxy only, the write budget as an endpoint list, `mcp_calls` counted, the dev reports both numbers); SKILL.md :602 — a report whose `mcp_calls` names a write tool (the `settings.json` deny list plus `update_pull_request`, which that list does not carry) is refused, ⛔ not accepted with a note; os-dev.md :369 says the same from the dev side. - (c) propagation: SKILL.md :91–:92 beside the three-charter-file reading — at fire time the seat also reads the latest `origin/main` touch of `.claude/settings.json`, `.claude/agents/*.md`, `.claude/hooks/*` against the shared checkout's HEAD; a touch not in HEAD ⇒ close the shift and re-seat in a fresh session before the next dispatch, ⛔ never advance the shared checkout in place. The reading is `scripts/pm/check-harness-current.mjs` (59 lines, git only, seat-side, ⛔ not wired into CI): exit 0 CURRENT, 1 STALE (each stale path with its touch), 2 UNDECIDED (shallow-clone negative that is not date-decided). - (d) fleet: SKILL.md :195 — the new-repo registration checklist gains the write-identity locks port (deny + hooks). The four repos without a port today: `cloud`, `objectos`, `hotcrm`, `www.objectos.ai`. Named here only; no cards from this PR — the seat that can reach each files its card (recorded on objectstack-ai#7623 until then). - (e) the managed-settings fact row: not landed in platform-readings (454/454, no payable pair in that file without deleting a ruled clause); recorded under Acceptance notes below with the doc sentences verbatim. ## Premise readings (falsified against the tree before writing; all UTC) - P1 (16:12Z, base `af3add1601`): all seven quoted lines read exactly as the dispatch quotes them — os-dev.md :51 「- GitHub 写一律走 REST 代理(`curl` 带环境 `GITHUB_TOKEN`),署名恒 App 的 `claude[bot]`。」 and :53 「- ⛔ 不用任何 MCP GitHub 写工具:用户账号署名,封号即隐;⛔ 不枚举板面、不宽词搜。」; SKILL.md :95 「- 用户账号仅三用:assignee、授权批准、维护者亲手;⛔ 席位与 dev 永不以用户账号写内容。」 and :96 「- 内容恒经 REST 代理(`claude[bot]`);批准账号永不跑席位、不作席位 claude.ai 的关联用户。」; core-rules :25 「- 用户账号仅三用:assignee、授权批准、维护者亲手;写恒经 REST 代理;批准账号永不跑席位。」; platform-readings :129 「- 容器 curl 的 REST 通道 = App installation token,core 15,000/时,与 GraphQL 池独立计。」; rest-channel :54 「- 直合仓另有 `PUT .../pulls/{n}/merge`;ccr 的 timeline actor 记 `claude[bot]`,MCP 记席位账号。」. Holds. - P2 (16:11:37Z): `git -C /home/user/objectstack rev-parse HEAD` = `84e6b05b6d295f1c744d236921300f447cf7791e`, `log -1 --format=%cI` = `2026-09-13T06:14:23+00:00`; `merge-base --is-ancestor 7ef05f9 HEAD` exit 1. Control legs for the negative (shallow checkout, `rev-list --count HEAD` = 4024): `is-ancestor 84e6b05 HEAD` exit 0 and, twelve commits deep, `is-ancestor d88a47d HEAD` (committed 2026-09-12T22:39:41Z) exit 0 at 16:12:17Z; the negative is also date-decided — `7ef05f9973` was committed 2026-09-13T23:27:23Z, seventeen hours after the shared HEAD. `grep -c 'mcp__github__' .claude/settings.json`: shared 1, worktree 15. Holds — with one sharpening: the shared file's single hit is a PreToolUse hook matcher (`mcp__github__enable_pr_auto_merge|mcp__github__merge_pull_request`), and the shared file has no `permissions.deny` key at all (`grep -c '"deny"'` = 0 against 1 on `origin/main`), so in this session no deny list was ever loaded, not a pre-lock-1 one. - P3 (16:12:09Z, `origin/main` = `af3add1601`): the grep hits are SKILL.md :31 (never edit the shared checkout), :160 (never verify main from its worktree), :506/:606/:774 (paths named as protocol/governed surfaces or as the report authority); core-rules :44/:149 (the same two); dispatch-runbook :215 (frontmatter `model:` exemption); platform-readings :30–:33 (merge-driver registration per clone), :214 (deny documented-not-measured), :344/:373/:415 (footer, transcript, sleep) and :413 (shallow-clone deepen); app-platform-boundary :60 and contract-review :57 (the word harness in other senses). None prescribes re-seating when a harness-loaded file lands after the session's clone; SKILL.md :86–:90 re-READS the three charter files, and reading does not reload the harness. Control `git grep -c '收班简报'` on SKILL.md = 5. Holds. - P4 (16:12:09Z): `派发令` hits are SKILL.md :153/:164/:214/:433/:444/:461/:478/:540/:543/:715/:790 and dispatch-runbook :184/:205/:232/:236 — all rule lines about what the order carries; 〈模板与表〉 holds only the claim-comment template. No fixed shape exists, so the `Writes:` mandate lands as a rule line (SKILL.md :538). Holds. - P5: os-dev.md :369–:370 are the `mcp_calls` / `api_writes` report fields; :57 already orders both counts. Holds; :369 rewritten, :370 untouched. - P6 (16:17:26Z on the base): `check-skill-line-ratchet` exit 0 with every one of the five files at its ceiling (812 / 403 / 454 / 82 / 151, headroom 0, table-row pins 342 / 0 / 0 / 0 / 0); `check:skill-frame-sync` exit 0; `git ls-remote --heads origin` matched only this branch for issue-17497/18205/18181/18158. Holds. - P7: `scripts/pm/dispatch-gates.mjs` is untouched; objectstack-ai#14290's `Restart-touch` surface is left alone; the seat-side check is the sibling file `scripts/pm/check-harness-current.mjs`. ## The mechanism, measured in this session (16:25Z) - The os-dev.md this dev runs under is the shared checkout's copy: its line 「通道先探后选…」 is in `git show 84e6b05:.claude/agents/os-dev.md` (1 hit) and absent on `origin/main` (0); `api_writes` is the inverse (0 in the old copy, 2 on `origin/main`); control `Worktree-first` 1 / 1. - `node scripts/pm/check-harness-current.mjs` from this worktree (shared checkout resolved through `--git-common-dir`): exit 1 — `.claude/settings.json` and `.claude/agents/*.md` latest touch `7ef05f9973` NOT in shared HEAD `84e6b05b6d`, `.claude/hooks/*` latest touch `d79f249915` (2026-09-12T09:41:28Z) in HEAD. `--shared /home/user/objectstack-issue-18205`: exit 0 CURRENT at `af3add1601`. `--shared /nonexistent`: exit 2. ## Gates (final head `7103d0b09f`, 16:28Z–16:37Z) `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` (no paths; change set derived from git, 6 paths, committed 6 / working tree 0 / untracked 0) printed 40 commands. All 40 run with redirect-then-capture, each recorded as `CMD :: exit N`: - 39 exit 0 on the first pass, including `check:pm-skill-ratchet`, `check:skill-frame-sync`, `check:pm-governed-prose`, `check:pm-skill-id-lint`, `check:nul-bytes`, `check:agent-model-declared`, `check:entry-guard`, `check:parse-guard`, `check-self-test-wired`, `check-scripts-symbol-anchors`, `check:commit-card-trailers`, `check:pm-governed-merges`. - `pnpm --filter @objectstack/lint run check:doc-formula-expressions` first read exit 3 = PREREQUISITE NOT MET (compiled `@objectstack/formula` and `@objectstack/lint` absent in the fresh worktree; the gate says "Nothing was measured"). Prerequisite cleared under the verify lock — `os-verify-lock.sh -c 'pnpm exec turbo run build --filter=@objectstack/formula --filter=@objectstack/lint --concurrency=2'`: VERDICT command-exit 0, held the lock 172 s, waited 0 s — then rerun: exit 0, "22 record-scoped formula example(s) across 438 files / 1377 TS blocks judged clean by @objectstack/formula." - Reconciliation: `dispatch-gates --ran ran.list --repo objectstack-ai/objectstack` at 16:37:21Z on `7103d0b09f`: "Run reconciliation — 40 derived, 40 run, 0 NOT-MEASURED, 0 UNRUN." (exit 0; the derived 40 is recomputed by the tool from the tree, never read back from the record). - Ratchet on the final head: every one of the five files at its ceiling, headroom 0, pins unchanged (SKILL.md widest table row 342). First pass on the working tree had caught os-dev.md :51 at 122 B (the list indent was outside the draft measurement); fixed in the second commit to 116 B. - Lint, narrowed and measured: the checked population is eslint's own config (`files: ['**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}']`, which covers `scripts/pm/*.mjs`); the only non-markdown file in the diff is `scripts/pm/check-harness-current.mjs`; `eslint --no-inline-config --format json` on it: 1 file, 0 errors, 0 warnings (exit 0); invariance: `eslint.config.mjs` states it "never enables type-aware linting (no `parserOptions.project`, no typed `@typescript-eslint` rules) for ANY file", so a one-file addition cannot move any untouched file's verdict. The repo-wide `pnpm lint` is CI's run. - Not run here, declared to CI: nothing else — the diff touches no package, so there is no ① build closure or ② package test suite; `.claude/**` and `scripts/pm/**` publish nothing, so `skip-changeset` applies (fast lane: `.claude/**` · `scripts/pm/**`). The seat writes the label; this container does not. ## Density paid inside each file - SKILL.md (four new lines, four merges): 终报要求 + 机器可核字段 → one line (drops only the implied 「⛔ 复述 PR body 叙事」); `mode:cloud` + build-heavy-M → one line (drops 「必须」); marker spellings + missing-notification → one line (the dropped 「仅凭 HTML 注释形式缺失永不读作报告未达」 is what 「两种拼写等效」 states); 报告契约 authority + no-second-copy → one line (drops the implied 「字段与拼写以那里为准」). - os-dev.md :53 drops 「⛔ 不枚举板面、不宽词搜」 — :50 (「⛔ 不扫 open issues、不拉板」) and :54 (single-card reads only) already carry it. - core-rules :25 (the compressed mirror) now carries the channel + identity reading; the approval-account clause could not fit beside it in 120 B and stays where it is authoritative, SKILL.md :97. ## 维护者速读(草稿) - 改了什么:① 署名跟令牌走、不跟账号走 —— GitHub 上写回读到的 `user.login` 只说明这条会话的令牌是 App 的还是用户的,不说明是谁在写;身份看文本里的 session ID。② dev 报告里出现任何 MCP 写工具即拒收,不带注放行。③ harness 读的文件(`settings.json`、agents、hooks)在 main 上动了而共享检出没跟上时,席位收班、换新会话再派,永不原地推进共享检出。 - 为什么改:锁 1 落地后 objectui 仍出现一条经 MCP 建的 PR,原因是运行中的会话只在克隆那一刻读一次这些文件;同时章程里「署名恒 `claude[bot]`」被四个会话的实测证伪。 - 风险与代价(含回滚):纯规则文本 + 一个只读 git 的席位脚本,零 CI 接线;回滚即 revert 这一个 PR。代价是每次开轮多一次 git 读数,与一次可能的换会话。 - 席位意见:(留空) - 你要做的:一个动作 —— 批准这份草稿,席位落地。 ## Acceptance notes - Item (e), recorded here instead of a fact row: code.claude.com/docs/en/settings 「Settings in cloud sessions」 states, verbatim: "**Shared project settings** (`.claude/settings.json`): read, because the file is part of the clone." / "**User and project local settings** (`~/.claude/settings.json` and `.claude/settings.local.json`): not read. Both stay on your machine, and the local file isn't in the clone." / "**Managed settings**: only server-managed settings reach a cloud session; a `managed-settings.json` file or MDM profile on your device doesn't." And code.claude.com/docs/en/server-managed-settings: "Server-managed settings are available for Claude for Teams and Claude for Enterprise customers." So a personal account has no managed tier, and the maintainer-level lever the card names (a user-level file written by the environment setup script inside the cloud VM) is not the file those sentences describe — the docs speak of the file on the user's own machine; whether a user file written inside the VM is read is not stated. Bearer: the round report (the card already routes the lever there). - The card's "1 `mcp__github__*` entry (pre-lock-1)" in the shared checkout's settings is a hook matcher, not a deny entry; the shared file has no `permissions.deny` at all. Whether a deny list loaded from the clone takes effect in a cloud session therefore remains documented-not-measured (platform-readings :214 stands); the first session cloned after `7ef05f9973` measures it by tool-table absence. Bearer: the skills seat's next fresh session. - `mcp__github__update_pull_request` edits PR bodies and titles through MCP and is not in `.claude/settings.json`'s deny list; the ACCEPT line names it explicitly for that reason. Reported in the dev report for the seat to file or fold (⛔ not changed here: `.claude/settings.json` is outside this card). - The script has no `--self-test` on purpose: it is not CI-wired (`check-self-test-wired` populates from workflows), it exports nothing (`check:entry-guard` rule two does not apply), and its three readings above are the measurement. Bearer: whoever wires it into a workflow later owes the self-test then. - `objectstack-ai#18181 remains open` (os-dev.md :287 label write is not addressed here); `objectstack-ai#18158 remains open` (the identity reading itself); the objectui port (PR objectstack-ai#9448) is untouched. Clause-②: no --- _Generated by [Claude Code](https://claude.ai/code/session_01DAcomhvR9kKizeYgg89Vo8)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #9408
The card is right about the mechanism and I reproduced it byte-for-byte before changing anything. Two of its four suggested repairs are implemented, one is subsumed, and one is not implemented because I measured it and it does not work — details below.
Reproduced first, on two specimens
This container is still a live specimen — the PM triage note expected it not to be.
/home/user/objectuiis shallow right now, boundary75444e38a, inside the pin range:Running the unmodified digest against it credited 12 entries to
75444e38aand exited 0 — PR #9398's competing derivation, regenerated. Grafting a second copy at183d09b78(the card's own recipe) reproduced the landed record exactly: 36 entries credited to the sink,110 non-merge commits, and the entry list byte-identical to the pre-correction artifact at83fe945d6(119 lines,diffclean). The index arithmetic holds independently:183d09b78at index 110,75444e38aat index 137.Not implemented: the arithmetic check, because it cannot fire
The card's fourth suggestion — no commit may be credited with more added changesets than
git show --name-onlyattributes to it — is described as catching all three bad runs. Measured against both runs I can reproduce, it catches neither:git show --name-onlyattributes183d09b78(landed record)75444e38a(this container)It cannot fire on any history.
git show --name-onlyon the sink runs the same empty-tree diff that produced the fault, and the digest's credited set is that attributed set minus theseendedup — a subset by construction. The comparison is a tautology: no false positives because no positives at all. Pinned as such in the self-test (C12) so it is not reintroduced later as a "cheap backstop".What replaces it: one true check
findRangeTruncationtests a parentless commit inside the range. That is the harm itself — the sink is the parentless commit — so it catches a--depthclone, a hand-written.git/shallow, a graft, agit replace, and unrelated histories without naming any of them. It is also strictly more precise than asking whether the clone is shallow: a shallow clone whose boundary sits at or beforefromwalks the range completely, and refusing it would be a false positive. In an untruncated history the range cannot contain a parentless commit at all, so a hit is always real.It lives in
classifyRange, the single shared implementation, soobjectui-range.mjscannot derive from a truncated walk either (it already wraps that call in a louddie()).Deepen first — measured, not assumed
git fetch --unshallowon objectui costs 6 seconds and ~4 MB, and takes the walk from 110 commits to the true 191. A degraded console changeset becomes permanent published CHANGELOG text, so repairing the input beats labelling a truncated derivation of it. The fetch is additive (adds objects, drops.git/shallow; moves no branch, touches no working tree), announced before and after, and opt-out-able withOBJECTUI_NO_DEEPEN=1.One measured trap it handles:
git fetch --unshallowin a checkout with no remote configured exits 0 and changes nothing. The range is therefore re-checked afterwards rather than the fetch status trusted — otherwise this card's failure would simply reappear one layer further in. Pinned in C17/C18.The other two PM assumptions, tested
⚠️ **Degraded list**, "NOT a complete account of the range", and the tip subject only. I extended the reason only — a truncated range must also be distinguishable from an absent endpoint, because the remedies differ and only one is a fetch away.to-endpoint validation is missing. Partly falsified. The digest CLI already validated both endpoints (exit 2, verified). The gap was inbump-objectui.sh's ownRANGE_OK, which now goes through--check-walkableand covers both.Tests
18 new checks (C1–C18) in
objectui-changeset-digest.mjs --self-test. The truncation is synthesized by writing.git/shallow, so the complete and truncated fixtures are the same commits differing in exactly one file — each assertion is about the graft, not about two repos that differ somehow.Both directions are pinned, per the explicit ask that a refusal-only test would pass on a guard that refuses everything: C1/C2 keep the complete walk, and C7 is the sharper control — a tree that is shallow with its boundary outside the range must still derive identically (
stillWhole.body === whole.body). C7 is exactly the false positive an--is-shallow-repositoryguard would produce.Reverse verification, at commit
68615c550: ablating both refusal call sites turns 8 checks red (C8, C9, C10, C13, C14, C15, C16, C17) and the ablated build emits the defect in miniature —5 releasing of 5 changesets added across 3 non-merge commitswith entries 1, 2 and 3 all credited to one sink. C3/C4/C5/C12 stay green by design: they measure the defect mechanism, not the guard. Restored withgit checkout HEAD --, byte-identical, green.Gates run against
68615c550(working tree clean, equal to head):Gate family derived from the changed paths with
node scripts/pm/dispatch-gates.mjs, which namescheck:objectui-changesetalone;check:nul-bytesand the pin-fresh consumer were added because the diff touches them.Scope
scripts/bump-objectui.shandscripts/objectui-changeset-digest.mjsonly, as dispatched. No changeset:scripts/is repo tooling and this PR publishes nothing, so it carriesskip-changeset..github/workflows/cut-rc.ymlhas the same object-presence test at line 285 and prints "range is walkable, the changeset digest will be complete" on the strength of it. It is currently harmless — that job clones objectui full, deliberately — so I did not widen this PR into a third file; filed separately instead.Generated by Claude Code
Generated by Claude Code