Skip to content

fix(pm): the governed-merges audit covers all four governed repos, attributes through the channel the session has, and answers the pre-arm question - #9713

Merged
os-support-ai merged 2 commits into
mainfrom
claude/issue-9619-governed-audit-scope-attr-test
Aug 18, 2026
Merged

fix(pm): the governed-merges audit covers all four governed repos, attributes through the channel the session has, and answers the pre-arm question#9713
os-support-ai merged 2 commits into
mainfrom
claude/issue-9619-governed-audit-scope-attr-test

Conversation

@os-support-ai

Copy link
Copy Markdown
Collaborator

Fixes#9619
Fixes#9550

One file changed: scripts/pm/check-governed-merges.mjs (the self-test lives inside it). No changeset — internal PM audit tooling, nothing published; skip-changeset applied.

What landed, in three parts

1. Multi-repo scope (#9619)

One invocation now sweeps all four governed repos. The register of repos is declared beside the register of surfaces:

repocheckout resolved from
objectstack-ai/objectstackthe script's own root (never the working directory)
objectstack-ai/objectuithe sibling directory beside it
objectstack-ai/cloudsame
objectstack-ai/objectossame

--repo-root ID=PATH relocates any of them; --repos a,b narrows the sweep. A checkout whose origin remote is not the expected slug is treated as absent, never audited under the wrong name.

An absent, wrong-origin, or unreadable checkout reports as UNAUDITED and the sweep exits 2. It never renders as a clean repo — nothing was found there because nothing was looked at (#4690). Live on this container, where cloud is not checked out:

governed-merges sweep: 17 governed merge(s) since 2026-08-18T04:10:09Z across 3/4 governed repo(s)
scanned 103 mainline commit(s); 17 API lookup(s).
✓ audited objectstack-ai/objectstack — tip cd455c83b @ 2026-08-18T15:31:31+00:00; 89 mainline commit(s) in window
✓ audited objectstack-ai/objectui — tip 9f23d2b32 @ 2026-08-18T07:12:27+00:00; 8 mainline commit(s) in window
✓ audited objectstack-ai/objectos — tip 511e2e440 @ 2026-08-18T19:27:33+08:00; 6 mainline commit(s) in window
⚠️ UNAUDITED objectstack-ai/cloud — no git checkout at /home/user/cloud
⛔ 1 governed repo(s) were NOT audited. An unaudited repo is not a clean repo (#4690):
nothing was found there because nothing was looked at.

That sweep surfaces two objectui and two objectos governed merges that no audit output could previously see at all.

2. Attribution through a channel the session actually has (#9619)

Channels are tried in order and the first success wins: env token (GITHUB_TOKEN / GH_TOKEN), then anonymous REST — no authorization header, which is what makes a container with no usable token resolvable for the public repos. When every channel fails, the entry still prints (marked UNAVAILABLE, never silently blank) and the reason is stated once per repo as a named line instead of the per-entry UNRESOLVED that used to bury the list it was attached to:

⚠️ attribution unavailable for objectstack-ai/cloud — 3 entries (PR #101, #102, #103);
channels tried — env token (GITHUB_TOKEN/GH_TOKEN): HTTP 401; anonymous REST: HTTP 403.

A measured correction to the dispatch premise. The assumption was that anonymous REST through the session proxy already works, verified with curl. It does — for curl. It does not for this script, because node's global fetch does not read HTTPS_PROXY, and that transport gap is the actual cause of the 401 the card reported. Four readings on the same URL, same container, 2026-08-18:

howresult
curl (reads HTTPS_PROXY)200, merged_by present
node fetch, no flag, anonymous403
node fetch, no flag, with the env token401
node fetch, NODE_OPTIONS=--use-env-proxy200, merged_by present

So a curl probe proves nothing about what this script sees, and the env token in an agent container is the literal placeholder string proxy-injected that the proxy swaps for a real credential — bypass the proxy and it is a bad token. scripts/check-required-contexts.mjs hit the identical trap under #9642 and its header already names this file as sharing it; that reading is now acted on rather than only warned about. The flag has to be present at process start (assigning process.env.NODE_USE_ENV_PROXY from inside is too late — measured, still 403), so sweep mode re-execs itself once with --use-env-proxy, guarded by process.allowedNodeEnvironmentFlags so an older node prints the hint instead of crashing on a bad option. --test and --self-test never re-exec; they touch no network.

Result, same window as above: 17 of 17 entries attributed, 0 UNAVAILABLE. Both channels verified live — (via env-token) normally, and (via anonymous) with both token variables unset.

What this deliberately does not claim.#9619's second comment measured that merged_by cannot distinguish a maintainer merge from a seat merge, because the maintainer also operates the seat accounts — objectui PR #5188 read merged_by os-steve and the maintainer's answer was 「5188 是我合并的」. A resolved column that reads as authoritative would be worse than the honest gap. So every sweep that resolves anything now prints:

ℹ️ merged_by names an ACCOUNT, not a principal — the maintainer also operates the seat accounts
(measured 2026-08-18: objectui PR #5188 read merged_by os-steve; asked directly, the maintainer
answered 「5188 是我合并的」). The column PROMPTS recognition; it never settles it.

The three options for giving merges a real discriminator stay recorded on the card; none is taken here.

3. The --test pre-arm predicate (#9550)

node scripts/pm/check-governed-merges.mjs --test PATH... answers "would a PR touching these paths be governed?" from GOVERNED_SURFACES — the derivation primitive for the standing rule the reporting seat adopted. The incident's root cause was recall in place of derivation; derivation is now one command:

$ node scripts/pm/check-governed-merges.mjs --test AGENTS.md # the #9527 file list
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 (#9495 regime).
No seat flips it ready, enqueues it, or arms auto-merge (AGENTS.md Prime Directive #14).
$ echo $?
3

Usable as --test $(gh pr diff --name-only PR), and --json for scripting. The register is repo-agnostic, so it answers for a PR in any of the four repos.

Exit codes

The refusal-to-read-as-clean contract survives unchanged; code 2's stated cause widens to cover the new "unaudited repo" case, which is the same class of incompleteness.

modecodemeaning
sweep0swept COMPLETELY — every governed repo audited, every entry attributed. 0 entries and 40 entries both exit 0
sweep1could not sweep at all — bad args, unreadable --since-ref, not one checkout resolved
sweep2swept but INCOMPLETE — a repo unaudited and/or an entry unattributed on every channel
--test0the paths are NOT governed
--test3the paths ARE governed. Deliberately not 1 or 2, so a governed verdict cannot be misread as an environment complaint
--test1no paths given. Silence is never a "not governed" answer

Header case law

#9550's measured timeline is added to the institutional-memory section the header collects — the widening at 07:32:55Z, the ready flip at 07:51:53Z (+19 min), the enqueue 13 s later, and the merge-queue removal on a conflict against the very commit that had made the file governed. The existing entries are preserved verbatim, along with the note that it was the conflict and not the discipline that stopped it.

Verification

All at 31818e0, the branch head.

  • pnpm check:pm-governed-merges77 assertions, up from 25 on main. New coverage: four-repo resolution (absent / wrong-origin / relocated), the proxy re-arm plan in all five directions, the channel chain and its single named fallback line, the --test predicate (governed, mixed, not-governed, six near misses, one case per surface), and the exit table.
  • pnpm check:cross-package-test-inputs — 33 self-test cases, 12 packages OK.
  • pnpm check:pm-dispatch-gates — 284 cases. Not named by the path derivation, run because its own self-test reads this file for watch hints; the skills/** hint assertion still holds.
  • pnpm check:nul-bytes — 6203 files, no raw control bytes; plus a direct control-character scan of the changed file.
  • Union re-derived with node scripts/pm/dispatch-gates.mjs on the real change set (1 path).

Reverse verification — three ablations, each restored and confirmed byte-identical afterwards, with the restored leg re-run green. No rebuild is involved: this script is executed directly as source and its self-test exercises the same file, so there is no dist/ for a stale artifact to hide in.

ablationobserved
absent checkout classified audited3 named failures, including the clean-window refusal
anonymous channel removed from the chain4 named failures naming the surviving channel list
--test never answers governed8 named failures, and the CLI answered 0 where it must answer 3

Draft, and it stays draft: scripts/pm/ is not a governed surface (--test scripts/pm/check-governed-merges.mjs answers exit 0, pinned in the self-test), so this takes an ordinary queue landing after PM review — but the PM lands it, not this seat.


Generated by Claude Code

…utes through the channel it has, and answers the pre-arm question
Three deliverables in the one audit script:
1. Multi-repo scope (#9619). One invocation sweeps objectstack, objectui,
cloud and objectos, resolved from the script's own root rather than the
working directory, so the answer no longer depends on where it is run. An
absent or wrong-origin checkout reports as UNAUDITED and exits 2 — never as
a clean repo (#4690).
2. Attribution through a channel the session actually has (#9619). Channels
are tried in order (env token, then anonymous REST) and the transport trap
is fixed with them: node's fetch does not read HTTPS_PROXY, so a proxied
run read 401/403 on every channel and looked like a credential problem.
Sweep mode re-execs once with --use-env-proxy. When every channel fails the
reason is stated ONCE per repo as a named line instead of a per-entry
UNRESOLVED that buried the list.
3. The --test pre-arm predicate (#9550). `--test <paths...>` answers "would a
PR touching these be governed?" from GOVERNED_SURFACES: exit 3 governed,
exit 0 not governed, exit 1 on an empty path list. The incident it comes
from was recall in place of derivation; derivation is now one command.
The header gains #9550's measured timeline and the reading that merged_by
names an account, not a principal — the maintainer also operates the seat
accounts, so a resolved column prompts recognition and never settles it.
Self-test: 25 -> 77 assertions.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WswfK2yNYT9hNnMH6TzAwL
…nel as a named failure
The channel-shape assertion indexed both[1] directly, so an ablation that
removes the anonymous channel crashed the self-test with a TypeError instead
of naming which pin broke. Red either way; only one of them tells the reader
what is wrong.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WswfK2yNYT9hNnMH6TzAwL
@os-support-aios-support-ai added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Aug 18, 2026 — with Claude
@os-support-ai
os-support-ai marked this pull request as ready for review August 18, 2026 16:37
@os-support-ai
os-support-ai added this pull request to the merge queueAug 18, 2026
Merged via the queue into main with commit 9bfd541Aug 18, 2026
23 of 24 checks passed
@os-support-ai
os-support-ai deleted the claude/issue-9619-governed-audit-scope-attr-test branch August 18, 2026 16:57
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/lskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants

@os-support-ai@claude