Skip to content

ci: fleet-write relay workflow (repository_dispatch → App token → executor) and lint wiring for its self-tests - #19726

Merged
os-zhuang merged 3 commits into
mainfrom
claude/issue-19701-fleet-write-relay
Sep 22, 2026
Merged

os-zhuang merged 3 commits into
mainfrom
claude/issue-19701-fleet-write-relay

Conversation

@os-support-ai

@os-support-ai os-support-ai commented Sep 22, 2026 •

Copy link
Copy Markdown
Collaborator

Part of #19701 — the relay workflow (PR A of two). Depends on PR B (claude/issue-19701-fleet-write-transport) landing first: the workflow's run: lines execute scripts/pm/fleet-write/validate.mjs and scripts/pm/fleet-write/execute.mjs from main, and the three lint.yml steps added here run check:pm-fleet-write-* rows that PR B adds to package.json. Kept as a draft for the maintainer to merge by hand once PR B is on main (.github/workflows/** — seat tokens cannot land workflow changes).

.github/workflows/fleet-write.yml

  • Trigger: repository_dispatch with types: [fleet-write] only. run-name: fleet-write REQUEST_ID so a seat's run-poller finds its run by name.
  • permissions: {} for the workflow's GITHUB_TOKEN; the job re-grants contents: read alone (the checkout needs it; persist-credentials: false). The run's only write path is the App token of the mint step, which exists only inside the execute step's environment and is revoked by the action's post step.
  • Four steps, no logic of their own: checkout → setup-node 22 (no package manager: the scripts are dependency-free) → validate.mjs --from-env --github-output (the payload through env:, never interpolated into a run: line; a refusal fails the run with every reason in the step summary and zero writes; on success it outputs repo_name) → actions/create-github-app-token@v2 with app-id: vars.OS_FLEET_APP_ID, private-key: secrets.OS_FLEET_PRIVATE_KEY, owner: objectstack-ai, repositories: STEPS.VALIDATE.OUTPUTS.REPO_NAME, permission-issues: write, permission-pull-requests: write, permission-metadata: read → execute.mjs with the payload, github.event.sender.login and the minted token through env:.
  • Sender gate, closed op list, caps, stop-at-first-failure and the summary table live in scripts/pm/fleet-write/ (PR B); this file is the invocation.

.github/workflows/lint.yml

Three steps after the card-closure helper's, spelled like their neighbours: pnpm check:pm-fleet-write-validate, pnpm check:pm-fleet-write-execute, pnpm check:pm-fleet-write-dispatch. check:self-test-wired requires the first two the moment fleet-write.yml names those scripts; the third is wired for the same reason its siblings are.

Maintainer's prerequisites for the live probe (from the card, not for this code)

  1. Organization secret OS_FLEET_PRIVATE_KEY — done 2026-09-22.
  2. Organization variable OS_FLEET_APP_ID = 5028251, visible to objectstack.
  3. App objectstack-fleet → repository access All repositories.

Live-probe plan (the director seat's, after this lands and 2–3 exist)

From a cloud seat with OS_FLEET_SESSION=session_… set: node scripts/pm/post-stamped.mjs --comment=19701 --file=probe.md (transport auto → dispatch). Expected: POST …/dispatches → 204, a run named fleet-write fw-… appears, completes success, the step summary carries one comment row, the read-back on the card finds the comment with user.login == objectstack-fleet[bot]. Two readings to take: the run start latency (tune OS_FLEET_DISPATCH_START_MS, default 90 s) and completion time (tune OS_FLEET_DISPATCH_TIMEOUT_MS, default 5 min). If the probe with automerge_enable is refused for permissions, add permission-contents: write to the mint step — deliberately not pre-granted.

Verification

Run in the relay worktree at 690954b (this branch carries only the two workflow files; the gates below judge the workflow tree and the lint wiring): 43 families reconciled with --ran — 42 run, 1 NOT MEASURED (check:type-check-debt, exit 3, needs the workspace build), 0 unrun; two of the 42 are red on this branch ALONE and green on the combined tree, for the one reason stated at the top: check-self-test-wired --self-test keys the three pnpm check:pm-fleet-write-* invocations to rows only PR B's package.json carries, and check:pm-dispatch-gates reports the same three as 'no root script'. Everything else green, including check:workflow-status-functions, check:workflow-step-name-quoting, check-step-collectors, check-aggregator-roster, check:required-contexts, check:pm-widening-tells, check:nul-bytes.

Round 2 at dbc820c (one commit on top of the origin/main refresh 7bae5b5, after PR B merged as 70dccce0): scripts/pm/fleet-write/validate.mjs now rides this PR too — its fixture spells the target as ${TARGET_OWNER}/objectstack instead of the slug 'objectstack-ai/objectstack', which check:declared-population-live read as a path declaration reaching no tracked file once the three lint.yml steps made the family a wired gate. No no-path-population marker: the family inherits four live leads from RELAY_FILES in ops.mjs, and check:pm-dispatch-gates' live case refuses a family that carries the marker while naming paths. Dev's run on the pushed tree: check:declared-population-live exit 0 (264 of 325 declaring families live), check:self-test-wired 229 scripts, check:pm-dispatch-gates 1905/1905, the three check:pm-fleet-write-* rows green; 60 families reconciled with --ran over the three changed paths, 59 green, 1 NOT MEASURED (check:type-check-debt, workspace build). Seat's reading of CI on dbc820c is in the final 速读 comment below.

The yaml parse of fleet-write.yml is exercised by check:workflow-status-functions and check:workflow-step-name-quoting. check:self-test-wired was run against a tree carrying BOTH branches' changes (PR B's scripts plus this workflow) to prove the wiring holds once both land: production run green (229 scripts CI runs ship a --self-test, every one run by CI), --self-test 73 cases across 11 batteries green; check:pm-dispatch-gates on that combined tree 1906/1906 with 0 unreachable families.

维护者速读(草稿)

  • 改了什么:新增 fleet-write.yml(repository_dispatch 监听,四步:校验 → 铸造 App token → 执行),lint.yml 加三个自测步骤。
  • 为什么改:云席位容器拿不到 App 身份(代理替换 Authorization、拒 /app/**),只能发 dispatch;这个 workflow 就是「方案 1」的中继,让写入以 objectstack-fleet[bot] 落地。
  • 风险与代价:token 在 runner 上铸、按目标仓与三项权限收窄、步骤结束即撤销;发送者门禁读目标仓权限,无名单变量。回滚 = 删两个文件。⚠️ permissions: {} 之下 job 单独给了 contents: read 供 checkout。
  • 席位意见:通过(终稿见评论区的「维护者速读(终稿)」)。
  • 你要做的:先合 PR B,再人合本 PR;确认组织变量 OS_FLEET_APP_ID 与 App 的仓库范围;之后由总监席发一次探针。

Generated by Claude Code


Generated by Claude Code

The repository_dispatch listener that validates a seat's payload, mints a
narrowed App token on the runner and executes the actions as the fleet;
lint.yml runs the three fleet-write self-tests the way it runs the other
pm tools'.

Claude-Session: https://claude.ai/code/session_01VhatbEZCV5JJBoU579UBCG
Co-authored-by: Claude <noreply@anthropic.com>

Copy link
Copy Markdown
Collaborator Author

Standing-down note from the dispatching seat (director, summon #27, session_0129ZpnaBcYZZ51rCvQiXg6C), 2026-09-22T12:26Z — ⛔ no state change.

Lint & Repo Gates is red on 690954b7 for one reason and it is not a defect of this diff: the three new lint.yml steps run pnpm check:pm-fleet-write-validate / -execute / -dispatch, whose package.json rows and scripts live only on PR #19725 (claude/issue-19701-fleet-write-transport). This PR is the declared second half of #19701: it lands after #19725 is on main, takes origin/main (update-branch), goes green, and is then merged by the maintainer's hand (.github/workflows/**). No re-run is spent on the current head — a re-run cannot change a missing-row failure. Reviewed and ACCEPTED on the card (#19701, the verdict comment after report 5775793585); kept draft until the landing order above.


Generated by Claude Code

Copy link
Copy Markdown
Collaborator Author

CI reading on the refreshed head 7bae5b51 — dispatching seat (director, summon #27, session_0129ZpnaBcYZZ51rCvQiXg6C), 2026-09-22T14:10Z. One new red, root-caused; a patch round is in flight on this branch.

Lint & Repo Gates fails at pnpm check:declared-population-live (job 106774664131): 「1 of 265 declaring families declare a population that reaches NOTHING in this tree — check:pm-fleet-write-validate declares objectstack-ai/objectstack, files scripts/pm/fleet-write/validate.mjs」. The family became a wired gate only when this PR's lint.yml steps landed on the tree, so PR #19725's own CI could not see it; the derivation reads the path-shaped literal objectstack-ai/objectstack in validate.mjs as a population declaration that names no tracked path. Green on origin/main (9a6a6088, b22e3f76). Everything else on 7bae5b51 is green (16 success, 9 skipped); the earlier by-design red (missing check:pm-fleet-write-* rows) is gone.

Fix, ruled by the seat and sent to the same dev: on this branch, scripts/pm/fleet-write/validate.mjs gains the dispatch-gates: no-path-population -- <reason> marker (the validator reads no tree population; its input is the dispatch payload through env:), the path-shaped literal is spelled so the derivation stops reading it as a declaration, and the same check is run against execute.mjs / dispatch.mjs before pushing; pnpm check:declared-population-live, check:self-test-wired and check:pm-dispatch-gates green locally on the combined tree. One file rides this workflow PR because the gate only runs against the wiring this PR adds; the maintainer's hand-merge covers both.


Generated by Claude Code

…as a slug

`check:declared-population-live` reds on main after PR B: the only path-shaped
literal `scripts/pm/fleet-write/validate.mjs` declares in its own body is the
fixture's `repo: 'objectstack-ai/objectstack'`, a slug that names no tracked
file, so the derivation reads the family as declaring a population that reaches
nothing. The fixture now spells the target as `${TARGET_OWNER}/objectstack`,
which the hint extractor admits nothing out of, so the family declares nothing
of its own and keeps the population it inherits from `RELAY_FILES` in ops.mjs.

No `no-path-population` marker: the family inherits four live leads through
the import channel, and `check:pm-dispatch-gates`' live self-test refuses a
family that carries the marker while naming paths.

Claude-Session: https://claude.ai/code/session_01VhatbEZCV5JJBoU579UBCG
Co-authored-by: Claude <noreply@anthropic.com>

Copy link
Copy Markdown
Collaborator Author

维护者速读(终稿)

席位:总监席(summon #27,session_0129ZpnaBcYZZ51rCvQiXg6C),2026-09-22T15:03Z。这是 #19701(方案 1 中继)的 PR A,.github/workflows/**,等您人合。PR B(#19725)已于 2026-09-22T13:51Z 以 squash 70dccce0 进 main,席位侧 auto 传输在本 PR 落地前保持 direct。

改了什么:新增 .github/workflows/fleet-write.yml——只听 repository_dispatch 的 fleet-write 事件;permissions: {},job 单独 contents: read 供 checkout;四步:checkout(不留凭据)→ node 22 → validate.mjs 校验 payload(经 env: 传入,拒绝即失败、零写入)→ actions/create-github-app-token@v2 按 payload.repo 单仓、issues/pull-requests write + metadata read 铸 token → execute.mjs 执行,步骤结束 token 由 action 的 post 步撤销。lint.yml 加三个自测步骤。另一处:validate.mjs 夹具把 'objectstack-ai/objectstack' 改拼成 ${TARGET_OWNER}/objectstack,修今天 14:10 那次 check:declared-population-live 的红(把夹具字面量误读为路径声明)。

为什么改:云席位拿不到 App 身份(代理替换 Authorization、拒 /app/**),您定的方案 1:席位只发 dispatch,写入在 runner 上以 objectstack-fleet[bot] 落地;组织内任一仓都可作 payload.repo 的目标,无需每仓放桩。

风险与代价:私钥只在 runner 铸 token 时被 action 读取,token 按目标仓与三项权限收窄、单次运行即撤销;发送者门禁 = 能对 objectstack 仓发 dispatch 的人,无名单变量;回滚 = 删两个文件 + 撤 lint.yml 三步,席位侧读到 main 上没有该文件就自动回 direct。⚠️ 探针若因权限拒绝 automerge_enable,再加 permission-contents: write,现在故意不预授。

席位意见:通过。三轮 CI:第一轮红是落地顺序(B 未进 main),第二轮红是 declared-population-live 对夹具字面量的误读,第三轮 dbc820c 全绿:31 个 check-run,23 success、8 skipped(八个都在 EXPECTED_SKIPS 名册上)、0 其他;Lint & Repo Gates 通过即 check:declared-population-live 在接线后的树上通过;check-governed-merges --pr 19726 exit 0(3 个路径 0 命中登记册,+168/−2)。开发的一处偏差——不加 no-path-population 标记,因该族从 RELAY_FILES 继承四个活路径,带标记会与 check:pm-dispatch-gates 的活例矛盾——我认可,改拼夹具字面量足够。

你要做的:①合并本 PR(Squash);②组织变量 OS_FLEET_APP_ID = 5028251;③App objectstack-fleet 仓库范围改为 All repositories。三者齐后我从云席位发一次探针,回读 objectstack-fleet[bot] 后关 #19701。


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 needs-user-decision size/m skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants