Skip to content

tooling(pm): govern hotcrm in check-governed-merges (#14867) - #14987

Merged
os-steve merged 3 commits into
mainfrom
claude/issue-14867-governed-repos-hotcrm
Sep 3, 2026
Merged

tooling(pm): govern hotcrm in check-governed-merges (#14867)#14987
os-steve merged 3 commits into
mainfrom
claude/issue-14867-governed-repos-hotcrm

Conversation

@claude

@claude claude Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Fixes #14867

GOVERNED_REPOS in scripts/pm/check-governed-merges.mjs gains objectstack-ai/hotcrm.
Single file; scripts/pm/** is not itself a governed path.

The ruling this executes

Maintainer ruling recorded by the director seat, comment 5523300327, 2026-09-03. Provenance:
maintainer, live PM chat, replying to decision batch #20 item 3 (this card and #14881 presented
as one question) with the recommendation 纳入; verbatim reply 「其他同意」 — adopts 纳入. Ruled
consequences, quoted from the record:

GOVERNED_REPOS in scripts/pm/check-governed-merges.mjs gains hotcrm; the header's quoted definition names it; the :288 prose note is replaced by the configuration it described; the hotcrm seat's hand-merge posture for its governed files is now audited rather than assumed, and the director seat's next audit window includes it. Every "CLEAN WINDOW" published before this change is re-read as "clean over the four configured repos".
Execution: domain:skills lane, S, scripts/pm/** (not a governed path itself) — one PR; skip-changeset; Clause-②: no. The sibling half lands under #14881.

The rationale cited in the header, from the same record: the governed-surface definition already
reads "agent instruction files, judged the same across repos"; the 2026-08-18 ruling's list
(objectui, cloud, objectos) was an enumeration of the repos in view that day, not an exclusion.

The 2026-08-18 quotation in the file header (「任何对 agents.md 等文件的修改…包括 objectui
cloud仓库」) is untouched — a verbatim ruling rewritten is a ruling rewritten. What names
hotcrm is the header's own derived statement of the set, with the 2026-09-03 ruling cited beside
it.

What the change buys, measured

The card's central claim — absence of coverage is indistinguishable from clean coverage — is
false after this change. The live sweep in this container, on the final commit:

governed-merges sweep: 37 governed merge(s) since 2026-09-02T16:15:51.877Z across 2/5 governed repo(s)
  ✓ audited  objectstack-ai/objectstack — tip 5bc2f2727 ...
  ✓ audited  objectstack-ai/objectui — tip 0e3b3be09 ...
  ⚠️  UNAUDITED  objectstack-ai/cloud — [no-checkout] no git checkout at /home/user/cloud
  ⚠️  UNAUDITED  objectstack-ai/objectos — [no-checkout] no git checkout at /home/user/objectos
  ⚠️  UNAUDITED  objectstack-ai/hotcrm — [no-checkout] no git checkout at /home/user/hotcrm
  ⛔  3 governed repo(s) were NOT audited. ...
⚠️  sweep INCOMPLETE — 3 governed repo(s) unaudited (objectstack-ai/cloud: no-checkout,
    objectstack-ai/objectos: no-checkout, objectstack-ai/hotcrm: no-checkout).

2/5 where it read 2/4; the hotcrm row is the same loud ⚠️ UNAUDITED the other unreadable
repos get, and the footer counts three refusals where it counted two. Before this change hotcrm
produced no row at all and the sweep still exited 2 on the other two — which is exactly the
silence the card describes.

The sharpest before/after is the one argument that validates ids against the register. Same
invocation, the base file versus this one:

invocation base 5bc2f27 this branch
--repos hotcrm ❌ --repos names no governed repo: hotcrm. Known: objectstack, objectui, cloud, objectos. (exit 1) audits hotcrm; with no checkout in this container: ❌ no governed repo could be audited — not one checkout resolved / objectstack-ai/hotcrm [no-checkout] (exit 1)
--repo-root hotcrm=PATH pointed at a checkout whose origin is another repo n/a objectstack-ai/hotcrm [wrong-origin]: the checkout at ... has origin objectstack-ai/objectstack, not objectstack-ai/hotcrm — hotcrm goes through the same #13423 identity discipline as the other four

Everything in the diff

  • GOVERNED_REPOS gains the hotcrm entry, appended last so the positional
    GOVERNED_REPOS[1] / [2] references in --self-test keep pointing at the same repos.
  • The header's derived statement of the set names hotcrm and cites the 2026-09-03 ruling, with
    the incident the gap cost recorded in the file's own idiom.
  • The four/five counts the register contradicts, all in the same file: the opening summary, the
    usage line, the deepening note, the --since-ref per-repo-window rationale, the constant's
    JSDoc, the --test repo-agnostic note, and the --self-test summary sentence. A count saying
    "four" beside a register holding five is the drift this file exists to make loud.
  • The :288 dead-mirror measurement: see the deviation below.
  • --self-test: 241 assertions at the merge base, 243 here.
    • four-governed-repos-declared becomes five-governed-repos-declared over the new ordered set
      — the pin doing its job, not a test weakened.
    • new: hotcrm-is-a-governed-repo-with-its-slug (#14867) pins membership by id and slug
      separately, so a later removal reds a check that names hotcrm rather than one that reads as a
      reordering.
    • new: a-configured-repo-with-no-checkout-is-UNAUDITED-never-silent pins the property this card
      bought, in the resolver fixture.

Reverse verification

Fix committed first, then the register entry deleted, --self-test re-run, then restored.
Mutation proven on disk (entry lines 1 -> 0; blob 8f1afdf21a -> 49365e96c1); restore proven on
disk (blob back to 8f1afdf21a, git diff HEAD empty). Predicted direction RED; observed:

✗ check-governed-merges --self-test — 4 failure(s)

  • five-governed-repos-declared: objectstack,objectui,cloud,objectos
  • hotcrm-is-a-governed-repo-with-its-slug (#14867): ["objectstack=objectstack-ai/objectstack","objectui=objectstack-ai/objectui","cloud=objectstack-ai/cloud","objectos=objectstack-ai/objectos"]
  • all-five-repos-are-resolved-not-just-the-self-repo:
  • a-configured-repo-with-no-checkout-is-UNAUDITED-never-silent: null

The second commit exists because the first run of this ablation did not read like that. It
threw a TypeError on byId.hotcrm.status and exited 1 — loud, but assert in this suite
COLLECTS failures and prints them at the end, so the throw aborted the run before any of the
named pins were printed. A removal must produce named red lines, not a stack trace over them; the
fixture now reads the row defensively, and the four lines above are the result.

Deviation from the dispatched route, declared

The dispatch asked me to "replace the prose that described 'hotcrm is not configured'" at :288.
Measurement falsifies that description of the note. The only occurrence of hotcrm in the
base file is inside the 2026-08-30 dead-mirror measurement, and it is not a statement that hotcrm
is unconfigured — it is a control-group reading: "objectos, hotcrm, objectui and
objectstack all resolved in the same batch, so this was a scope change for one repo and not a
broken channel." That sentence is a record of what three probes returned, it is still true, and it
is load-bearing (it is what rules out a broken channel). The dispatch's own instruction — "delete
only what the configuration now states" — therefore deletes nothing there.

So the measurement stays verbatim and the note now says what that batch was: on 2026-08-30 hotcrm
was probed by hand, as a control this sweep had no row for, and it was not configured here
until the 2026-09-03 ruling. Rewriting a measured reading to satisfy a prose instruction is the
same class of error as editing a quotation, one level down.

Falsified mechanism assumption, for the record

--since-ref ID=REF ids are not validated against GOVERNED_REPOS. resolveWindow puts a
pinned id straight into resolveRefDate(ref, repoId); only --repos checks membership. Measured:
--since-ref hotcrm=SHA returns the byte-identical refusal on the base file (where hotcrm is
unknown) and on this branch (where it is governed) — does not resolve to a commit in any repo it names. So that argument picked up nothing automatically, in either direction. Stated rather than
changed: making it validate is a behaviour change no ruling covers.

Verification

Gate family re-derived on the final commit with
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack (no paths), harvested with
--commands so no spelling or section is dropped: 22 families. Every exit code captured by
redirect before any pipe. Verdict lines are quoted in the report comment on the card.

check:pm-governed-merges✓ check-governed-merges --self-test: 243 assertions.

node scripts/check-test-completeness.mjs is NOT MEASURED locally, in its own words:
PREREQUISITE NOT MET — this gate grades a saved 'turbo run test' log, and no log was named (exit
3, its documented not-a-finding code).

Repo-wide ESLint was run in full rather than narrowed, through the shared verify lock:
pnpm exec eslint . --no-inline-config --format jsonVERDICT command-exit 0 · held the lock 72s,
5824 files linted, 0 errors, 0 warnings, the changed file present in that population.

Mirror site found, not widened

.claude/skills/pm-dispatch/SKILL.md:723 carries a prose mirror of the governed-repo set
(「objectui、cloud、objectos 一并在内」) that this change makes stale. It is a governed surface,
so touching it here would fork this PR. A second site says the same thing as a count —
.claude/hooks/guard-governed-enqueue.sh:54, "the same call answers for all four governed repos".
Both are filed as #14984 (unassigned, unlabelled) rather than folded in. check:pm-governed-prose
does not cover them: its scope is GOVERNED_SURFACES, not GOVERNED_REPOS — verified by running it
green on this branch.

🤖 Generated with Claude Code

Generated by Claude Code


Generated by Claude Code

`GOVERNED_REPOS` gains `objectstack-ai/hotcrm`, per the maintainer ruling
recorded on #14867 (2026-09-03, verbatim 「其他同意」 adopting 纳入). The
governed-surface definition already read "agent instruction files, judged the
same across repos"; the 2026-08-18 ruling's list (objectui, cloud, objectos)
was an enumeration of the repos in view that day, not an exclusion.

The gap this closes is a silence, not a wrong row: a CONFIGURED repo that
cannot be read prints a loud `⚠️ UNAUDITED` row, while a repo that was never
configured prints nothing at all — indistinguishable in the output from a repo
that swept clean. Meanwhile the `repo:hotcrm` seat hand-merged that repo's
`AGENTS.md` chain as governed with no post-merge audit behind it.

- the header's derived statement of the set names `hotcrm` and cites the
  2026-09-03 ruling beside it; the 2026-08-18 quotation is untouched;
- the four/five repo counts in the header, the usage line, the `--since-ref`
  rationale, the constant's JSDoc and the `--test` note follow the register;
- the `:288` dead-mirror measurement keeps its recorded batch verbatim and now
  says what that batch was (a hand probe of a then-unconfigured control);
- `--self-test`: the ordered-ids pin moves to the new set (the pin doing its
  job), plus two new assertions — `hotcrm` pinned by id AND slug so a later
  removal reds a check that names it, and a fixture pinning that a configured
  repo with no checkout renders UNAUDITED rather than nothing.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019RfFHiRCSs3JXLK4cwcfox
… stack

`assert` in `--self-test` COLLECTS failures and prints them at the end, so a
throw inside a fixture aborts the run before any collected failure is shown.
Measured on the #14867 reverse verification: deleting the `hotcrm` register
entry made `byId.hotcrm` undefined, and the new no-checkout fixture threw a
TypeError — exit 1, loud, but the two pins that NAME hotcrm never printed.

Read the row defensively so a removal reds as three named lines instead.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019RfFHiRCSs3JXLK4cwcfox
@os-steve
os-steve added this pull request to the merge queue Sep 3, 2026
Merged via the queue into main with commit 5d4d55a Sep 3, 2026
31 checks passed
@os-steve
os-steve deleted the claude/issue-14867-governed-repos-hotcrm branch September 3, 2026 17:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

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

Projects

None yet

2 participants