feat: Comprehensive CRM example demonstrating all ObjectStack protocol features - #14
Merged
Merged
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Co-authored-by: hotlong <[email protected]>
- Add phone field type helper to Field factory - Update Field.select/multiselect to support both array and object signatures - Add factory methods (Action.create, Dashboard.create, Report.create) - Use z.input and parse for factory methods to support default values Co-authored-by: huangyiirene <[email protected]>
Co-authored-by: hotlong <[email protected]>
- Add tests for Field.phone() helper - Add tests for Field.select() backward compatibility (old and new API) - Add tests for Field.multiselect() backward compatibility - Add tests for Action.create() with default value handling - Add tests for Dashboard.create() with default value handling - All 279 tests passing Co-authored-by: huangyiirene <[email protected]>
Copilot
AI
changed the title
[WIP] Add example of CRM functionality for protocol
feat: Comprehensive CRM example demonstrating all ObjectStack protocol features
Jan 19, 2026
Document backward-compatible API with examples for both old and new signatures Co-authored-by: huangyiirene <[email protected]>
- Extract options from config object to avoid confusion - Change spread order to options first, then finalConfig - Ensures processed options always take precedence Co-authored-by: huangyiirene <[email protected]>
huangyiirene
approved these changes
Jan 19, 2026
huangyiirene
marked this pull request as ready for review
January 19, 2026 12:18
This was referenced Jun 2, 2026
akarma-synetal
pushed a commit
to akarma-synetal/framework
that referenced
this pull request
Sep 17, 2026
…ors — the PRODUCER half of the sort seam (objectstack-ai#17914) Fixes objectstack-ai#17053 Clause-②: no⚠️ **Governed surface** — this diff touches `skills/**`, so under Prime Directive objectstack-ai#14 it is human-merge only. ⛔ Not queued, not ready-flipped, no auto-merge armed by this round. ## The seam, which is what this is graded on objectui ruled one sort orthography platform-wide — the array (objectui#8221, decision batch objectstack-ai#77, 2026-09-07, option B) — and objectui PR objectstack-ai#8758 executes it: `convertSortToQueryParams` refuses a runtime string and its diagnostic names the array form. `ListViewSchema.sort` is the **producer** of exactly those documents: `object.list.sort` is what `deriveRelatedLists` reads. So a view authored with `sort: 'created_at desc'` validated here, cleanly, and then failed downstream — the contract minting a shape its consumer rejects, with the author told off by the wrong layer. Triage graded p2 on that asymmetry, not on the string. This PR is the producer-side pull-back. ## Reproduction, re-taken on today's tree Taken on `origin/main` at `a9c6477` (the card measured against installed 17.3.0; this is the re-take), with `bogusProp` refused by name on the same call as the firing control: | input to `ListViewSchema.safeParse` | BEFORE | AFTER | |---|---|---| | `sort: 'name desc'` | PARSES | REFUSED at `sort` — `invalid_type`, prescription naming the array | | `sort: '-name'` | PARSES | REFUSED at `sort` | | `sort: [{ field, order }]` | PARSES | PARSES, value-for-value unchanged | | `sort: 42` | REFUSED `sort/invalid_union` | REFUSED `sort/invalid_type`, zod's default message | | CONTROL `bogusProp` | refused by name | refused by name | The control fires in both columns, so both the PARSES and the REFUSED readings are verdicts rather than a schema reporting nothing. ## Requirement 1 — the census, and it is NOT a silent zero The narrowing is not free, so the population was measured. The census swept both the TS and JSON spellings of a string-valued `sort` across the whole tree and each hit was read as a **structure**, never counted as a token. **It found three live authored sites on the narrowed slot, all converted here:** 1. `examples/app-showcase/src/ui/views/task.view.ts` — `sort: 'estimate_hours desc'` on a shipped list view, carried since objectui#2601 as a deliberate live coverage fixture for the string form. 2. `packages/lint/src/showcase-shape.fixtures.ts` — the frozen snapshot of that same shipped shape (it tracks the app through `defineView`; its subject, the three nameless form sections, is untouched). 3. `skills/objectstack-ui/rules/list-views.md` — the published rule taught the clause in two code blocks. ⇒ **A migration entry is therefore owed, and this PR carries one** (below). A silent narrowing would have been refused, correctly. **Lit control — the census could have found one, and an independent instrument agrees.** With the legacy string put back on the real shipped showcase view and nothing else changed, `tsc` reds at exactly that line: ``` src/ui/views/task.view.ts(208,7): error TS2322: Type 'string' is not assignable to type '{ field: string; order: "asc" | "desc"; }[]'. ``` Mutation proven landed (`git hash-object` a83cbee → 38a2f9b, occurrence count 0 → 1); restore proven by hash equality back to a83cbee and an empty `git diff HEAD`. **Read, not grepped — sites deliberately NOT converted.** ObjectQL `query.sort` and the wire `normalizeSortNodes` (different doors, different dialects); `packages/spec`'s `book`/`doc` field-mapping records whose `sort: 'order'` is an unrelated key of the same name; and the `packages/lint` rule fixtures, which feed the PRE-parse walker and never reach this schema. ## Requirement 2 — objectstack-ai#16553 does NOT cover this, re-measured Re-read rather than assumed. objectstack-ai#16553 is **closed as completed**, and its title and body bound it to `ComponentPropsMap` for `object-grid` / `object-calendar`. Its landed artifact, the semantic entry `18.object-block-sort-item-array`, names only those two doors and states in its own acceptance criteria that `record:related_list` is the one deliberate exception — `ListViewSchema` appears nowhere in it. The decisive reading is the reproduction above: on today's `origin/main`, with that work already merged, `ListViewSchema.sort` still accepted `'name desc'`. The gap is real and this PR is what closes it. ## What changed `sort` survives as a key, one union arm lighter, so this is a **value** narrowing with no `retiredKey()` tombstone to hang a prescription on. The surviving array member's own `error` map carries it, keyed on `issue.input` being a string — the same shape `view.type`'s retired `'page'` value and `view.exportOptions`' retired `'pdf'` value already use in this schema. Every other invalid value, and any string reaching a **descendant** (a misspelled `order`, say), keeps zod's default report, so nobody is told a clause they never wrote "was removed". **Migration** — `list-view-sort-string-clause-to-array`, a D2 conversion wired into the protocol-18 chain step, not a semantic TODO: the rewrite is lossless and wholly mechanical. `'created_at desc'` is the tuple; a bare field name meant ascending and is written out as `order: 'asc'`; a comma-separated clause becomes one entry per key, in the same order. A clause that does not parse as that grammar is left alone and meets the door instead — the `'-field'` dialect belongs to `RecordRelatedListProps.sort`, never reaches `convertSortToQueryParams`, and retiring it was not ruled, so guessing a direction for it would invent an ordering the author never wrote. **A real consequence the gates caught.** Removing the string arm made the sort entry's own keys visible to the liveness walk, which reported `view/list.sort` as an undeclared container inheritance. Drilled with in-repo evidence (`normalizeSortNodes` reads both `field` and `order`) rather than parked in the shrink-only baseline, so the container-coverage numbers did not grow. ## Ablation — both directions, with a cost-direction leg Subject resolves through a same-package relative source import, so no `dist` leg is in play; on-disk proof is still taken on every leg. Each leg: mutate → prove it landed (occurrence count AND `git hash-object`) → run → restore → prove restored by hash AND empty `git diff HEAD`. Baseline blob `6053ffc`. | leg | mutation | occurrences | hash | result | |---|---|---|---|---| | 1 — defect direction | re-admit the `z.string()` union arm | 5 → 6 | 6053ffc → a46df91 | **3 failed** / 3 passed | | 2 — **cost direction** | over-narrow the surviving arm (`order` enum loses `'desc'`) | 0 → 1 | 6053ffc → 48a1bcb | **2 failed** / 4 passed, on the POSITIVE pin | | control | unmutated tree | — | 6053ffc | 6 passed, 0 failed | Leg 2 is the cost direction on purpose: the price of this change is collateral narrowing of the spelling that has to keep working, and it shows the positive pin catches exactly that. ## Verification | run | exit | |---|---| | `pnpm --filter @objectstack/spec test` (`vitest run --project local`) | **0** — 474 files, 13492 passed | | `pnpm --filter @objectstack/spec test:repo` (`vitest run --project repo`) | **0** — 31 files, 523 passed | | `pnpm --filter @objectstack/spec check:generated` | **0** — "All 15 generated artifacts are up to date." | | `pnpm --filter @objectstack/spec build` | **0** | | typecheck: spec · lint · example-showcase | **0** each | | `node scripts/check-skills-token-ratchet.mjs` | **0** | | `node scripts/check-nul-bytes.mjs` | **0** | Every exit code above was captured before any pipe (`cmd > file 2>&1; EXIT=$?`), and each gate's own printed verdict line is what is quoted. Regenerated artifacts moved exactly as a value narrowing should: nine doc rows across three `content/docs/references/**` files and one react-blocks contract row each lost the `string |` arm, and nothing else moved. ## Clause-② — the two limbs, answered from the regenerated artifacts with a lit control Declared `no` as dispatched, and ⛔ not flipped by this round; the seat sets the final value. - **(a) Does the final diff add any exported symbol? — ZERO new exports.** `packages/spec/api-surface/**` and `packages/spec/export-origins/**` are **byte-identical** to the base commit `a9c6477`. - **(b) Does it add any key on a published payload? — No.** `packages/spec/authorable-surface/**` and `authorable-defaults/**` are likewise byte-identical. The diff removes a union arm; it declares no new key anywhere. **Lit control, so those zeros are measurements and not a blind instrument.** One dummy `export const OsIssue17053LitControl = 1;` appended to the same file, then rebuild + `gen:api-surface` + `gen:export-origins`, moved **both** artifacts by exactly one line each and named the symbol: ``` packages/spec/api-surface/ui.json:275: "OsIssue17053LitControl (const)", packages/spec/export-origins/ui.json:271: "OsIssue17053LitControl": "src/ui/view.zod.ts#OsIssue17053LitControl (const)", ``` Mutation proven landed by hash (6053ffc → ba113de); source and both artifacts restored and proven back at 6053ffc with a clean tree. ## Changeset `@objectstack/spec: minor`, graded against this repo's own precedent for an accept-set narrowing — the sibling `object-block-sort-item-array` changeset took `minor` for the same ruling under the launch-window convention for breaking changes. It carries the ADR-0087 registration marker. Publish surface measured rather than assumed, with controls: - `@objectstack/spec` `files[]` ships `dist` **and** `src/**/*.zod.ts`, so the edited `view.zod.ts` is literally published ⇒ a changeset is owed. - `@objectstack/lint` — **no changeset owed.** Its `files[]` is `["dist","README.md","CHANGELOG.md"]`, and the edited fixture symbol `SnapshotTaskViews` has **zero** occurrences in `packages/lint/dist/`, while the positive control `validateSortableFields` has **three**. The grep fires; the fixture is simply not published. - `examples/app-showcase` is `private: true`. - `skills/**` is in no package's `files[]`. ## Skills line budget `skills/objectstack-ui/rules/list-views.md` — 306 lines before, 306 after (net 0). Package total over all `SKILL.md` files: 6134 before, 6134 after (no `SKILL.md` was edited). The binding reading is the token ratchet, which reds where lines do not. The first draft came in at 3154 tokens against a 3011 ceiling (+143). ⛔ The ceiling was not raised and no re-wrap was used as currency: the net increase was paid **entirely by deleting content** — the retired string examples this change makes wrong. Final reading **3009 / 3011, gate exit 0**. The migration prescription therefore lives in the parse error, the changeset and the upgrade guide rather than in the published rule, which is where an upgrading author actually meets it. ## 维护者速读(草稿) **改了什么** — `ListViewSchema.sort` 不再接受旧的字符串子句(`'created_at desc'`),只接受 `{ field, order }[]` 数组。同批把树内三处仍在写字符串的文档改成数组,并补上一条 D2 迁移条目。 **为什么改** — objectui 已经裁定「一个拼法,数组」并在 PR objectstack-ai#8758 里让消费端**运行时拒收字符串**。spec 是那些文档的**生产者**,于是出现最坏的缝:文档在上游通过校验、在下游失败,作者被错误的那一层训斥。分诊原话:**契约铸造了一个消费者拒收的形状**。p2 判在这个不对称上,⛔ 不判在字符串本身。 **风险与代价(含回滚)** — 代价是真实的:用旧字符串写的文档停止通过校验。普查**测出**树内三处(showcase 列表视图、lint 快照、已发布技能),全部已转换,因此这不是静默收窄——D2 条目 `list-view-sort-string-clause-to-array` 可机械重写作者源码,存量行按既有路径重放。`RecordRelatedListProps.sort` 的 `'-field'` 方言**未被触碰**(不同方言、未经裁定),该形状的字符串本条目也拒绝猜测方向。回滚 = revert 本 PR:收窄是纯删除一条 union 分支,无数据迁移、无存量改写,回滚不留残迹。 **席位意见** — (留空) **你要做的** — 定 Clause-② 终值(本轮实测两肢均为零,见上)。本 PR 触 `skills/**` ⇒ 受管面,按 Prime Directive objectstack-ai#14 需**人工合并**。 ## 验收备注 Out-of-scope observations, noted and deliberately NOT filed: - `packages/lint`'s `readSortKeys` keeps a string arm for `sort`. It is a **pre-parse** walker on raw authored stacks, so it is defensive rather than dead, and `packages/lint` is a sibling round's declared face. Carrier: whoever next revisits that rule. Noted, not filed. - `packages/lint/src/showcase-shape.fixtures.ts` is the one file in this diff inside `packages/lint`, the face declared for objectstack-ai#17319. It is a one-line value change forced by `tsc` (the snapshot tracks the shipped app through `defineView`), and it does not touch that round's subject. Flagged here so the seat can see the overlap rather than discover it at merge. - The conversion walk reaches `stack.views[]` in all three persisted spellings but **not** `objects[].listViews.*` — the same boundary `view-page-mount-removed` states for itself. An object body carrying a string clause is refused at its own door rather than converted. Stated in the conversion's docblock rather than left to be discovered. Noted, not filed: widening the walk is its own card with its own population measurement. --- _Generated by [Claude Code](https://claude.ai/code/session_01MkQhmuuJAVDjmeWNixwDDH)_ --------- Co-authored-by: Claude <[email protected]>
akarma-synetal
pushed a commit
to akarma-synetal/framework
that referenced
this pull request
Sep 17, 2026
…ce tiering, ui/triage lane lines (objectstack-ai#17942 chain) (objectstack-ai#18018) Fixes objectstack-ai#17971 Fixes objectstack-ai#17742 Fixes objectstack-ai#17624 Part of objectstack-ai#17942 Part of objectstack-ai#17950 Part of objectstack-ai#17933 Part of objectstack-ai#17934 Part of objectstack-ai#17951 Dev session `session_01DAcomhvR9kKizeYgg89Vo8` on branch `claude/issue-17942-charter-revision` (worktree `objectstack-issue-17942`). Rules-layer charter revision for the eight-card family whose chain head is objectstack-ai#17942. Deferred to the follow-up references PR (files occupied by PR objectstack-ai#17990 / objectstack-ai#17992, seat-landable once this PR lands the tiering): `contract-review.md` (the objectstack-ai#17934 落地前检③ exception, objectstack-ai#17933's per-lane table, objectstack-ai#17950's 落地前检③ line, objectstack-ai#17942's 复核归属 / 独立性件), `dispatch-runbook.md` (objectstack-ai#17942's 跨车道直接接手), `lanes/director.md` + `decision-analysis.md` (objectstack-ai#17951's director half); the objectstack-ai#17950 register / queue-guard script items are their own script PR. objectstack-ai#17934 has no edit here — its ruling lives in a deferred file. ## Rulings landed (verbatim, one commit per card) - **objectstack-ai#17942** — 5651976160 「同意」 to `1A(+三类表)·2(1)·3A·4B`; 5652079343 「以上接受你的建议。」; 5652544529 ruling ③ 「同意」 (「一律经过分诊 … 唯一例外:在飞卡的衍生子卡 … 立卡者不查重,只在卡面附 3–5 个查重词 … 查重缓存住在分诊席自己的容器里」). Q3 withdrawn — no self-routing text had reached SKILL.md (`git grep -E '自路由|自定级|self-rout' origin/main -- .claude/skills/pm-dispatch` exits 1; control 「分诊座位唯一」 hits), so nothing came out. The 「分诊不重指派」 wording does not exist in the tree (git grep exits 1) — nothing to narrow. - **objectstack-ai#17950** — 「我点头」 to the boundary 「规则层(你确认)… 事实层(席内契约复审档复核后入队):只有 `.claude/skills/pm-dispatch/references/**`」. - **objectstack-ai#17971** — 5652306063 「C. approve 后不管后续改动都由席位落地:」. - **objectstack-ai#17933** — the maintainer's misreading 「不是说你的 ui 车道不需要契约复审了吗?为什么还有 `needs:contract-review`」; `lanes/ui.md` :38 now states the applicability. - **objectstack-ai#17742** — self-triage 5642823259 (fourth disjunct if the population is small); count posted on the card first (5653074219): `pm:queue` ∧ `domain:*` ∧ ¬`priority:*` = 7 objectstack / 31 objectui at 2026-09-13T11:46Z. - **objectstack-ai#17951** — 「③ 受管面手合本身也是一条队列。能否简化我的审核步骤,比如我召唤项目总监时可以批量决定?」 — SKILL.md director line paid (+1 inside the 812), AGENTS.md PD objectstack-ai#14 clause. - **objectstack-ai#17624** — 5650203410 「同意」 to B; precondition measured: `lanes/triage.md` :7 differed from the SKILL.md line only by 「分诊」 inserted and 「裁定」 dropped — no rule lost. ## Acceptance greps (`git grep -c -F` at `BASE` → this head; every file's control still hits) | card | 0 → 1 phrase | file | control (1 → 1) | |:--|:--|:--|:--| | objectstack-ai#17942 | 「认领即跟到 MERGED」, 「分诊座位唯一生产」, 「在飞卡衍生三分」; 「简单阻塞项」 3 → 0 | SKILL.md | 「每个方案必须沿四条固定评估轴分析」 | | objectstack-ai#17950 | 「受管面两层」 / `Landing is tiered` / 「两层分档」 | SKILL.md / AGENTS.md / lanes/skills.md | `GOVERNED_SURFACES` / 「QA 波次由维护者手动触发」 | | objectstack-ai#17971 | 「席位落地」 0 → 3 (SKILL) and 0 → 1 (core-rules); `landed by the owning seat` | SKILL.md, core-rules.md, AGENTS.md | 「一座位一车道双射」 | | objectstack-ai#17933 | 「条款②复核本席适用」; 「不凭记忆或继承的注记」 1 → 0 | lanes/ui.md | 「零读数恒配点亮的正控」 | | objectstack-ai#17742 | 「析取 ④」, 「未定级数另计析取④」; core 「队列卡缺域或缺定级」 | SKILL.md, core-rules.md | 「分诊座位唯一」 | | objectstack-ai#17951 | 「受管草稿呈为一批」; `director seat requests as ONE` | SKILL.md, AGENTS.md | `A version release is performed by the maintainer` | | objectstack-ai#17624 | 「六态属他席」; 「分诊不挂」 1 → 0 | lanes/triage.md | 「饥饿守卫」 | ## Line budgets (net 0 per file, paid by density inside the file — payments named in each commit) SKILL.md 812 → 812 (widest table row 342 B, untouched); core-rules.md 151 → 151; AGENTS.md 1075 → 1075 (widest row 768 B, untouched; the `node -e` helper line under PD objectstack-ai#14 is the one deletion that is not a rule); lanes/ui.md 38 → 38; lanes/triage.md 7 → 7; lanes/skills.md 33 → 33. Every added line ≤ 120 B (`git diff BASE..HEAD | grep '^+' | awk 'length>120'` prints nothing); the whole-file `awk 'length($0)>120'` baseline is 23 / 23 on SKILL.md and 15 → 14 on AGENTS.md — all table rows or the dropped helper line, the shape the ratchet exempts (the PM's "empty over edited files" expectation is false on `origin/main` already). Decision frame block byte-identical: md5 `3327d02c56f8a0eca88569dad2270f32` (now at :733–:754). ## Deviations from the dispatch, stated - P1 「:454 leave」 falsified: the ruling lifts the S-level cap that :454 stated; leaving it would keep SKILL.md contradicting Q2 class 2 while the references PR cannot touch SKILL.md and stay seat-landable — the four 简单阻塞项 lines became the three-class rule (commit 1). - 5652544529's 「lanes/triage.md gains the cache line」: triage.md is 7/7 and its only spendable line went to ruling B — the cache rule lands in SKILL.md 〈分诊座位职责〉 instead. - The `references/**` spelling in prose: `check:pm-governed-prose` reds any `**`-shaped code span outside the register, so the tier is spelled `.claude/skills/pm-dispatch/references/` (AGENTS.md) and 本技能 `references/` (SKILL.md). - SKILL.md 〈复核〉 had a carve-out letting `.claude/` hooks/workflows/settings PRs self-land on the skills seat's review; the objectstack-ai#17950 ruling names hooks and settings in the rules layer, so those three lines became the tiering lines (provenance beyond the shallow window not read). - Table rows left untouched (widest-row pins): 状态模型 rows still list 「跨车道移交」 / 「跨域 PR 指定车道」; the bullet lines govern. ## Gates (derived with `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` on the merged head `269933d9`; all 18 run in the foreground, exit codes captured before any pipe, `--ran` reconciled: 18 derived, 18 run, 0 unrun) `check:pm-skill-ratchet` ✓ (SKILL.md 812/812 · core-rules 151/151 · AGENTS.md 1075/1075 · ui 38/38 · triage 7/7 · skills 33/33; both widest-row pins at headroom 0) · `check:pm-skill-id-lint` ✓ 27 files clean · `check:skill-frame-sync` ✓ · `check:pm-governed-prose` ✓ (5 register surfaces named, no over-claim) · `check:pm-governed-merges` ✓ · `check:nul-bytes` ✓ 8593 files · `check:refd-timer-probe` ✓ · `check:doc-authoring` ✓ · `check:required-contexts` ✓ · `check:watch-hint-literal` ✓ · `check:agent-test-spelling` ✓ · `check:docs-audit-scope` ✓ · `check:driver-memory-census` ✓ · `check-closing-keyword-parity` (+ self-test) ✓ · `check-comment-mask-corpus` ✓ · `check-governed-queue-guard --self-test` ✓ 183 cases · `@objectstack/lint check:doc-formula-expressions` ✓ (first run exit 3 PREREQUISITE NOT MET — formula/lint unbuilt; built under the verify lock, 2m52s, then ✓). No package is touched ⇒ no build/test closure owed; changeset: `.claude/**` and `AGENTS.md` publish nothing ⇒ `skip-changeset`. ## Acceptance notes (noted, not filed; 承接者 named) - `.claude/agents/os-dev.md` :50 「先搜再立」 still asks the dev to dedupe before filing; ruling ③ moves dedupe to triage (filer attaches keywords). 承接者: the skills seat — a class-1 sub-issue of objectstack-ai#17942 (os-dev.md has its own ceiling; not in this PR's file surface). - `lanes/ui.md` :25 and objectui `AGENTS.md` §9 still read 「停在 draft 等人合」 — ruling C applies to objectui governed PRs too (the card names objectui#9374 / objectstack-ai#9377). 承接者: the skills seat, sibling-repo PR. - `SKILL.md` :113 `pm:retriage` row and :106 assignee row (table rows) still name 改路由 / 跨车道移交 without the pre-dispatch qualifier; the bullet 「`pm:retriage` 改路由只对未派发卡」 governs. 承接者: whoever next edits those rows under the 342 B pin. ## 维护者速读(草稿) **改了什么**:把这周你在聊天里定下的八条裁决写进 PM 章程的规则层——认领的卡跟到合并(碰到 spec 面借复核不换席)、衍生子卡与阻塞项的三分表、定级/路由/查重收回分诊席(立卡者只附查重词)、受管面分两层(只有 pm-dispatch 的 references 目录经席内复核后入队,其余仍由你确认)、你批准之后由席位自己落地(后续推送也是)、ui 车道条款②适用面写明、分诊 sweep 补第四态、总监席把待你确认的受管草稿一批呈报、分诊席「并发出现的六态属他席」。SKILL.md、核心条款、AGENTS.md 第 14 条、三个车道文件,行数全部不变,每处新增都在同文件删重付账。 **为什么改**:每条都是已裁未落地的章程滞后;裁决原文逐字引在各 commit 与上文。 **风险与代价(含回滚)**:不发布任何包、无 changeset;规则层文本改动,回滚 = revert 本 PR。风险在两处判断:AGENTS.md 第 14 条为付行数删掉了 `node -e` 打印受管面的辅助命令(注册表所在文件仍点名);〈复核〉里「`.claude/` hooks/settings 纯代码面由 skills 席自审直接落地」的旧例外被你 09-13 的分层裁决覆盖,已删。 **未含**:references 层的对应条文(contract-review.md 的落地前检③例外与逐车道适用表、dispatch-runbook.md 的接手细则、director.md 与 decision-analysis.md 的总监批呈)另起一个 references PR,待 PR objectstack-ai#17990 / objectstack-ai#17992 落地后由席位自落;objectstack-ai#17950 的注册表与队列守卫脚本项另起脚本 PR。 **一句问**:同意「hooks/settings 的旧自审直落例外」随分层裁决一并删除吗(是/否)? --- _Generated by [Claude Code](https://claude.ai/code/session_01DAcomhvR9kKizeYgg89Vo8)_ --------- Co-authored-by: Claude <[email protected]>
akarma-synetal
pushed a commit
to akarma-synetal/framework
that referenced
this pull request
Sep 17, 2026
… tier (objectstack-ai#18036) Fixes objectstack-ai#18020 Part of objectstack-ai#17950. The maintainer tiered the governed surface on 2026-09-13 (「我点头」) and the charter text landed with PR objectstack-ai#18018, but `check-governed-queue-guard.mjs` still demanded an authorized approval for every governed path — the tier was declared, not enforced. The merge-group leg now learns it. ## What changed A governed pull request whose governed paths **all** lie under `.claude/skills/pm-dispatch/references/` is satisfied by the skills seat's **review of record on the current head** — a `## Contract review` comment on the PR thread carrying a `Reviewed-by:` line and a `Served-tier:` reading that stands — in place of the approval. One rules-layer path in the same diff and today's predicate is the only way through. **Monotone by construction.** The tier leg is consulted *only* for an entry no authorized approval satisfied, so it can lift a refusal and can never create one. Nothing that passes the queue today newly refuses. **Recognition is imported, never re-implemented** — the heading marker, head-sha span test and newest-of resolution (`check-half-states.mjs`), the `Reviewed-by:` / `Served-tier:` readers (`check-clause2-carriers.mjs`). Zero new parsers; `check-clause2-carriers.mjs` carries one **export-only** change (`const` → `export const REVIEWED_BY_LINE`, value expression md5-identical). ## Acceptance greps (both directions) | reading | before | after | | --- | ---: | ---: | | `REFERENCES_TIER_PREFIX` in the guard | 0 | 9 | | `readServedTier` / `servedTierStands` imported | 0 / 0 | 4 / 4 | | `GOVERNED_APPROVERS` (lit control — still hits) | 34 | 39 | | self-test cases | 183 | 229 | | the tier VALUE spelled in the guard | 0 | 0 | | predicate bodies md5-identical to their pre-change selves | — | **16 of 16 untouched** | ## Two measurements the route turned on 1. **The cycle is real, and indirect.** A module-scope import of the recognisers deadlocks (node exits 13, "Detected unsettled top-level await") — measured directly *and* through `check-clause2-carriers.mjs`, which imports H31's file. So the import is **lazy**, which is legal only because this file's dispatch no longer carries a top-level `await`. That precondition is pinned against this file's own source; ablation D (restoring `await main()`) reds exactly that one case, and ablation C (in the self-test dispatch) reproduces the exit-13 deadlock. 2. **The thread read widens no scope.** `GET /repos/{o}/{r}/issues/{n}/comments` answers `X-Accepted-GitHub-Permissions: issues=read; pull_requests=read`, and GitHub documents the semicolon as separating *alternative* permission sets. The workflow's existing `pull-requests: read` is sufficient; ⛔ no workflow change. ## Reverse verification (mutate → prove on disk → run → restore) | ablation | cases red of 229 | | --- | ---: | | A — drop the prefix's trailing slash | 2 | | B — `entrySatisfied` accepts any record state | 12 | | C — top-level `await` in the self-test dispatch | exit 13, deadlock | | D — top-level `await` in the live dispatch | 1 (the precondition pin) | Each leg proved its mutation on disk before running, and its restore by blob hash against `HEAD`. ## Acceptance notes - `AGENTS.md` PD objectstack-ai#14's sentence 「the queue guard refuses an unpinned governed diff」 stays true: a references-only PR is pinned by its record. No prose changed; `check:pm-governed-prose` is not in the derived set for this diff and is green when run anyway. - Noted, not filed: `makeLabelReader`'s docblock says the issues-labels route "needs `issues: read`, which this workflow does not grant". The live API answers `issues=read; pull_requests=read` for it too, so the stated reason is stale — the choice to read the pull object is still right (it reuses a call the leg already makes). Successor: whoever next edits that reader, in this same file. Authored by the `domain:skills` seat, session `session_01DAcomhvR9kKizeYgg89Vo8`. ## Gates (all at `f088df57`) `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` derived **37** families; all 37 run, every one with its exit code captured before any pipe, all **0**. `--ran` reconciles: *37 derived, 37 run, 0 NOT-MEASURED (a DERIVED zero), 0 UNRUN*. Includes `check:pm-dispatch-gates` (1682 self-test cases), `check:pm-clause2-carriers`, `check:nul-bytes`, `check:refd-timer-probe`, `check:closing-target-claim`, `check:whole-set-label-write`, and the guard's own `--self-test` (229 cases). `check:pm-governed-prose` is **not** in the derived set for this diff — no prose surface changed — and was run anyway: green, *2 instruction surfaces name all 5 registered governed surfaces and claim no others*. `eslint . --no-inline-config` ran the **whole** population rather than a narrowing: **6722 files, 0 errors, 0 warnings**. No `parserOptions.project` and no typed rules are configured, so no untouched file's verdict can move with this diff. --- _Generated by [Claude Code](https://claude.ai/code)_ Co-authored-by: Claude <[email protected]>
akarma-synetal
pushed a commit
to akarma-synetal/framework
that referenced
this pull request
Sep 17, 2026
…ratch path and never trusts a file it did not write this turn (objectstack-ai#18061, half A) (objectstack-ai#18094) Fixes objectstack-ai#18061 **Half A only.** Half B (the `check-reference-carrier-shape` blind spot) is excluded per triage 5657095122 — see Acceptance notes. Nothing here measures, asserts or touches it. ## What the card measured One session's concurrently dispatched subagents share ONE flat scratchpad directory. Generically-named files — `pr.json`, `card.json`, the `card_comments.json` / `card-comments.json` pair — written minutes apart by sibling rounds overwrite each other **silently and well-formed**: a reviewer that re-read `pr.json` would have reported a confident verdict about the wrong diff. The card corrects its own reporting reviewer: cross-session isolation **held** (exactly one session directory exists under the scratchpad root), so the scope is session-specific but not agent-specific — a seat collides with its own siblings. ## The change — one rule line, in the review subagent's order `.claude/skills/pm-dispatch/references/contract-review.md`, immediately after the two lines that already spell that order out (when to spin an isolated reviewer up; what to feed it): > - 隔离复核子代理暂存全写按所审 PR 命名的 `SCRATCHPAD/pr-N/`,⛔ 不读非本轮自写的暂存。 Two clauses, both from the triage's `⛔ Scope for half A`: 1. a **round-unique** scratch directory named by what is being reviewed — the shape `.claude/agents/os-dev.md` already mandates dev-side as `issue-N/`, applied to the PR; 2. ⛔ never read a scratch file you did not write yourself **in the same turn** — the half that survives any path scheme.⚠️ The path is spelled `SCRATCHPAD/pr-N/` **in this body only**. The file itself carries the repo's ordinary angle-bracket placeholder spelling, and every quotation below respells it the same way (`issue-N/`, `batch:N`, `--test PATHS`); angle-bracket fragments do not survive this surface intact (AGENTS.md, *GitHub mutates body BYTES*). ## Paying for the line — the file was at its ceiling `check:pm-skill-ratchet` before the change, 2026-09-14T01:52Z, at `f27e86b08`: ``` ✓ check-skill-line-ratchet: .claude/skills/pm-dispatch/references/contract-review.md: widest table row is 0 bytes (pin 0; headroom 0). ✓ check-skill-line-ratchet: .claude/skills/pm-dispatch/references/contract-review.md is 60 lines (ceiling 60; headroom 0). ``` Headroom 0, so the line is paid for by a **density merge**, not by deleting a rule. The two adjacent per-item lines ② and ③ of the review record became one: ```diff -- ② semver 定级:变更级别与 changeset 声明一致。 -- ③ 边界旗处置:dev 挂旗与 `open_questions` 逐旗答复或升级。 +- ② semver 定级与 changeset 声明一致;③ 边界旗:dev 挂旗与 `open_questions` 逐旗答复或升级。 ``` Every clause survives. ② keeps `semver 定级`, and `变更级别` is the referent of `定级` — the grading IS the change level, so the agreement clause against the changeset declaration is intact. ③ loses only the head noun `处置`, whose content is the body that follows it (`逐旗答复或升级`). ⛔ No rule is dropped, and no wrap is counted as a line. `check:pm-skill-ratchet` after the change, 2026-09-14T02:03Z, at `7ded5bad7` — exit 0: ``` ✓ check-skill-line-ratchet: .claude/skills/pm-dispatch/references/contract-review.md: widest table row is 0 bytes (pin 0; headroom 0). ✓ check-skill-line-ratchet: .claude/skills/pm-dispatch/references/contract-review.md is 60 lines (ceiling 60; headroom 0). ``` Widest line in the file is unchanged at 120 bytes (the 120-byte per-line rule the ratchet enforces for non-table lines); the new rule line measures 118 bytes and the merged line 115. ## The concurrency-budget half of the subject already landed The triage notes that a path scheme alone does not address the lock starvation the same wave caused. ⛔ No budget rule is added here, because two already exist and are quoted below as they stand today at `f27e86b08` (the angle-bracket placeholder in the first is spelled `N` for this surface only): - `.claude/skills/pm-dispatch/SKILL.md:60` — `| `batch:N` | 同时在飞的 dev 上限 | 默认 `2`;`n` 的维护者天花板 `5` |` - `.claude/skills/pm-dispatch/SKILL.md:436` — `- 第 N 单派发前读 `scripts/pm/os-verify-lock.sh --status`:到达深度 ≥ `LOCK_DEPTH_HOLD`(= 2)即等。` (`references/core-rules.md:11` carries the same pair in one line.) Both landed in `7ef05f997` as write-identity lock 3. ## Premise readings Taken in the worktree at `f27e86b08` before the first edit. **P1 — the dev half of the remedy is already landed. TRUE.** 2026-09-14T01:50:36Z, `sed -n '30,35p' .claude/agents/os-dev.md`: ``` 32: - Scratchpad 按 issue 隔离:在 scratchpad 目录下建 `issue-N/` 子目录,临时文件全写进去。 33: - 同批 agents 共用一个 scratchpad 目录,自然命名的文件会被彼此静默覆盖。 ``` ⇒ ⛔ os-dev.md is not touched by this PR. **P2 — the colliding rounds had no rule to follow. TRUE.** Same timestamp, `grep -c -i scratch`: ``` .claude/skills/pm-dispatch/references/contract-review.md : 0 .claude/skills/pm-dispatch/SKILL.md : 0 .claude/skills/pm-dispatch/references/dispatch-runbook.md : 0 .claude/agents/os-dev.md : 2 ← lit control, hits at :32 and :33 ``` The lit control rules out a broken matcher. And the runbook is **not** where the review subagent's order is templated: its only mention of this review points away, at `:161` — `无主阻塞项的契约面(含 `packages/spec`)走 `contract-review.md` 独立性件的隔离达档复核` — and its `## 派发词构造细则` section templates the **dev** dispatch word, not the reviewer's brief. ⇒ ⛔ `dispatch-runbook.md` is not touched either; `contract-review.md` is the whole surface. **P3 — the file is at its ceiling. TRUE.** 2026-09-14T01:52Z; the two ratchet rows quoted above, headroom 0 on both. ⇒ the density merge is mandatory, not stylistic. ## Verification **Gate families** — derived in the worktree, run with exits captured by redirect-then-capture (⛔ never through a pipe), recorded and reconciled: `node scripts/pm/dispatch-gates.mjs --ran /tmp/ran-18061.txt` at `7ded5bad7`: ``` ✓ dispatch-gates --ran: 15 derived famil(ies) accounted for — 15 run, 0 NOT-MEASURED (a DERIVED zero — all 15 recorded an exit code and none of them is 3). ``` All 16 commands (the 15 derived plus `pnpm check:pm-governed-prose`, recorded as outside the derivation) exited 0, including `check:pm-skill-ratchet`, `check:skill-frame-sync`, `check:pm-governed-merges`, `check:pm-skill-id-lint`, `check:nul-bytes`, `check:doc-authoring`.⚠️ One of them needed a second run to produce a reading at all. `pnpm --filter @objectstack/lint run check:doc-formula-expressions` first exited **3** — `PREREQUISITE NOT MET`, its own text saying *"Nothing was measured … It is NOT a finding"*. That is NOT MEASURED, ⛔ not a failure. Its prescribed fix (`turbo run build --filter=@objectstack/formula --filter=@objectstack/lint`) was run under the shared verify lock (`os-verify-lock: VERDICT command-exit 0 · held the lock 2s · waited 0s`), after which the gate exited **0**. **Governed-surface tier.** `node scripts/pm/check-governed-merges.mjs --test .claude/skills/pm-dispatch/references/contract-review.md` — exit 3 (the deliberate GOVERNED code, ⛔ not a finding): ``` governed-surface predicate: 1 of 1 path(s) hit the register (5 surfaces, repo-agnostic). ⛔ GOVERNED — a human merge is the review record for this PR (objectstack-ai#9495 regime). .claude/** ×1 — the agent instruction tree (skills, agents, hooks, settings) ``` Landing tier, read from `scripts/pm/check-governed-queue-guard.mjs`'s own exported predicate: ``` REFERENCES_TIER_PREFIX = .claude/skills/pm-dispatch/references/ governedTierFor(['.claude/skills/pm-dispatch/references/contract-review.md']) = references ```⚠️ Note for the dispatching seat: the `--test PATHS` predicate lives on `check-governed-merges.mjs`, ⛔ not on `check-governed-queue-guard.mjs` — the latter reads `GITHUB_EVENT_PATH` and exits 1 with *"could not look must never exit 0 here"* when given a path. The tier above is computed from its exported `governedTierFor`, which is the same decision. **Lint — a proven narrowing, not a skipped run.** The repo-wide `pnpm lint` is CI's run. The narrowing here is total, and all three readings are present: 1. **Population, read from eslint's own config** (⛔ not guessed): `ESLint#isPathIgnored('.claude/skills/pm-dispatch/references/contract-review.md')` → `true`; lit control `isPathIgnored('scripts/pm/check-skill-line-ratchet.mjs')` → `false`. 2. **File count, from `--format json`**: `eslint THAT-FILE --no-inline-config --format json` → exit 0, `errorCount: 0`, one warning reading *"File ignored because no matching configuration was supplied."* — **zero lintable files** in this diff. 3. **Immutability for untouched files**: the diff is one markdown file that eslint's flat config does not match at all, so no untouched file's verdict can move. Type-awareness does not enter — the file is never parsed. **Control characters.** `grep -naP` over the changed file for the C0/DEL set: no hits (exit 1). `pnpm check:nul-bytes` exit 0. **Changeset: none owed, and the label is the declaration.** `changeset-check` (`.github/workflows/pr-automation.yml`) declares exactly **two** exemptions — the `skip-changeset` label, and the changesets release PR pinned by branch **and** author. There is ⛔ **no path exemption**, so `.claude/**` earns no automatic pass: the label IS the declaration, and it is applied on this PR. The substantive test is satisfied independently — nothing published moves, `.claude/**` ships in no package's `files[]`. ## Acceptance notes - **Half B is excluded per triage 5657095122** — 「⚠️ ⛔ **Do not let half B ride along on half A's PR.**」 Different lane, different surface; one half is measured and the other is a hypothesis. ⛔ Nothing here measures, asserts or touches `packages/lint/scripts/check-reference-carrier-shape.mjs`, and this PR's closing keyword is written as instructed by the dispatching seat. - **The concurrency-budget half of the subject already landed** and is quoted above rather than re-legislated: `batch` default `2` and the `LOCK_DEPTH_HOLD` (= 2) arrival-depth wait, both from `7ef05f997`. - noted, not filed: the dispatch word named `check-governed-queue-guard.mjs --test PATHS`; that flag is on `check-governed-merges.mjs`. A tooling-usage note for the dispatching seat, ⛔ not a repo defect — neither script is wrong. Carrier: this PR's reviewing seat. - noted, not filed: this session's own scratchpad shows the card's exact shape — 499 flat entries at the root, and the name `issue-18061` is already taken **by a file** another round wrote, so the per-issue subdirectory `.claude/agents/os-dev.md` mandates could not be created under that name. This round wrote under `dev-18061/` instead and re-fetched the card body itself rather than trusting the sibling's file. Live confirmation of the finding, ⛔ not a second finding. Carrier: this card. `Clause-②: no` — `.claude/skills/pm-dispatch/references/**` is internal dispatch doctrine, not published `skills/**`; the diff makes no falsifiable operator or contract-semantics claim, adds no exported symbol and no key on a published payload. ## 维护者速读(草稿) **改了什么。** 席内契约复核细则加一条:隔离复核子代理的临时文件必须写进按所审 PR 命名的独立目录,且不读本轮不是自己写的临时文件。文件正好卡在 60 行天花板上,这一行由把复核记录 ②③ 两条并成一条来支付——两条的每个子句都还在,⛔ 没有删规则。 **为什么改。** 一个席位同时派五个复核子代理时,它们共用同一个扁平暂存目录,`pr.json` 这种通名文件被兄弟轮次静默覆盖。失效形态是**静默且格式完好**:复核者拿到的是另一个 PR 的 payload,却会给出一个自信的裁决——受管面复核上的「自信的错答案」,⛔ 不是工具小毛病。跨会话隔离本身没坏,坏的是同一会话内部。 **风险与代价(含回滚)。** 代价是复核子代理多建一层目录。风险面只有一处:②③ 合并后阅读密度变高,但两条的操作性内容一字未少。回滚 = `git revert` 单个 commit,受管面无运行时影响,不发布任何包,无 changeset。 **席位意见。** (留空,待席内定稿) **你要做的。** 这是受管面(`.claude/**`),按 Prime Directive objectstack-ai#14 只能由你手动合并:⛔ 不入队、⛔ 不挂 auto-merge、⛔ 不翻出 draft。落地档位为 `references`。请确认那条新规则的中文措辞与本文件的机读语域一致,以及 ②③ 合并是否可接受。 --- _Generated by [Claude Code](https://claude.ai/code/session_01DAcomhvR9kKizeYgg89Vo8)_ Co-authored-by: claude <[email protected]>
akarma-synetal
pushed a commit
to akarma-synetal/framework
that referenced
this pull request
Sep 17, 2026
…records that mean them (objectstack-ai#18098) Part of objectstack-ai#14361 — ⛔ this PR deliberately does NOT close it; see *"What this PR leaves open on the same card"* below. Clause-②: no Director ruling **B — the repo-qualified spelling** (comment `5507409725`), **as amended by the same director seat in comment `5507573601`**, which is the operative form of the ruling and is independently mandated by AGENTS.md Prime Directive objectstack-ai#13. See *"The one place this PR departs from the dispatch brief"* below — please read it before reviewing the diff. ## What was wrong From this repository's point of view `ADR-0071` named two unrelated decisions and only one of them had a record here. - `docs/adr/0071-dataset-semantic-layer-depth.md` is **ADR-0071: Dataset semantic-layer depth — multi-hop joins**. - The identity / SCIM citations mean something else entirely: the enterprise-identity decision taken in `objectstack-ai/cloud`, whose **open mechanism half** has been mirrored into this repo since 2026-09-07 as [ADR-0134](../blob/main/docs/adr/0134-env-side-scim-provisioning.md). `check:adr-anchors` was green over all of them, because the number *resolves* — it just resolves to a real page about the wrong subject, which is worse than a dangling id: a plausible record invites belief instead of a second question. ## The count, re-derived from the tree The card said 39, measured on `e854a531a`. Re-derived on this branch's merge base `66e34d14d`, per line rather than per file: | bucket | lines | disposition | |---|---|---| | identity meaning, bare | **44** | re-pointed (43 → `ADR-0134`, 1 → `cloud ADR-0071`) | | identity meaning, already repo-qualified | 1 | untouched — `auth-plugin.ts:1210` `cloud ADR-0071 verification objectstack-ai#1` | | dataset meaning | 22 | ⛔ untouched — they match the local record | | `docs/adr/**` (the 0071 record, ADR-0134, ADR-0135) | 18 | ⛔ untouched — governed surface | | six package CHANGELOGs | 15 | ⛔ untouched — historical archive | | generated `content/docs/references/system/auth-config.mdx` | 2 | regenerated from its producer, never hand-edited | | **total `ADR-0071` lines in tracked files** (`pnpm-lock.yaml` excluded) | **102** | over 46 files | **How 44 differs from 39.** The card counted *files* (43, of which 39 identity); this counts *citation lines*, and several files carry more than one (`auth-manager.ts` 8, `auth-manager.test.ts` 9, `auth-config.zod.ts` 4). The surface also grew between `e854a531a` and today: the two mirror ADRs landed, `auth-manager.test.ts` and `last-admin-guard.{ts,test.ts}` gained citations, and the two changesets the card counted have since been released into CHANGELOGs (`.changeset/` carries no `ADR-0071` today). 24 source files are touched; the 39 in the card is neither the file count nor the line count of what actually needed moving. ## How each citation was classified — per site, by meaning, never by path The split is not a judgement call this PR invented. **AGENTS.md Prime Directive objectstack-ai#13** states it directly: > Decisions that draw the open/closed or commercial boundary live in `objectstack-ai/cloud` and are cited from this repo as `cloud ADR-NNNN` — ⛔ never as a bare number […] When a cloud decision's **mechanism half** governs open code here, this repo carries its own ADR — own number […] the commercial half left in cloud. So the question asked at every site was: *which half of the cloud decision is this sentence about?* **(b) the open mechanism half → `ADR-0134`, 43 sites.** Every one of them is describing code that lives in this repository, and ADR-0134's own Consumers list names those exact files: - `plugin-auth` (14) — effective SCIM forces the better-auth `admin` plugin on; the construction-time refusal when `plugins.admin: false` sits beside effective SCIM; `active:false` → ban + session revocation; `@better-auth/scim` accepting no `schema` option; the SCIM/SSO adapter model map. - `plugin-auth` tests (10) — the four pins on the refusal message, re-judged in place (see below). - `spec` (7) — the `admin` flag's docblock and `.describe()` text, `public-auth-features.ts`'s `notes`, and the v17 `default-changes.ts` upgrade note. - `platform-objects` (9) — the `protection.reason` on the eight `sys_scim_*` objects and the `sys_user` action note. - `qa/dogfood` (3), `pnpm-workspace.yaml` (1, the rc.2 seven-model migration), `docs/qa/platform-checklist/areas/identity-auth.json` (1). **(a) the commercial / boundary half → `cloud ADR-0071`, 1 site.** `auth-manager.ts:3682`, *"the paid Identity lifecycle"* — that is D6 of the cloud record, the paid-Enterprise-Identity framing which ADR-0134 §*What stays in the cloud record* explicitly refuses to restate. It stays cited to cloud. **Dataset meaning → untouched, 22 sites.** Each was read, not inferred: all 22 are about multi-hop `include` chains, the 3-hop limit, join allowlists and Cube joins — `service-analytics` (15), `spec/src/ui/dataset.zod.ts` (3), `lint/validate-dataset-references.{ts,test.ts}` (2), `analytics.mdx` (1), `query-syntax.mdx` (1). ## Positive control against over-rewriting The discrimination rule is not a regex over the id — it is an explicit per-file allowlist of identity files, with a qualifier-aware substitution inside them. Two independent proofs that nothing on the dataset side moved: ``` $ git diff --stat origin/main -- packages/services/service-analytics packages/lint \ packages/spec/src/ui/dataset.zod.ts content/docs/data-modeling/analytics.mdx \ content/docs/protocol/objectql/query-syntax.mdx docs/adr/ (empty) ``` and, in the other direction, the already-qualified `cloud ADR-0071` at `auth-plugin.ts:1210` survived a pass over its own file untouched — the substitution skips any id already carrying a `CROSS_REPO_QUALIFIERS` word. ## Bare `ADR-0071` still resolves — the gate is not weakened Identical summary lines, before and after: ``` main check-adr-anchors: OK (53 anchored file(s) … 35477 citation(s) across 4521 file(s) resolve; 1022 decision-letter citation(s) …) HEAD check-adr-anchors: OK (53 anchored file(s) … 35477 citation(s) across 4521 file(s) resolve; 1022 decision-letter citation(s) …) ``` 22 bare `ADR-0071` citations remain in the tree and the gate resolves every one of them. **Ablation**, to show that is a measurement and not a vacuous pass — one bare dataset citation mutated to an id with no record: ``` on-disk proof : ADR-0071 3 -> 2 ; ADR-9071 injected = 1 ABLATION check-adr-anchors exit = 1 • ADR-9071 is cited by 1 file(s) but names no record under docs/adr/ — restored blob : 5c90e77 RESTORE SETTLED: blob matches HEAD and 'git diff HEAD' is empty ``` The restore was settled by blob hash plus an empty `git diff HEAD`, ⛔ never by an exit code — `trap` is unreliable in this container (objectstack-ai#17875). ## Pins re-judged in place, ⛔ none deleted `auth-manager.test.ts` carries four `toThrow(/…ADR-0071…/)` pins on the operator-facing refusal message and one test name. They pin the message's *content*, and the content moved, so the pins move with it — the assertions still pin exactly what they pinned before: that the refusal names the ADR that explains the coupling. Reverse-verified that they are live rather than decorative: with the shipped message mutated back to `ADR-0071`, the two message pins fail loudly. ``` MUTATED vitest exit = 1 AssertionError: expected [Function] to throw error matching /plugins\.admin[\s\S]*ADR-0134[\s\S]*p…/ AssertionError: expected [Function] to throw error matching /OS_SCIM_ENABLED[\s\S]*ADR-0134/ restored : f9b5c2a RESTORE SETTLED: blob == HEAD and 'git diff HEAD' empty ``` ## Generated docs `content/docs/references/system/auth-config.mdx` lines 105 and 215 follow their producer (`auth-config.zod.ts`'s `.describe()`), regenerated with `pnpm --filter @objectstack/spec gen:schema && … gen:docs`. Exactly two lines drifted; nothing else in the 222 generated files moved. `check:docs`, `check:generated` and `check:authorable-surface` are green. ## Changeset — measured, not defaulted `patch` for `@objectstack/plugin-auth`, `@objectstack/platform-objects`, `@objectstack/spec`. Published bytes really do move, measured against each package's `files[]` after a build: - `@objectstack/spec` — `files[]` lists `src/**/*.zod.ts`, so the changed `.describe()` ships verbatim; the generated `json-schema/` bundle (also in `files[]`) carries it too. - `@objectstack/plugin-auth` — `dist/index.mjs` carries 4 `ADR-0134`, including the operator-facing refusal string (positive control: a shipped literal greps at 1; negative control: a comment-only marker greps at 0). - `@objectstack/platform-objects` — `dist/index.mjs` carries the 9 `protection.reason` strings. `patch` and not `minor`: no export, no schema shape, and no accept/refuse face moves. The refusal fires on exactly the condition it fired on before; only the ADR number inside its sentence changes. The changeset says so, because a deployment grepping that message for `ADR-0071` is the one consumer this can surprise. ## Reverse-read — which existing sentence does this make false? Three, all of them in `docs/adr/**`, which this lane ⛔ must not touch (governed surface, PD objectstack-ai#14). None is falsified in substance; each goes tense-stale: 1. `docs/adr/0134-env-side-scim-provisioning.md:36` — *"Identity code that writes a bare `ADR-0071` today therefore cites, by this repo's own convention, the wrong document."* After this lands, no identity code does. The sentence's headline claim — that `ADR-0071` is an ambiguous string in this repo — stays **true**: the local 0071 is still the dataset record and cloud's 0071 still exists. 2. `docs/adr/0134:38` and `:294` — *"Re-pointing the existing bare citations is objectstack-ai#14361's work and is deliberately ⛔ not done by this file."* Still true about the file; the pointer becomes past tense. 3. `docs/adr/0135-identity-and-access-architecture.md:59` — *"Re-pointing the identity surface's existing bare citations at this record is [objectstack-ai#14361]"* — same class, and see the scope note below, because the `ADR-0024` half of that sentence is **still outstanding and still true**. **Reverse direction — a sentence this makes true rather than false:** `docs/adr/0134:37`, *"**Always write `cloud ADR-0071` for the SCIM record**, and `ADR-0134` for this one."* That instruction was correct and simply unobeyed by the tree; this PR is the tree obeying it. **Zero** other sentences in the tree assert a present-tense count or claim about these citations — the only `39 files` strings in the repo belong to `packages/cli` and are about something else entirely. ## The one place this PR departs from the dispatch brief The dispatch brief asks for **every** identity citation to read `cloud ADR-0071`, citing ruling `5507409725`. Three sources on `main` say otherwise, and they agree with each other: 1. **The ruling's own amendment**, comment `5507573601`, same director seat, 14 minutes later, self-titled *"Ruling amended"*: *"**Target for the (b) set** (citations that mean the cloud decision's *open mechanism half*): the **new local ADR numbers** that objectstack-ai#14506 / objectstack-ai#14507 / objectstack-ai#14508 land — not `cloud ADR-NNNN`"*, and *"the operator-facing refusal text in `auth-manager.ts:359` re-pointed to the new local number"*. 2. **The release comment** `5594577301` that unblocked this card restates it: *"the (b) citation targets are the new local ADR numbers those cards land"*, naming ADR-0134 as the mirror of cloud 0071. 3. **AGENTS.md Prime Directive objectstack-ai#13**, quoted above, which is binding regardless of any comment and says mechanism-half → this repo's own number. Under the unamended reading, ruling B is still satisfied by this diff (every identity citation is now unambiguous and the gate discriminates), but 43 of the 44 would read `cloud ADR-0071` instead of `ADR-0134`. If the reviewer prefers that reading it is a one-command flip on this branch; I did not pick a side silently, which is why this section exists. ## What this PR leaves open on the same card The amendment also folds the sibling collision class into this card — bare `ADR-0024` and bare `ADR-0081`, the mirrors of which landed as ADR-0135 and ADR-0133 — *"one card over the identity surface (0071 + 0024 + 0081) … so it is not paid twice"*. The dispatch brief scopes this lane to `ADR-0071` only, and the sibling surface is large: **151** bare `ADR-0024` lines and **86** bare `ADR-0081` lines, most of which legitimately mean this repo's own `0024-mcp-connectors` and `0081-trusted-react-page-tier` records and must **not** move. `docs/adr/0135:260` says so in as many words — *"Whether any individual bare `ADR-0024` citation should move … cannot be a search-and-replace"*. That is a real per-site pass with its own budget, and `docs/adr/0135:260` says so in as many words. `objectql-adapter.ts:58` now reads `See ADR-0024 / ADR-0134.`, where the `ADR-0024` half is knowingly left for it. ⭐ **This PR therefore delivers the `ADR-0071` third of objectstack-ai#14361 — the 44 sites above — and nothing else.** The per-site judgement over bare `ADR-0024` and bare `ADR-0081` is the remaining work of **the same card**, not a new one: the amendment's Scope line reads *"the identity surface's **whole collision class, one pass**: bare `ADR-0071`, bare `ADR-0024`, bare `ADR-0081` — every citation read for its meaning"*, and `docs/adr/0135:260` calls the `ADR-0024` half *"objectstack-ai#14361's per-site call"*. That is why the first line of this body says `Part of objectstack-ai#14361` and not `Closes`: merging this while two thirds of the ruled scope is unwritten would close the card on a third of its work. The card stays open for the next round. ## Verification | what | result | |---|---| | `pnpm check:adr-anchors` | green, byte-identical summary to `main` | | `pnpm --filter @objectstack/{spec,plugin-auth,platform-objects} test` | 476 + 111 + 40 files, **16 517** tests, all pass | | same three, `typecheck` | green (incl. `check:test-typecheck` ledgers, unmoved) | | `check:authorable-surface` · `check:docs` · `check:generated` | green | | `check:platform-checklist` · `check:doc-anchors` · `check:docs-single-h1` · `check:doc-authoring` · `check:docs-spec-enumerations` | green | | `check:nul-bytes` · `check:published-files` · `check:corpus-claim-drift` · `check:quick-reference-counts` · `check:comment-mask-{adoption,corpus}` · `check:spec-docblock-symbol-anchors` | green | | changeset gates (`check-changeset-fixed`, `check-changeset-no-major`, `check-empty-changeset`, `check:changeset-gate-self-tests`) | green | | `check:doc-frontmatter` · `check:docs-section-name` · `check:keyed-text-bounds` · `check:platform-object-tenancy-census` · `check:reference-carrier-shape` · `check:adr-0087-registration` | green | | `eslint . --no-inline-config` — the **whole repo**, not a narrowing | **6743** files, 0 errors, 0 warnings, at `8d2dfb3` | 39 gate commands, every one `exit 0`, captured before any pipe. **Declared narrowing.** `node scripts/pm/dispatch-gates.mjs --ran` derives 123 commands from this change set and accounts 39 of them. I ran the families this card names plus every one I could see implicated, and — since it turned out to fit the foreground budget — the repo-wide lint union rather than a narrowing of it. The remaining derived families are CI's farm, not this lane's run. `packages/qa/dogfood`'s three edits are comment-only — no dogfood boot was run locally, and that layer is declared to CI. --- _Generated by [Claude Code](https://claude.ai/code)_ --------- Co-authored-by: Claude <[email protected]>
akarma-synetal
pushed a commit
to akarma-synetal/framework
that referenced
this pull request
Sep 17, 2026
….md's spec pointer states the landed rule (objectstack-ai#18037) (objectstack-ai#18111) Fixes objectstack-ai#18037 Two `references/` lines still carried pre-ruling wording after the charter chain landed. `landing-operations.md` — the file a seat reads at exactly the moment it decides whether to flip ready — sent **every** governed hit to the human-merge lane, and `lanes/ui.md` :22 restated the rule its SKILL.md target had retired. Both lines now state the landed rules. Two files, equal line counts, no ceiling moved. ## What changed **`.claude/skills/pm-dispatch/references/landing-operations.md` :27–:29** — the pre-ready / pre-arm path fork now names the two landed landing tiers: - **facts tier** — a PR whose governed paths **all** lie under this skill's `references/`: the in-seat contract-tier review record carries it through 落地前检三条, then ready and the queue (`contract-review.md`; the guard side of the same tier is the queue guard's references tier, already landed). - **rules layer** — everything else: the 四件套, draft until a human merge or an authorized APPROVED review, after which the claiming seat lands it (ruling C). - ⛔ Neither tier is ever approved by a seat, and the kept half 「清标即落地同受此闸,漏判会被队列守卫在 merge group 里拒收」 survives verbatim. **`.claude/skills/pm-dispatch/references/lanes/ui.md` :22** — the pointer now states the rule its target actually carries (新 `packages/spec` 工作恒归 `domain:spec` 席,已派发卡 ⛔ 不因此转席), still pointing at SKILL.md 〈多仓协调〉. Line :25 is untouched. ## The rules the rewrites agree with (verbatim on `origin/main` `66aa2d98d`, untranslated) - ruling A, SKILL.md :230 — 「新 `packages/spec` 工作恒由 `domain:spec` 席收口,不论谁需要它;已派发卡 ⛔ 不因触 spec 转席。」 - the tiering, SKILL.md 〈复核〉 :625–:626 — 「受管面两层:事实层仅本技能 `references/`,其余为规则层(含发布 `skills/**` 与 SKILL.md)。」 / 「规则层四件套等人合;事实层 PR(受管路径全在该目录)经席内达档复核后 ready → 入队。」 - `contract-review.md` :47 — 「规则层等维护者的字;受管路径全在 `.claude/skills/pm-dispatch/references/` 者达档过本三条入队。」 - ruling C, `lanes/ui.md` :25 (landed, untouched here) — 「⇒ 命中即停 draft;⛔ 未获授权批准不 ready 不入队不自合、永不批准,获批后认领席落地。」 ## Premise readings P1–P4 Taken in worktree `objectstack-issue-18037` on the branch base `66aa2d98d`, before any edit, at **2026-09-14T03:05:55Z**: - **P1** — `git grep -n '人工合并道' -- .claude/skills/pm-dispatch/references/landing-operations.md` → **exit 0**, hit at `:27`; its neighbour `:28` read 「⛔ 不翻 ready、不入队;清标即落地同受此闸,漏判会被队列守卫在 merge group 里拒收。」 ✅ premise holds. - **P2** — `git grep -n '凡触' -- .claude/skills/pm-dispatch/references/lanes/ui.md` → **exit 0**, hit at `:22`; the SKILL.md line it points at (:230) states the retiring rule. ✅ premise holds. - **P3** (control) — `git grep -n 'mergeable_state' -- .claude/skills/pm-dispatch/references/landing-operations.md` → **exit 0**, 2 hits (`:29`, `:31`). ✅ premise holds. - **P4** — `node scripts/pm/check-skill-line-ratchet.mjs` → **exit 0**, the two rows verbatim: - `✓ check-skill-line-ratchet: .claude/skills/pm-dispatch/references/landing-operations.md is 69 lines (ceiling 69; headroom 0).` - `✓ check-skill-line-ratchet: .claude/skills/pm-dispatch/references/lanes/ui.md is 38 lines (ceiling 38; headroom 0).` -⚠️ The card's `80/80` for `landing-operations.md` is **stale** — the ceiling ratcheted down to 69 before this card was dispatched. 69/69 and 38/38 are what bind, and both files come out of this PR at exactly those numbers. ## The executable criterion, before and after Measured at **2026-09-14T03:16:09Z** (after the edit, same worktree), exit codes captured before any pipe: | grep | before | after | required | | --- | --- | --- | --- | | `人工合并道` in `landing-operations.md` | exit 0 (`:27`) | **exit 1** | exit 1 ✅ | | `凡触` in `lanes/ui.md` | exit 0 (`:22`) | **exit 1** | exit 1 ✅ | | control `mergeable_state` in `landing-operations.md` | exit 0, 2 hits (`:29`, `:31`) | **exit 0, 2 hits** (`:30`, `:32`) | must still hit ✅ | Repo-wide, `git grep '人工合并道\|凡触' -- .claude/` now exits 1: no residue elsewhere on the surface. ## Density payment, and why no ceiling moved The fork needs three lines where two stood (one tier per line, plus the kept 清标 half). ⛔ The ceiling was not raised and ⛔ no rule was deleted — the line is paid for inside the same file by merging the two lines that both said the same thing, that stripping labels happens in the same action as confirming MERGED: - old `:34` 「确认 MERGED 的同一动作里给 `Part of` 卡收口,`Fixes` 卡代关但标也须摘。」 - old `:37` 「摘标与 MERGED 确认是一个动作,⛔ 不拆到下轮巡检。」 - now one line — 「确认 MERGED 同一动作里给 `Part of` 卡收口、`Fixes` 卡代关但标也须摘,⛔ 不拆到下轮巡检。」 Every clause of both lines survives: the `Part of` close-out, the `Fixes` auto-close with its label still owed, the same-action requirement, and ⛔ 不拆到下轮巡检. **Per-line budget.** The ratchet's second rule is `MAX_LINE_BYTES = 120`. Every line written here was measured with the gate's own `classifyLine` before it was written to disk — 117 / 112 / 111 bytes (the three new `landing-operations.md` lines), 118 bytes (the merged line), 120 bytes (`lanes/ui.md` :22) — each classified `null`, i.e. compliant. ## Gates ### Follow-up commit `460b36b33` — 等人合 → 等人批 The PM seat's pre-read of head `7900d80b` caught a wording defect in the new rules-layer line: 「四件套留 draft 等人合」 restated the human-MERGE lane this card exists to remove. Under ruling C the rules layer waits for a human APPROVAL and the claiming seat merges — the shape `lanes/ui.md` :25 already carries — so the line now reads 「四件套留 draft 等人批,⛔ 不翻正式不入队;获授权批准后认领席落地。」 Byte-neutral (112 bytes, `classifyLine` returns `null`), equal line count, no ceiling moved, pushed as a second commit rather than an amend.⚠️ Worth a seat's eye: SKILL.md :626 — the landed tiering line itself — still spells the same endgame 「规则层四件套等人合」, and AGENTS.md Prime Directive objectstack-ai#14 keeps 人工直合 as one of the two endings. This PR aligns the runbook line with `lanes/ui.md` :25 and ⛔ does not touch either of those; the wording difference across the surface is noted, not filed. Derived, not recalled: `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` with no paths passed, so the change set comes from the merge base (2 paths, working tree). All 15 derived families re-run **after the final commit**, at `460b36b33` (and identically at `7900d80bd` before the follow-up): ```text node scripts/check-closing-keyword-parity.mjs :: exit 0 node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0 node scripts/check-comment-mask-corpus.mjs :: exit 0 node scripts/pm/check-governed-queue-guard.mjs --self-test :: exit 0 pnpm --filter @objectstack/lint run check:doc-formula-expressions :: exit 0 pnpm check:agent-test-spelling :: exit 0 pnpm check:doc-authoring :: exit 0 pnpm check:driver-memory-census :: exit 0 pnpm check:nul-bytes :: exit 0 pnpm check:pm-governed-merges :: exit 0 pnpm check:pm-skill-id-lint :: exit 0 pnpm check:pm-skill-ratchet :: exit 0 pnpm check:refd-timer-probe :: exit 0 pnpm check:skill-frame-sync :: exit 0 pnpm check:watch-hint-literal :: exit 0 pnpm check:pm-governed-prose :: exit 0 (dispatch-named; the derivation scores it an artifact roster, i.e. silent, not clear) ``` The ratchet at `460b36b33` still reads `landing-operations.md is 69 lines (ceiling 69; headroom 0)` and `lanes/ui.md is 38 lines (ceiling 38; headroom 0)`, and the criterion still passes on that head (人工合并道 exit 1, 凡触 exit 1, control `mergeable_state` exit 0 with 2 hits). Reconciliation, from `node scripts/pm/dispatch-gates.mjs --ran … --repo objectstack-ai/objectstack`: ```text Run reconciliation — 15 derived, 15 run, 0 NOT-MEASURED, 0 UNRUN. ✓ dispatch-gates --ran: 15 derived famil(ies) accounted for — 15 run, 0 NOT-MEASURED (a DERIVED zero — all 15 recorded an exit code and none of them is 3). ``` One gate needed a prerequisite: `check:doc-formula-expressions` first exited **3** (PREREQUISITE NOT MET — `@objectstack/formula` and `@objectstack/lint` not built), which is not a finding. Built through the shared verify lock (`OS_VERIFY_LOCK_SLOT=dev-18037`, `VERDICT command-exit 0 · held the lock 1s · waited 0s`) and re-run to exit 0. Beyond the gates: control-character self-scan over both files (`grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]'`) exits 1 — clean. Repo-wide scans (`pnpm lint` and the rest of the lint farm) are CI's run, not this PR's local owing. ## Changeset None owed; `skip-changeset` is the declaration. The Check Changeset job (`.github/workflows/pr-automation.yml`, job `changeset-check`) has **no path exemption** — it reads the PR's label list (the frozen payload as a fast path, then two live re-reads) and exempts exactly two things: the `skip-changeset` label and the `changeset-release/main` release PR authored by `github-actions[bot]`. Nothing published moves here: `.claude/**` lives outside every package, and no manifest's `files[]` names it (0 of 82 manifests). ## Acceptance notes Out-of-scope observations, noted and ⛔ not filed (no card, no label): - `contract-review.md` :45 still reads 「③ PR 全部 check 全绿,⛔ 非 required 子集;受管面不适用,draft-only 终局不变。」 while :47 immediately below carves the references tier out of that 「受管面不适用」. The two lines are consistent read in order — :47 is the refinement — but a reader who greps only for 落地前检③ meets the older half first. Noted, not filed: the next PR that touches `contract-review.md`'s 落地前检 block is the carrier. It is prose density, not a defect, and it is not this card's surface. - The scope fences from triage were kept: out of scope: objectstack-ai#18019 (`lanes/ui.md` :25, `os-dev.md` 先搜再立, the SKILL.md table rows) — not addressed here and left open; out of scope: objectstack-ai#18020, the queue-guard half — not addressed here, and this PR does not depend on it. ## 维护者速读(草稿) **改了什么** — 两行落地指引的措辞。落地跑册 `landing-operations.md` 原来说「受管面一命中就整个 PR 走人工合并道、⛔ 不翻 ready 不入队」;现在按已落地的两层分档说话:受管路径全在 pm-dispatch 自己的 `references/` 目录里的,经席内达档复核走队列落地;其余(SKILL.md、AGENTS.md、agents、hooks、ADR、发布 skills 等)照旧留 draft 等维护者的字或授权批准。`lanes/ui.md` 的 spec 指针原来说「凡触 `packages/spec` 一律转席」,而它指向的那条规则早已改成「新 spec 工作归 spec 席;已派发卡不因触 spec 转席」,现在指针与目标一致。 **为什么改** — 旧措辞在被读到的那一刻就是错的指令,而且失败方向是静默的:席位会把一个本可落地的 PR 停在 draft 上,等一个裁决已经取消了的维护者点击,没有任何东西会报错。指针那条更尖:读者信了指针而不去跟进目标,拿到的是已退休的规则,却带着现行规则的权威。 **风险与代价(含回滚)** — 只动两个文件里的散文,无代码、无产物、无发布物;两文件行数不变(69/38),棘轮上限未动。风险是措辞被读窄或读宽:facts 层的判据必须是「受管路径**全部**在该目录」,本 PR 按此原文写。回滚 = 还原这一个 commit,没有任何迁移或数据面。 **席位意见** — (留空,待席位定稿) **你要做的** — 一个动作:确认两层分档的措辞与您在 SKILL.md 〈复核〉 与 `contract-review.md` 上已落地的裁决一致;若一致,本 PR 属事实层,按已落地规则经席内复核走队列,无需您合并。 Clause-②: no --- _Generated by [Claude Code](https://claude.ai/code)_ --------- Co-authored-by: Claude <[email protected]>
akarma-synetal
pushed a commit
to akarma-synetal/framework
that referenced
this pull request
Sep 17, 2026
…03 for the seat token, MCP rerun_failed_jobs 201 (objectstack-ai#18025) (objectstack-ai#18129) Fixes objectstack-ai#18025 One row in `.claude/skills/pm-dispatch/references/platform-readings.md`, in the Actions / re-run block, plus the ceiling increment it costs. No other file moves. Dev session `session_01DAcomhvR9kKizeYgg89Vo8` on branch `claude/issue-18025-platform-readings-rerun-channel` (worktree `objectstack-issue-18025`), off `origin/main` `ca788604`, BASE `ca7886047b27667122f9c2c44de3b6e67eb361fe`. No merge was needed: nothing landed on `main` under either path since the branch point. ## What landed `platform-readings.md` :285, placed immediately after the existing re-run pair (:283–:284): ```text - 读数 2026-09-13:席位 REST `/jobs/{id}/rerun` 403、MCP `rerun_failed_jobs` 201 ⇒ 重跑可做,锁 1 未禁。 ``` 117 bytes against the 120-byte line cap; one matter on the line; a dated 读数 in the file's own register voice. It records the CHANNEL split the card measured, and nothing else: - the seat's REST token (`GITHUB_TOKEN` through the session proxy) answers **403** `Resource not accessible by integration` on `POST /repos/objectstack-ai/objectstack/actions/jobs/103706765153/rerun`; - MCP `actions_run_trigger` with `method: rerun_failed_jobs`, `run_id: 34750740645`, answers **201** and re-queues the failed shards — attempt 2 visible on the run. The consequence rides the same line, which is why the row is worth a ceiling increment at all: **the one confirming re-run of a failure that is not this PR's is exercisable by the seat**, and lock 1 (PR objectstack-ai#18072, `7ef05f997`) does not take it away — `actions_run_trigger` is state-shaped, so it is not on the write-identity deny list. The standing text that read 「re-run is 403 to this seat」 was true of ONE channel, which is the error the triage comment 5654613958 generalises: 「the seat cannot do X」 is a claim about a channel, ⛔ never about the seat. Two things the 120-byte cap did NOT buy, declared rather than quietly dropped: - **the 403's message text.** Error prose is not pinned unless a consumer parses its original words; nobody parses this one, and the operative discriminant is the status pair 403 / 201. The full text stays on the card. - **the `POST` verb and the `/actions` path segment.** The path `/jobs/{id}/rerun` exists only as a POST, and :291 already carries the sibling `GET /actions/jobs/{id}/logs` spelling, so the family is legible from the neighbourhood. Spelling both would have cost 8 bytes the line does not have. ## Ceiling 453 → 454 — the standing exception, no decision card `scripts/pm/check-skill-line-ratchet.mjs`: the `platform-readings.md` ceiling moves by exactly the landed delta, and the raise is recorded as the FOURTEENTH `ruledRaises` record on the cross-file-move declaration, in the same shape as the thirteenth (added by objectstack-ai#18072 at `7ef05f997`) and quoting the same ruling verbatim and untranslated — pm-dispatch SKILL.md 〈分诊座位职责〉: > 唯一例外:`platform-readings.md` 增量抬上限到落地行数,免决策卡,记 `ruledRaises` 引常设裁决。条件:席位验收评论逐条核实、去重计数(候选/落地/已有/拒收)、一事一行、不计重排 ⛔ No other ceiling moves. ⛔ No decision card, because the exception says none is owed. **Density was MEASURED, not assumed** — the exception's own precondition and the 2026-08-17 no-re-wrap-funding rule: - of the file's **427** adjacent bullet pairs, **ZERO** merge within the 120-byte cap; the smallest merged width is **134 B**. - the two neighbours the row joins offer **37** spare bytes (:283, 83 B) and **6** spare bytes (:284, 114 B), against the row's **115** bytes of content after the `- ` marker. Neither can absorb it, and folding it into :283 would put a second matter on a line — 一事一行. ⇒ the row could not be paid in place, so the increment is +1 and the landed count is 454. ## Premise readings — all four hold, none falsified Taken on this worktree at `BASE` `ca788604` unless noted. | # | premise | reading | verdict | |:--|:--|:--|:--| | P1 | the file is 453 lines at ceiling 453 | `node scripts/pm/check-skill-line-ratchet.mjs` at 2026-09-14T03:42Z, exit 0: `✓ check-skill-line-ratchet: .claude/skills/pm-dispatch/references/platform-readings.md is 453 lines (ceiling 453; headroom 0).` | **holds** | | P2 | no row states the channel split | `git grep -n -i -E 'Resource not accessible|actions/jobs|rerun_failed_jobs'` on the file at 2026-09-14T03:40Z returns exactly TWO hits, quoted below | **holds** | | P3 | the objectstack-ai#18085 footer fact is already on :333–:334 | quoted below, byte-for-byte from `BASE` | **holds — already present** | | P4 | no in-flight branch touches the file | scan at 2026-09-14T03:47Z, below | **holds** | **P2, every hit quoted:** ```text 283: - `rerun_failed_jobs` 复用原 run 的提交与合并 ref,不拿新 main 重算。 291: - ⇒ 两者都答不了到底挂在哪;`GET /actions/jobs/{id}/logs` 被出口代理拒绝,CONNECT 403。 ``` :283 states only that a re-run reuses the original run's commit and merge ref — a fact about WHICH TREE is re-run, silent on WHO may re-run it. :291 is a different endpoint (`GET .../logs`, not `POST .../rerun`) failing a different way (the egress proxy's CONNECT 403, not GitHub's `Resource not accessible by integration`). Neither says a re-run is unavailable, so the card's 「if the file already carries a row saying re-runs are unavailable, replace it rather than add」 branch does not fire: this is an ADD, and the two lit controls prove the grep was looking in the right place rather than returning a silent zero. **P3, already present, ⛔ not a second row:** ```text 333: - 裸 REST `PATCH /pulls` 追加一个裸页脚并保留既有 session-URL 页脚,差恰 58 字节。 334: - 同路送无页脚正文存回恰一条(平台裸形)⇒ 该格处方是不送页脚,⛔ 不是不重送正文。 ``` **P4:** `git ls-remote --heads origin` lists 1102 branches; a name grep for `reading|rerun|re-run|channel|18025` hits only `claude/issue-13326-platform-readings-family` (landed long ago), `claude/issue-10979-...`, `claude/issue-7018-...` and this branch. Every remote-tracking ref dated today (`2026-09-14`) was then checked directly — `objectstack-ai#18074`, `objectstack-ai#18037`, `objectstack-ai#18085`, `objectstack-ai#18061`, `objectstack-ai#18055`, `objectstack-ai#18060`, `objectstack-ai#18047`, `objectstack-ai#18069`, `objectstack-ai#17959` — and `git log origin/main..origin/BRANCH -- the-file` returns **0** commits for each.⚠️ Scope declared: that scan sees the refs this container has fetched, which is every in-flight seat branch in this shift but not a branch pushed from elsewhere and never fetched here. ## Verification counts for the seat's ACCEPT One matter per line, deduplicated, re-wraps not counted: | | count | what | |:--|--:|:--| | candidates | 2 | ① the re-run channel split (REST 403 / MCP 201); ② the PR-body footer reading from objectstack-ai#18085's dev report | | landed | 1 | ① as :285, 117 B | | already present | 1 | ② — :333–:334 carry it verbatim; refused as a duplicate rather than restated | | refused | 0 | — | ⇒ landed 1 = the ceiling delta 1 = 453 → 454. ## Gates `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` at `9303a7b7e` (no paths passed — the script derives its own change set: 2 paths vs merge base `ca7886047`, three-dot semantics): **39 families**. Every one run in the foreground with its exit code captured by redirect-then-capture BEFORE any pipe; **39 of 39 exit 0**. Reconciliation, `--ran` with the exit code recorded per family: ```text Run reconciliation — 39 derived, 39 run, 0 NOT-MEASURED, 0 UNRUN. ✓ dispatch-gates --ran: 39 derived famil(ies) accounted for — 39 run, 0 NOT-MEASURED (a DERIVED zero — all 39 recorded an exit code and none of them is 3). ``` One family needed a second pass: `pnpm --filter @objectstack/lint run check:doc-formula-expressions` first answered **exit 3 — PREREQUISITE NOT MET** (`@objectstack/lint` not built; nothing measured, ⛔ not a finding). Built under the shared verify lock (`OS_VERIFY_LOCK_SLOT=issue-18025`, `VERDICT command-exit 0 · held the lock 1s · waited 0s`) and re-run: **exit 0**. The reconciliation above is the post-fix record. Named explicitly by the dispatch, and the roster families whose baseline sits under a directory this diff is in — outside the derived 39, each run and each exit code captured the same way: ```text pnpm check:pm-governed-prose :: exit 0 node scripts/check-published-list-mirrors.mjs :: exit 0 node scripts/check-published-list-mirrors.mjs --self-test :: exit 0 node scripts/check-skills-token-ratchet.mjs :: exit 0 node scripts/check-skills-token-ratchet.mjs --self-test :: exit 0 ``` Rule ⑤ — this diff edits a gate script, so that script's own suite is owed beyond the derived families. `scripts/pm/check-skill-line-ratchet.mjs` has no `*.test.ts`: `git grep -l` over `*.test.ts` / `*.test.mts` / `*.test.mjs` / `*.spec.ts` returns nothing, and its suite IS its `--self-test`, wired into `pnpm check:pm-skill-ratchet` (in the 39, exit 0): `✓ check-skill-line-ratchet self-test: 157 cases pass.` Its siblings that read the same module — `check:ratchet-remedy-authority`, `check:pm-dispatch-gates` — are in the 39 and green. Line-ratchet verdict after the edit: ```text ✓ check-skill-line-ratchet: .claude/skills/pm-dispatch/references/platform-readings.md is 454 lines (ceiling 454; headroom 0). ✓ check-skill-line-ratchet: cross-file move into .claude/skills/pm-dispatch/references/platform-readings.md: +11 (314→454, less 129 lines of ordinary ruled raise) against a net source decrease of 20 … ``` The move's own arithmetic is unchanged at +11 against −20, which is what the `ruledRaises` record is for. Control characters: `grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]'` over both changed files is empty, and `pnpm check:nul-bytes` is in the 39 at exit 0. Every added prose line is ≤ 120 B (the added row is 117 B; the added ratchet comment lines are JS source, outside that cap). ⛔ Not measured here, by design: whole-farm CI. The derivation itself names 52 artifact-roster families, 11 declared-wide families, 14 pending-changeset families and 1 path-scheduled CI job as outside the derived 39 — CI owns those. ## Changeset None owed, and `skip-changeset` is the declaration rather than a shortcut. There is no `check-changeset-presence.mjs` in this tree; the changeset gate is `changeset-check` in `.github/workflows/pr-automation.yml`, and it reads **no path filter at all** — its only two exemptions are the `skip-changeset` label (re-read live, because the event payload's label snapshot is stale by construction) and the `changeset-release/main` branch pushed by `github-actions[bot]`. So a docs/tooling PR cannot be exempted by its paths; it has to carry the label. Nothing here publishes: both paths — `.claude/skills/pm-dispatch/references/platform-readings.md` and `scripts/pm/check-skill-line-ratchet.mjs` — are on the fast-track non-publishing list (`.claude/**`, `scripts/pm/**`), inside no released package's `files[]`. ## Acceptance notes - noted, not filed (承接者: the skills seat reviewing this PR): the register now carries THREE `/actions/jobs/{id}/...` readings across :283–:291 — re-run tree reuse, the re-run channel split, and the logs endpoint's proxy CONNECT 403. They are three separate matters and each is one line, so no line merges; the observation is only that a future 段落 boundary there would read better if the endpoint family were contiguous. Not a defect, not a contract breach, not an authoring trap ⇒ ⛔ no card. - noted, not filed (承接者: 无): the card body's own budget line reads 「449/449 — pay inside the file」, measured when it was filed on 2026-09-13T12:28Z. The file was 453/453 by the time of dispatch. Nothing to act on — the card's instruction was the ceiling DISCIPLINE, not the number — and no PR or person will read that line again once this lands. Clause-②: no — the diff widens no declared contract; it records a reading and pays its line. ## 维护者速读(草稿) **改了什么**:事实表 `platform-readings.md` 加一行(453 → 454),记一条读数:CI 失败 job 的重跑,席位自己的 REST 令牌回 403,而 MCP 的 `rerun_failed_jobs` 回 201 并把失败分片重新排队。顺带把 `check-skill-line-ratchet.mjs` 里这个文件的行数上限抬 1,按常设例外记一条 `ruledRaises`,引用裁决原话。 **为什么改**:此前的记录只说「重跑对席位是 403」,那是一个**通道**的事实,被当成了**席位能力**的事实。结果是:一条本来能执行的规则(CI 红了先做一次确认性重跑)被当作做不到,objectstack-ai#18010 那张卡直接把它写进了阻塞原因,还惊动维护者问「17990 你自己不能解决吗」。这一行把通道和能力分开,下次没人再为这件事找人点一下。 **风险与代价(含回滚)**:只动文档与一个门禁脚本的常量,不发布任何包,不改运行时行为。代价是事实表长 1 行(每个座位每次会话都要读它,这就是上限存在的理由);本次为此实测了密度——全文 427 对相邻条目没有一对能合并进 120 字节,所以省不出来。回滚 = revert 本 PR 的那一个 commit。 **席位意见**:(留空,定稿成评论) **你要做的**:这是受管面(`.claude/**` + `scripts/pm/**`),按 Prime Directive objectstack-ai#14 只能由你手动合并 —— 席位不合、不进队列、不挂 auto-merge。你要确认的是两件事:① 这一行的读数属实(卡面 objectstack-ai#18025 有原始测量表);② 抬 1 行的上限动作走常设例外、不另开决策卡,是否照你的意思。 --- _Generated by [Claude Code](https://claude.ai/code/session_01DAcomhvR9kKizeYgg89Vo8)_ Co-authored-by: Claude <[email protected]>
akarma-synetal
pushed a commit
to akarma-synetal/framework
that referenced
this pull request
Sep 17, 2026
…e direction is never an error (objectstack-ai#18135) Fixes objectstack-ai#17963 ## Why `references/lanes/spec.md` answered one question — what **trips** clause ② — and was silent on the other: whether declaring `Clause-②: yes` on a narrowing anyway is allowed. With the rulebook silent, a PR body became the authority. The measured cause, verbatim from PR objectstack-ai#17498's body: > This is a spec narrowing, and the objectui#8285 precedent says a spec narrowing declares `yes`. That is a per-card director ruling, not rulebook text, and nothing in that body says so. Both statements are true at once — the rulebook says what trips clause ② mechanically, the ruling said what one card should declare — but nothing said so in one place, so the next author either argues about which text wins or quietly stops declaring on narrowings because the rulebook says they do not trip. The conservative direction was already sanctioned in this same skill, just never joined to the narrowing rule. `references/contract-review.md` (unchanged by this PR) carries both: > `Clause-②: yes | no` 按设计临时:只定是否必过席内契约复核的保守方向,⛔ 非终审。 > claim 拿不准 ⇒ 按 `yes` 挂标走席内契约复核;⛔ 不建全量分类学与 claim 时决策程序。 ## The edit One rule line, inserted directly under the rule it reconciles (`:19` → new `:20`): ```text - 收窄不触发条款②,但按 `yes` 申报恒不是错误;⛔ 个案裁决不改本行。 ``` 93 bytes. It carries all three ruled halves: a narrowing does not TRIP clause ②, declaring `yes` on one is never an error, and a per-card ruling does not change this line. No issue number in operational text, so the provenance above lives in this PR rather than in the rule. ## Line budget — net 0, ceiling untouched `lanes/spec.md` is pinned at 43 lines (`scripts/pm/check-skill-line-ratchet.mjs`), zero headroom, every line ≤ 120 bytes. - **Extending `:19` in place was measured first and does not fit.** `:19` is **108 bytes**; the budget leaves **12 bytes** — four CJK characters — and the shortest wording carrying the new substance needs 30 or more. - **The line is therefore paid for by deleting content, not by re-wrapping.** `- 生成物门禁重生成提交,⛔ 手改。` leaves the same section. Its rule survives elsewhere, in files that outrank this one: - `AGENTS.md` → the Documentation Guardrails table (**AUTO-GEN** ❌ Never hand-edit. Regenerated by …), § *Touched `packages/spec`? Regenerate its artifacts BEFORE pushing* (match the change to the gate and regenerate up front; no `check:` script regenerates anything), and Multi-agent discipline §11 (`pre-commit` refuses a commit that still owes a regeneration). - this skill → `references/core-rules.md` 「入队资格是每一个检查全绿 ⛔ 不是必查子集;碰生成物的 PR 入队前先同步再重生成。」 and `references/landing-operations.md` §A. - Arithmetic: 43 → 43 lines, +1 / −1. `LC_ALL=C awk 'length($0)>120'` over the file prints nothing, before and after. ## Acceptance greps (both directions) | assertion | reading | |:---|:---| | new clause present | `git grep -n '恒不是错误' -- .claude/skills/pm-dispatch/references/lanes/spec.md` → 0 hits on `origin/main`, 1 hit (`:20`) here | | lit control (the grep is not vacuous) | `git grep -n '收窄' -- …/lanes/spec.md` → `:19` still hits, plus the new `:20` | | file surface | `git diff --stat origin/main` → `1 file changed, 1 insertion(+), 1 deletion(-)` | | neighbours untouched | `git diff --stat a90a9f2 -- …/contract-review.md …/core-rules.md …/SKILL.md` → empty for each | | line count | 43 → 43 | | byte width | every edited line ≤ 120 bytes (`LC_ALL=C awk`) | ## Gates Derived with `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` (no paths — the tool takes its own change set), all run in the foreground with `$?` captured before any pipe, then reconciled with `--ran`: > ✓ dispatch-gates --ran: 15 derived famil(ies) accounted for — 15 run, 0 NOT-MEASURED (a DERIVED zero — all 15 recorded an exit code and none of them is 3). All 15 exit 0, plus `pnpm check:pm-governed-prose` (exit 0, outside the derivation, named by the dispatch). The named ones: `check:pm-skill-ratchet`, `check:pm-skill-id-lint`, `check:pm-governed-prose`, `check:nul-bytes` — the ratchet's own verdict line reads `✓ check-skill-line-ratchet: declared cross-file moves: 1, total ceilings down 9 lines.` with no ceiling raised. Seven of the sixteen first exited **3 (PREREQUISITE NOT MET)** in the fresh worktree — not a verdict. Six cleared after `pnpm install`; `check:doc-formula-expressions` also needed `pnpm exec turbo run build --filter=@objectstack/formula --filter=@objectstack/lint`, run under `scripts/pm/os-verify-lock.sh` (`VERDICT command-exit 0 · held the lock 172s · waited 0s`), and then exited 0. ## Local verification scope The diff touches no package, so there is no dependency-closure build and no package test or typecheck to owe. The repo-wide lint is CI's run, and the narrowing here is an empty intersection **measured by eslint itself**, not asserted: `pnpm exec eslint --no-inline-config --format json` on the edited path reports one file, 0 errors, and the warning `File ignored because no matching configuration was supplied` — every `files:` selector in `eslint.config.mjs` names only `.ts/.tsx/.mts/.cts/.js/.jsx/.mjs/.cjs`, so a Markdown file is outside the linted population and cannot move any untouched file's verdict. ## Changeset `skip-changeset` label, applied through the additive labels endpoint and read back. `.claude/**` ships in no package's `files[]` — it publishes nothing. ## Acceptance notes (out of scope, not filed) - The card's own citations have drifted by line number: it cites `contract-review.md:9` for 「拿不准 ⇒ 按 `yes`」, which on `a90a9f267` is `:14` (`:9` is the 保守方向 line), and `core-rules.md:113` for the review rule, which on `a90a9f267` is `:112` (`:113` is the dispatch-word rule). Nothing in the tree is wrong; this is why the new line cites by content and not by number. Successor: whoever reads this card next — no repo change owed. - `lanes/spec.md:25` points at 「SKILL.md 模型分档」, which is not a heading in `SKILL.md`; the rules it means are under `### 派发` and the keyword does occur there (`:501`), so the pointer resolves by grep, not by section. Polish, not a defect. Successor: the next edit to either file. ## 维护者速读(草稿) **改了什么** —— PM 技能包 spec 车道说明加一行规则:收窄不触发条款②,但按 `yes` 申报恒不是错误,个案裁决不改这条。行数配额是零余量,这行由同一节里删掉「生成物门禁重生成提交,⛔ 手改」买单 —— 那条规则在 AGENTS.md 与本技能包的 core-rules / landing-operations 里都还在,不是丢掉。 **为什么改** —— 规则只写了「什么触发条款②」,没写「不触发的能不能照样申报」。空白处被一份 PR 正文顶上去当了权威:它引一张个案裁决说收窄要申报 `yes`。两句话其实都对(一个讲机制,一个讲那张卡),但没有一处把它们放在一起,下一个作者就得在两份文本之间二选一,或者干脆不再申报。一行话把口子合上,保守方向照旧许可,成本是零 —— 语义面卡本来就按契约复审档施工。 **风险与代价(含回滚)** —— 代价是删掉的那条生成物提醒不再出现在 spec 车道页,读者要去 AGENTS.md 看(那份文件本来就要求全文读,且冲突时它为准)。风险低:纯说明文字,无代码、无发布面、无生成物。回滚 = revert 这一个 commit,文件回到 43 行原样。 **席位意见** —— **你要做的** —— 受管面(`.claude/**`),按 Prime Directive objectstack-ai#14 由维护者人工合并:本 PR 保持 draft,未挂 ready、未入队、未开 auto-merge。确认那一行读起来就是你要的裁决,以及「删这条买那条」的取舍你接受,然后人工合并。 _Generated by [Claude Code](https://claude.ai/code/session_01DAcomhvR9kKizeYgg89Vo8)_ --- _Generated by [Claude Code](https://claude.ai/code/session_01DAcomhvR9kKizeYgg89Vo8)_ Co-authored-by: Claude <[email protected]>
akarma-synetal
pushed a commit
to akarma-synetal/framework
that referenced
this pull request
Sep 17, 2026
…us get-session (objectstack-ai#18140) Closes objectstack-ai#18079 `Clause-②: no` — this neither loosens an accept set nor widens a published contract surface. It tightens one CI assertion onto the contract the code already declares. ## The question this card asked, and the answer objectstack-ai#18079 named two hypotheses and asserted neither: either the anonymous `GET /auth/get-session` behaviour regressed (fix the auth surface), or the smoke's expectation is stale (fix the probe). **The second is true, and it is established from the tree rather than inferred.** `packages/plugins/plugin-auth/src/anonymous-session-refusal.ts` exists for exactly this, and its header records the ruling: > Director seat, decision batch objectstack-ai#117 item 4 (2026-09-12), maintainer verbatim 「17238 B」 — the server answers the platform's standard ADR-0112 failure envelope with HTTP 401 instead of `200` + `null`, and `SessionResponseSchema` is UNTOUCHED. The charter rule quoted in that ruling: 「spec 与代码不一致默认改代码,改协议单独立卡非选项」. `AuthManager.handleRequest` calls it on the one seam every vendor route passes through, and the call site says in as many words that this one **is** the admission move: >⚠️ NOT the same kind of change: that one is forbidden to move admission and this one IS the admission move (`200` -> `401`). So the 401 is the declared contract. The smoke was asserting a shape the platform deliberately stopped serving. ⇒ **The auth surface is not touched by this PR.** Three independent legs agree on the exact envelope, so the assertion is pinned to a measurement and not to a guess: | leg | source | reading | |---|---|---| | the ruling | `anonymous-session-refusal.ts` header | 401 + ADR-0112 envelope | | the wire | run 34774426350 / 34728125950 job log | `{"success":false,"error":{"code":"UNAUTHENTICATED","message":"Sign in first"}}` | | the spec | `packages/spec/src/api/errors.zod.ts:174` | `401: 'UNAUTHENTICATED'` | ## Bisect — the true first failure, not the observed window The card cautioned that the observed window (back to `b06b2db5c4`) is not the start, because the runs on `a83dbb6124` and `6d647858b7` read `no-run` and `cancelled`. Correct — and the real boundary is a day earlier and is **not a `main` commit at all**. - Causal commit on `main`: **`374d9d3afa`** — `fix(plugin-auth)!: an anonymous get-session is refused with the declared 401 envelope, not answered 200 null (objectstack-ai#17881)`, 2026-09-12T19:15:14Z. `git log --diff-filter=A` names it as the commit that *adds* the refusal module; the shallow graft boundary here is `ca0a1f83d6` (2026-07-29), far older, so that add is genuine and not a graft artifact. - Last green RC smoke: run `34726616436`, 2026-09-12T23:54:27Z, status posted to RC head `a1effc8e44` (`chore: version packages`, 2026-09-12T18:12:26Z). - First red RC smoke: run `34728125950`, 2026-09-13T00:30:11Z, job `103645853837`, status posted to RC head `e612087feb` (`chore: version packages`, 2026-09-13T00:29:49Z). Its log carries the same assertion and the byte-identical body as the card's evidence run. Those two runs carry the **same** `main` head sha (`a9c6477904`) and opposite verdicts, which is the card's "NOT main-red" point showing up as data: the smoke tests the release candidate, not the commit the check attaches to. The decisive probe is therefore on content, not on timing — ``` anonymous-session-refusal.ts platform-admin-gate.ts (nonsense path) a1effc8 LAST GREEN RC ABSENT PRESENT ABSENT e612087 FIRST RED RC PRESENT PRESENT ABSENT ``` The firing control is present in both trees (the probe reaches them) and the nonsense control is absent in both (the probe can say no). `374d9d3afa` (19:15:14Z) falls inside the interval between those two `chore: version packages` commits (18:12:26Z → 00:29:49Z), so the two readings cross-validate. ## What changed One file, `scripts/publish-smoke.sh`: 1. The anonymous probe now expects **401** and is paired with `assert_body '.error.code == "UNAUTHENTICATED"'`. The status alone would be satisfied by an origin check, a rate limiter or any later guard while measuring nothing — the same reasoning the `SELF_REGISTRATION_CLOSED` probe below it is already written under, and what this file's header means by *"every assertion here is an HTTP status plus a `code` this repo owns and publishes"*. 2. A comment pinning **why**, naming objectstack-ai#17881, objectstack-ai#17238 and the ruling, so the next reader does not "fix" it back — the card asked for this explicitly. 3. The header's declared-contract table updated to match. The **signed-in** probe is deliberately untouched and still asserts 200: the refusal seam converts only a 200 whose body is exactly `null`, so that answer is byte-identical to before. This is the objectstack-ai#14000 move repeated — that card re-pinned this same script to the declared contract for `SELF_REGISTRATION_CLOSED` rather than touching auth runtime code, and left a standing ⛔ against relaxing an assertion *back toward 200*. This change runs the other way (200 → 401), which is the direction that prohibition protects. ## Verification - `bash -n scripts/publish-smoke.sh` → exit 0. - The new `jq` filter, tested against the byte-exact body from job `103645853837` and two controls: observed body → exit 0; a 401 carrying `INVALID_ORIGIN` → exit 1; the old `null` → exit 1. So the assertion accepts the real refusal and rejects both a foreign guard and the retired shape. - The full derived gate family for this diff — 26 commands from `node scripts/pm/dispatch-gates.mjs --commands` — all exit 0, including `check:bash32-floor` and `check:nul-bytes`. - Control-character self-scan over the edited file: no matches. -⚠️ The smoke itself is **NOT MEASURED** locally. It packs ~70 tarballs, installs and builds a project outside the workspace and boots a dev server; that is far past this container's foreground budget and its shared-verification discipline. The real verdict is the next `publish-smoke / packed-tarballs` status on the release-candidate head. ## Reverse-read, both directions **Which currently-true sentence does this make false?** Inside this file, the header's `→ 200 (anonymous)` row — updated in the same diff, so the file does not contradict itself. Outside it, none: I grepped `get-session` across `packages`, `scripts`, `docs`, `content` and `.github` and no other statement depends on the smoke asserting 200. **Which currently-false sentence does it make true?** Two. The file's own claim that it asserts *"the DECLARED first-run contract"* was false for this row and is now true. And the status text `Fresh install of the release candidate: auth + CRUD green`, which this gate has been unable to post since 2026-09-13T00:30Z, becomes reachable again — that is the release-blocking half. **Zero results reported as such:** no other consumer of the smoke's expectation exists; no docs page restates it. ## Noted separately The same grep found that objectstack-ai#17881 moved the wire answer but left the **client SDK** still documenting `-> 200 null` (`packages/client/src/index.ts:1474`) and a test double still modelling it. That is `domain:services`, not this lane, and it is filed on its own card — see objectstack-ai#18139. It is not addressed here. ## Merge channel⚠️ The dispatch brief expected this fix to land in `.github/workflows/publish-smoke.yml` and warned that such a PR cannot be armed by the PM seat (HTTP 422, token lacks `workflows`). **That premise does not hold** — the probe lives in `scripts/publish-smoke.sh`, the workflow's driver script, and the workflow YAML contains no assertion at all. This diff touches no path under `.github/workflows/`, so that caveat does not apply to it. It also touches no governed surface under Prime Directive objectstack-ai#14. No changeset: the changed file is a CI driver shipped by nothing. Measured rather than assumed — the root package is `private: true`, and of the 70 package manifests declaring `files[]`, zero name this path. The `skip-changeset` label carries that, not this sentence. --- _Generated by [Claude Code](https://claude.ai/code/session_012GKcPZbMoGq7WPzKLfRBTU)_ Co-authored-by: Claude <[email protected]>
akarma-synetal
pushed a commit
to akarma-synetal/framework
that referenced
this pull request
Sep 17, 2026
…he pre-ruling landing (objectstack-ai#18148) Fixes objectstack-ai#18083 `scripts/pm/check-governed-queue-guard.mjs` decides on an authorized APPROVED review and has since the 2026-09-04 predicate landed. What had not moved was the text a seat is told to consult: the docblock called a draft awaiting the maintainer's own merge the regime's terminal state, and the printed remedy's preferred option told a seat to take the PR out of the queue "and leave the merge to the maintainer". Since the charter chain landed (PR objectstack-ai#18018 `9489e2c0`, PR objectstack-ai#18038 `c185d087`, PR objectstack-ai#18051 `137eb00e`) the rule is ruling C — issue objectstack-ai#17971, maintainer 2026-09-13, verbatim and untranslated: > C. approve 后不管后续改动都由席位落地: So the narration under-permitted: it told a seat not to do the thing the rule now says it should do once an authorized approval is on record. ## What moved Wording only. ⛔ No decision branch changed — `entrySatisfied`, the approval reduction, the tier split and every exit code are untouched. Anchored by content, not by line number. 1. **Docblock, the "why the PR leg must not redden" paragraph.** The healthy resting state is now a draft *waiting for an authorized approval*, in the landed rule's own words from `.claude/skills/pm-dispatch/references/landing-operations.md`: 「四件套留 draft 等人批,⛔ 不翻正式不入队;获授权批准后认领席落地。」 2. **Docblock, a new paragraph for what happens after the approval** — ruling C, quoted verbatim, plus `SKILL.md`'s operational half: 「席位落地 = 过落地前检、清标、ready、auto-merge,踢出/变基同法。」 The seat's ready → enqueue is now stated as the correct next act there rather than a violation. 3. **Docblock, the "this guard cannot stop a direct merge" paragraph.** The direct merge is now the OTHER landing rather than the only one — `references/core-rules.md`: 「受管面由维护者人合或授权批准后席位落地」. The objectstack-ai#11387 measurement it cites is kept verbatim as history. 4. **The `pull_request` leg's EARLY WARNING rendering.** The ⛔ list (flip ready / enqueue / arm auto-merge) is now explicitly conditional — "while no authorized APPROVED review is on record", with AGENTS.md Prime Directive objectstack-ai#14's own "lift only for that approval" — and a new ✅ line says what a seat DOES do after the approval. 5. **The `merge_group` refusal remedy.** Steps 1 → 2 are now an order rather than a menu: out of the queue first, then the authorized approval, and the CLAIMING SEAT lands it from there. The unapproved direct merge (人工直合) is kept as the landing that PR still has. 6. **Two internal comments** that restated the same pre-ruling shape: the tier-default asymmetry cost (was "costs one hand merge") and the exit-code precedence note. The references tier is untouched and still says the in-seat review of record lands it: remedy option 3 renders byte-for-byte as before, and both of its pins (offered on a references-only refusal, withheld on a rules-layer one) still pass. ## Acceptance readings Taken on `a2e4cd7d9`, in the worktree, against merge base `a90a9f267`. **The two pre-ruling phrases, in the narration and the printed text: N → 0.** | reading | before | after | |---|---|---| | `git grep -c -E 'leave the merge to the maintainer\|human merge IS the review record'` | 2 | 2 | | … of those, hits in narration or printed remedy | 2 | **0** | | … of those, hits inside the self-test pin that FORBIDS them | 0 | 2 | Both remaining hits are the new negative assertion itself — a comment and the regex literal in `⛔ a-refusal-never-tells-a-seat-to-leave-the-merge-to-the-maintainer-nor-calls-that-merge-the-record`. A pin has to name what it forbids; neither is text the guard ever prints. **The landed phrase: 0 → 5** (`git grep -c 'CLAIMING SEAT lands\|claiming seat lands'`). **Every hit of the pre-ruling wording enumerated** (`git grep -n -E 'hand merge|human merge|leave the merge|人工合'`), 6 before → 4 after, and each remaining one accounted for: - `:44` 「人工合并即人工审核」 (docblock) — **moved**. - `:52` "the human merge IS the review record" (docblock) — **moved**. - `:536` "costs one hand merge" (tier-default comment) — **moved**, now "costs one authorized approval". - `:1171` 「人工合并即人工审核」 (EARLY WARNING rendering) — **moved**. - `:1242` "leave the merge to the maintainer. A human merge" (remedy) — **moved**. - `:2010` (was `:1983`) the `objectstack-ai#9319` replay fixture's name, quoting PR objectstack-ai#9238's own body: "a .claude/skills PR whose own body said 'awaiting a human merge'" — **stays**. It is a historical measurement naming what that PR said in 2026; rewriting it would falsify the fixture. - `:2429`, `:2436`, `:2468` — **new**, the three negative pins (two English phrases, one 人工合并即人工审核). **Self-test:** `node scripts/pm/check-governed-queue-guard.mjs --self-test` :: exit 0, **233 cases before → 238 after** (5 added, none removed, no battery floor lowered). **Diff surface:** `git diff --stat a90a9f2` names exactly one file, `scripts/pm/check-governed-queue-guard.mjs`, 104 insertions / 32 deletions. `origin/main` advanced under this worktree during the run (`a90a9f267` → `739ab526d`, a shared-ref hazard AGENTS.md names), so the anchor above is the merge base, not the moving ref. **Governed?** No — `node scripts/pm/check-governed-merges.mjs --test scripts/pm/check-governed-queue-guard.mjs` :: exit 0, "NOT governed — ordinary queue landing applies". This PR is opened as a draft and the seat lands it; nothing here flips ready or arms anything on its own. ## Reverse verification — the new pins can actually fail One-shot, from the committed state, with a `trap … EXIT INT TERM` restoring absolute paths. Predicted direction: **turns red**. - HEAD blob `f7938efe94a20f34a3c1e6f07e2aaca393f16a47`. - Mutation: the remedy's step-2 line rewritten back to the pre-ruling wording. On-disk observation, anchored on both texts: landed anchor 1 → **0**, injected stale phrase 0 → **1**; blob `87b912dbc8acaf5af5d02fb86cc06eb2bcffd986` ≠ HEAD blob, so it reached disk. - Mutated leg: `--self-test` :: **exit 1**, `✗ 2 of 238 case(s) failed` — exactly `a-refusal-orders-the-remedy-DRAFT-then-the-authorized-APPROVAL-then-the-CLAIMING-SEAT-lands-it` and `⛔ a-refusal-never-tells-a-seat-to-leave-the-merge-to-the-maintainer-nor-calls-that-merge-the-record`. - Restore leg: `git checkout HEAD -- FILE` → blob back to `f7938efe…` (byte-identical), `git diff HEAD` empty, `git status --porcelain` empty, anchor restored 1, injected 0. - Restored leg: `--self-test` :: exit 0, 238 cases pass. ## Gates Derived with `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` (no paths, three-dot against merge base `a90a9f267`) — **33 commands, all run in the foreground, every exit code captured before any pipe, all `exit 0`.** Reconciled: ``` node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --ran RAN_FILE Run reconciliation — 33 derived, 33 run, 0 NOT-MEASURED, 0 UNRUN. ✓ 33 derived famil(ies) accounted for — a DERIVED zero — all 33 recorded an exit code and none of them is 3. ``` Including `node scripts/pm/check-governed-queue-guard.mjs --self-test` :: exit 0 and `pnpm check:nul-bytes` :: exit 0. `pnpm check:pm-governed-merges` :: exit 0 was run too, though the derivation does **not** place it for this path — it is a `--self-test`-only checker-health family here, so its green grades that checker's fixtures, not this diff. Control characters: `grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]'` over the changed file — no hits. No changeset: `scripts/pm/**` ships in no package's `files[]`, so this publishes nothing — the `skip-changeset` label carries it. ## Acceptance notes Two observations outside this card's one-file surface, noted and not filed (this seat's write budget for the round is the branch push, this PR, the label and one report comment): - `scripts/pm/check-governed-merges.mjs` :140 and `scripts/check-required-contexts.mjs` :288 both still say "a human merge IS the review record" for a governed PR. Neither is wrong — it is one of the two terminals the charter names 「终局两条:人工直合即审核记录;授权批准 ⇒ 席位落地。」 — but neither mentions the second one. That is an omission rather than a contradiction, so it is not the class this card is. Issue objectstack-ai#18083 fences the first file off from this PR by name. - `.claude/hooks/guard-governed-enqueue.sh` :548 was checked and is already ruling-C shaped (its steps read draft → wait for the approval → "Then enqueue"), so it needed nothing. _Generated by [Claude Code](https://claude.ai/code/session_01DAcomhvR9kKizeYgg89Vo8)_ --- _Generated by [Claude Code](https://claude.ai/code/session_01DAcomhvR9kKizeYgg89Vo8)_ Co-authored-by: Claude <[email protected]>
akarma-synetal
pushed a commit
to akarma-synetal/framework
that referenced
this pull request
Sep 17, 2026
…lock 1, the two-tier landing and ruling C (objectstack-ai#18119) (objectstack-ai#18182) Fixes objectstack-ai#18119 **Governed rules layer** — `.claude/skills/pm-dispatch/SKILL.md` + `AGENTS.md` + `.claude/skills/checklist-test/SKILL.md`. This PR opens as DRAFT and draft is its finished state: it waits for an authorized APPROVED review by an account in `GOVERNED_APPROVERS`, after which the claiming seat lands it (Prime Directive 14; ruling C, maintainer verbatim, untranslated: 「C. approve 后不管后续改动都由席位落地:」, recorded on objectstack-ai#17971). Never ready, never queued, never auto-merge, and no seat approves it under any account. Base `origin/main` `fffe3e5e7f` (fetched 2026-09-14T11:14Z); this branch `claude/issue-18119-rules-layer-four-lines` at `6dc9ac3e52`, one commit, three files, net 0 lines in each (`git diff --numstat`: 1/1 · 3/3 · 3/3). ## What changed — seven equal-line edits, no other line moves Every rewritten line stays within the enforced 120-byte cap (`MAX_LINE_BYTES = 120` in `scripts/pm/check-skill-line-ratchet.mjs`); byte widths after each row. | File | Line | Before | After | |---|---|---|---| | pm-dispatch `SKILL.md` | 378 | ``- ⛔ `issue_write` 会替换标签集并清空未传字段:写入时必须回传 `assignees`。`` (96 B) | ``- 标签/assignee 写恒经 `scripts/pm/label-write.mjs`(四步、回读);⛔ 永不 MCP `issue_write`(锁 1 已拒)。`` (119 B) | | pm-dispatch `SKILL.md` | 615 | `- PR 上的标签 = 待维护者审阅,不入六态;与请审同为等人合清单,随合并或撤回判决离开。` (116 B) | `- PR 上的标签 = 待维护者审阅,不入六态;与请审同为等人批清单,随获批或撤回判决离开。` (116 B) | | pm-dispatch `SKILL.md` | 626 | `- 规则层四件套等人合;事实层 PR(受管路径全在该目录)经席内达档复核后 ready → 入队。` (115 B) | `- 规则层四件套等人批;事实层 PR(受管路径全在该目录)经席内达档复核后 ready → 入队。` (115 B) | | `AGENTS.md` (PD 14) | 273 | `uncertified recompute, drift or a hand-authored sibling keeps it governed. Unapproved, the bypass direct merge` (114 B) | `uncertified recompute, drift or a hand-authored sibling keeps it governed. Unapproved, no seat lands it: the` (112 B) | | `AGENTS.md` (PD 14) | 274 | `(人工直合) is the only landing. **Landing is tiered**: a PR whose governed paths all lie under` (102 B) | `ending is that approval, then the owning seat. **Landing is tiered**: a PR whose governed paths all lie under` (113 B) | | `AGENTS.md` (PD 14) | 284 | `human merge IS the review record, ⛔ not a relaxation. Behind it: the queue guard refuses an unpinned governed` (115 B) | `spent approval IS the review record, ⛔ not a relaxation. Behind it: the queue guard refuses an unpinned governed` (118 B) | | checklist-test `SKILL.md` | 123 | ``用 `issue_write`(github MCP)立单:`` (36 B) | ``经 REST 代理 `POST .../issues` 立单(`Content-Type: application/json`;⛔ 永不 MCP `issue_write`,锁 1 已拒):`` (117 B) | Notes on the wording, each taken from the facts layer rather than invented: - :378 uses `rest-channel.md` :38–:39's words (`scripts/pm/label-write.mjs`, 四步、回读, ⛔ 永不 MCP `issue_write`, 锁 1 已拒); the full four-step rule already lives at SKILL.md :148, so the parenthetical is a pointer, not a restatement. - :615 — 等人合清单 → 等人批清单; the exit clause 随合并…离开 → 随获批…离开 keeps the same semantics in the ruling-C mechanism: :617 has the seat clear the label (清标) as the first landing step, on the approval, so the label leaves on approval or on a withdrawn verdict, not on the merge. - :626 — 等人合 → 等人批, the operative token of `landing-operations.md` :28 (「四件套留 draft 等人批,⛔ 不翻正式不入队;获授权批准后认领席落地」). The 获授权批准后认领席落地 half already stands at SKILL.md :616–:617 (「席位落地 = 过落地前检、清标、ready、auto-merge」), and no spelling that carries both halves fits the 120-byte cap with the 事实层 half unchanged (the shortest measured 131 B), so the line carries the token and :617 carries the mechanism. - AGENTS.md :273–:274 — the 人工直合 ending becomes the ending `lanes/ui.md` :25 states (「⛔ 未获授权批准不 ready 不入队不自合、永不批准,获批后认领席落地」): unapproved, no seat lands it; the ending is that approval (the authorized APPROVED review by a `GOVERNED_APPROVERS` account named four lines above), then the owning seat. The sentence says nothing about what the maintainer may do by hand, so it does not contradict the queue guard's own refusal text (see Consistency, item 5). - AGENTS.md :284 — "a human merge IS the review record" is the phrase the queue guard's self-test asserts ABSENT from its refusal text (`check-governed-queue-guard.mjs` :2436, "never … calls that merge the record"); under ruling C the maintainer's word is spent once, as the approval, so "a spent approval IS the review record" — same sentence, same line count. - checklist-test :123 — the REST channel in `rest-channel.md`'s own spelling (`POST .../issues`, request body as JSON). The field list that follows (标题 · 标签 · 正文, :125–:150) is already the shape of a REST issue body — title, labels, body — so no line below :123 needed to move. ## Premise readings (all against `origin/main` `fffe3e5e7f`) - **P1 content** (2026-09-14T11:16Z) — all five anchors matched by content at the named numbers: SKILL.md :378, :615, :626; AGENTS.md :274 (「(人工直合) is the only landing.」 inside PD 14); checklist-test :123. A sixth line of the same shape sits at SKILL.md :610 (not named by the card; see Consistency item 1). - **P2 ratchets** (2026-09-14T11:16Z) — `pnpm check:pm-skill-ratchet` exit 0 on the tip: SKILL.md 812/812 (widest row 342/342), AGENTS.md 1075/1075 (widest row 768/768), checklist-test 234/238 (widest row 221/221), core-rules 151/151. Frame block :733–:754 md5 `3327d02c56f8a0eca88569dad2270f32` before and after — identical. - **P3 core-rules.md** — the target grep `issue_write|等人合|人工直合` reads 1 hit, NOT 0: :128 「…代裁清单、等人合项、受管合并审计与五指标。」 — the round-report rule, which mirrors SKILL.md :672 (「awaiting a human merge 项」), not any of the four lines. The control `grep -c 'label-write\|四件套'` reads 0 in that file as well (neither control word is in core-rules today; stated as read). Per the dispatch's branch, core-rules.md is untouched; the mirrored pair is listed under Consistency item 2. - **P4 facts layer** — quoted verbatim: `rest-channel.md` :37 「✓ 标签加法 `POST .../issues/{n}/labels`,定向删 `DELETE .../issues/{n}/labels/{name}`;加法优先。」 :38 「标签/assignee 写恒经 `scripts/pm/label-write.mjs`:四步内建、回读、回退整组 PATCH 回传 assignees。」 :39 「⛔ 永不 MCP `issue_write`(锁 1 已拒);会话分类器拒改动 ⇒ 无通道,交有通道席位立卡。」; `lanes/ui.md` :25 「⇒ 命中即停 draft;⛔ 未获授权批准不 ready 不入队不自合、永不批准,获批后认领席落地。」; `landing-operations.md` :27 「受管路径全在本技能 `references/` 者事实层:席内达档复核过落地前检三条即转正式入队。」 :28 「其余为规则层:四件套留 draft 等人批,⛔ 不翻正式不入队;获授权批准后认领席落地。」 :29 「⛔ 两层不由席位批准;清标即落地同受此闸,漏判会被队列守卫在 merge group 里拒收。」 - **P5 serial** (2026-09-14T11:16Z) — `GET /pulls?state=open` returns exactly the six the dispatch named (18176, 18175, 18173, 18131, 18096, 17076); each `/files` list read via REST; zero hits on the three files. - **P6 lock 1** — `permissions.deny` on the tip carries **14** entries, not 15, with `mcp__github__issue_write` first among them; the same 14 at lock 1's own commit `7ef05f997`. The 15 was a miscount; the substance (the tool is denied) holds. ## Consistency — sentences of the same retired shape this PR does NOT move (the card's fence: no other line) 1. pm-dispatch `SKILL.md` :610 「② PR 留给维护者看得见地悬着;终局两条:人工直合即审核记录;授权批准 ⇒ 席位落地。」 — still two endings, with the human direct merge as the review record (the exact phrase retired at AGENTS.md :284 here and in the queue guard). Not named by the card; left as is. An equal-line candidate within the cap, for the seat to accept or reject: 「- ② PR 留给维护者看得见地悬着;终局一条:授权批准即审核记录 ⇒ 席位落地。」 2. pm-dispatch `SKILL.md` :622 「④ 轮次报告单列 awaiting a human merge。」 and :672 「…awaiting a human merge 项…」 with its core-rules mirror :128 「等人合项」 — the round-report item still named after the human merge; per 〈优先级〉 :44 the :672 ↔ :128 pair moves together, in one PR. 3. `AGENTS.md` :255 (the PD 14 headline) — "confirmed and merged by the maintainer, by hand — or confirmed by an authorized approval and then landed by the owning seat" still names the hand merge as ending one. NOT changed on purpose: `scripts/pm/check-governed-prose.mjs` :148 anchors its AGENTS.md region on that exact sentence (`start: 'A governed surface is confirmed and merged by the maintainer, by hand'`), so the sentence and the anchor must move in the same PR — outside this card. 4. `AGENTS.md` :799 (Skills section) — "human-merge only, or queued under Prime Directive objectstack-ai#14's pinned-approval path" — outside PD 14, outside the card. 5. The mechanism layer still names 人工直合 for the unapproved case: `scripts/pm/check-governed-queue-guard.mjs` :1197 and :1273 print "Unapproved, the maintainer's own direct merge (人工直合) is the only landing this pull request has", and its self-test :2441 pins that word present; `landing-operations.md` :49 keeps 人工直合 as the main-red one-line exception; `lanes/director.md` :44 says 「等人合清单」. The :274 wording chosen here is compatible with all of them (it constrains seats, not the maintainer). 6. Script header comments still carrying the phrase: `scripts/pm/check-governed-merges.mjs` :140 and `scripts/check-required-contexts.mjs` :288 ("human merge IS the review record") — comments, not rules. ## Gates (foreground, exit captured before any pipe, all on `6dc9ac3e52`) Derived with `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` (18 families; the seat's derivation named the same set). Each ran with stdout and stderr redirected into its own log file and the exit status captured immediately after, before any pipe: | # | Family | Exit | Verdict line | |---|---|---|---| | 1 | `node scripts/check-closing-keyword-parity.mjs` | 0 | OK (3 parsers agree on all 9 keywords…) | | 2 | `… --self-test` | 0 | 30 assertions, 5 mutations each driven to red | | 3 | `node scripts/check-comment-mask-corpus.mjs` | 0 | 6752 files, 0 disagree, 0 unparseable | | 4 | `node scripts/pm/check-governed-queue-guard.mjs --self-test` | 0 | 238 cases pass | | 5 | `pnpm --filter @objectstack/lint run check:doc-formula-expressions` | 3 → 0 | first run PREREQUISITE NOT MET (not a verdict: `@objectstack/formula`, `@objectstack/lint` unbuilt); built under `scripts/pm/os-verify-lock.sh` (`VERDICT command-exit 0 · held the lock 5s · waited 0s`), re-run exit 0: 58 self-test cases, 22 record-scoped examples / 438 files clean | | 6 | `pnpm check:agent-test-spelling` | 0 | 0 violations — 508 files | | 7 | `pnpm check:doc-authoring` | 0 | 15340 strings across 978 spec sources clean | | 8 | `pnpm check:docs-audit-scope` | 0 | 14 release-owned pages review-only; scope injection live | | 9 | `pnpm check:driver-memory-census` | 0 | every declaration ledgered | | 10 | `pnpm check:nul-bytes` | 0 | scanned 8655 text files, no raw ASCII control bytes | | 11 | `pnpm check:pm-governed-merges` | 0 | 328 assertions; the real generator declared 9 outputs and certified this tree | | 12 | `pnpm check:pm-governed-prose` | 0 | 2 instruction surfaces name all 5 registered governed surfaces and claim no others | | 13 | `pnpm check:pm-skill-id-lint` | 0 | 27 files clean | | 14 | `pnpm check:pm-skill-ratchet` | 0 | SKILL.md 812/812 (342/342) · AGENTS.md 1075/1075 (768/768) · checklist-test 234/238 (221/221) · core-rules 151/151 | | 15 | `pnpm check:refd-timer-probe` | 0 | 6747 files swept | | 16 | `pnpm check:required-contexts` | 0 | 7 required contexts pinned; 6 instruction surfaces scanned against 2 retired names | | 17 | `pnpm check:skill-frame-sync` | 0 | the one declared copy is internally coherent; 74 markdown files scanned for undeclared copies | | 18 | `pnpm check:watch-hint-literal` | 0 | 70 declarations across 4 rostered names | Reconciliation (`--ran`, exit codes recorded per command): `Run reconciliation — 18 derived, 18 run, 0 NOT-MEASURED, 0 UNRUN.` … `✓ dispatch-gates --ran: 18 derived famil(ies) accounted for — 18 run, 0 NOT-MEASURED (a DERIVED zero — all 18 recorded an exit code and none of them is 3).` Also outside the derivation: the pre-push hook `check:commit-card-trailers` on the push (1 commit, no card relation, no model identifier in the trailer pair); the control-byte self-scan `grep -naP` over the three files reads none. Changeset: none owed. The three paths are `.claude/**` and `AGENTS.md` — nothing any package's `files[]` ships. The `changeset-check` job in `pr-automation.yml` has two exemptions (the `skip-changeset` label and the release PR) and no path exemption, so the label is applied through the additive endpoint `POST /issues/{n}/labels` right after this PR opens and read back; the read-back is recorded in the os-dev-report comment on the card (this body is not re-sent). ## 维护者速读(草稿) - 改了什么:四行规则文本(加同段两句)追上已落地的锁 1、两级落地与裁决 C —— `issue_write` 已被拒、规则层等的是批准而不是人合、批准后由认领席落地。 - 为什么改:规则层与它自己的 references 说法相反,会把席位训练回已退役的机制;文档以实际实现为准。 - 风险与代价(含回滚):零机制变化,纯文案,等行数;回滚即 revert 本 PR。 - 席位意见:(留空) - 你要做的:一次授权批准(os-zhuang / hotlong 任一账号 APPROVE);之后由认领席落地,不需要你再点合并。 ## Acceptance notes - noted, not filed: Consistency items 1–6 above — same retired-shape residue in lines the card's fence excludes (SKILL.md :610/:622/:672, core-rules :128, AGENTS.md :255/:799, the guard's refusal text and two script comments). 承接者: the `domain:skills` seat (owner of SKILL.md, `references/**` and `scripts/pm/**`); items 3 and 5 need a gate/anchor edit in the same PR as the prose. - noted, not filed: the dispatch's P6 count (15) versus the measured 14 — a miscount in the dispatch text, no file to fix. 承接者: 无. - Deviation, declared: the dispatch asked for a three-line 速读; `.claude/agents/os-dev.md` fixes five segments, and that file wins on conflict — five compact bullets above. - No footer block was sent with this body (the platform appends one on create per `platform-readings.md` :338); the body was read back after creation. Clause-②: no --- _Generated by [Claude Code](https://claude.ai/code/session_01DAcomhvR9kKizeYgg89Vo8)_ Co-authored-by: Claude <[email protected]>
akarma-synetal
pushed a commit
to akarma-synetal/framework
that referenced
this pull request
Sep 17, 2026
…ol — the reading discipline names the broken-instrument case (objectstack-ai#18044) (objectstack-ai#18194) Fixes objectstack-ai#18044 **Governed rules layer** (`.claude/skills/**`, Prime Directive objectstack-ai#14): this PR is a DRAFT by design and stays one — never ready, never queued, never auto-merged, no approving review from any seat. Ruling C on objectstack-ai#17971: 「C. approve 后不管后续改动都由席位落地:」 — the skills seat runs the four-piece and lands after an authorized APPROVED review. One commit (`7ec2a70acc`), two files, net 0 lines. ## What changed | file:line | before (bytes) | after (bytes) | |---|---|---| | SKILL.md :161 | `- 零命中须用确定存在的邻近词反查;控制词须与主张同主体,否则该零作废,不是阴性。` (113) | `- 零命中须配同主体必中词,否则该零作废;同主体 = 同语料/路径形/包界/引法/失效形态。` (116) | | SKILL.md :162 | `- 同主体 = 同语料、同路径形、同包界、同引法、同失效形态:跨行短语配跨行控制词。` (114) | `- 同仪器的控制词双零是仪器坏,⛔ 不读作缺席:换法重画再报;哑火仪器与干净结果同值。` (117) | | core-rules.md :45 | `- 零命中须用必中词反查,且与主张同语料/路径形/包界/引法/失效形态,否则该零作废。` (113) | `- 零命中须配同主体必中词,否则该零作废;同仪器的控制词双零是仪器坏,⛔ 不读作缺席。` (117) | Byte widths are line content without the newline, as `scripts/pm/check-skill-line-ratchet.mjs` measures them (budget 120 per line). The two SKILL.md lines, mapped to the card's suggested shape: - **(a) kept** — every zero needs a same-subject lit control or the zero is void: `零命中须配同主体必中词,否则该零作废`. The same-subject enumeration stays on :161 in core-rules's slash spelling (`同主体 = 同语料/路径形/包界/引法/失效形态`); `同主体` remains a defined term because `.claude/agents/os-dev.md` :72 uses it. - **(b) new** — a control drawn with the same instrument as the reading is not a control: subject 0 and control 0 together indict the instrument, not the corpus, and the control is re-drawn a different way before either number is reported: `同仪器的控制词双零是仪器坏,⛔ 不读作缺席:换法重画再报`. - **(d) new** — the one-line form from comment 5663849431, 「an instrument that cannot fire returns the same value as a clean result」: `哑火仪器与干净结果同值` (哑火 = misfire, cannot fire). - **(c) did not fit** — see Acceptance notes. core-rules :45 mirrors (a)+(b) in compressed form in the same PR (SKILL.md 〈优先级〉: 「一条规则在本文与核心条款一处改动,另一处同 PR 同改」). Its two clauses are byte-identical to the first clause of :161 and the first clause of :162, so a grep for either clause fires in both files. ## Premise readings — all on `origin/main` = `a81a9d6e79` (worktree BASE), 2026-09-14 - **P1** (content, 12:54Z): :161, :162 and core-rules :45 read byte-for-byte as the dispatch quoted them (the *before* column). Subject grep for a same-instrument clause, `同一仪器|同一工具|失效.*亮控|不能发火|cannot fire`, over both files: 0 (grep exit 1). The lit control had to be re-drawn: the first draw, `亮控`, read 0 in both files — the corpus does not spell the concept that way — so it was re-drawn with the corpus's own spellings: `控制词` = 3 hits in SKILL.md, `必中词` = 1 hit in core-rules (:45). Premise holds: neither file said a control drawn the same way as its reading is not a control. - **P2** (ratchets, 12:55Z): `pnpm check:pm-skill-ratchet` on the untouched tip, exit 0. Ceilings from the script's own table: SKILL.md 812 lines / widest table row 342 B; core-rules 151 lines / no table rows. Measured on the tip: 812, 342 (at :244), 151; longest prose line 120 B (:98). Frame block `sed -n '733,754p' SKILL.md | md5sum` = `3327d02c56f8a0eca88569dad2270f32`. After the edit: 812 / 342 / 151, longest prose line still 120 (:98, untouched), frame md5 unchanged `3327d02c56f8a0eca88569dad2270f32`. - **P3** (facts layer, 12:54Z): `grep -n -E '亮控|控制词' references/platform-readings.md` = five rows — :188, :189, :191, :192, :255 — all about the `search_issues` channel control (「控制词命中只证通道活着」, 「零仍不是读数」). The new lines restate none and contradict none; :192 already says a control HIT proves only the channel, and this PR adds the other direction: a control MISS proves the instrument, not the corpus. - **P4** (serial, 12:55Z): the nine open PRs' file lists via REST `/pulls/N/files`, paged until a page came back under 100: 18192 (1 file), 18191 (1), 18189 (1), 18187 (25), 18186 (1), 18175 (17), 18131 (2), 18096 (3), 17076 (470 across five pages). None touches `pm-dispatch/SKILL.md` or `references/core-rules.md`. Control for the hit-regex: it fires on both literal paths (`true true`). `git ls-remote --heads origin | grep issue-18044` lists this branch only. - **P5** (seam, 12:56Z): objectstack-ai#15410 open — 「the repo mandates `--self-test`, gates that it is wired, and documents nothing about its shape: 168 hand-rolled assertion helpers, and only 20 of 170 can be shown to fail on zero cases」 — the gate-side half; objectstack-ai#13014 closed — 「A gate that reads a site with the wrong extractor passes VACUOUSLY instead of failing」. Named here, nothing taken. ⛔ Nothing mechanised in this PR: no script, no gate, no self-test change. ## Gates — run on `7ec2a70acc`, sweep finished 13:08Z, each exit captured before any pipe Derived by `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` (16 families) plus `pnpm check:required-contexts`, which the seat's own derivation named (this tree derives 16; the tool files the extra run as 「Outside this card's derivation … Not an error」). | command | exit | |---|---| | `node scripts/check-closing-keyword-parity.mjs` | 0 | | `node scripts/check-closing-keyword-parity.mjs --self-test` | 0 | | `node scripts/check-comment-mask-corpus.mjs` | 0 | | `node scripts/pm/check-governed-queue-guard.mjs --self-test` | 0 | | `pnpm --filter @objectstack/lint run check:doc-formula-expressions` | 3 → 0 (see below) | | `pnpm check:agent-test-spelling` | 0 | | `pnpm check:doc-authoring` | 0 | | `pnpm check:driver-memory-census` | 0 | | `pnpm check:nul-bytes` | 0 | | `pnpm check:pm-governed-merges` | 0 | | `pnpm check:pm-governed-prose` | 0 | | `pnpm check:pm-skill-id-lint` | 0 | | `pnpm check:pm-skill-ratchet` | 0 | | `pnpm check:refd-timer-probe` | 0 | | `pnpm check:skill-frame-sync` | 0 | | `pnpm check:watch-hint-literal` | 0 | | `pnpm check:required-contexts` | 0 | `check:doc-formula-expressions` first exited 3 — 「PREREQUISITE NOT MET — the workspace package `@objectstack/formula` is not built」 — which is not a verdict. Built `@objectstack/formula` + `@objectstack/lint` under `scripts/pm/os-verify-lock.sh` (「VERDICT command-exit 0 · held the lock 17s · waited 0s」), re-ran, exit 0. Reconciliation, `--ran` with exit codes recorded (separate invocation from `--commands`): 「Run reconciliation — 16 derived, 16 run, 0 NOT-MEASURED, 0 UNRUN.」 … 「✓ dispatch-gates --ran: 16 derived famil(ies) accounted for — 16 run, 0 NOT-MEASURED (a DERIVED zero — all 16 recorded an exit code and none of them is 3).」 Control-byte scan of both files (`grep -naP` over C0/DEL): clean, exit 1. Changeset: none owed — `.claude/**` publishes nothing; `skip-changeset` added through the additive labels endpoint and read back. ## 维护者速读(草稿) **改了什么**:读数纪律补一句话,占原来的两行、不加行:「一个零要配一个必中的控制词」这条不变;新增——控制词和主读数用同一把尺子量出来的「双零」,读作尺子坏了,不读作「语料里没有」,先换个画法重画控制再报任一个数;一句话形式:哑火的仪器和干净的结果给的是同一个值。核心条款 :45 同 PR 压缩镜像。 **为什么改**:一天之内四次撞上同一个坑(卡片正文两例、分诊席一例、objectui 席四例,其中一例是写进四份派发令的常设指令,四个 agent 都「照做并报告满足」,而那条指令根本打不响)。旧文只要求「零要配控制词」,没说控制词自己也回零时该怎么读——于是两个零被当成了干净的阴性。 **风险与代价(含回滚)**:纯规则文本,零机械化、零代码、不发包;行数 812/812、151/151 不动,最宽表行 342 不动,框架块 :733–:754 的 md5 不动。措辞压到 116/117/117 字节,读者第一遍可能要多看一眼「同仪器」「哑火」两个词。回滚 = revert 本 PR 的一个 commit。 **席位意见**:(留空,席位定稿成评论) **你要做的**:读两行新文加核心条款一行;认可即授权批准,席位落地;不认可,指出哪个词。 ## Acceptance notes - **(c) did not fit and is NOT in the rules layer.** The cross-line / concatenated-literal line as a candidate, `- 拼接后才有的短语先拼后搜,有无两向皆然:字符串拼接、短语内 markdown 强调、折行。`, measures 113 B — a third line, ⛔ forbidden by the equal-line fence. Folded into :162 as `…⛔ 不读作缺席:换法重画再报;拼后才有的短语先拼后搜;哑火仪器与干净结果同值。` the line measures 151 B against a 120 B budget. Per the dispatch's fallback, (a)+(b)+(d) landed. (c) in one sentence for whoever carries it: a phrase that only exists after joining (string concatenation, markdown emphasis inside a phrase, a wrapped line) is searched joined, in both the present and the absent direction — the absent direction is the one in which a defect looks like success. - **The old :162 tail 「跨行短语配跨行控制词」 was replaced, not silently dropped.** It was one worked instance of 同失效形态, which stays in the enumeration; the new :162 covers the same case from the other side — a cross-line control that reads 0 under a single-line grep is now a broken-instrument reading, not a negative. - **Vocabulary, so the seat's post-check control fires on the right spelling:** the new clause spells the concept `同仪器` (not `同一仪器`) and the cannot-fire form `哑火` (not `不能发火`). Phrases to grep for: `同仪器的控制词双零`, `不读作缺席`, `哑火仪器`. The dispatch's P1 expected-absent pattern (`同一仪器|…|不能发火`) still reads 0 on the new text — that is the pattern's spelling, not a missing change. - **The card's headline 「a control drawn with the same instrument is not a control」 is carried in its operational form** (`双零是仪器坏,⛔ 不读作缺席`) rather than the literal `不算控制`: a same-subject control is by construction drawn with the same grep (同引法 / 同失效形态), so the literal form would read as contradicting :161; what makes such a control "not a control" is that its zero is void, which is what the line says. Both spellings did not fit: :162 with `不算控制` added measures 130 B. - **Mechanisation seam — named, not taken.** objectstack-ai#15410 (open) holds the gate-side half: self-tests that cannot be shown to fail on zero cases. This PR is the reading-side principle. The proposal in comment 5663849431 — a check that refuses to report a number unless its self-test leg fired — sits on the seam between the two. objectstack-ai#13014 (closed) is the same class one layer down. objectui's Console Performance Budget bot (comment 5664006956) is an existence proof of the remedy shape: 「not measured」 in place of a verdict, the failed precondition named, derived artifacts withheld. - **Instance-4 lesson for dispatch orders** (comment 5663849431), not landed as text: a standing check that ships in a template ships with its own firing leg — 「confirm X occurs 0 times」 handed to four agents was satisfied by all four and could not fire. Noted for the seat's dispatch-order authoring; a rules-layer line for it would not fit here either and is a separate card if the seat wants one. - **Write-channel ordering:** the draft PR was opened AFTER the gate sweep rather than before it — the dispatch's write budget enumerates one `POST /pulls` and no body PATCH, and the sweep on a three-line prose diff took minutes with no lock wait, so the verdicts went into the one body write. - noted, not filed: this tree derives 16 families for the diff where the seat's earlier derivation named 18 (incl. `check:required-contexts`); `--ran` files the extra run as 「Outside this card's derivation」, which the tool itself calls not an error. 承接者:无. Clause-②: no --- _Generated by [Claude Code](https://claude.ai/code/session_01DAcomhvR9kKizeYgg89Vo8)_ Co-authored-by: os-dev <[email protected]> Co-authored-by: Claude <[email protected]>
akarma-synetal
pushed a commit
to akarma-synetal/framework
that referenced
this pull request
Sep 17, 2026
…board it names (objectstack-ai#18223) Part of objectstack-ai#17512⚠️ Deliberately `Part of`, and the dispatch asked for a closing keyword. One half of the card is undelivered and is the seat's to rule on — see **The half this does NOT deliver**. A merge that closed the card here would close it while nothing sweeps the surfaces the card is about. One word flips it back if the seat disagrees. Clause-②: no Reason: this is a gate ADDITION and widens no acceptance set. The diff is one new `scripts/check-issue-citations.mjs` plus one new root-manifest `check:*` key; no existing gate's rule, threshold, ledger, exemption list or population is touched, and nothing that used to be refused is now admitted. ## What this adds `scripts/check-issue-citations.mjs` — extracts `#N` citations from a declared surface set and resolves each against this repository's board. | mode | what it does | verdict | |---|---|---| | default (diff-scoped) | judges only the citations **this change adds**, against the merge base | exit 2 on a finding | | `--census` | the whole declared surface | report-only, always exit 0 | | `--probe-cause` | adds the web-endpoint probe that separates transferred from deleted | — | | `--list` | extraction only, no network | — | | `--self-test` | offline; 56 cases over 6 batteries with a per-battery floor | exit 1 on a breach | | — | the board could not be read | exit 3, never a quiet pass | ## Re-measured on today's tree, before anything was written The card was measured 2026-09-10. Re-measured 2026-09-14 against `2d3d1c969`: | number | 2026-09-10 | 2026-09-14 | carrier today | |---|---|---|---| | 16785 | 404 | **404** | the `dataset-compiler.ts` scope note is GONE; the number now survives only in a changeset that already records it as resolving to nothing | | 16685 | 404 | **404** | still cited from `content/docs/releases/v17/17-4.mdx:408` (a published page), `measure-result-type.ts:115` and `aggregate-field-type-compatibility.ts` | | 14313 · 14832 · 14366 | 404 | **404** | unchanged | Controls, same call, same token: 16099, 16737, 17444, 17560, 17242, 15809 all **200**. ⭐ **The card's own control has partly decayed, and that is the sharpest reading here.** The card cited 16783 / 16784 / 16786 / 16787 — the immediate neighbours of 16785 — as all resolving, proving a hole in a dense sequence. Today only **16784** answers 200; **16783, 16786 and 16787 answer 404**. Three numbers stopped resolving in four days, with no change to this repository. ## The surface is ~500× the five instances Enumerating the whole board (159 cursor pages) and judging every citation offline against it: ``` allocation frontier 18,219 the highest number ever minted here resolvable 15,834 holes 2,385 13.1% of every number this repo ever minted ``` and through this gate's own `--census --json`: ``` 33,750 citations judged across 2,357 files 28,359 resolve 1,410 resolve as a PULL REQUEST, not an issue 1,196 cross-repo — UNJUDGED, never a finding 2,785 UNRESOLVABLE: 103 sites / 71 distinct on the release pages 2,682 sites / 439 distinct in package docblocks ``` A second instrument written before the gate existed (a scratch scan with the same projection) answered 2,782 on the same tree minutes earlier; the two agree to within the three citations the frontier moved by while they ran. **Those two readings together decide the gate's shape.** A tree-wide blocking verdict is refused for two reasons, not one: 2,785 findings is the permanently-red gate this repo retired, and — worse — the predicate is **not a function of this tree**. 16783 / 16786 / 16787 prove a still tree goes red because somebody else deleted an issue. So the default verdict is **diff-scoped**: the half of the class an author owns, which cannot red on a still tree and which stops the debt growing. The 2,785 standing sites are a `--census` reading, not a verdict. ## The four 404 causes, kept apart ⛔ The card's hardest constraint. Each arm has its own decision procedure, and none of them is a guess. | cause | how it is decided | measured live? | |---|---|---| | `cross-repo-unjudged` | the citation NAMES another repo (`owner/repo#N` or `repo#N`) — never resolved, ⛔ never a finding | ✅ `objectui#4356` stayed silent in the live ablation | | `never-issued` | the number is beyond the allocation frontier | ✅ `#18888` against frontier 18221 | | `transferred` | the web endpoint still redirects out of this repo (the API does not keep that redirect) | ⛔ **NOT MEASURED** — no positive specimen exists on this board; exercised only by the self-test stub | | `deleted` | minted, absent from the board, and the web endpoint 404s too — named as the residual class | ✅ 16785 under `--probe-cause` | Without `--probe-cause` the last two do not separate and the finding carries `allocated-but-absent`, which is a refusal to guess rather than a third cause. Every unresolvable instance re-probed on 2026-09-14 (16785, 16783, 14366, plus twelve sampled at random from the census) answered 404 on **both** endpoints, so no citation measured here was transferred — and this gate's silence is ⛔ not evidence that none ever was. ## Both directions, measured live on this branch The ablation ran against the live board, on the committed implementation, with the mutation proven on disk before the reading was taken and the restore proven by blob hash afterwards. ``` leg 0 tree clean at HEAD, diff-scoped run exit 0 "no issue citations added" mutate one docblock line in a declared surface blob f318190 -> bd2007413 (marker count 0 -> 1) leg 1 diff-scoped run with --probe-cause exit 2 1 deleted, 1 never-issued, 1 cross-repo-unjudged [deleted] ...aggregate-field-type-compatibility.ts:8 objectstack-ai#16785 [never-issued] ...aggregate-field-type-compatibility.ts:8 #18888 leg 2 git checkout HEAD -- THE_PATH blob restored to f318190, git diff HEAD empty diff-scoped run exit 0 "no issue citations added" ``` ⛔ A first attempt at this ablation wrote nothing to disk (a `perl -i` invocation that swallowed its argument and still exited 0). The on-disk marker count caught it and the run was declared void rather than re-rolled quietly; the reading above is from the run that landed. ## The scope contract, written down The card rules that the hard part is scope, so it is a table with a reason per row rather than a habit. **Declared** — start where the damage is measured: - `content/docs/releases/**/*.mdx` — the published release pages; 16685's carrier. - `packages/**/src/**/*.ts` and `.tsx`, **comment prose only** (through `scripts/symbol-anchors.mjs#commentProse`, so a gate's own fixtures and string literals are blanked and line numbers survive) — 16785's carrier. **Deferred, and the table is applied as a hard EXCLUSION rather than kept as prose** (the first draft declared it and swept the files anyway — 5,102 sites instead of 2,785 — which is why `surfaceFor` now checks the deferred globs first): - `**/CHANGELOG.md` — 27,978 citations, generated release prose, and the surface the card means by 「会被 changelog 引用淹没」. - `scripts/**` — objectstack-ai#15809's lane. ⛔ Not folded; the card is explicit that folding the siblings fails the fix. - `docs/adr/**` — a governed surface with its own anchor corpus; a citation finding there would force a governed-surface PR over a number somebody else deleted. - `.changeset/**` — consumed and deleted at release. - `packages/**/*.test.ts` and siblings — the next candidate widening, held back so the first installation is judged on the surfaces the card measured damage on. Three grammar narrowings, each measured rather than assumed: a two-digit floor (all 133 one-digit `#N` tokens in the declared surfaces are ordinals — `Prime Directive objectstack-ai#9`, `acceptance objectstack-ai#5` — and none names an issue), a six-digit ceiling (zero such tokens exist, which keeps hex colours out), and `NON_CITATION_HEADS` for numbering systems that are not the board's (`Directive objectstack-ai#14`, `batch objectstack-ai#127`, `re-charter objectstack-ai#26` — 405 sites). ## The half this does NOT deliver — the seat's ruling, not an oversight ⛔ **No workflow invokes this file.** `.github/workflows/**` was out of the dispatch's file surface, and a census of the root manifest's 160 `check:*` keys found **every one of them named by a workflow**, directly or through its alias — there is no precedent here for a gate that CI does not run. The manifest carries `check:issue-citations`, which runs the `--self-test` and nothing else (the `check:pm-half-states` shape, for the same reason: the live modes need a board and a credential). So today this is a tool a seat runs, and `scripts/pm/check-half-states.mjs` states what that is worth: 「an alarm added to a script nobody runs is still silence」. The two homes it wants are in **different lanes**, which is the substance of the ruling being asked for: 1. **the diff-scoped verdict** belongs in `lint.yml`'s `Lint & Repo Gates` job — per PR, already holds a `GITHUB_TOKEN`, and the verdict is a fact about the diff; 2. **`--census`** belongs in the patrol lane (`half-state-patrol.yml`) — scheduled and report-only, which is the only honest posture for a reading a third party can change between runs. Neither is installed. A follow-up card owning `.github/workflows/**` can install both; this branch cannot. ## Tests - `node scripts/check-issue-citations.mjs --self-test` — 56 cases, 6 batteries, per-battery floor plus a roster-size floor and a verdict handshake. Batteries: `grammar` (14), `causes` (12), `transport` (7), `scope-contract` (12), `diff-scope` (6), `live-corpus` (3). The `diff-scope` battery builds a throwaway git repository and asserts the green direction and the red direction on the same tree; `causes` provokes all four 404 arms including the two the live tree has no specimen for; `transport` asserts the two board strategies answer board-identically over one stubbed transport. - `node scripts/pm/dispatch-gates.mjs --commands` → 36 families, all run, **36 run / 0 NOT-MEASURED / 0 UNRUN** (`--ran` reconciled with exit codes). Four exited 3 (PREREQUISITE NOT MET, no `dist/`), were re-run after a full build under the shared verify lock, and all four then exited 0. - `pnpm lint` (full repo, `eslint . --no-inline-config`) — exit 0, no narrowing to declare. Run on `061ab1ac1`. - `pnpm build` under `scripts/pm/os-verify-lock.sh` — 73/73 tasks successful. ## changeset: none, measured No publishable package's `files[]` can ship `scripts/` (0 of 70 publishable manifests carry an entry that could), and the root manifest is `private: true`. Negative control: the new file's `CITATION_SURFACES` appears in 0 built `dist/` trees. Positive control, same instrument: `AGGREGATE_FIELD_TYPE_COMPATIBILITY` appears in 10. Nothing published moves, so `skip-changeset`. ## Acceptance notes Noted, not filed — out of scope here, and each names who would meet it: - **The `#N` citation debt is a 2,785-site class, not a five-instance one**, and this branch repairs none of it. The carriers of 16685 that are still live (a published release page and two `packages/spec` / `service-analytics` docblocks) are deliberately untouched: the disposition pattern the card records is to strike or annotate in place with the reason, never to guess a replacement number, and picking three sites out of 2,785 by hand is arbitrary. Successor: whoever the seat gives the wiring ruling to, since the census is the input to that decision. - **The `/issues` list endpoint refuses `page` beyond a depth** on this repository ("Pagination with the page parameter is not supported for large datasets") and GitHub's own `Link` header answers in the `repositories/{id}/...` form, which this session's proxy refuses. Both are handled here (`normalizeNextUrl`), and any future board enumerator in this tree hits the same two walls. Successor: none today — no other script in this tree enumerates the whole board. - **`--census` costs 159 requests and grows with the board.** Fine on a schedule, wrong per PR; that asymmetry is the reason the two lanes above are named separately. --- _Generated by [Claude Code](https://claude.ai/code/session_017ef78bLdybu3AffehKkhfk)_ --------- Co-authored-by: Claude <[email protected]>
akarma-synetal
pushed a commit
to akarma-synetal/framework
that referenced
this pull request
Sep 17, 2026
… records that decide them (objectstack-ai#18228) Part of objectstack-ai#14361 Clause-②: no Nothing this diff touches is an accept/reject surface: no schema, no validator, no guard predicate and no error code moves. Comments, doc comments, one operator-facing refusal sentence and four translation leaves move, and the only runtime string that changes is the ADR number inside a message whose condition, status and code are untouched. Segments 2 and 3 of the card. Segment 1 (`ADR-0071` → `ADR-0134`) landed as objectstack-ai#18098 and the card was returned to `pm:queue` for these two. ## The governing ruling The director's **amendment** (comment `5507573601`), not the ruling 14 minutes before it. The amendment changed both the target — a **local** ADR number for the open mechanism half, ⛔ not a `cloud ADR-NNNN` spelling — and the scope: `0071 + 0024 + 0081`, one trip. Its three `Blocked-by:` mirror ADRs were released by the H19 unlock double-check (`5594577301`), and all three records are on `main` today: `ADR-0133` (mirrors cloud 0081 D1/D3/D4), `ADR-0134` (mirrors cloud 0071), `ADR-0135` (mirrors cloud 0024's open half). ## The classification rule, written down before any edit `ADR-0024` names two unrelated decisions from this repository's point of view, and the split is not guessable from a path. Three objective criteria decided every line: 1. **The local record has no D-letters at all.** `docs/adr/0024-mcp-connectors.md` numbers its Decision section `§1`–`§5` and contains zero occurrences of `SSO`, `ai_seat` or `D5.2`. So any `ADR-0024 Dk` citation is, mechanically, not about the local record. 2. **`ADR-0135` publishes the mirror boundary itself.** Its Decision section restates cloud ADR-0024's D1, D2, D4, D5, D5.2, D6, D7 and D9, keeping the cloud record's lettering on purpose — *"The lettering is the cloud record's, kept so that a citation of `cloud ADR-0024 D5.2` and a citation of this record's D5.2 name the same clause."* Its "What stays in `cloud` ADR-0024" table names D3, D5.1, D8, D10 and V1 and states the rule: *"code in this repository that means one of them must keep citing `cloud ADR-0024`"*. 3. **The `②` spelling is identified by name.** ADR-0135 D6: *"Domain verification is opt-in — the clause this repository's code cites as `ADR-0024 ②`."* So `ADR-0024 ②` becomes `ADR-0135 D6`, which is letter-checkable where `②` was not. ## `ADR-0024` — the three-way split, which adds up Measured per **citation line** with the anchors gate's own regex (`ADR_CITATION` in `scripts/check-adr-anchors.mjs`), `pnpm-lock.yaml` excluded, on merge base `1bdbf82cb`. | Class | Lines | Disposition | |---|---:|---| | **Points correctly at the local MCP record** | 15 | ⛔ untouched — byte-identical to `main` | | **Points at a clause `ADR-0135` restates** | 73 | re-pointed to `ADR-0135` **with its letter** | | **Points at a clause that stays in cloud** | 6 | re-pointed to `cloud ADR-0024` | | **Published archive — 7 package CHANGELOGs** | 36 | ⛔ untouched (the ruling's fence) | | **Governed surface — `docs/adr/**`** | 22 | ⛔ untouched (Prime Directive objectstack-ai#14) | | **Undeterminable** | **0** | — | | **Total** | **152** | | The dispatch brief's figure was **151**, taken 2026-09-14. Re-measured today it is **152**; the one added line is inside `docs/adr/0135-*.md`, which this diff does not enter. Letters written, each cross-checked against the record's own decision index: `D4` ×4 (source-of-truth marking), `D5.2` ×27 (the break-glass invariant), `D6` ×40 (SSO per environment, including the `②` clause), `D9` ×2 (environment users and org membership) — 73. Cloud-qualified: `cloud ADR-0024 V1` ×1 (the SSO default-role provisioning — the very site ADR-0135's own consequence paragraph names as belonging to cloud) and `cloud ADR-0024 §7` ×5 (the `ai_seat` synthesis).⚠️ **`§7` is a judgement, stated plainly rather than buried.** It is in neither of ADR-0135's two lists — not restated, and not in the "stays in cloud" table. It is provably not the local record (which has no `§7` and no seat concept), and `ai_seat` appears **zero** times anywhere under `docs/adr/`. `cloud ADR-0024 §7` is the only spelling that is true; re-pointing it at `ADR-0135` would have put a pointer on a record that does not carry the clause. ⛔ No decision was invented — if the reviewer prefers a different disposition, these are 5 lines. ## `ADR-0081` — **nothing is owed, and that is the finding** 87 bare citation lines today (the brief said 86). Read per line, the split is not the `ADR-0071` shape at all: | Class | Lines | |---|---:| | Points correctly at the **local** record (`0081-trusted-react-page-tier.md` — the `kind:'react'` tier) | 22 | | Published archive — 8 package CHANGELOGs | 41 | | Governed surface — `docs/adr/**` (incl. 17 lines that deliberately **quote** the historical bare label) | 21 | | Identity/org meaning, inside a dated `history[]` revision entry in `docs/qa/platform-checklist/areas/identity-auth.json` | 1 | | Points at the wrong record in live code | **0** | | **Total** | **87** | **The identity re-pointing for `0081` already landed** — `d1c86a745`, *"docs(adr): qualify the pre-repo ADR-0081 citations as cloud ADR-0081, letter-checked per site"* (objectstack-ai#15612, 2026-09-07). Every live `ADR-0081 D1` / `D2` site across `platform-objects`, `plugin-auth`, `organizations`, `verify`, `plugin-security` and `publish-smoke.sh` already carries the `cloud` qualifier; bare `ADR-0081 D1` in those two package trees, excluding CHANGELOGs, measures **zero**, with `cloud ADR-0081 D1` reading 7 as the firing control. The one history-entry line is left alone deliberately: `docs/qa/platform-checklist/README.md` states that run records pin the revision they ran against and that the history exists to make old run records interpretable — the same archive class as a CHANGELOG. ## Measurement controls Every zero carries a firing control and a dark control, because a zero from a dead instrument looks identical to a real one. | Reading | Value | Firing control | Dark control | |---|---:|---|---| | bare `ADR-0024` | 152 lines / 53 files | `ADR-0069` → 431 lines, `ADR-0079` → 214 | `ADR-9024` → **0**; `git grep ADR-0024ZZZ` → **0 files** | | bare `ADR-0081` | 87 lines / 41 files | same | `ADR-9081` → **0**; `git grep ADR-0081ZZZ` → **0 files** | | bare `ADR-0081 D1` in `platform-objects` + `plugin-auth`, non-CHANGELOG | **0** | `cloud ADR-0081 D1` → 7 | `cloud ADR-0081ZZZ D1` → 0 | ## Gate strength provably did not fall — it rose `check:adr-anchors`, run on the merge base and on this branch, and every delta accounted: ```text merge base 1bdbf82 : 133 decision number(s); 35559 citation(s) across 4538 file(s) resolve; 1022 decision-letter citation(s) this branch 5e2bfda : 133 decision number(s); 35572 citation(s) across 4539 file(s) resolve; 1036 decision-letter citation(s) ``` - **+13 citations / +1 file** = exactly the changeset file this PR adds (7 `ADR-0024` + 6 `ADR-0135` mentions in its prose). Nothing else in the corpus moved. - **+14 decision-letter citations** = the new `ADR-0135 Dk` in the two *anchored* files (`auth-manager.ts` 10, `auth-plugin.ts` 4). Those 14 are now letter-checked against ADR-0135's decision index, where the bare `ADR-0024 Dk` they replaced was checked against nothing. - Bare `ADR-0024` still resolves exactly as before: the 15 MCP-meaning lines are byte-identical to `main`. ## Ablations — three legs, restores settled by blob hash, never by an exit code Each mutation is proved on disk by a before/after `grep -c` on the exact text, **read before any result is read**. Restores are settled by `git hash-object` equality with the `HEAD` blob plus an empty `git diff HEAD`; the `trap` was kept only as a crash-path convenience. No permanent test file was left by any leg. These gates read tracked source text, not `dist/`, so no rebuild sits between mutation and reading. | Leg | Mutation | Expected | Observed | |---|---|---|---| | **A1** | `ADR-0135 D5.2` → `ADR-0135 D3` in `auth-manager.ts` | red | **exit 0 — did NOT fire.** See the finding below | | **A2** | `ADR-0135 D5.2` → `ADR-0135 D99` in `auth-manager.ts` | red | exit 1 — `ADR-0135 D99 is cited by 1 file(s), but ADR-0135 declares no D99` | | **B** | `ADR-0024 §2` → `ADR-9024 §2` in `mcp-connector.ts` (an untouched local-MCP line) | red | exit 1 — `ADR-9024 is cited by 1 file(s) but names no record` | A2 is the leg that matters: the letter check **does** fire on the citations this PR writes, so the 73 re-pointed letters are validated rather than decorative. B shows the resolution check still reads the 15 lines this PR deliberately left alone. ###⚠️ Finding surfaced by leg A1 — filed, ⛔ not fixed here `decisionIndexFor()` in `scripts/check-adr-anchors.mjs` harvests a decision letter from **any** markdown table cell (`DECISION_TABLE_CELL`). ADR-0135's *"What stays in `cloud` ADR-0024"* table has rows `| D3 |`, `| D5.1 |`, `| D8 |`, `| D10 |` — the table whose whole purpose is to say those clauses are **not** recorded here. They land in ADR-0135's decision index anyway, so `ADR-0135 D3` passes the letter check even though a reader following it lands on a row that explicitly disclaims the clause. That is the same "resolves, but not to what the citation says" family this card is about, one layer down. It is ⛔ not fixed here: the fix has to distinguish a table of decisions a record MAKES from a table of clauses it DISCLAIMS, which is a design call and not a mechanical edit. `ADR-0133` has the same shape for cloud 0081 D2. This PR is unaffected — every letter it writes (`D4`, `D5.2`, `D6`, `D9`) is a real `### Dk` heading. ## Reverse read — which existing sentence does this change make false? ⛔ Not zero, and all of it lands on `docs/adr/**`, which this lane may not edit (Prime Directive objectstack-ai#14). Filed, not fixed: 1. `docs/adr/0135-*.md:196` — *"the clause this repository's code cites as `ADR-0024 ②`"* is present tense and is **now false**: no code cites `ADR-0024 ②` any more. This PR is what falsifies it. 2. `docs/adr/0135-*.md` consequence paragraph — *"`auth-manager.ts` cites `ADR-0024 V1`"* now reads stale: that site says `cloud ADR-0024 V1`. Its neighbouring *"Until then the citation still resolves to `docs/adr/0024-mcp-connectors.md`"* has had its "until then" end. 3. **The reverse direction** — `docs/adr/0133-*.md:40` — *"The code still carries the pre-repo labels. Several files in `packages/platform-objects` and `packages/plugins/plugin-auth` cite `ADR-0081 D1` in comments today"* was **already false on `main`** before this diff, falsified by objectstack-ai#15612 on 2026-09-07. Independent of this change. Statements with a rev or a date attached were checked and are **not** stale: ADR-0135's "measured 64 citing lines on the commit that introduced it" is anchored to that commit and stays true. ## Changeset — measured, not assumed The only criterion is whether published bytes move; `files[]` ships `dist` in all five. Measured against the built tree with a positive control: | Package | What moves | User-visible? | |---|---|---| | `@objectstack/plugin-auth` | the operator-facing break-glass refusal `detail`, and the guard's registration log | **yes** —⚠️ a deployment grepping that message for `ADR-0024` should grep `ADR-0135` | | `@objectstack/platform-objects` | `sys_sso_provider` field help + `protection.reason`, and the matching leaf in all four shipped locales | **yes** | | `@objectstack/spec` | the `ssoDomainVerification` doc comment, shipped in `dist/` and as `src/**/*.zod.ts` | no behaviour | | `@objectstack/core`, `@objectstack/cli` | doc comments in `dist/` only | no behaviour | So `skip-changeset` is **wrong** here and the label is not applied; a patch changeset naming all five is included. ## The four locale files are not hand-edited generated output `packages/platform-objects/src/apps/translations/*.objects.generated.ts` carry the `ADR-0024 ②` citation in a translated `help` leaf. Their own header is the rule: *"Edit translations in place… Merge only fills gaps: correcting a source label/description does not push the correction into a leaf here that already holds a translation… Re-translate it by hand when its source changes."* Re-running the extractor would have been a **no-op** on these four leaves. The producer (`sys-sso-provider.object.ts`) was corrected as well, and `check:i18n-coverage` reports `13 config(s), 621 baselined untranslated string(s), none new`. No generated reference doc carries `ADR-0024` (`grep` over `content/docs/references/` reads **0**), so nothing under `content/docs/references/` needed regenerating — the spec site changed is a `/** */` doc comment, not a `.describe()` string. ## Verification - **Gates: 117 of 117 derived families accounted, 0 NOT-MEASURED, 0 UNRUN.** `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --ran RECORDFILE` at `5e2bfda81`, every exit code captured before any pipe and recorded as `COMMAND :: exit CODE` (the literal word `exit`, then the numeric code). Three families first returned `exit 3` / `exit 1` **PREREQUISITE NOT MET** because sibling packages had no `dist` (`check:skill-examples`, `check:dual-build-cjs-loads`, `check:i18n-coverage`); after a full `pnpm build` all three were re-run **whole** and are green. ⛔ None of the three is counted as a finding or as a green measurement in its refused state. - **Also run, outside the derivation:** `check:adr-anchors`, `check:adr-links`, `check:adr-symbol-anchors` — all green. - **Tests:** `spec` 477 files / 13611 tests · `core` 51 / 1316 · `platform-objects` 40 / 575 · `plugin-security` 113 / 2181 · `plugin-auth` 111 / 2360 — **792 files, 20043 tests, 0 failures** (`VERDICT command-exit 0`, 464s under the shared verify lock). `@objectstack/cli` `--project unit`: 207 files / 2971 tests, 0 failures. The `integration` tier is declared to CI — this diff touches no spawn entry, only a comment in `serve.ts`. - **Typecheck:** `spec`, `core`, `platform-objects`, `plugin-auth`, `plugin-security`, `cli` — `VERDICT command-exit 0`, 0 `error TS`.⚠️ An earlier run of the same command reported `TS7016` against `packages/spec/dist/**`; that run raced the concurrent gate sweep over `dist/` and is reported as the artefact it was, not as a finding — the clean re-run after `pnpm build` is the reading. - **Lint: not narrowed.** The full union `eslint . --no-inline-config --format json` at `5e2bfda81`: **6756 files eslint itself selected, 0 errors, 0 warnings**. `eslint.config.mjs` carries no `parserOptions.project` and no typed rules, so no edit here could move a verdict on an untouched file. - **Build:** full `pnpm build` green before the final measurements. ## Why this is `Part of` and not `Fixes` Segment 2 is done. Segment 3 is measured to owe **zero edits**, but that is a reading a human should ratify rather than a seat closing the card on its own — and the residue the card still names is the three `docs/adr/**` sentences above, a governed surface this lane ⛔ may not touch. Closing is the PM's call. ⛔ Draft on purpose. ⛔ No auto-merge armed, not flipped out of draft, not queued. --- _Generated by [Claude Code](https://claude.ai/code/session_017ef78bLdybu3AffehKkhfk)_ --------- Co-authored-by: Claude <[email protected]>
akarma-synetal
pushed a commit
to akarma-synetal/framework
that referenced
this pull request
Sep 17, 2026
…bucket into the REST channel register (objectstack-ai#18259) Fixes objectstack-ai#18130 Measurement-first register work on `.claude/skills/pm-dispatch/references/rest-channel.md`. The card's readings 2 and 3 (`ccr/ready_for_review`, `PUT ccr/auto_merge`) were already on :48 / :51 and are not re-recorded. What this branch owes is one probe per UNEXERCISED CCR route named by the session proxy's own 403 body, plus the shared per-user MCP bucket fact. Every route below earned its row by being invoked once; nothing here is written from the card's text. Session: `session_01HZfg2AwVX191qCizp88gQr` (skills seat, dev subagent). All probes ran on THIS draft PR, 2026-09-15T04:48Z–04:49Z. No probe touched another seat's PR. ## Probe log | # | Route | Method / body sent | Status | Body stored / returned | Read-back | |---|---|---|---|---|---| | P1 | `https://api.github.com/graphql` | POST, `markPullRequestReadyForReview` mutation | **403** | proxy's own body, 629 B, naming the five CCR routes verbatim | n/a — refused before GitHub sees it | | P2a | `POST /repos/{o}/{r}/pulls/18259/comments` | `commit_id` + `path` + `line: 49` + `side: RIGHT` | **201** | review comment `id: 4012020890`, `user.login: claude[bot]` | thread appears in `ccr/review_threads` | | P2b | `GET .../pulls/18259/ccr/review_threads` | GET | **200** | `[{"resolved":false,"outdated":false,"path":"…rest-channel.md","line":49,"comment_ids":[4012020890]}]` | — | | P2c | `POST .../ccr/comments/4012020890/resolve` | POST `{}` | **200** | `{"comment_ids":[4012020890],"resolved":true}` | `review_threads` → `"resolved":true` | | P2d | `POST .../ccr/comments/4012020890/unresolve` | POST `{}` | **200** | `{"comment_ids":[4012020890],"resolved":false}` | `review_threads` → `"resolved":false` | | P2e | `POST .../ccr/comments/4012020890/resolve` | POST `{}` | **200** | `{"comment_ids":[4012020890],"resolved":true}` | `review_threads` → `"resolved":true` (final state) | | P3a | `PUT .../pulls/18259/ccr/auto_merge` | PUT `{"merge_method":"SQUASH"}`, PR in DRAFT | **422** | `{"message":"Pull request Pull request is a draft"}` | `GET /pulls/18259` → `auto_merge: null` (nothing stored) | | P3b | `DELETE .../pulls/18259/ccr/auto_merge` | DELETE, nothing armed | **422** | `{"message":"Can't disable auto-merge for this pull request."}` | `GET /pulls/18259` → `auto_merge: null` | | P4a | `POST .../pulls/18259/ccr/ready_for_review` | POST `{}` | **200** | `{"draft":false}` | `GET /pulls/18259` → `draft: false`; timeline `ready_for_review`, actor `claude[bot]` | | P4b | `POST .../pulls/18259/ccr/convert_to_draft` | POST `{}` | **200** | `{"draft":true}` | `GET /pulls/18259` → `draft: true`; timeline `convert_to_draft`, actor `claude[bot]` | P1's route list, quoted from the 403 body itself rather than from the card: `GET /repos/{owner}/{repo}/pulls/{n}/ccr/review_threads`, `POST /repos/{owner}/{repo}/pulls/{n}/ccr/comments/{comment_id}/resolve` (or `/unresolve`), `PUT` or `DELETE /repos/{owner}/{repo}/pulls/{n}/ccr/auto_merge`, `POST /repos/{owner}/{repo}/pulls/{n}/ccr/ready_for_review`, `POST /repos/{owner}/{repo}/pulls/{n}/ccr/convert_to_draft`. It matches the card's quotation word for word. ### What the probes settled, against the dispatch's assumptions - **P2 (which id does `resolve` want?)** — the review-COMMENT id. `ccr/review_threads` returns no thread id at all: each element carries `resolved`, `outdated`, `path`, `line` and `comment_ids`, and `comment_ids[0]` is exactly what the resolve route accepts. The register row names which. - **P3 (auto-merge on a draft)** — the PUT is REFUSED, and the refusal is the reading: 422, nothing stored, `auto_merge` still `null` on read-back. The DELETE then also answers 422 ("Can't disable auto-merge for this pull request") because nothing was armed — so a 422 from DELETE is not evidence of a failed disarm, and the read-back is what decides. - **`DELETE` on an ARMED PR stays unmeasured here, deliberately.** Arming auto-merge would require a non-draft PR, and this PR's diff touches a governed surface, where Prime Directive objectstack-ai#14 forbids arming outright. Recorded as not measured rather than inferred. - **`convert_to_draft`** was already written as a fact on :48 under a tick it had never earned; the probe makes the tick true. No new row is owed for it, so none is added. ## Rows landed — five, and what paid for each The file is at its 82-line ceiling with no standing raise exception, so the currency is deleted content, never a re-wrap. Line count 82 → 82; bytes 6,738 → 6,965. | Row added (bytes) | Paid by | |---|---| | `- 两只桶:MCP 记链接用户 5000/时…` (102 B) | tick-convention row + the no-tick prohibition, 108+56 B over 2 lines → 114 B over 1: `不是全局事实` restated `按席位类别限定`, and `的形状` / `复述` / `一个` went with it | | `- ✓ 线程 GET …/ccr/review_threads…` (115 B) | check-runs/actions row + quota-read row, 88+61 → 120 over 1: `端点自身` and `自读` deleted | | `- 线程自己建:POST …/pulls/{n}/comments…` (109 B) | update-branch row + its rationale tail, 91+81 → 114 over 1: the `它是…手段` framing deleted, and `不重写历史` is entailed by `真合并提交` | | `- ✓ POST …/ccr/comments/{id}/resolve…` (114 B) | the two `expected_head_sha` rows, 70+85 → 112 over 1: the pinned error prose `no new commits on the base branch` deleted — nothing parses it, and the standing rule is not to pin error copy | | `- ⛔ PUT …/ccr/auto_merge 在 draft 上 422…` (112 B) | bare-`PATCH`-draft row + the read-back rule, 97+113 → 115 over 1: `timeline 的 ready_for_review` restated the read-side timeline row | A sixth deletion came first, in its own commit: the two provenance dates left on the bare-`PATCH` row and the MCP-fallback row (19 B and 33 B). They are the class the file's own rules-only lowering removed; deleting them is what let the rows above fit. No rule was dropped. Every merged row keeps both of its rules; what left the file is restatement, framing, an entailment and one pinned error string. ## Gates `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack`, no paths, derived 15 families on head `95065f36`; all 15 run in the foreground with `$?` captured by redirect before any pipe; reconciled with `--ran`: ``` Run reconciliation — 15 derived, 15 run, 0 NOT-MEASURED, 0 UNRUN. ✓ dispatch-gates --ran: 15 derived famil(ies) accounted for — 15 run, 0 NOT-MEASURED (a DERIVED zero — all 15 recorded an exit code and none of them is 3). ``` One family first answered `exit 3` (PREREQUISITE NOT MET — `@objectstack/formula` and `@objectstack/lint` not built), which is not a red gate; after `turbo run build --filter=@objectstack/formula --filter=@objectstack/lint` under the shared verify lock it exits 0. `check:pm-dispatch-gates` is not in this card's derivation. Reverse verification on the head: - `wc -l` → **82**, equal to the ceiling. - `awk 'length($0)>120'` → prints nothing, for touched and untouched lines alike. - `pnpm check:pm-skill-ratchet` → 0 · `pnpm check:pm-skill-id-lint` → 0 (27 files clean, pattern `/#[0-9]{3,}/g`) · `pnpm check:pm-governed-prose` → 0. `pnpm lint` is CI-owned and was narrowed, with the narrowing proved rather than assumed: every `files:` selector in `eslint.config.mjs` is a JS/TS extension glob and none names `.md`; `eslint --no-inline-config --format json` over the one changed file reports `File ignored because no matching configuration was supplied`, 0 errors and 0 warnings from rules; and since the diff is a single markdown file outside that population, no untouched file's verdict can move. `skip-changeset`: `.claude/**` is on the fast track — nothing any package's `files[]` ships moves here. ## 维护者速读(草稿) **改了什么** —— PM 席位的 REST 通道对照表新增五条读数:评审线程的三条 CCR 路由(取线程、 解决、取消解决)、auto-merge 在 draft 上的两个拒绝,以及 MCP 与 REST 分属两只限流桶这一事实。 **为什么改** —— 会话代理自己的 403 报文点名了这批路由,但表里此前只有其中两条被实调过。 未实调的路由写进表里就是「未带 ✓ 的形状当已验证事实」,而这正是该表第一节明令禁止的。 这一轮每条路由都在本 PR 自己的 draft 上跑过一次才落行。两只桶那条解决的是另一件事: 席位的 MCP 状态动作会被同一 GitHub 用户下别的会话的读耗光,而 CCR 路由走的是另一只桶, 所以「换通道」在这里是合法退路,不是违规续写。 **风险与代价(含回滚)** —— 只改一个 markdown 参考表,不改任何运行时代码,发布面零变化。 文件行数仍是 82(天花板),字节数 +227。回滚 = revert 本 PR,无迁移、无残留状态。 探针在本 PR 上留下一条评审评论线程(已 resolved)与两次 draft 翻转,PR 终态是 draft、 auto-merge 未挂载。 **席位意见** —— **你要做的** —— 无需维护者动作:本 PR 的受管路径全部落在 `references/**`,按分层裁定 走席位 contract-tier 复审后进队列。 ## Acceptance notes - noted, not filed: `added_to_merge_queue` now appears three times in this one file (the read-side timeline row, the write-side queue-read row, and queue criterion ②), and the write-side row still spells `git rev-list --parents` although :66 routes that spelling to `platform-readings.md` — against the file's own `⛔ 不在两处各存一份`. Consolidating it would free two more lines. Carrier: the next PR that pays density on this file — the rules-layer change already queued behind this one touches :53 in the same block. - noted, not filed: `DELETE .../ccr/auto_merge` on an ARMED pull request has no reading in this repo yet, and cannot get one from a governed-surface PR. It needs a non-governed card that legitimately arms auto-merge and disarms it again. Carrier: none today. - The probe thread on this PR is left RESOLVED. The PR ends draft, `auto_merge: null`. --- _Generated by [Claude Code](https://claude.ai/code)_ --------- Co-authored-by: Claude <[email protected]>
akarma-synetal
pushed a commit
to akarma-synetal/framework
that referenced
this pull request
Sep 17, 2026
…e refused spelling (objectstack-ai#18270) Fixes objectstack-ai#18141 `references/contract-review.md` :28 defined the review of record's shape as 「所审 head sha 码段」 and never said the span holds the sha ALONE. A seat reading it in good faith writes the key and the sha into ONE span; `H51_SHA_SPAN` matches a code span that is hex and nothing else, so such a record names no head and C6 reads 「no review of record on that head」 where a complete review exists. Taken by the file's own 「先删容许出错的构造,再让正确形态成唯一拼写」 order: the prose names the one correct spelling, and the reader keeps refusing the other while NAMING it. The accept set is unchanged — a second accepted spelling would be the trap's twin. ## What changed - `.claude/skills/pm-dispatch/references/contract-review.md` :28 — 「所审 head sha 码段」 becomes 「所审 head sha 独占码段」, and the line sheds 「判词」 to pay for it. The file is 60 lines at its 60-line ceiling before and after; the touched line is 118 bytes (119 before). `check:pm-skill-ratchet`: "contract-review.md is 60 lines (ceiling 60; headroom 0)". - `scripts/pm/check-clause2-carriers.mjs` — - `HEAD_KEY_IN_SPAN` + `headSpanHoldsKey(pair)`: a DIAGNOSIS, read only AFTER the locator has already answered `absent`. It chooses no comment, admits none, and returns nothing for a comment the locator can already read. The newest-of idiom is `latestMarkedComment`, the same one the locator resolves with — never a second one. - C6's absent row is now two sentences: the empty case keeps 「a cleared gate with nothing behind it」, and a pair whose heading comment wrote the head inside the key's span gets a row naming the comment, quoting the span the seat actually wrote, and prescribing the fix (key outside the span, sha in a span of its OWN, `--template` prints the whole record). - The shared shape sentence now names the one spelling on both branches. - The docblock quotation of the rule line was updated in the same edit, so the file does not quote a sentence this PR replaced. - 19 self-test cases in their own battery (658 before, 677 after; roster floor 23, now 24). ## The measurements this PR was dispatched to take **P1 — falsified in its live half; the mechanism half stands.** The dispatch expected C6 to read 「no review of record」 for comment 5652813288 (PR objectstack-ai#17986) today. Measured on the exact bytes (GET, not edited) against this tree: ``` contractReviewHeadMatch(body, head) = "db55ea6dd" hex-only spans in the body: 884e834, fc28c1d, ..., db55ea6, 8cdd696, 53ded82bf7a494f54e344e19099dbf00854b8694 spans that prefix the head: db55ea6 key-in-span line ALONE: null locateReviewOfRecord: { state: "found", id: 5652813288, sha: "db55ea6dd" } ``` Its `Head-sha:` span names nothing — the mechanism the card describes is real — but the record is still FOUND, because its own prose quotes the head in a bare span of its own ("Cross-file staleness, searched at `db55ea6dd`"). So the sha C6 reports for that record is read off the prose and not off the line the seat wrote it on. Replayed end to end through the CLI (`--pair 17986 --pair-json`, verbatim bytes): no C6 row, the C6-RECORD note naming `db55ea6dd`; exit 4 comes from C4 (`Implemented-by: branch …`, half written) and C7 (no `Served-tier:` line), both facts about that record that predate this PR. The defect is the SPELLING, not that comment — the same record trimmed to the spelling alone reads the refusal (below). **P2 — holds.** `contractReviewRecordLines` prints `Head-sha: ` followed by the sha in a span of its own, and the `--template` note already says "7 to 40 hex in a span of ITS OWN; a span holding the key as well is not a sha". The prose now agrees with it, and the self-test derives its refused fixture by COLLAPSING the template's own line rather than retyping the key — a template that renamed the key reds this battery instead of drifting past it. **P3 — the distinction was absent; it is the sentence that was added.** Before this PR both cases printed 「a cleared gate with nothing behind it, indistinguishable from never reviewing」. Pinned three ways now: a bare-sha span record reads `found`; a key-in-span record is `absent` AND earns the refusal naming the spelling; a comment with no heading is the plain absence with no spelling sentence. **P4 — holds, pinned.** `locateReviewOfRecord` chooses the same comment it chose before: a refused spelling is never chosen over a correct record and never chosen at all (pinned in both arrival orders, and the pair with a correct record earns no C6 row and no spelling sentence). ## Reverse verification - Ablation (fix committed first, mutation proved on disk, restored byte-identical): replacing `const keyed = headSpanHoldsKey(pair);` with `const keyed = null;` turns **5 of 677** self-test cases red (`ABLATED EXIT=1`). Mutated blob `a3d4174b` vs HEAD blob `af1a124a`; after restore the blob is `af1a124a` again and `git diff HEAD` is empty. An earlier run of the same ablation moved only 3 cases — two pins were reading a comment count and a thread name, which survive the ablation; both were retied to the sentence the branch composes and are in the 5. - Offline replay of the refused spelling (`--pair 17986 --pair-json`, the record trimmed to comment 5652813288's spelling): exit 4, one row, C6, reading 「⚠️ The SPELLING is why, and this pair is NOT the empty case: the PR thread's comment 5652813288 … writes this head INSIDE one code span, as `Head-sha: db55ea6`」 with the remedy naming the span of its own and `--template`. - Live control, a pair carrying a correct record: `--pair 18256` still exits 0 with the C6-RECORD note on comment 5674761187 (head `c96b507db288c20bf270c66c6137dc6fa7e79576`). ## Gates Derived with `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack`, no paths, reconciled with `--ran`: ``` Run reconciliation — 42 derived, 42 run, 0 NOT-MEASURED, 0 UNRUN. EXIT CODES — all 42 accounted famil(ies) carry one, so the NOT-MEASURED count above is DERIVED from them. ``` `pnpm --filter @objectstack/lint run check:doc-formula-expressions` first exited **3** (PREREQUISITE NOT MET — `@objectstack/formula` and `@objectstack/lint` unbuilt). Built under the shared verify lock and re-run: exit 0. `pnpm check:pm-dispatch-gates` ran to completion (exit 0), not cap-killed. `pnpm check:pm-clause2-carriers`: 677 cases pass. Lint, as a proved narrowing rather than a repo sweep: `pnpm lint` is `eslint . --no-inline-config`; of this diff's two paths only `scripts/pm/check-clause2-carriers.mjs` is inside eslint's own population — the `.md` comes back "File ignored because no matching configuration was supplied". `--format json` returns 2 entries, 1 linted, 0 errors, 0 warnings. `eslint.config.mjs` states of itself that it "never enables type-aware linting (no `parserOptions.project`, no typed `@typescript-eslint` rules) for ANY file", so this diff cannot move the verdict of a file it does not contain. No package typecheck is owed: the diff is one `.mjs` under `scripts/` and one `.md`. Union head: the readings above were taken at `c3533346`. ## Deviation from the dispatch The dispatch said the clause is "paid by density" at 60/60. `.claude/agents/os-dev.md` states that the only legal currency for the line ratchet is DELETED CONTENT and that a re-wrap must never buy a line for new content, so folding two clauses into one line to free a 61st was not available, and no clause in this file is redundant enough to delete. The clause therefore lands INSIDE :28: the file never grows, the ceiling row is untouched, and the payment is 「判词」, whose fact is carried by the `PASS/FAIL` token it stood behind. Flagged here rather than chosen silently. ## Acceptance notes - noted, not filed: `contractReviewHeadMatch` scans the WHOLE comment, so the head it reports can come from a span in the prose rather than from the record's own `Head-sha:` line — which is how comment 5652813288 reads `found` today despite the refused spelling. H51's declared shape is "the head sha written as a code span somewhere in the comment", so this is declared behaviour, not a contract violation; it does mean the trap is survivable for some records and not others. Successor: whoever next touches H51's recognition shape. - noted, not filed: that same record carries `Implemented-by: branch claude/…` (C4 half-written) and no `Served-tier:` line (C7). Both are already recorded on the card; a merged record is not edited. ## 维护者速读(草稿) **改了什么** —— 契约复核记录的 head sha 从此必须单独占一个码段:`contract-review.md` :28 的措辞改成 「所审 head sha 独占码段」,并由同一行删去「判词」买单(文件仍是 60 行,不动天花板)。机读一侧, `check-clause2-carriers.mjs` 在判定「本 head 无复核记录」之后,额外说出**为什么**:如果有人把 `Head-sha:` 和 sha 写进同一个码段,这条 C6 行会点名那条评论、引用他写的码段,并给出一次就能改对的修法。 **为什么改** —— 旧措辞只说「head sha 码段」,照字面写就会落进读不出的拼写:一份完整的复核记录,机器读 起来和「根本没人复核」完全一样。先让正确形态成为唯一拼写,再让拒收带上理由。 **风险与代价(含回滚)** —— 受理集合没有变宽:被拒的拼写仍然被拒,定位器选哪条评论一字未动(两个到达 顺序都已钉住)。新增的只是一句诊断文案与 19 条自测。回滚 = revert 本 PR,无数据、无产物、无发布面。 **席位意见** —— **你要做的** —— 这是 `references/` 层受管面,按 Prime Directive objectstack-ai#14 走席内契约档复核 → ready → 入队, 不需要维护者逐条拍板;若对「删掉『判词』来买行」这笔密度支付有异议,请在此处说一句,我按你的说法改。 --- _Generated by [Claude Code](https://claude.ai/code/session_01HZfg2AwVX191qCizp88gQr)_ --------- Co-authored-by: Claude <[email protected]>
akarma-synetal
pushed a commit
to akarma-synetal/framework
that referenced
this pull request
Sep 17, 2026
…t-stating surface is now a census (objectstack-ai#18244) Fixes objectstack-ai#17798 `AGENTS.md`'s merge-queue paragraph named SIX required contexts and called everything else "advisory and rides through". The live ruleset has SEVEN — `Governed Surface Queue Guard`, enrolled 2026-08-27 (objectstack-ai#12427) and pinned in `REQUIRED_CONTEXTS` by objectstack-ai#15233. The sentence a review seat acts on therefore classified the one gate that refuses a zero-review governed PR as advisory, which is verbatim the incident shape that guard exists to prevent. Nothing reddened on it, and that is the half worth fixing. `mustName` is a FLOOR: it reds when a listed literal goes stale and is blind to one going MISSING. So the registry grew, every surface's list stayed legal, every gate stayed green, and the sentence stayed behind. The same misclassification has now landed twice — 2 to 6 by the objectstack-ai#9677 ruling, 6 to 7 here. ## 维护者速读(草稿) **改了什么。** 两处。一、`AGENTS.md` 的合并队列段落从「六个必需上下文」改成七个,把第七个 `Governed Surface Queue Guard` 写进名单(等行数改写,1075 行没动)。二、`scripts/check-required-contexts.mjs` 的 `INSTRUCTION_SURFACES` 增加一个 `statesTheSet` 声明:声明了它的文件,其名单必须与注册表**逐个且等长**地对上。注册表本身(`REQUIRED_CONTEXTS`)一行未动,那是 objectstack-ai#15233 的面。 **为什么改。** 这句话不是描述,是审核席翻 ready / 挂 auto-merge / 入队前照着做的操作指令。它把治理面守卫说成 advisory,而那个守卫的职责恰恰是拒掉零审查的治理 PR —— objectstack-ai#12427 的事故形态。更关键的是:上一次入列(objectstack-ai#15233 加第七行)时,全部门禁是绿的,没有任何东西提示这句话已经过期。同一个漏洞已经发生两次,所以这次不只是手跟一遍数字,而是把「谁陈述了整个集合」变成机器可查的:下一次(第八个)入列时,加注册行的那个 PR 自己会变红。 **风险与代价(含回滚)。** 风险低。新规则只对**显式声明** `statesTheSet: true` 的条目生效,今天是两个文件(`AGENTS.md` 与 pm-dispatch 的 `platform-readings.md`);`review-checklist.md` 被明确归类为**不陈述集合**(它点名的是审核席亲手确认的两个 job,不是集合),保留它原有的两名下限,集合变大不会误伤它。声明也不能被悄悄摘掉换取豁免:名单已覆盖整个集合却没声明的条目同样变红。代价:每次入列多一处必须同 PR 跟进的数组。回滚 = `git revert`,两个文件都是纯文本,没有生成物、没有发布面、没有数据迁移。 **席位意见。** **你要做的。** 确认一件事:第七个上下文 `Governed Surface Queue Guard` 今天确实在 Settings 的必需集合里(卡面 2026-09-12 的实测读数是七个,本 PR 不改 Settings)。其余不需要你操作。本 PR 触及受管面 `AGENTS.md`,按 Prime Directive objectstack-ai#14 走人工合并或已批准的队列路径。 ## What changed **1. `AGENTS.md` :505-:510 — six to seven.** The seventh name inserted, "six" to "seven" in all three places, equal-line at the 1075 ceiling (before 1075 / after 1075 / ceiling 1075). Each context literal is kept whole on one line: the scan matches them contiguously, and a wrap that split `TypeScript Type Check` across a line break made the surface red. That is a real trap for the next hand-follow, so it is recorded here rather than only avoided. **2. `INSTRUCTION_SURFACES` gains `statesTheSet`.** An entry that declares it must name the registry EXACTLY — membership *and* count: - omitting a member reds, naming the omitted literal and the count it is short by; - padding past the registry length reds on the count, so a duplicate cannot mask a member lost to a typo; - a full list with the declaration dropped reds ("a list that covers the whole set IS a statement of it"), so the exemption cannot be taken silently; - ablating every declaration reds rather than ticking (objectstack-ai#4690). `review-checklist.md` is classified the other way and keeps its two-name floor: it names the two required jobs a seat confirms by hand — its own next line sends the seat to `true-green.md` for the rest — so it never claimed to enumerate the set, and the set growing must not red it. **3. Ten self-test cases, battery floor 31 to 41.** The 6-of-7 omission and its restore-leg ablation; the eighth-row enrolment end to end, plus the hand-off where following the ARRAYS clears the census red and leaves the naming floor demanding the PROSE; the padded duplicate; the undeclared full list; the no-declaration floor; and the two classification pins (which surfaces state the set, by NAME never by count; and the checklist's partial list staying legal). ## Evidence Baseline first, on `origin/main` `b3b43b6` in a clean worktree, BEFORE any edit — the known pit from hold note 5651882793 (PR objectstack-ai#17803 reported `--self-test` red on a pre-existing `branches: [main]` filter on `governed-surface-guard.yml`): ``` node scripts/check-required-contexts.mjs --self-test exit 0 159 assertions node scripts/check-required-contexts.mjs exit 0 7 required context name(s) pinned across 3 workflow(s) ``` **The known pit is NOT red on `main` today.** The residual named in the hold note is gone; the work below is measured against a green baseline, not against a standing red. After (`78959ab`): `--self-test` exit 0, 169 assertions; the pin exit 0. **Reverse verification, both legs from the committed implementation, each with its on-disk mutation proved and each restored byte-identical (`git hash-object` vs the HEAD blob, `git diff HEAD` empty):** | leg | mutation (proved on disk) | result | |---|---|---| | A — the registry array rots back | drop `'Governed Surface Queue Guard'` from the `AGENTS.md` entry's `mustName` (grep 2 to 1; `git diff --numstat` 0/1) | **RED**, exit 1: "declares statesTheSet: true, so its mustName must be the required set EXACTLY — it lists 6 name(s) against a registry of 7, missing 'Governed Surface Queue Guard'". Self-test exit 1 too. | | B — the numeral alone rots back | `seven contexts block` to `six contexts block` in `AGENTS.md`, all seven literals still listed (numstat 1/1) | **GREEN, exit 0** — reported as measured, not as expected. The scan pins literals, never the word introducing them. Recorded as a residual in the script header rather than implied covered. | | B2 — the prose drops the literal | delete `` and `Governed Surface Queue Guard` `` from the paragraph (grep 1 to 0; numstat 1/1) | **RED**, exit 1: "AGENTS.md no longer names the required context 'Governed Surface Queue Guard'" | | floor control | battery floor 41 to 42 | **RED**, exit 1, naming the exact count: "registered 41 case(s), below its pinned floor of 42" — so the floor binds at headroom 0 and 41 is the measured count, not a number below it | Leg B is the honest finding of this PR: the census forces every enrolled literal INTO the prose, so a stale numeral now sits next to a complete list rather than a short one. Bounded, not covered; pinning the numeral needs the arbitrary-literal recognition the script header already measured as out of reach. **Derived gate union**, run after the final commit, on `78959ab` (`git rev-parse --short HEAD`), each exit captured by redirect before any pipe: ``` node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack -> 37 command(s) from a 2-path change set (AGENTS.md, scripts/check-required-contexts.mjs) ``` 36 of 37 exit 0. The one not measured: `pnpm check:pm-dispatch-gates` (`scripts/pm/check-dispatch-gates.mjs`) runs past this session's foreground ceiling — it was still printing passing cases at 560s. NOT MEASURED, reason: runtime exceeds the foreground cap; CI owns it. Its subject matter — the `ROOT_FILE_WATCH_HINTS` declaration this file carries — is separately green under `pnpm check:watch-hint-literal` (exit 0) and under this script's own `the dispatch-gates declaration (objectstack-ai#9979)` battery (6 assertions). `--ran` reconciliation is reported in the dev report; the beyond-derivation families this card owes because it edits a gate script — `check:required-contexts` and its `--self-test` — are the two green readings above. `git grep` finds no `*.test.*` naming `check-required-contexts.mjs`, so that script has no separate test suite to owe. **Lint, narrowed with the three readings that make a narrowing a measurement:** 1. covered population read from eslint's own config, not guessed: `npx eslint --print-config scripts/check-required-contexts.mjs` reports exactly **2 rules enabled** for this path (`no-restricted-imports`, `comment-swallow/no-code-inside-block-comment`); `eslint.config.mjs` declares no markdown population at all, so `AGENTS.md` is outside the lint verdict in either direction; 2. file count read from `--format json`: 1 file, 0 errors, 0 warnings; 3. invariance for untouched files: this repo runs one `eslint.config.mjs` which **never enables type-aware linting for any file** (no `parserOptions.project`, no typed rules — `eslint.config.mjs` :326-:329, with its own positive-control measurement recorded there), so this diff cannot move any untouched file's verdict. The repo-wide `pnpm lint` scan is CI's run. **`skip-changeset`, measured rather than asserted:** both paths lie outside every package directory, and of the 70 published packages none has a `files[]` entry escaping its own directory (0 entries starting with `../` or `/`). Nothing published moves. ## Acceptance notes Out of scope, noted, not filed: - **`.claude/skills/pm-dispatch/references/platform-readings.md` :385-:386 becomes FALSE when this lands.** It reads 「⭐ 本表的 `mustName` 不要求排他 ⇒ 第七个加注册行不会让本表变红」 and 「⇒ ⛔ 门绿不是本行已对的读数:计数行只能手跟改」. After this PR that entry declares `statesTheSet: true` and the registry growing DOES red it, so a seat reading those two lines would keep hand-following a line the gate now holds. Not fixed here: the file is outside this card's declared REGION claim (`AGENTS.md` :505-:510 plus this script), and the script's own header records `.claude/skills/pm-dispatch/**` as a surface a dev seat may not edit — the reason the checklist half of objectstack-ai#9325 was its own card. **Successor: the `domain:skills` seat, in its own lane.** Dedupe words: `platform-readings`, `mustName 不要求排他`, `计数行只能手跟改`, `statesTheSet`, `required contexts 的名单`. - The count WORD in a set-stating surface stays hand-followed (leg B above). Recorded as a residual in the script header, in the paragraph that already records the paraphrase-drift and shortening-rename residuals. No card: it is bounded by the census and closing it needs recognition the header measured as out of reach. - The triage grading comment 5651027386, which this card's acceptance is quoted from, answers **HTTP 404** — it is not on objectstack-ai#17798 (the card carries exactly 3 comments) and the direct comment endpoint does not find it. Its content survives verbatim inside hold note 5651882793 and in this dispatch's own text, which is what acceptance items 1, 2 and 4 were read from here. Recorded as NOT MEASURED against the primary source, not as "no flags". Nothing else was touched. `REQUIRED_CONTEXTS` is byte-identical to `origin/main`. --- _Generated by [Claude Code](https://claude.ai/code/session_01HZfg2AwVX191qCizp88gQr)_ --------- Co-authored-by: Claude <[email protected]>
akarma-synetal
pushed a commit
to akarma-synetal/framework
that referenced
this pull request
Sep 17, 2026
…d reads the REST token class per write, not per session (objectstack-ai#18359) Fixes objectstack-ai#18320 Fixes objectstack-ai#18334 ONE file, one fold: `.claude/skills/pm-dispatch/references/platform-readings.md`. 463 to 465 lines (ratchet ceiling 466, headroom 1); every edited and every added line is at or under 120 bytes; the widest-table-row pin stays at 0. ## Hunk A — the denied enqueue tool's rows (the first card): lines 41, 49, 50, 58, 60 Lock 1 denies MCP `enable_pr_auto_merge` and `disable_pr_auto_merge` (PR objectstack-ai#18317), and four rows still taught a seat how to call them. The live enqueue route is REST `PUT .../pulls/{n}/ccr/auto_merge`, which `rest-channel.md` owns. - **`:49` retired** (the `mergeMethod: "SQUASH"` calling convention). It is a write-side recipe, and `:133` of this same file rules that write-side recipes live in `rest-channel.md` and are never restated here — 「逐操作通道归属、写侧配方与队列路由三读法见 `rest-channel.md`,⛔ 不在本表复述」. `rest-channel.md:50` already carries the live spelling with `{"merge_method":"SQUASH"}`. Re-keying it here would have created the exact duplicate that row forbids. - **`:58` retired** (the tool succeeds on an already-`mergeable_state: clean` PR, against its own description). Its entire content is a contradiction between one MCP tool and that tool's description; nothing of it survives for the REST route. - **`:60` retired** (quota exhaustion returns success with no mount, so verify the effect and not the echo). Its conclusion is carried for the live route by `rest-channel.md:52` 「⛔ `auto_merge` 与回显都不作数」, and inside this file by the surviving row 「回显两向不可靠 … ⛔ 不拿它当任何方向的证据」, which now stands directly above 「效果读数 = …」. The dispatch asked for no duplicate, so no replacement row was written. - **`:41` re-keyed TOOL-NEUTRAL**, deliberately not keyed to the REST unload. The surviving truth (unloading auto-merge alone does not kick this repo's queue) was measured on the MCP `disable` call. I could cite no measurement of that same behaviour on `DELETE .../ccr/auto_merge`: `rest-channel.md:51` measures only the 422 an unmounted PR answers, which is a different question. Keying the row to the REST route would have asserted an unmeasured platform fact, so it now reads 「补救:转 draft 与卸载 auto-merge 都做 —— 本仓卸载 auto-merge 单独不踢队。」 - **`:50` re-keyed in place**, forced by retiring `:49`: `:50` opened with 「它」 and `:49` was its antecedent. The reading is unchanged and the new subject is route-neutral — 「挂上的 auto-merge 存的方法恒为 `merge`」. This is the one hunk outside the four named lines, and it is declared as a deviation in the report. After this hunk, zero rows in the file name either denied tool in any voice, so no history row was needed either: the deny itself is already recorded for the operation in `rest-channel.md:53`, which is where `:133` says it belongs. ## Hunk B — the REST token class (the second card): lines 129-130 become 126-132 `:129` said the class is fixed per session — 「按会话定」. Two measured flips inside one session, each with no seat action and with `GET /user` constant, falsify that: - the triage seat, 2026-09-15: `claude[bot]` at 15:54Z, then `os-sam` at 22:55Z; - this seat, 2026-09-15 into 09-16: `claude[bot]` at 20:04Z, then `os-zhuang` at 01:53Z. The rows now say: two classes, both at core 15,000/h; the class follows the Claude Code account and NOT the session, and can flip between two writes of one session with no seat action; both flips are named by date and account; the class is read from EVERY write's own read-back (`user.login` plus `user.type`) and is never carried forward from the round-open marker, whose identity reading is dated rather than standing; and `performed_via_github_app`, `GET /user` and the core rate-limit header all answer the same for both classes, so none of the three discriminates. The consequence is split across two rows because it does not fit one 120-byte line: a user-class write is author-bound — a suspended account 404s its comments and its filed cards, while labels, state, titles and bodies survive — so the durability calculus is re-run per class and never assumed to sign as `claude[bot]`; and under the user class the PR's author IS that user, so requesting them as reviewer answers 422. That 422 was measured on PR objectstack-ai#18351 at 02:32Z. The second measurement table comes from objectstack-ai#18350, which the second card carries as its duplicate. ## Verification Reverse verification, before and after, on the one file: | reading | before (`1411cf2c`) | after | |---|---|---| | `grep -n -E 'enable_pr_auto_merge\|disable_pr_auto_merge'` | `:41 :49 :58 :60` | zero hits (grep exit 1) | | `grep -n -E 'installation\|user-to-server'` | `:129` only | `:126` only | | 「按会话定」 | present on `:129` | absent; `:127` reads 「⛔ 不按会话定」 | | every-write read-back | absent | `:129` 「类只认每次写回读的 …」 | | consequence row | absent | `:131` and `:132` | | `wc -l` | 463 | 465 | | widest line | 120 B | 120 B, zero lines over 120 B | Firing control — `rest-channel.md` is untouched and still carries the live route: `grep -n 'ccr/auto_merge' .claude/skills/pm-dispatch/references/rest-channel.md` still answers `:50` and `:51`, byte-identical. `git diff --stat` against the merge base is the one file, 9 insertions and 7 deletions. Ratchet, both readings, quoted from the gate itself: ``` ✓ check-skill-line-ratchet: .../platform-readings.md is 465 lines (ceiling 466; headroom 1). ✓ check-skill-line-ratchet: .../platform-readings.md: widest table row is 0 bytes (pin 0; headroom 0). ``` `node scripts/pm/dispatch-gates.mjs --commands THE-FILE` derived 16 families; all 16 were run, every one at exit 0, and reconciled with `--ran`: `✓ dispatch-gates --ran: 16 derived famil(ies) accounted for — 16 run, 0 NOT-MEASURED`. Outside that derivation I also ran `pnpm check:pm-settings-deny-roster` (its roster sits under `.claude/`, so its silence would not have been evidence in either direction) and the path face `node scripts/pm/check-governed-merges.mjs --test THE-FILE`, which answers GOVERNED as expected. No changeset: nothing versioned moves. `.claude/**` ships in no package `files[]`. ## Landing This diff touches `.claude/**`, a governed surface (Prime Directive objectstack-ai#14). Every governed path lies under `.claude/skills/pm-dispatch/references/`, so the landing tier is the skills seat's in-seat review at `CONTRACT_REVIEW_TIER` rather than the maintainer's word. **This PR stays DRAFT.** I requested no reviewers, touched no ccr route, armed no auto-merge and flipped nothing. ## 维护者速读(草稿) **改了什么** — 派发座位的平台事实表改了两处。一是删掉三条、改写两条教座位去调一个已被 锁 1 禁掉的 MCP 入队工具的规则,活路线的写法本来就在 `rest-channel.md`。二是把「REST 写 的身份按会话固定」这条改成实测的样子:身份跟 Claude Code 账号走,一次会话中间会变,每次 写都要自己回读一次。 **为什么改** — 这两条都是写着的事实与实测不符。前者让座位学一个它调不到的工具;后者更贵: 座位的耐久性判断(评论和卡会不会随账号被封而 404)整个建立在「署名恒为 `claude[bot]`」 上,而这一班里已经两次实测到写入落成了用户账号。四天内已经有两个分诊账号在班中被封并因此 丢掉全部书面记录,所以这条假设错的方向正是丢数据的方向。 **风险与代价(含回滚)** — 只动一个内部指令文件,不发布、不进任何包、无运行时影响。棘轮还 剩 1 行余量。回滚 = revert 这一个 commit。⛔ 本 PR 不改写入通道本身:换一个低权限账号或 改回 App installation token 是维护者的决定,这里只把事实表改成实测的样子。 **席位意见** — **你要做的** — 目前不需要你做任何事。这是 references 层,按现行分层由技能席位在座评审后 自己落地;⛔ 不需要你的批准。若你希望把「写入身份」这件事本身处理掉(专用低权限账号,或 恢复 App installation token),那是另一张卡。 --- _Generated by [Claude Code](https://claude.ai/code/session_01HZfg2AwVX191qCizp88gQr)_ Co-authored-by: Claude <[email protected]>
akarma-synetal
pushed a commit
to akarma-synetal/framework
that referenced
this pull request
Sep 17, 2026
…s — the unranged git log walked all of main and made force-with-lease dead letter (objectstack-ai#18351) Fixes objectstack-ai#18178 ## The defect `AGENTS.md` Multi-agent §3 allows `--force-with-lease` only while all five criteria hold. Criterion ③ stated the bar — *nobody else has ever pushed it* — and then operationalised it as the author and committer sets of an **unranged** `git log` over the branch's remote ref. With no range that walks the branch's entire ancestry, all of `main` included, so the sets are main's whole history plus the agent and the test cannot pass for **any** branch cut from `main`. The stated criterion is satisfiable and right; only its operationalisation was impossible — a reader who follows the words never force-pushes, one who follows the intent does, and that divergence is the bug. The card was filed after the dev on objectstack-ai#17330 declined to take a reading it could not prove and shipped four `wip:` commits plus a merge instead. ## The change One hunk, two lines, `AGENTS.md` :473–:474. The parenthetical is re-keyed to the branch's **own** commits — the range form `origin/main..` in front of the branch ref. Criteria ①②④⑤ are untouched, ③'s bar is untouched (same words, same strictness), and the file stays at its 1075-line ceiling with no new line. ```diff - `claude/issue-*`; ② this worktree created it; ③ nobody else has ever pushed it (the - author and committer sets of `git log origin/BRANCH` are you alone); ④ no open PR + `claude/issue-*`; ② this worktree created it; ③ nobody else has ever pushed it (the author and + committer sets of its own commits, `git log origin/main..origin/BRANCH`, are you alone); ④ no open PR ```⚠️ `BRANCH` above stands for the angle-bracket placeholder the file actually spells; this body writes the word instead, because a GitHub body sanitizer eats short angle-bracket fragments. The file itself is unchanged in that respect — read the diff for the real bytes. ## Ratchet and byte budget | reading | before | after | |:--|--:|--:| | `wc -l AGENTS.md` (ceiling 1075) | 1075 | 1075 | | line :473 | 90 B | 101 B | | line :474 | 88 B | 108 B | | lines :472 / :475 / :476 / :477 | untouched | untouched | | `git diff --stat` | — | 1 file, 2 insertions, 2 deletions | `scanLineLengths` (the gate's own scanner, budget 120 B) reports **0 offenders** on the edited file. ## Reverse verification Taken on this very branch, a real `claude/issue-*` branch, and on a constructed control.⚠️ This checkout is **shallow**, so the unranged sets are truncated relative to the card's full-history reading (33 / 32 / 1); the *shape* is what reproduces, and it reproduces exactly. ``` BEFORE — today's literal test, on this branch git log --format='%an|%cn' origin/claude/issue-18178-force-with-lease-criterion-scope | sort -u -> 7 pairs (Claude|Claude, Jack Zhuang|GitHub, Leehom|GitHub, Warren Buffett|GitHub, claude[bot]|GitHub, os-elon-musk|GitHub, os-try-charles|GitHub) CONTROL — the same literal test on origin/main itself -> 6 pairs — the branch's 7 minus Claude|Claude, i.e. main's set plus the agent, exactly as the card measured. `comm -23` of the two sets prints one line: Claude|Claude. AFTER — the re-keyed test, same branch git log --format='%an|%cn' origin/main..origin/claude/issue-18178-... | sort -u -> 1 pair Claude|Claude (2 commits in range) FIRING CONTROL — the re-keyed test on a branch that genuinely has a second pusher (a throwaway local repo, two clones, two identities; no probe branch was pushed to origin) unshared branch, literal test -> 4 pairs (wrongly refuses) unshared branch, re-keyed test -> 1 pair (permits — correct) SHARED branch, re-keyed test -> 2 pairs Claude|Claude + os-try-charles|os-try-charles (still refuses — the bar is not loosened) ``` ## The one design choice: which scoped spelling The card proposed `$(git merge-base origin/main origin/BRANCH)..origin/BRANCH` and called it a proposal, not a prescription. This PR ships the plain two-dot range `origin/main..origin/BRANCH`. Both compute the same commit set — verified on the live branch and on both control branches, 1 / 1 / 2 pairs either way — and the two-dot form is one moving part fewer. The deciding measurement is how each **fails**: ``` # the substitution form, when merge-base returns nothing (shallow clone, unrelated # histories, a ref not fetched) — the range silently becomes HEAD..origin/BRANCH exit=0 pairs=0 on a branch a second agent had just pushed -> "you alone" is VACUOUSLY true, and the criterion permits exactly the clobber it forbids # the two-dot form under the same fault exit=128 fatal: ambiguous argument ... unknown revision -> loud, no verdict at all ``` That failure is live in this repository, not hypothetical: `git merge-base origin/main REF` exits 1 with empty output here for a branch whose base lies outside the shallow window. Preferring the spelling that cannot degrade into a different question is the same principle the card is about — a test that answers confidently and wrongly is worse than one that stops. ## 维护者速读(草稿) **改了什么** — `AGENTS.md` 多 agent 纪律 §3 里判断「分支是不是只有我一个人推过」的那条命令, 从 `git log` 加分支名(走遍整条祖先,包含整个 `main`),改成只看分支自己的提交(`origin/main..` 区间)。 正文两行,判据本身一字未动。 **为什么改** — 原命令对任何从 `main` 切出来的分支都必然返回「多人」,所以第五条允许的 `--force-with-lease` 实际上永远用不了:条文写着允许,测出来永远禁止。按字面读的 agent 从不 force-push, 按意图读的会,两种读法分叉本身就是缺陷。已经有一张卡的开发因此多推了四个 `wip:` 提交和一个 merge。 **风险与代价(含回滚)** — 改的是规则文本,不是代码,没有运行时影响。风险是「改松了判据」, 本 PR 用一个构造出来的、确有第二个推送者的分支做了发火对照:新命令仍然读出两组身份、仍然拒绝。 回滚成本 = 还原两行文字。 **席位意见** — (留空,待席位定稿) **你要做的** — 这是受管面(`AGENTS.md`),需要您的一次授权批准;批准前没有任何 agent 席位会合并、 入队或挂 auto-merge,PR 保持 draft。 ## Acceptance notes - noted, not filed: the ratchet's line-length verdict prints **only on failure** (`check-skill-line-ratchet.mjs` `run()` logs `lv.msg` under `if (!lv.ok)`), so a green run says nothing about the 120-byte axis for any file. Not a defect class — a silent green is the normal shape for that gate — measured positively here by calling `scanLineLengths` directly instead of reading the gate's silence. Bearer: none; noted for the next author who cites that gate's green as width evidence. - Scope held: `AGENTS.md` only, criterion ③'s parenthetical only; :472 and :475–:477 were not re-flowed and not touched. ## Landing Governed surface (`AGENTS.md`, Prime Directive objectstack-ai#14) — `check-governed-merges.mjs --test` answers **GOVERNED**, 1 of 1 path. The PR stays **draft**; no reviewer was requested, no auto-merge armed, nothing flipped. It waits for the maintainer's authorized approval, which the owning seat requests. --- _Generated by [Claude Code](https://claude.ai/code/session_01HZfg2AwVX191qCizp88gQr)_ Co-authored-by: Claude <[email protected]>
akarma-synetal
pushed a commit
to akarma-synetal/framework
that referenced
this pull request
Sep 17, 2026
…table, so its self-test is hermetic on a box that carries the sibling (objectstack-ai#18365) Fixes objectstack-ai#18321 ## The defect `.claude/hooks/guard-governed-enqueue.selftest.sh`'s case "an exception-row path in a repo this container cannot resolve" rested on a premise about the **box**, not about the hook: that no `objectstack-ai/cloud` checkout sits beside this one. The hook resolves a sibling checkout by comparing origin slugs under the parent directory of its own repo root, so on a container that *does* carry a sibling `cloud` checkout the guard resolved it, recomputed the register predicate on that tree with `--root`, got the governed answer and **blocked**. The matrix read `54 passed, 1 failed` there, and was green in CI only because the runner mounts no sibling. `lint.yml`'s step comment calls these matrices hermetic; this case was not. ## What changed **The hook** now reads `OS_GOVERNED_ENQUEUE_SIBLING_ROOT` for the directory a sibling is resolved under. It moves **where** the search looks and nothing else — the origin-slug comparison remains the entire admission rule, so a sibling that resolves is audited exactly as before, and a root holding no matching checkout resolves nothing. No fail-open path was added or widened. | value | meaning | |:---|:---| | unset | the parent of this checkout — today's behaviour, to the byte | | empty | identical to unset; an empty value is an accident, and the safe reading of an accident is "no override", never "look nowhere" | | a directory carrying no matching checkout (one that does not exist included) | nothing resolves; the run proceeds exactly as on a box without the sibling | **The matrix** now owns its own premise: the "cannot resolve" case points the lookup at a directory it creates and knows is empty, and one new case pins the other half — a sibling that *does* resolve is audited on its own tree. The throwaway sibling is built in the self-test (`git init` plus an `origin` naming the target repo is the whole admission requirement) and removed by the existing `trap` on the matrix's own temp root, so no new cleanup path was needed. Both temp directories live under that root by construction. The new case is written as **agreement with the register**, not as a copied verdict — the shape this file's own header says it learned the hard way, where a hard-coded `expect allow` went red over an upstream register change the hook had nothing to do with. ## Reverse verification BEFORE, on `origin/main` `1411cf2c`, this container, `/home/user/cloud` present: ``` 54 passed, 1 failed FAIL want=allow got=block an exception-row path in a repo this container cannot resolve ``` AFTER, at `5391e5c0`, same container, `/home/user/cloud` still present — four injection states, all `exit 0`: ``` UNSET exit=0 56 passed, 0 failed EMPTYDIR exit=0 56 passed, 0 failed NONEXISTENT exit=0 56 passed, 0 failed EMPTYSTRING exit=0 56 passed, 0 failed ``` **"Unset changes nothing" is proved directly**, not inferred: the *untouched* matrix was run against the *changed* hook, and its output was byte-identical to the baseline log (`diff` empty — still `54 passed, 1 failed`, still the same one case). The variable's semantics were also measured against the hook directly, with the real sibling present, on a payload targeting `objectstack-ai/cloud`: ``` unset -> block (resolves the real sibling) empty string -> block (identical to unset) =/home/user -> block (explicit, same directory as the default) empty dir -> allow (nothing resolves) non-existent -> allow (nothing resolves) ``` **Firing control.** Two were run, each mutating the committed file, proving the mutation landed on disk by hash, and restoring with `git checkout HEAD --` under a `trap` (`git diff HEAD` empty afterwards, blob hash back to the HEAD blob): - *the control this card prescribed* — give the new case a **not-governed** fixture: **stays green**, `56 passed, 0 failed`. It cannot fire, and that is a property of the assertion rather than a gap: an agreement assertion flips the register leg and the hook together, so they still agree. Reported rather than papered over. - *a control that targets the property under test* — the throwaway sibling's `origin` names a different repo, so it is no longer admitted: **red**, `55 passed, 1 failed`, `FAIL want=block got=allow a sibling checkout that resolves is audited, never waved through`. This is the mutation that corresponds to "the hook stopped consulting the sibling tree", which is what the case exists to catch. Diff confined to the two files in the declared surface: ``` .claude/hooks/guard-governed-enqueue.selftest.sh | 92 +++++++++++++++++++--- .claude/hooks/guard-governed-enqueue.sh | 41 +++++++++- 2 files changed ``` ## Gates `dispatch-gates.mjs` derives 13 families from the real change set (2 paths, three-dot vs merge base `1411cf2c6`); all 13 ran and all recorded `exit 0`. Reconciliation: ``` Run reconciliation — 13 derived, 13 run, 0 NOT-MEASURED, 0 UNRUN. ``` `check-doc-formula-expressions` first returned **exit 3 (PREREQUISITE NOT MET — nothing measured)** because two workspace packages were unbuilt; it was re-run to `exit 0` after a targeted build, and only that second reading is recorded. The path face `check-governed-merges.mjs --test` answers **GOVERNED** (`.claude/**`), as expected. `shellcheck` is **not measured**: the tree wires no shellcheck step (`lint.yml` runs the hook matrices, it does not lint them) and the binary is absent from this container. `bash -n` parses both files and `check:bash32-floor` passes. ## Acceptance notes Two things measured on the way, both **out of scope for this PR** and neither fixed here: 1. **The case never reached the fail-open it claimed to pin.** The comment that used to sit on it described the "no checkout of the target repo is available" branch. It does not reach that branch, in any environment including CI: with nothing resolved the register is asked *without* `--root`, answers about *this* tree, finds the path byte-exact against its own generator and lifts it, so the hook leaves at the cleared-predicate `exit 0` with **empty stderr**. That branch is reachable — a path hitting the exception row but absent from the generator's declared output set returns governed with a non-empty `exceptions` list — but no case in the matrix reaches it today. The stale comment is corrected here because it sits on the case being re-keyed; pinning the branch is separate work. 2. **The slug reader keeps a `.git` suffix.** Its path character class owns the dot and is greedy, so an origin of `https://github.com/objectstack-ai/cloud.git` yields `objectstack-ai/cloud.git` and matches nothing. A sibling cloned with the conventional URL therefore does not resolve. The same expression also derives the slug for a bare `gh pr merge NUMBER`, where a non-matching slug makes the API read 404 and the guard fail open. Measured here; reported for its own card. The new case uses the bare URL form on purpose and says so in place, so nobody "tidies" a `.git` onto it. Also noted, not filed: `lint.yml`'s step is named for the two matrices that existed when it was written, while discovery now picks up more. The step comment is explicit that discovery is the contract and that a hard-coded list would be the defect, so the name is illustrative rather than a ledger — nothing to reconcile. Whoever adds the next hook matrix is the one who reads it. `lint.yml` is outside this PR's declared surface and was read, not edited; its hermetic claim becomes true rather than edited. Serial context: PR objectstack-ai#18317 touched `.claude/settings.json` and landed before this branch was cut; it is unrelated to either hook file and nothing here depends on it. ## 维护者速读(草稿) **改了什么** — 给这个 PreToolUse 守卫加了一个环境变量,用来指定「到哪个目录下去找兄弟仓 checkout」。默认不设时行为与今天逐字节一致。自测脚本据此改写了一个用例,并新增一个用例。 **为什么改** — 这个自测用例原本依赖「这台机器上没有 cloud 仓的 checkout」这个环境事实,而不 是依赖守卫本身的行为。凡是挂了 `/home/user/cloud` 的机器上它就红,CI 绿只是因为 runner 上 恰好没有。测试的结论必须由被测代码决定,不能由机器上还挂了什么决定。 **风险与代价(含回滚)** — 风险低。变量只改变「去哪里找」,不改变「找到了算不算数」—— 判定仍然是比对 origin slug,所以误设一个值只会让它找不到(退回到本来就存在的放行分支), 不会让它放过本该拦截的东西。已逐项实测 unset / 空串 / 空目录 / 不存在目录四种取值。回滚就是 还原这两个文件,无数据迁移、无发布物、无下游依赖。 **席位意见** — (待席位填写) **你要做的** — `.claude/**` 属受管面,按 Prime Directive objectstack-ai#14 需要一次授权的 APPROVED 审核; 本 PR 保持 draft,未请求任何 reviewer,未触碰 auto-merge。除批准外无需其他动作。 --- 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01HZfg2AwVX191qCizp88gQr --- _Generated by [Claude Code](https://claude.ai/code/session_01HZfg2AwVX191qCizp88gQr)_ --------- Co-authored-by: Claude <[email protected]>
os-justin
pushed a commit
that referenced
this pull request
Sep 17, 2026
…ord's own landing PR and commit Both records landed on `main` on 2026-09-07 (0132 via #16215, squash `c677cda816`; 0133 via #16267, squash `13c08356ab`, both merged by the authorized approver), while their `**Status**` lines still read `Proposed — awaiting the maintainer's hand-merge, which is (itself) the acceptance act`. Read by their own rule the acceptance act occurred; only the line was never updated. Anyone judging citability from the Status field got the wrong answer, and no gate reds on it. Each line now takes the form the positive controls 0130 :3 / 0131 :3 already use — `Accepted (date) — accepted by the merge that landed it on `main` ([#PR](url), commit `sha`), which is (itself) the acceptance act for a governed surface (Prime Directive #14).` — with that record's own landing facts, keeping each file's local form (0132 wraps under a list dash, 0133 is one unwrapped list line). 0132's trailing `Nothing below is settled until this record merges` sentence is dropped, which is what 0130 and 0131 did with theirs; 0133's trailing sentence says something else entirely (this file records no new decision) and is kept byte-for-byte. Nothing else moves: no decision text, no heading, no other line, and ADR-0134 is untouched — its `Proposed` mirrors cloud ADR-0071's own state and is self-explanatory. Claude-Session: https://claude.ai/code/session_01Gqi43smmqjJ5sUrhfoPeKu Co-authored-by: Claude <[email protected]>
os-justin
pushed a commit
that referenced
this pull request
Sep 17, 2026
AGENTS.md rule 2 handed the dev `git ls-remote --heads origin | grep issue-<n>` as a one-command pre-check and said only what the command IS, never what a HIT means. The reading rule stood only in the fact layer (pm-dispatch references), which the dev definition never points at, so the layer holding the rule had no reader and the layer with the reader had no rule. The failure direction is the silent one: a stale head reads as "already claimed" and nothing goes red. One clause on the existing sentence states the reading. Paid in-file: the Skills section's restatement of the governed-surface queue mechanism retires, since Prime Directive #14 already carries it. Co-authored-by: Claude <[email protected]> Claude-Session: https://claude.ai/code/session_01Gqi43smmqjJ5sUrhfoPeKu
This was referenced Sep 17, 2026
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.
Created production-grade CRM example (~2,900 LOC) showcasing complete protocol surface area. Previous example only covered 3 basic objects with minimal features.
Objects Added
New:
Enhanced (Account, Contact, Opportunity):
UI Components
Example
Protocol Coverage
Bilingual documentation (EN/中文) with learning resources and architecture patterns.
Original prompt
💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.