Skip to content

docs: correct four false claims about CI coverage and gh pr edit - #956

Merged
mforce merged 2 commits into
mainfrom
docs/928-agents-md-narrative
Sep 25, 2026
Merged

mforce merged 2 commits into
mainfrom
docs/928-agents-md-narrative

Conversation

@mforce

@mforce mforce commented Sep 25, 2026 •

Copy link
Copy Markdown
Owner

Part of #928

Four statements in AGENTS.md were wrong against this tree. This PR corrects
those four and the matching claims in their decision records. Nothing else in
AGENTS.md changes: git diff origin/main -- AGENTS.md touches exactly 4 of its
252 lines, and every other rule paragraph is byte-identical.

gh pr edit works. Exit 0 on gh 2.101.0 against this PR. The rule kept the
gh api -X PATCH fallback for an older gh.

The simulation boot harness is in CI. e2e-smoke.yml runs
tools/simulation/bootstrap.sh and verify-harness.sh against
docker-compose.sim.yml on every pull_request touching src/**, web/**,
tools/simulation/**, deploy/** and the root build inputs. The real gaps are a
PR outside that path filter, and k6-baseline.yml, which is workflow_dispatch
only. The #370 rule said "deliberately not in CI, and nothing tells you when you
break it".

AppHost.Tests is in CI, as ci.yml's apphost matrix leg, and
AppHostModelTests pins the entries the AppHost declares, including
SharedState__Redis__ConnectionString. The real gap is narrower: no test derives
the API's REQUIRED-key set and checks the AppHost supplies it, so a newly required
key surfaces only under aspire run. The #565 rule said "deliberately not in CI".

Activating the dormant MCP tier turns CI red. The #843 rule said that adding
a tool type under Cluckwork.Api.Mcp or mapping MapMcp "makes that description
stale, not red". AdapterTierRealTreeTests.McpTierRow_IsDormantToday asserts
report.Dormant contains that namespace, so activating the tier fails the test.
The rule now says CI turns red and that the assertion is dropped in the same
commit. 843-adapter-tiers.md carried the same claim as "goes stale (not red)"
and now names the failing test, which its own activation checklist already treats
as the signal.

Record changes, all of them corrections rather than moved text: dated amendments
on 370-sim-harness-boot-guards.md and 565-aspire-local-orchestration.md, which
both carried the same two stale claims, the "goes stale (not red)" sentence in
843-adapter-tiers.md, and the shared-state inventory in
271-single-serving-instance.md, which named four #543 registrations and an
ILease port. SharedStateRegistration.cs registers three and no ILease,
because ReportConcurrencyCapRegistration constructs RedisLease and
InProcessLease itself to pin each permit to its granting backend.

Why this is not #928

#928 asked for the inline narrative in the 47 linked rules to move into the
records. That was built and dropped by owner decision. Two Codex review rounds
found 10 rule losses across the moved paragraphs: two in round 1 (#819's
legacy audit backfill instruction, #845's "Insights owns no tables") and eight in
round 2 (the #843 dormant-tier claim, now corrected here; the
same-email-across-farms allowance, the rejected i18n guard's
matching-strategy warning, where AdapterTier.KnownSurfaces validates
privilege, the cross-owner FK name-and-direction key, and the 30-table,
40-adapter and 30-interface fail-closed floors). The move saved about 3% of the
file. Losing a rule silently re-enables a shipped defect, so the trade was not
worth it.

The body is Part of #928, not Closes #928, because the move #928 asks for is
not delivered here. #928 stays open for the owner to close or re-scope.

Guards: dotnet test tests/Cluckwork.Application.Tests --filter "FullyQualifiedName~ImagePin|FullyQualifiedName~RealTree|FullyQualifiedName~Documentation"
34 passed, and dotnet test tests/Cluckwork.Api.IntegrationTests --filter "FullyQualifiedName~SchemaDocsTests" 4 passed.

@mforce

mforce commented Sep 25, 2026

Copy link
Copy Markdown
Owner Author

Accounting for #928

AGENTS.md 8,705 -> 8,392 words (-313, -3.6%), across 47 rule paragraphs
carrying a docs/decisions/ link (45 distinct records; the issue's worklist of
48 rows includes four with no record).

Why the -27% prediction did not hold

The prediction came from word counts. Reading the rules gives a different answer:
the issue's own "what must stay inline" list — the imperative including its
negative form, every identifier, every exception and opt-out, every accepted risk
and "not guaranteed" statement, every wrong-fix warning — already covers most of
the linked-rule text. Twenty-five records are at or near 50 words already and are
imperative end to end. The genuine incident narrative in the remaining twenty came
to about 330 words, and that is what moved. Getting to ~6,300 from here means
dropping identifiers or limits, which the issue rules out and which #921's review
sent a PR back for.

Shortened or corrected (20 records)

record words before after delta
530-multi-farm-tenancy 350 302 -48
782-ci-job-gating 272 246 -26
732-farm-code-rename 219 199 -20
846-adapter-reach-ratchet 213 182 -31
271-single-serving-instance 209 176 -33
775-ci-test-matrix 200 163 -37
565-aspire-local-orchestration 190 201 +11
819-business-record-chronology 188 172 -16
688-i18n-help-label-pairing 174 135 -39
280-seed-and-simulation 157 136 -21
843-adapter-tiers 145 119 -26
394-write-contract-callers 117 100 -17
845-table-owners 103 86 -17
824-style-guard-conversion 102 100 -2
776-backend-coverage 96 87 -9
277-spa-e2e 93 89 -4
847-seam-surface-guard 87 83 -4
370-sim-harness-boot-guards 66 86 +20
347-process-role 65 62 -3
407-writing-a-guard 45 38 -7

Deliberately left (25 records)

record words
514-module-ledger 157
579-suspension-issuance-window 90
264-farm-timezone 89
848-generated-coupling-matrix 80
729-owner-only-farm-configuration 79
146-ci-security-gates 69
269-transient-db-retry-boundary 67
364-credential-epoch-revocation 67
407-migration-freeze 66
260-proxy-trust 65
261-postgres-tls-floor 64
283-migrations-base-provisioning 61
510-jwt-key-boot-check 60
500-audit-actor 58
404-production-logs 57
265-break-glass-recovery 53
332-gss-kerberos 52
283-first-run-admin-provisioning 47
263-migrate-command 47
266-container-health-probe 45
505-audit-events-no-time-partition 44
318-design-time-migration-connection 36
267-container-hardening 35
417-schema-docs 34
351-releases 32

Records that received text: 271-single-serving-instance 1,479 -> 1,578,
280-seed-and-simulation 818 -> 979, 370-sim-harness-boot-guards 254 -> 426,
565-aspire-local-orchestration 1,201 -> 1,334, 732-farm-code-rename 1,466 -> 1,503.

Why each left record was left

  • 514-module-ledger (157) — the nine owner names, the cell shape, the guard
    name, "never widen a reason", the Customer.cs counter-example and the
    Platform hub's membership list are all identifiers or wrong-fix warnings.
  • 579-suspension-issuance-window (90), 264-farm-timezone (89),
    729-owner-only-farm-configuration (79) — four named premises, the
    tzdata/ICU prohibition, the role matrix. Every sentence is a condition an
    agent can break.
  • 269, 364, 407-migration-freeze, 500, 505, 510, 848 — each is
    imperative plus one failure mode. Cutting the failure mode leaves a rule
    nobody can size.
  • 146, 260, 261, 263, 265, 266, 267, 283-first-run-admin-provisioning,
    283-migrations-base-provisioning, 318, 332, 351, 404, 417 — 34 to
    69 words each, already at the target, no narrative to move.

Corrections carried here

Three code-versus-doc mismatches the issue listed, each verified against this
tree rather than argued:

  1. gh pr edit works. Exit 0 on gh 2.101.0 against this PR. The rule now
    says so and keeps the gh api -X PATCH fallback for older gh.
  2. Developer experience: add an Aspire AppHost for local orchestration and observability #565's "the AppHost is deliberately not in CI" is wrong. ci.yml:189
    runs an apphost matrix leg, and AppHostModelTests asserts the
    SharedState__Redis__ConnectionString entry and the
    WithReference(database, connectionName: "Default") relationship. What no job
    covers is narrower: nothing derives the API's REQUIRED-key set and checks the
    AppHost supplies it, so a new required key still surfaces only under
    aspire run. Rule and record now say that.
  3. Sim harness (#243) rotted silently: 4 breakages, no CI ever ran it #370's "deliberately not in CI" is wrong. e2e-smoke.yml runs
    tools/simulation/bootstrap.sh and verify-harness.sh against
    docker-compose.sim.yml on every pull_request touching src/**, web/**,
    tools/simulation/** or deploy/**. The gaps that remain are a PR outside
    those paths and k6-baseline.yml, which is workflow_dispatch only. The file
    also no longer contradicts itself against Daily entry: require grading to reconcile sellable eggs before submit #394.

The preservation check

check.py (kept out of the repo; the issue did not ask for a committed tool)
splits AGENTS.md into rule blocks, groups them by the record each links to,
and runs three tiers.

  • Tier 1, hard. Every backticked token, link target and issue reference
    present before must still be present in AGENTS.md after. Identifiers,
    exceptions and opt-outs do not move. A deliberate drop needs an
    ALLOWED_DROPS entry with a reason.
  • Tier 2. A bare number or CamelCase identifier may leave a rule only if it
    appears in that rule's decision record.
  • Tier 3, report. Negation and accepted-risk vocabulary per rule, before and
    after, so a rule that quietly lost its limit is visible.

Both failing tiers were watched going red on a mutation before the pass started:
deleting RateLimiting:AllowNoTrustedProxies=true from the #260 rule reds tier 1;
deleting the 17/16 live-registration counts from the #271 rule reds tier 2.

Seven deliberate drops, each one an ALLOWED_DROPS entry: UPDATE "Accounts"
(names the superseded hand-guarded procedure; the prohibition "never a raw
UPDATE" stays inline and the record keeps the procedure), and Nabebenta,
naibibenta, maibebenta, benta, <strong>, includes() (Tagalog morphology
evidence for a guard that was built and rejected, held in full by
688-i18n-help-label-pairing.md).

Guards run

  • dotnet test tests/Cluckwork.Application.Tests --filter "FullyQualifiedName~ImagePin|FullyQualifiedName~RealTree|FullyQualifiedName~Documentation" — 34 passed, which covers RenameAccountDocsTests (it pins sentences in 732-farm-code-rename.md and asserts AGENTS.md links it) and TenancyDocsFreshnessTests.
  • dotnet test tests/Cluckwork.Api.IntegrationTests --filter "FullyQualifiedName~SchemaDocsTests" — 4 passed, the Postgres and Redis image-pin tracked-file sweeps plus the schema-docs freshness check.
  • The walk behind Background worker has no single-runner guarantee — double-runs if scaled >1 instance #271's count was re-run rather than copied: grep -rn 'AddSingleton' src/ --include='*.cs' | wc -l gives 19 and AddHostedService gives 1 on 2026-09-25. The record now carries both that and the 2026-08-16 figure of 13; the rule keeps the "trust no written count" warning and no longer carries a number.

Not done

  • The four worklist rows with no decision record (the #632 registry-key rule,
    the #508 audit-ordering rule, which links a plan rather than a record, the
    SPA egg loop and the run commands) are untouched. Shortening a rule with
    nowhere to move its text is deletion, which the issue lists as a non-goal.
  • No new records were created, no rule changed meaning, and every rule is still
    one paragraph.

mforce added a commit that referenced this pull request Sep 25, 2026
 r1)

Codex gpt-6-sol round 1 at 07517a0. Two P1 rule losses restored inline: the
legacy audit backfill instruction (#819) and "Insights owns no tables" (#845).
Both are plain sentences carrying no unique identifier, so the token-level
preservation check passed them.

Method fix: a sentence-level audit now classifies every sentence whose CONTENT
left a rule as moved, reworded, elsewhere, corrected or stays, and fails on an
unclassified loss, a "stays" that is not inline, or a classification that no
longer matches. Re-deleting "Insights owns no tables" reds it while the token
check stays green.

Also narrows the e2e-smoke path-filter claim, rewrites 370's stale
"human-started" passage as dated history, replaces 565's contradicting
"not in CI" line with the real gap, corrects 271's ILease inventory against
SharedStateRegistration.cs and ReportConcurrencyCapRegistration, and replaces
732's "did the opposite on all four counts" with the exact historical facts.
@mforce

mforce commented Sep 25, 2026

Copy link
Copy Markdown
Owner Author

Codex gpt-6-sol round 1 at 07517a0 — all seven accepted and fixed in b67548d

1. P1, AGENTS.md #819 legacy backfill. Restored inline verbatim: "Legacy
audit backfills use only exact whitelisted row events, falling back to the
documented unknown sentinel." Correct call. I classified it as a completed
one-off migration; it reads as an instruction for any future backfill and the
record was the only copy.

2. P1, AGENTS.md #845 Insights. Restored inline: "Insights owns no tables."
Also correct, and the reasoning in the finding is the part I missed. The scanner
accepts a new Insights table the moment someone declares it under that owner, so
this is a design constraint with no guard behind it, not the guard-internal
detail I took it for.

3. P2, e2e-smoke.yml path filter. The claim now reads "a PR outside that
workflow's path filter gets no signal" rather than naming four of the nine paths.

4. P2, 370 record. The "deliberately not in CI / every path into it is
human-started" passage is now dated history ("Until 2026-08-08 that harness was
deliberately not in CI"), and it points forward to the trigger today.

5. P2, 565 record. The contradicting line is replaced with the narrow gap:
AppHost.Tests runs as ci.yml's apphost leg and pins what the AppHost
declares, and no test compares the API's required-key set with the AppHost
wiring, so a new required key surfaces only under aspire run.

6. P2, 271 record. Checked against source before rewriting.
SharedStateRegistration.cs registers IConnectionMultiplexer,
IClaimOnceStore and IFixedWindowCounter, and no ILease;
ReportConcurrencyCapRegistration constructs RedisLease and InProcessLease
itself. The inventory now says two capability ports plus the multiplexer, states
that there is no ILease registration, and explains why #545 did not take one:
pinning a permit to its granting backend needs both backends in hand.

7. P2, 732 record. Replaced with the exact facts: the hand-guarded
UPDATE "Accounts" bumped Version by hand rather than in the domain, wrote no
audit row, and checked neither the slug pattern nor the reserved set. "Did the
opposite on all four counts" was my sentence and it was wrong about Version.

The method, not only the instances

Both P1s passed check.py because it compares TOKENS and a dropped constraint
sentence carries no unique identifier. sentence-audit.py now splits every
changed rule paragraph into sentences, finds each sentence whose CONTENT no
longer survives anywhere in that rule, and requires a verdict of moved,
reworded, elsewhere, corrected or stays. It fails on an unclassified
loss, on a stays that is not inline, and on a classification matching no loss,
so the table cannot rot once a sentence is restored.

Red-tested on the finding it was built for: re-deleting "Insights owns no
tables." fails the sentence audit while check.py stays green on the same
mutation.

The full re-audit is in the PR body. Twenty rules changed, 44 sentences rewritten
with all content retained inline, 58 sentences whose content left a rule, each
classified with where it went. None is classified stays. Two further
restorations came out of it that round 1 did not name, both in the #846 rule:
the walk's coverage of typed lambda, local-function and anonymous-method
parameters, and the top-level handler key including literal HTTP method lists and
named handlers. Both change what an agent believes the guard sees.

Guards after the fixes: dotnet test tests/Cluckwork.Application.Tests --filter "FullyQualifiedName~ImagePin|FullyQualifiedName~RealTree|FullyQualifiedName~Documentation"
34 passed, and dotnet test tests/Cluckwork.Api.IntegrationTests --filter "FullyQualifiedName~SchemaDocsTests" 4 passed.

AGENTS.md is 8,435 words at b67548d, up from 8,392 at 07517a0 because the
restorations and the path-filter correction cost more than they saved.

Three statements in AGENTS.md were wrong against this tree, and each was
verified before rewriting.

gh pr edit works. Exit 0 on gh 2.101.0 against PR #956. The gh api -X PATCH
fallback stays for older gh.

The simulation boot harness is in CI. e2e-smoke.yml runs bootstrap.sh and
verify-harness.sh against docker-compose.sim.yml on every pull request touching
src, web, tools/simulation, deploy and the root build inputs. The real gaps are
a PR outside that path filter and k6-baseline.yml, which is dispatch only.

AppHost.Tests is in CI, as ci.yml's apphost matrix leg, and AppHostModelTests
pins the entries the AppHost declares. The real gap is that no test derives the
API's required-key set and checks the AppHost supplies it, so a newly required
key surfaces only under aspire run.

The 370 and 565 records carried the same two stale claims and now carry dated
amendments instead. The 271 record's shared-state inventory named four
registrations and an ILease port; SharedStateRegistration.cs registers three and
no ILease, because ReportConcurrencyCapRegistration constructs RedisLease and
InProcessLease itself to pin each permit to its granting backend.

Part of #928
@mforce
mforce force-pushed the docs/928-agents-md-narrative branch from b67548d to 7f89aef Compare September 25, 2026 18:31
@mforce mforce changed the title docs: move inline narrative out of AGENTS.md rules into their decision records docs: correct three false claims about CI coverage and gh pr edit Sep 25, 2026
AGENTS.md said that adding a tool type under Cluckwork.Api.Mcp or mapping MapMcp
"makes that description stale, not red". AdapterTierRealTreeTests.McpTierRow_IsDormantToday
asserts report.Dormant contains that namespace, so activating the tier fails the
test. The rule now says CI turns red and that the assertion is dropped in the
same commit. The 843 record's "goes stale (not red)" sentence carried the same
claim and now names the failing test, which its own activation checklist already
treats as the signal.
@mforce mforce changed the title docs: correct three false claims about CI coverage and gh pr edit docs: correct four false claims about CI coverage and gh pr edit Sep 25, 2026
@mforce
mforce merged commit 7d73bfe into main Sep 25, 2026
15 checks passed
@mforce
mforce deleted the docs/928-agents-md-narrative branch September 25, 2026 19:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant