Skip to content

fix(devx): the objectui pin guard tests walk completeness, not object presence - #9448

Merged
os-steve merged 1 commit into
mainfrom
claude/issue-9408-shallow-walk-guard
Aug 18, 2026
Merged

os-steve merged 1 commit into
mainfrom
claude/issue-9408-shallow-walk-guard

Conversation

@os-steve

Copy link
Copy Markdown
Collaborator

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/objectui is shallow right now, boundary 75444e38a, inside the pin range:

$ git -C /home/user/objectui rev-parse --is-shallow-repository   ->  true
$ cat .git/shallow                                               ->  75444e38a07e...
$ git rev-list --count --no-merges 665661ab0932..82a94170c405    ->  137   (truth: 191)

Running the unmodified digest against it credited 12 entries to 75444e38a and exited 0 — PR #9398's competing derivation, regenerated. Grafting a second copy at 183d09b78 (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 at 83fe945d6 (119 lines, diff clean). The index arithmetic holds independently: 183d09b78 at index 110, 75444e38a at 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-only attributes to it — is described as catching all three bad runs. Measured against both runs I can reproduce, it catches neither:

specimen credited to sink git show --name-only attributes fires?
boundary 183d09b78 (landed record) 36 72 no
boundary 75444e38a (this container) 12 47 no

It cannot fire on any history. git show --name-only on the sink runs the same empty-tree diff that produced the fault, and the digest's credited set is that attributed set minus the seen dedup — 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

findRangeTruncation tests a parentless commit inside the range. That is the harm itself — the sink is the parentless commit — so it catches a --depth clone, a hand-written .git/shallow, a graft, a git 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 before from walks 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, so objectui-range.mjs cannot derive from a truncated walk either (it already wraps that call in a loud die()).

Deepen first — measured, not assumed

git fetch --unshallow on 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 with OBJECTUI_NO_DEEPEN=1.

One measured trap it handles: git fetch --unshallow in 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

  • The degraded path is genuinely honest. Confirmed, unchanged: it emits ⚠️ **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 in bump-objectui.sh's own RANGE_OK, which now goes through --check-walkable and 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-repository guard 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 commits with 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 with git checkout HEAD --, byte-identical, green.

Gates run against 68615c550 (working tree clean, equal to head):

pnpm check:objectui-changeset   OK  (120 checks; digest + objectui-range self-tests)
pnpm check:nul-bytes            OK  (6122 files)
pnpm check:objectui-pin-fresh --self-test   OK

Gate family derived from the changed paths with node scripts/pm/dispatch-gates.mjs, which names check:objectui-changeset alone; check:nul-bytes and the pin-fresh consumer were added because the diff touches them.

Scope

scripts/bump-objectui.sh and scripts/objectui-changeset-digest.mjs only, as dispatched. No changeset: scripts/ is repo tooling and this PR publishes nothing, so it carries skip-changeset.

.github/workflows/cut-rc.yml has 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

… 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
@claude claude Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Aug 18, 2026
@os-steve
os-steve marked this pull request as ready for review August 18, 2026 01:37
@os-steve
os-steve enabled auto-merge August 18, 2026 01:38
@os-steve
os-steve disabled auto-merge August 18, 2026 01:39
@os-steve
os-steve enabled auto-merge August 18, 2026 01:39
@os-steve
os-steve disabled auto-merge August 18, 2026 01:39
@os-steve
os-steve added this pull request to the merge queue Aug 18, 2026
Merged via the queue into main with commit afe0195 Aug 18, 2026
26 of 28 checks passed
@os-steve
os-steve deleted the claude/issue-9408-shallow-walk-guard branch August 18, 2026 02:04
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/m skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants