Skip to content

feat(pm/fleet-write): the relay token carries workflows write on every stroke, spent by automerge_enable on a pull that changes a workflow file - #22827

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-22708-relay-workflows-write
Oct 11, 2026
Merged

os-zhuang merged 1 commit into
mainfrom
claude/issue-22708-relay-workflows-write

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #22708
Clause-②: no

Ruling A on the card (comment 6104822731; maintainer 「22708 A」): the fleet relay's App token carries workflows: write on every stroke, so a seat may enqueue a pull that changes .github/workflows/**. This PR is that change, and it is itself the last workflow-file pull a human enqueues: it changes fleet-write.yml, so the token it widens cannot land it.

Who wrote it, and under which rule. The PM seat itself (skills seat 2, session_011u73oxZ5X95qrARPTeoU6v), not an os-dev, under the PM red line's one exception: a maintainer's explicit per-PR authorization of an internal-tooling PR. The authorization, verbatim, from the maintainer in this seat's session chat at 2026-10-11T10:52Z: 「22708 你可以处理:执行 #22708 裁决 A:按该卡开发报告(评论 6106463015)里 deviations D1、D2 的说明,修改 scripts/pm/fleet-write/ops.mjs、.github/workflows/fleet-write.yml、scripts/pm/fleet-write/validate.mjs。跑 validate.mjs、execute.mjs、dispatch.mjs 的 --self-test 和相关 gates,然后开 draft PR。」 The first dev's attempt (report 6106463015) was stopped by the session's permission classifier at the validate.mjs pins; its measured premises and its prepared ops.mjs + fleet-write.yml patch are used here unchanged.

What changed

  • scripts/pm/fleet-write/ops.mjs: PERMISSIONS gains workflows: 'write' between contents and metadata (the order the mint's literal list and the pins compare); the automerge_enable row gains alsoSpends: Object.freeze(['workflows']); the header's permission paragraph, the PERMISSIONS JSDoc, the alsoSpends field doc and the transfer section's "one widening" sentence are updated, and the lowered human floor is recorded beside the grant with the ruling's comment id. STROKE_SCOPED_PERMISSIONS is unchanged: the grant is literal, not per stroke.
  • .github/workflows/fleet-write.yml: the mint step declares the literal permission-workflows: write after permission-contents, with a ONE-consumer comment; the header's grant list and the two "four grants" sentences read five.
  • scripts/pm/fleet-write/validate.mjs: the alsoSpends pin expects [['pr_create', ['issues']], ['automerge_enable', ['workflows']]]; the map pin and the token-grants pin carry the fifth grant; two new cases in the refused-by-construction battery (floor 20 to 22): workflows is spent by automerge_enable alone as alsoSpends and is no row's permission; PERMISSIONS minus metadata equals the union of every row's permission and alsoSpends, both exactly actions / contents / issues / pull-requests / workflows. The tokenPermissionsOf doc comment says five.
  • .claude/skills/pm-dispatch/references/landing-operations.md:67 (Tier S; one line for one line, 117 bytes; the file stays 101/101): diagnostic step ③ now reads the token as carrying workflows: write by the ruling, so a refusal there is a grant drift (the App's accepted permissions), not a missing grant.
  • ⛔ No new gate. .claude/skills/pm-dispatch/SKILL.md:263 (the human-floor sentence) is untouched.

Verification (head db16cfc7d; base b7cd1af9d, origin/main when the worktree was cut)

  • Red-before control: with only PERMISSIONS widened and the pins at base, node scripts/pm/fleet-write/validate.mjs --self-test exits 1, "3 of 128 case(s) failed" (the alsoSpends pin, the map pin, the token-grants pin). After the pins: 130 cases pass across 10 batteries. execute.mjs --self-test 111 cases and dispatch.mjs --self-test 216 cases, exit 0; pnpm check:pm-fleet-write-validate, -execute, -dispatch exit 0.
  • node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack from the worktree with no paths derived 61 commands at b7cd1af9d; every one ran alone with its exit captured before any pipe (the tool's documented idiom); the reconciliation line: ✓ dispatch-gates --ran: 61 derived famil(ies) accounted for — 61 run, 0 NOT-MEASURED (a DERIVED zero — all 61 recorded an exit code and none of them is 3).
  • pnpm --filter @objectstack/lint run check:doc-formula-expressions first answered exit 3 (PREREQUISITE NOT MET: @objectstack/formula and @objectstack/lint unbuilt); the @objectstack/lint... closure was built under the verify lock (VERDICT command-exit 0, 141 s) and the rerun exits 0. pnpm check:pm-dispatch-gates (the long battery) exit 0.
  • check-skill-line-ratchet: landing-operations.md is 101 lines (ceiling 101; headroom 0). check:pm-skill-id-lint, check:nul-bytes, check:pm-governed-merges, check:pm-governed-prose, check:skill-frame-sync, check:required-contexts, check-self-test-wired, check-self-test-workflow-commands, check-whole-set-label-write, check-scripts-symbol-anchors, check-ci-filter-parity exit 0 (all in the 61).
  • No package source changes, so no package build or test is owed beyond the gates. Changeset: none; scripts/pm/**, .github/** and .claude/** ship in no package's files[], so the PR takes skip-changeset.

Premises (report 6106463015, re-read on this base)

  • A1: actions/create-github-app-token@v3 declares the permission-workflows input (action.yml:143, read at the tag ref; the sha read was refused by the proxy).
  • A3: execute.mjs has no token-grant surface (PERMISSIONS imported at :164, used nowhere), so the pin lives in validate.mjs.
  • REFUSED_PATH_FAMILIES carries /\/git\// and /\/contents\//, so the wider token reaches no file write through any row; the refused-by-construction battery pins that over every path every op can spell.

Risk the maintainer checks before this merges (NOT MEASURED from the seat)

The mint step requests permission-workflows: write on EVERY relay stroke. If the objectstack-fleet App's registration, or the organization installation's accepted permissions, lacks Workflows: Read and write, actions/create-github-app-token fails on every stroke: every relay write stops, not only this class of pull. /app/** is unreachable from seat egress, so this cannot be read here. Check: the App's Repository permissions show Workflows = Read and write, and the installation on objectstack-ai has accepted that change. The first relay stroke after the merge is the measurement; if the mint fails, revert this PR.

Acceptance notes

维护者速读(草稿)

改了什么:车队中继(fleet-write)铸出的 App 令牌在每一笔写上常驻 workflows: write;automerge_enable 行登记它花费这项授权(改 .github/workflows/** 的 PR 入队需要);validate 自测钉死:授权集与 workflow 字面输入一致、workflows 只由该行花费、授权集不比各行所花更宽。另把 landing-operations.md 里「token 缺 workflows 权限」那条诊断改成「令牌常驻该权限,仍拒即授权漂移」。不加门禁。

为什么改:你裁了 A(「22708 A」):以后改 CI 工作流文件的 PR 由席位中继入队,不再每张等人点一下。本 PR 自己也改了工作流文件,所以它是最后一张需要人点入队的。

风险与代价(含回滚):最大风险在 App 侧:铸币会在每一笔申请 permission-workflows: write;若 App 注册或组织安装没接受 Workflows 读写权限,每一笔中继写都在铸币处失败,整个中继停摆,不只这一类 PR。席位测不了这项(到不了 /app/**)。回滚 = revert 本 PR。代价:令牌权限面永久多一项(可改写工作流文件的能力),安全/权限边界的人工地板为这一项降低,出处已写在代码旁。

席位意见:(席位在评论里定稿)

你要做的:两步。① 先确认 objectstack-fleet App 的 Repository permissions 里 Workflows 为 Read and write,且组织安装已接受;② 然后点一次 ready + auto-merge(或入队)。合并后第一笔中继写就是实测,失败即 revert。


Generated by Claude Code

…y stroke, spent by automerge_enable on a pull that changes a workflow file

Ruling A on the card (comment 6104822731, maintainer 「22708 A」): the
`automerge_enable` row gains `alsoSpends: ['workflows']`, `PERMISSIONS`
gains `workflows: 'write'` between `contents` and `metadata`, the mint
step declares the literal `permission-workflows: write`, and the
validate self-test pins the grant: the alsoSpends pin names both rows,
the map pin and the token-grants pin carry the fifth grant, and two new
refused-by-construction cases pin that `workflows` is spent by
`automerge_enable` alone and that `PERMISSIONS` is nothing wider than
the union of every row's spends. The `ops.mjs` header and the
landing-operations reference record the lowered human floor beside the
grant, citing the ruling, so a later reader does not take the wider
token for a drift. No new gate. `STROKE_SCOPED_PERMISSIONS` is
unchanged: the grant is literal, not per stroke.

Red-before: with only `PERMISSIONS` widened, `validate.mjs --self-test`
reds 3 of 128 cases; with the pins, 130 pass. execute 111, dispatch 216.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011u73oxZ5X95qrARPTeoU6v
@github-actions github-actions Bot added size/s ci/cd documentation Improvements or additions to documentation labels Oct 11, 2026
@objectstack-fleet objectstack-fleet Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Oct 11, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: db16cfc7d50457e7f869c7c45ccc1477a44493f9
Local-runs: probe — the reviewer is also the implementer (the maintainer's per-PR authorization, quoted in the PR body); the 61-gate ledger, the three relay self-tests and the red-before control ran in the implementing worktree before the push and are not re-run for this record

Reviewed by the skills seat 2 in seat (session_011u73oxZ5X95qrARPTeoU6v, served model equal to the constant's value at get_session, re-read for this record), on the diff b7cd1af9d..db16cfc7d as pushed, the card #22708 with ruling 6104822731 and the dev's report 6106463015, and the head's check-runs. Written 2026-10-11T12:05Z. Independence disclosed: the same session wrote the diff; the post-merge governed-merges audit is the compensating control, and the landing is the maintainer's click (the ruling's own clause), not this seat's stroke.

① Derived judgments

  • Faces touched: PM tooling (scripts/pm/fleet-write/ops.mjs, validate.mjs), the relay workflow (.github/workflows/fleet-write.yml), and one governed rule-text line (.claude/skills/pm-dispatch/references/landing-operations.md:67, Tier S). No packages/**, no published package, no accept set, no public surface: Clause-②: no is correct.
  • The ruling's five parts, each read in the diff: automerge_enable gains alsoSpends: ['workflows']; the mint step gains the literal permission-workflows: write; PERMISSIONS and its "that set and nothing wider" sentence move together (workflows: 'write' between contents and metadata); validate's self-test pins the grant (the alsoSpends pin names both rows; the map pin and the token-grants pin carry the fifth grant; two new refused-by-construction cases pin that workflows is spent by automerge_enable alone and that PERMISSIONS minus metadata equals the union of every row's spends); the lowered human floor is recorded beside the grant in the ops.mjs header and in the landing-operations line, both citing the ruling. execute.mjs has no token-grant surface, so no execute pin was invented (A3). STROKE_SCOPED_PERMISSIONS unchanged, SKILL.md:263 untouched, no new gate: B and C are not taken.
  • Verified in the tree, not from prose: REFUSED_PATH_FAMILIES carries /\/git\// and /\/contents\//, so the wider token reaches no file write through any row; the refused-by-construction battery judges every path every op can spell against those families (validate.mjs:625). The red-before control reproduces: with only PERMISSIONS widened, 3 of 128 cases red; with the pins, 130 pass.
  • Governed line: one for one, 117 bytes, landing-operations.md stays 101/101 (check-skill-line-ratchet exit 0); the sentence keeps step ③ a diagnostic and names the ruling's comment id (no issue number, check:pm-skill-id-lint exit 0).

② Semver level

No published package touched; skip-changeset is the correct declaration and is on the PR.

③ Boundary flags

  • The maintainer's own check before the merge (NOT MEASURED from the seat): the fleet App's Repository permission Workflows must be Read and write and accepted on the organization installation; otherwise actions/create-github-app-token fails on every stroke. Named in the PR body and the 速读.
  • Still NOT MEASURED, owed on [Decision] fleet relay: should the auto-merge row spend workflows: write so seats can enqueue pulls that change .github/workflows/**, or stay a human-queue item? #22708 after the merge: the first pull the widened token enqueues.
  • Landing: Tier S by path, but the PR changes fleet-write.yml, so the relay cannot enqueue it; the maintainer clicks ready and auto-merge (or enqueues) after the App check.
  • Check-runs on this head: 35 check-runs on db16cfc7d: 24 success, 11 skipped, none red (read 2026-10-11T12:04Z); the skips are path-filtered or opt-in on a diff that touches no package (Build Core, Temporal Conformance (live PG + MySQL), Check Changeset, Check PR Size, Auto Label, Console Pin Gate, Dogfood Verify CLI, the dogfood shard placeholder, Build Docs, Packed-tarball smoke (opt-in) twice), the same set the maintainer enqueued and merged on PR docs(governed): ADR-0087, AGENTS.md and the retirement playbook describe the D4 projections and the migration registry as main builds them #22796; required contexts: TypeScript Type Check, Lint & Repo Gates, Test Core, Dogfood Regression Gate, Governed Surface Queue Guard success, Build Core and Temporal Conformance skipped by path filter. check-expected-skips.mjs --pr 22827 could not run in the seat container (exit 1 before any judgement), as the seat notes record.

Implemented-by: claude/issue-22708-relay-workflows-write
Reviewed-by: session_011u73oxZ5X95qrARPTeoU6v

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

维护者速读(终稿)· PR #22827 · #22708

席位 2(session_011u73oxZ5X95qrARPTeoU6v,zhuangjianguo)对照自己写并复读的 diff(b7cd1af9d..db16cfc7d,4 文件 +55/−24)定稿 · 2026-10-11T12:06Z。本 PR 由席位亲手实现(你 10:52Z 在席位会话里的逐 PR 授权,原话在 PR 正文),所以复核与实现同一会话,记录里已申明;合并后的受管面审计是补偿控制。

改了什么:车队中继铸出的 App 令牌在每一笔写上常驻 workflows: write;automerge_enable 行登记它花费这项授权(改 .github/workflows/** 的 PR 入队需要);validate 自测钉死三件事:授权集与 workflow 字面输入一致、workflows 只由该行花费、授权集不比各行所花更宽(改前红 3/128、改后 130 过)。landing-operations.md 那条「token 缺 workflows 权限」诊断改成「令牌常驻该权限,仍拒即授权漂移」。不加门禁;STROKE_SCOPED_PERMISSIONS 不动。

为什么改:你裁了 A(「22708 A」):以后改 CI 工作流文件的 PR 由席位中继入队,不再每张等人点。本 PR 自己也改了工作流文件,所以它是最后一张要人点入队的。

风险与代价(含回滚):最大风险在 App 侧:铸币会在每一笔申请 permission-workflows: write;若 App 注册或组织安装没接受 Workflows 读写权限,每一笔中继写都在铸币处失败,整个中继停摆。席位到不了 /app/**,测不了。回滚 = revert 本 PR。代价:令牌权限面永久多一项(可改写工作流文件的能力);安全/权限边界的人工地板为这一项降低,出处已写在 ops.mjs 头注与 landing-operations 里。

席位意见:ACCEPT。61 条派生门禁全跑、对账 61/61;CI 全绿(两项 path-skip 与昨天 #22796 相同);REFUSED_PATH_FAMILIES 实读含 /git/ 与 /contents/,令牌变宽也到不了文件写。合并后第一笔中继写是实测,本席记在卡上。

你要做的:两步。① 先到 objectstack-fleet App 设置确认 Repository permissions → Workflows = Read and write,且 objectstack-ai 组织安装已接受;② 再对本 PR 点一次 ready + auto-merge(或入队)。合并后第一笔中继写失败即 revert。


Generated by Claude Code

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

Labels

ci/cd documentation Improvements or additions to documentation size/s skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

3 participants