Skip to content

docs(corpus): launchpad/docs/corpus/AGENTS.md -- the corpus authoring node (#636) - #1462

Merged
tucktuck101 merged 1 commit into
launchpadfrom
task/636-corpus-agents-md
Aug 27, 2026
Merged

docs(corpus): launchpad/docs/corpus/AGENTS.md -- the corpus authoring node (#636)#1462
tucktuck101 merged 1 commit into
launchpadfrom
task/636-corpus-agents-md

Conversation

@serina-mcfall

Copy link
Copy Markdown

Summary

Creates launchpad/docs/corpus/AGENTS.md, the first hand-authored node in the documentation corpus, giving a cold-start agent the instructions to create, update and retire one node. Its YAML front matter is validated by the checker #623 merged, so this is machine-checked input rather than prose. Also replaces one test in that checker's suite which asserted the corpus was empty and therefore could not survive the first authored node.

Related issue

Closes #636

Issue type

Task


Agent provenance

Field Value
Harness / provider Claude Code
Model claude-opus-5[1m]
Session reference worklog sid 0db40467
Initiating human @serina-mcfall

Objective

Create launchpad/docs/corpus/AGENTS.md as the canonical instruction node for authoring corpus nodes, satisfying #605's criterion that it enable create/update/retire without oral guidance.

Impacted components

launchpad/docs/corpus/AGENTS.md
launchpad/plans/2026-08-26-issue-636-corpus-agents-md.md
launchpad/project-intelligence/corpus/tests/test_validate.py

Approach and rejected alternatives

The document links the schema, the ADRs and CONTRACT.md rather than restating them. A second copy of an enum or a field matrix drifts silently, and the checker never reads body prose — so a stale copy would stay green forever. What the document does add is the part living nowhere else: what the checker actually establishes, and what a passing run does not mean.

Rejected: restating the front-matter contract inline. More convenient for a reader, but it creates a second source of truth for values the schema already owns, with no mechanism to detect divergence.

Rejected: putting the repository revision in a provenance front-matter field. The DoD asks for provenance, but node.schema.json sets additionalProperties: false and defines no such field — adding one fails validation. The schema calls the evidence array the provenance ledger, so the revision is recorded there as a commit citation.

Scope note. The plan barred touching #623's suite. One test had to change anyway: test_real_corpus_root_currently_has_no_content_outside_schema asserted the corpus root was empty, which this issue necessarily falsifies, and it runs in CI. The widening was authorised and is recorded in the plan's LEFT OUT section, with review-tests added to the review roster as a consequence.

Verification

Command run:

python3 launchpad/project-intelligence/corpus/validate.py
python3 -m unittest discover -s launchpad/project-intelligence/corpus/tests -p "test_*.py"
python3 -m unittest discover -s launchpad/docs/corpus/schema/tests -p "test_*.py"

Raw output:

UNVERIFIED  corpus-agents: evidence entry 1, citation 1: is a commit reference, which names no openable file
PASS  corpus validation found no errors; 1 item(s) reported unverified
real-corpus exit=0

............................FAIL  corpus root does not exist: .../tests/fixtures/does-not-exist-anywhere
...................................................
----------------------------------------------------------------------
Ran 79 tests in 0.314s

OK

...........................
----------------------------------------------------------------------
Ran 27 tests in 0.033s

OK

The single UNVERIFIED notice is the provenance entry's commit citation, which names no openable file by nature. The FAIL corpus root does not exist line is a test's captured stdout for the missing-root case, not a failing test — the suite reports OK.

The create/update/retire procedures were also exercised literally against a scratch corpus root, with a negative control proving that root was the one scanned:

=== NEGATIVE CONTROL: prove this root is the one being scanned ===
FAIL  charter-bad: schema violation at type: failed 'enum' constraint (...)
exit=1

=== RETIRE alpha per the procedure: status change, file retained ===
PASS  corpus validation clean
exit=0 (inbound edge from beta still resolves)

=== What the doc WARNS about: delete instead of retire ===
FAIL  charter-beta: relationship target 'charter-alpha' does not match any known node id
exit=1
  • Tests or checks were run and the raw output is pasted above
  • The diff is confined to the scope of the linked issue
  • No secrets, keys, tokens or hostnames were added to tracked files

Not verified

  • No agent harness was tested reading this file as its resolved AGENTS.md. preflight_core.py resolves it as the nearest rules file for the subtree, but it resolves the path without parsing content, so whether YAML front matter degrades the file for a harness that actually reads it as instructions is unknown.
  • relationships.schema.json was not read directly. node.schema.json states a test guards the two relationship enums against drift; that test was not run.
  • Live GitHub status of the referenced issues was not re-checked at time of writing. The Scope table claims none of task: document corpus standard for atomicity #1307task: define the threat model corpus template #1351 have merged.
  • Whether developer belongs in audiences alongside agent and reviewer was not decided.
  • The final cross-model re-verification of the seven review conditions had not returned when this body was written. This PR is a draft until it does.

Security implications

No change to exposure or blast radius. The change adds one Markdown document and edits one test; no runtime code, no credentials, no network surface, no new CI permissions. The corpus validate workflow triggers on pull_request rather than pull_request_target, so this branch's content executes with fork permissions and no repository secrets.

One documentation-level note with a security flavour: the citation table now states explicitly that a GitHub link pinned to a full SHA is checked syntactically only — the validator never contacts GitHub, so a link naming a file that has never existed passes. That was previously implied to be verified, and an author trusting the old wording could have cited a nonexistent source.

Escalations

Raised rather than decided:

  1. audiences. Two independent reviewers disagreed. The adjudicator declined it — nothing in the tree consumes the field. The cross-model final pass called it merge-blocking, since the document instructs reviewers directly. Resolved in favour of the latter (reviewer added); developer left open. This is taxonomy precedent for the remaining 45 corpus nodes.
  2. review-docs does not exist. The review gate's roster selects it for .md paths and no such agent exists, so the gate fails closed on every docs-heavy change. Not specific to this PR — it will block the whole corpus rollout.
  3. Forward references to unlanded issues (task: document corpus standard for atomicity #1307task: define the threat model corpus template #1351, task: document corpus standard for generated content #1316, task: encode ADR-0029's claim-type classification and unestablished/flagged state in #605's schema/validator #1410, task: document README.md #639, task: document corpus standard for evidence #1314, bug: corpus validator accepts path:line citations whose line does not exist #1459) are cited inline in the Scope section. Honest today, but the list ages; the final review suggested a generated or date-scoped mechanism eventually.
  4. type: agent vs governance. Decided by the initiating human as precedent for the remaining nodes; governance was the rejected alternative on the grounds it would eventually absorb every process document and stop discriminating.
  5. bug: corpus validator accepts path:line citations whose line does not exist #1459 was filed during this work — the validator accepts path:line citations whose line does not exist. This PR routes around it by preferring bare paths rather than changing the validator.

@serina-mcfall serina-mcfall added the by:agent Filed or authored by an AI agent, not a human label Aug 26, 2026
@serina-mcfall
serina-mcfall marked this pull request as ready for review August 26, 2026 04:50
@tucktuck101 tucktuck101 self-assigned this Aug 26, 2026

@tucktuck101 tucktuck101 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Request changes

High — the provenance-update shortcut can certify stale evidence

launchpad/docs/corpus/AGENTS.md:338-344, 400-404 says an empty git diff --name-only <recorded-sha> -- <the ledger's paths> result means every cited source is unchanged and every claim still stands. That is false for valid citation forms this document permits. A path:line citation is not a filesystem pathspec without normalization, while commit, graph-edge, tool-result, external URL, and GitHub-link citations cannot be checked by that command at all. The command can return empty while a cited source changed or was never checked.

Limit the shortcut to normalized repository-file paths; explicitly exclude non-file citations; and state only that the selected files are unchanged. Re-verification, not an empty path-only diff, establishes that a FACT still holds.

Medium — the citation table misattributes seven validator forms to a six-form contract

launchpad/docs/corpus/AGENTS.md:190-205 says CONTRACT.md defines six citation shapes, then presents seven rows after combining file line/range and adding GitHub links plus external URLs. launchpad/project-intelligence/CONTRACT.md:80-92 defines the six contract forms as file range, file line, bare path, graph edge, tool result, and commit. GitHub/external URLs are validator-recognized inputs, not forms defined by that contract.

Separate the contractual six shapes from the validator's additional URL handling so the new governing document has one unambiguous taxonomy.

Medium — a core instruction lacks a supporting ledger entry

launchpad/docs/corpus/AGENTS.md:127-130 directs authors to keep one independently maintainable idea per node and file newly discovered second concepts as separate tasks. The document requires a front-matter evidence entry for every substantive claim (:156-158), but its only #636 TEAM_KNOWLEDGE entry (:82-84) supports the recorded-revision requirement, not this one-node procedure. The linked issue makes independent maintainability a definition-of-done requirement.

Add a correctly classified evidence entry for the one-node rule, attributed to #636 or a more authoritative accepted source.

What looks good

  • Front matter uses schema-defined fields and valid conditional evidence fields; omitting relationships is correct for the first node.
  • The document correctly distinguishes structural citation validation from semantic support and warns that non-fatal UNVERIFIED notices are not proof.
  • The create/update/retire structure, stable-ID guidance, and retirement relationship handling are clear.
  • The replacement discovery test removes the obsolete empty-corpus assumption, compares against an independent walk, and retains the fixture test as the controlled exclusion proof.

Panel: fable + codex.

@tucktuck101 tucktuck101 removed their assignment Aug 26, 2026
serina-mcfall added a commit that referenced this pull request Aug 26, 2026
#636)

Three sibling agents authored corpus nodes following this document literally, and
reported what broke. These four are the defects that produced real false claims in
their work, not stylistic complaints. Filed as #1471; fixed here because #1462 is
still open and 45 further nodes would inherit them.

1. THE ONLY-NODE JUSTIFICATION WENT FALSE, AND TWO AGENTS COPIED IT.
   "A node with no sibling to point at correctly declares none" read as a general
   rule. It was a fact about one moment -- when this was the corpus's only node --
   and stopped holding as soon as a second existed. Both the #1309 and #1307 agents
   reproduced the reasoning and wrote a justification that was false at the time
   they wrote it; their adjudicators caught it, not their own reviews. Now says to
   enumerate what exists and give the real reason, and records why.

2. AN ISSUE-ONLY SOURCE HAD NO HONEST CLASS. The citation table lists tool results as
   legitimate while "Three things a passing run does not mean" says a FACT resting
   only on UNVERIFIED citations "has not been checked by anything", with one named
   exception. A claim whose only source is a GitHub issue has no openable form and
   cannot be SHA-pinned, so both passages applied and disagreed. The #1307 agent hit
   this with nothing honest to choose. Now states it: TEAM_KNOWLEDGE with provided_by
   naming the issue, which is what ADR-0029 requires of GitHub history anyway.

3. A RANGE WAS READ AS A MAPPING. The not-covered table paired eight subjects with
   "#1307-#1351", which is 45 issues and no subject-to-issue pairing at all. The
   #1307 agent cited this table for nine invented mappings -- exactly the "FACT cited
   to a file that does not discuss the claim" failure the document warns about two
   sections earlier. Now says outright that it is a range, not a mapping, and gives
   the search command.

4. CREATE STEP 3 ROUTED SOMETHING STEP 8 DID NOT ACCEPT. Step 3 sends
   expected-but-unverified items to the scope section; step 8 said only "state what
   the node does not cover". A boundary and a confidence disclosure are different
   things, so working the steps in order left that category homeless. Both the #1307
   and #1309 agents reported it independently. Step 8 now names both.

Verified: schema errors 0, 21 ledger entries, validator exit 0, 79 tests OK.

Refs #636 #1471

Signed-off-by: Serina Mcfall <serina.mcfall@gmail.com>
@serina-mcfall

Copy link
Copy Markdown
Author

Held in draft deliberately — the cross-model gate has not run

Do not mark this ready for review yet. This is not an oversight.

The review pipeline for corpus work under #605 requires a cross-vendor final pass. That
pass could not run:

$ codex exec "Reply with exactly: OK"
ERROR: Your workspace is out of credits. Ask your workspace owner to refill in order to continue.

No alternative external provider is installed (gemini, agy, pi, llm, ollama all
absent), so the gate currently has exactly one provider and it is unavailable. Tracked as
#1467.

Same-model reviews did run and their findings were addressed — see this PR's own
verification and escalations sections for what each stage found. But same-vendor passes
are not a substitute here, and the reason is recorded rather than assumed: on #120, three
same-model passes missed what a single cross-model pass caught immediately. On #1462 the
same thing happened three times in a row — each cross-model pass found defects that the
same-model reviews before it had cleared, including one that had already been "fixed"
twice.

Decision (Serina, 2026-08-26): hold every corpus PR in draft until credits are
refilled, rather than waive the gate.

When credits return, the sequence is: run the cross-model final on this branch's current
head, address or explicitly refute what it finds, then mark ready.

@serina-mcfall
serina-mcfall marked this pull request as draft August 26, 2026 06:21
@serina-mcfall
serina-mcfall marked this pull request as ready for review August 26, 2026 06:27
@serina-mcfall

Copy link
Copy Markdown
Author

Correction: the draft hold above is lifted

My earlier comment on this PR said "do not mark this ready for review". That no longer
applies, and the comment should not be read as overriding anyone.

Serina marked this PR ready for review deliberately, superseding the earlier hold. I
converted it back to draft twice before establishing that, which was my error — the
ready_for_review events attributed to her account were her, not an automation.

What is still true, and is the reason the earlier comment existed:

The cross-vendor final review has not run on this branch. codex exec reports
ERROR: Your workspace is out of credits, and no alternative external provider is
installed, so the gate has one provider and it is unavailable — tracked as #1467.
Same-model reviews did run and their findings were addressed.

So this PR is review-ready with a known, recorded gap in review coverage, rather than
being blocked by it. That is a legitimate posture, and it is a decision rather than an
oversight — but a reviewer should know it before merging, which is why this correction
names it rather than quietly deleting the earlier comment.

When credits return, the cross-model pass on the current head is still worth running.

@serina-mcfall serina-mcfall self-assigned this Aug 26, 2026
serina-mcfall added a commit that referenced this pull request Aug 26, 2026
…1311)

A same-vendor review-final pass, run because the cross-model gate's confirmation
died on credits, found that all three earlier fixes corrected the instance they
were raised against and not the class. That is the failure mode a same-vendor
reviewer is worst at catching, so it is worth naming: the sweep, not the
sentence, was the deliverable.

1. "no check reads the field" survived in two more places after the previous
   commit corrected the section it was raised in -- including under "Expected but
   not verified", the sentence that tells a reader what WAS established. Both now
   say no check branches on WHICH value the field holds. Swept: no occurrence of
   the false phrasing remains in either file.

2. The authority table still credited the whole SHOULD list to "this node, and
   nothing else" while SHOULD 2 restates AGENTS.md's step 3. #1481, filed by the
   commit that wrote the table, already recorded that SHOULD 2 is a restatement --
   so the document and its own issue disagreed. The table now splits SHOULD 2 out.

3. The duplication measurement -- the most falsifiable claim in the document --
   had no ledger entry, which is exactly the omission finding 3 asked to sweep
   for. It now has one, plus entries for the validator never reading body prose
   and for the #1481/#1473 cross-references.

Also: the phrase count was still low (five shared runs, not four; MUST 6 matches
the AGENTS.md *Updating a node* section rather than the retirement one, so entry
18 now says "retiring or updating"); adding the duplication entry made the
AGENTS.md citation count two, so the prose saying "One evidence entry cites" is
corrected again; the excluded `developer` audience is now disclosed rather than
silently decided; and the plan's ALREADY TRUE block, a committed artefact a
future agent reads, no longer asserts the claim the gate refuted.

MERGE-ORDER PROOF, prompted by #1488 defect B. AGENTS.md step 9 covers
relationships to unmerged nodes but says nothing about bare-path CITATIONS to
them, which carry the identical hazard. Three-way check with the negative control
that makes it worth anything:

  1. this node alone on a clean origin/launchpad -> exit 1, failing on exactly
     the two AGENTS.md citations (entries 13 and 18)
  2. control, same node with those two citations swapped for a file that IS on
     launchpad -> exit 0, proving the check discriminates
  3. the ACTUAL PR merge result, origin/launchpad + this whole branch -> exit 0

Test 3 is the one that decides it: this branch carries AGENTS.md, so the merge
result resolves both citations whichever of #1462 or this PR lands first. The
citations are kept -- they source the retirement-procedure claims and the
duplication measurement -- and the dependency stays disclosed in the body.

Ledger reconciled against the finished body per #1488 defect C. 23 entries, 22
bare-path citations all resolving, one commit-only FACT, no relationships key.
Validator exit 0; 79 + 27 tests OK.

Refs #605, #1481, #1488

Signed-off-by: Serina Mcfall <serina.mcfall@gmail.com>
serina-mcfall added a commit that referenced this pull request Aug 26, 2026
…dger (#1314)

Two defects from #1488, filed by the #1312 sibling, both of which applied here.

DEFECT B -- MERGE-ORDER HAZARD IN BARE-PATH CITATIONS. AGENTS.md step 9 forbids a
RELATIONSHIP target absent from the merge target and gives a git ls-tree check
for it. It says nothing about CITATIONS, and the hazard is identical: a citation
resolves against whatever tree the validator runs in, so a path present only on
the authoring branch passes locally and is a hard error after merge.

This node had it. Nine ledger entries cite launchpad/docs/corpus/AGENTS.md,
which is absent from origin/launchpad. Measured, with the negative control that
makes the proof mean anything:

  TEST 1  node on a clean origin/launchpad tree      -> FAIL, 9 errors, exit 1
  TEST 2  same node, AGENTS.md citations swapped out -> PASS, exit 0

Test 2 is what proves those citations are the cause rather than something else
in the node. Without it test 1 shows only that something is wrong.

The citations are NOT removed -- nine of this node's claims are about what
AGENTS.md says, and it is based on #1462 precisely because it cannot exist
without that file. The fix is to state the constraint with its measurement and
gate the merge: this node must never reach launchpad ahead of #1462. The general
rule is now written down, because step 9 does not carry it, citing #1488.

DEFECT C -- LEDGER/BODY RECONCILIATION. A full both-directions pass found 40
body claims with no ledger entry, concentrated in the merge-order section I had
just written -- which is exactly the failure mode #1488 describes, claims
shifting as the document is written. Added seven entries covering AGENTS.md step
9, AGENTS.md as the lifecycle and provided_by source, memory.py's narrower field
contract, COMPATIBILITY.md's additive case, #605's outcome text, and the
#1488/#1462 gating. Extended three entries whose body text had outgrown them:
ADR-0029's two claim-type rankings, memory.py's stale-flagging, and the schema
README.

Trimmed rather than sourced where the body had quietly extended a sourced entry:
five cells in the scope-and-omissions table carried clauses AGENTS.md does not
record. The table now says outright that its mappings are AGENTS.md's and not
this node's inferences.

The reverse direction was clean -- no entry lacked a body home -- except that
the authority entry was pointed at rather than claimed. The body now states
where the MUSTs and SHOULDs get their authority.

Caught while re-verifying: adding entries moved the AGENTS.md citation count
from seven to nine, making the sentence stating it false. Fixed, and the number
re-derived from the ledger rather than edited by hand.

Verified: TEST 1 and TEST 2 both re-run against the final node; validator exit 0
with 2 expected UNVERIFIED notices; 79 corpus tests OK; 27 schema tests OK.

Refs #1314. Cites #1488.

Signed-off-by: Serina Mcfall <serina.mcfall@gmail.com>
serina-mcfall added a commit that referenced this pull request Aug 26, 2026
…ed at (#636)

Closes the review panel's High finding and one of its two Mediums on #1462.

HIGH -- the provenance-update shortcut could certify stale evidence. Two
passages said an empty `git diff --name-only <recorded-sha> -- <ledger paths>`
established that every cited source was unchanged and every claim still stood,
and the second went further: "the command is the check". Both are false for
citation forms this document itself permits.

Measured, not argued. A file that DOES differ reports as changed with a bare
path and reports empty output with exit status 0 once `:127` or `:219-221` is
appended -- no error, no warning. So an unnormalized `path:line` citation makes
a changed file indistinguishable from an unchanged one, which is the worst
available failure shape for a provenance check. Recorded as a new FACT entry
with the tool-result citation that demonstrates it.

The rule is now stated with its reach: normalize positions off the path first;
only bare path, file line and file range are in scope; graph edge, tool result,
commit and the two URL forms are excluded explicitly, so the branch is simply
unavailable when a ledger contains one. And the conclusion is narrowed to what
a diff can support -- those files are unchanged -- with re-verification, not an
empty path-only diff, left as the only thing that establishes a FACT still
holds.

MEDIUM -- the one-node rule had no supporting ledger entry. "One node is one
independently maintainable idea" is a substantive instruction and the ledger's
only #636 entry backed the recorded-revision requirement instead. Added as
TEAM_KNOWLEDGE attributed to #636's definition of done, which states it
near-verbatim, matching the existing entry's classification for the same source.

Also corrected a wrong-direction cross-reference: "*Updating a node* below" in
Scope and omissions, where that section is above it.

The recorded revision stays at 0052f5a. Checked by the corrected procedure
rather than asserted: the one non-file citation was excluded, the nine
normalized file paths were passed to `git diff` against 0052f5a, and the
result was empty -- so the cited sources are unchanged and moving the revision
would assert a re-check that added nothing.

The second Medium is not addressed because it is stale, and the evidence is in
the PR thread rather than in this commit: the citation table was already
corrected by ebe2daf and now says outright that it is seven rows and is not a
summary of CONTRACT.md section 3. The remaining cross-document disagreement
between CONTRACT.md, validate.py and this file is tracked as #1478.

Signed-off-by: Serina Mcfall <serina.mcfall@gmail.com>
@serina-mcfall

Copy link
Copy Markdown
Author

Review addressed — two findings fixed, one is stale

Pushed as eb8de57b5. Taking them in the order they were raised.

High — the provenance-update shortcut can certify stale evidence · fixed

Confirmed, and the failure mode is worse than the finding describes. I measured it rather than reasoning about it:

$ git diff --name-only 0052f5a7820ca4ca261efa233feb8bb53858ade6 -- launchpad/docs/corpus/AGENTS.md
launchpad/docs/corpus/AGENTS.md

$ git diff --name-only 0052f5a7820ca4ca261efa233feb8bb53858ade6 -- launchpad/docs/corpus/AGENTS.md:127
$ echo "exit=$?"
exit=0

$ git diff --name-only 0052f5a7820ca4ca261efa233feb8bb53858ade6 -- launchpad/docs/corpus/AGENTS.md:219-221
$ echo "exit=$?"
exit=0

A file that does differ reports as changed with a bare path and reports empty output with exit 0 once a position suffix is appended. No error. No warning. So an unnormalized path:line citation makes a changed file indistinguishable from an unchanged one — a silent false negative in the one direction a provenance check must never fail.

I hit it live while addressing this, which is the best argument for the fix: a throwaway script I wrote to apply the corrected procedure mis-parsed my own tool-result citation as a file path, and git diff accepted that garbage pathspec silently and contributed nothing to the output. Third demonstration, unplanned.

Both passages are rewritten:

  • Updating a node step 4, branch 4 no longer names the command at all. It says establishing that branch requires re-verification rather than a diff, points at the corrected section, and states that if any citation is a graph edge, tool result, commit or URL then the branch is not available — re-verify or leave the revision alone.
  • The old "How to check this node's own recorded revision" is now "Checking whether cited files moved — and what that does not establish." It gives the command, then bounds it: empty output means those files are unchanged and nothing more; it is a narrowing step, not a certification. Normalize the trailing :<line> / :<start>-<end> off before passing a path, with the indistinguishability above spelled out as the reason. Only bare path, file line and file range are in reach; graph edge, tool result, commit and the two URL forms the validator recognises beyond §3 are excluded explicitly, because a claim resting on a tool result is untouched by any git diff and its status afterwards is exactly what it was before — unknown.

The old line "the command is the check" is gone. The honest reading is now stated as one sentence in the document: the file-naming citations I normalized and passed are unchanged; every other claim in this ledger is unverified by this command.

New ledger entry recording the measurement, as a FACT on a tool-result citation:

"A git pathspec carrying a citation's :line or :start-end suffix matches nothing and reports empty output with exit status 0, so an unnormalized file citation makes a changed file indistinguishable from an unchanged one."

It reports as a non-fatal UNVERIFIED notice, which is correct — a tool result names no openable file, and this document's own guidance says to take the honest class rather than force a checkable one.

Medium — the citation table misattributes seven validator forms to a six-form contract · stale, not fixed

This was already corrected before the review landed, so there is nothing to change. At branch head, launchpad/docs/corpus/AGENTS.md:196-199:

CONTRACT.md §3 enumerates six shapes — file range, file line, bare path, graph edge, tool result, commit. […] The two URL rows below are forms validate.py recognises and §3 does not enumerate, so this table is seven rows and is not a summary of §3.

The fix is commit ebe2daf72, which is this branch's head. The review was submitted 2026-08-26 05:36Z; the review on #1480 at 10:51Z the same day cites ebe2daf72 by hash as having made exactly this correction. So the two reviews disagree about the current text and the later one is right.

Flagging rather than silently skipping, since "the reviewer was looking at an older commit" is a claim that deserves the evidence attached.

The remaining genuine disagreement — validate.py's module docstring still says citations are parsed against "CONTRACT.md section 3's six forms" while it implements a URL form, and CONTRACT.md §3 does not enumerate one — is real but spans three files and is tracked as #1478 (parented to #605). Out of scope here.

Medium — a core instruction lacks a supporting ledger entry · fixed

Confirmed. "One node is one independently maintainable idea" is a substantive instruction, and the ledger's only #636 entry backed the recorded-revision requirement instead.

Added as TEAM_KNOWLEDGE attributed to #636's definition of done, which states it near-verbatim:

  • The document represents one independently maintainable knowledge node; any newly discovered second concept/contract/procedure is filed as a separate task instead of being folded into this document.

TEAM_KNOWLEDGE with provided_by is the right class per this document's own rule for issue-sourced claims — an issue URL is an external URL to the validator, so it cannot be a FACT on a citation — and it matches how the sibling #636 entry is already classified.

Also fixed, not from the review

A wrong-direction cross-reference: Scope and omissions said "Updating a node below" where that section is at line 353, above it.

The recorded revision stays at 0052f5a

Checked by the corrected procedure rather than asserted. The one non-file citation (commit 0052f5a7…) was excluded as out of reach, the nine normalized file paths were passed to git diff against the recorded revision, and the result was empty:

NON-FILE CITATIONS EXCLUDED (out of reach of any git diff):
  entry 1: commit 0052f5a7820ca4ca261efa233feb8bb53858ade6

NORMALIZED FILE PATHS (9):
   .github/workflows/launchpad-corpus-validate.yml
   Justfile
   launchpad/decisions/ADR-0028-corpus-canonical-representation.md
   launchpad/decisions/ADR-0029-corpus-evidence-precedence.md
   launchpad/docs/corpus/schema/node.schema.json
   launchpad/docs/corpus/schema/relationships.schema.json
   launchpad/project-intelligence/CONTRACT.md
   launchpad/project-intelligence/corpus/validate.py
   launchpad/scripts/preflight_core.py

git diff --name-only 0052f5a7 -- <those paths>:
  (empty)

So the cited sources are unchanged and moving the revision would assert a re-check that added nothing — branch 4 of the rule, applied as the corrected text now permits it to be. The new FACT entry rests on a tool result whose behaviour is not repository-version-dependent, so it holds at the recorded revision too.

Verification

$ python3 -m unittest discover -s launchpad/project-intelligence/corpus/tests -p "test_*.py"
............................FAIL  corpus root does not exist: .../tests/fixtures/does-not-exist-anywhere
...................................................
----------------------------------------------------------------------
Ran 79 tests in 0.370s

OK

$ python3 launchpad/project-intelligence/corpus/validate.py
UNVERIFIED  corpus-agents: evidence entry 1, citation 1: is a commit reference, which names no openable file
UNVERIFIED  corpus-agents: evidence entry 20, citation 1: is a graph-edge or tool-result citation, which names no openable file
PASS  corpus validation found no errors; 2 item(s) reported unverified

(The FAIL line inside the test output is a test's captured stdout asserting the not-found path, not a failing test — the run reports OK.)

Not verified

  • No agent harness was tested reading the rewritten section as its resolved AGENTS.md. Same gap the node already discloses about itself; unchanged by this edit.
  • I did not re-verify the other nineteen ledger claims against their sources. The git diff above establishes only that their cited files have not moved, which is exactly the narrower thing this PR now says that command means. Consistent with the rule; worth stating plainly rather than letting the diff imply more.
  • The normalization rule is prose, not enforced. Nothing in validate.py checks that an author stripped the position suffix before running the command, because the command is run by hand. bug: corpus validator accepts path:line citations whose line does not exist #1459 already tracks the adjacent gap that line numbers in citations are never checked against file length.

Merge order — unchanged and worth restating

This PR's diff is the shared #636 base that all five other corpus PRs also carry, byte-identical:

AGENTS.md      = e42228305d   (identical on #1462, #1468, #1469, #1470, #1477, #1480 before this push)
test_validate  = ffdff6ffc0   (same)

So #1468, #1469 and #1477 being APPROVED + CLEAN is misleading: each of their diffs contains the AGENTS.md this review blocked. Merging any of them before this PR lands the High finding on launchpad. This PR merges first, then the five rebase onto launchpad so each shrinks to its own standard — which also re-triggers the gate on them, and #1480's High finding is about AGENTS.md's wording, so it can only be settled once this file is final.

@serina-mcfall

Copy link
Copy Markdown
Author

@tucktuck101 re-requesting review — three commits have landed since your
CHANGES_REQUESTED at 2026-08-26T05:36:53Z:

commit committed (UTC) subject
806af4e41 2026-08-26T06:13Z fix four AGENTS.md defects found by authoring against it
ebe2daf72 2026-08-26T06:19Z fix a false attribution and the relationship-target trap
eb8de57b5 2026-08-26T20:45Z the revision shortcut cannot certify what it never looked at

The last one is the largest: it rewrites the "checking whether cited files moved"
procedure to say that a git diff is a narrowing step and not a certification, and that
the shortcut is unavailable for citations naming no file. It also adds two evidence
entries.

Worth knowing while you look: ten other open corpus PRs carry this file, and all ten
carry the pre-fix copy (e42228305d) rather than this branch's current one
(8c6c21a85e). Three of them are already APPROVED. So this PR is the keystone — nothing
else in the set can safely merge until it lands, and the rest will need rebasing onto
launchpad afterwards.

No claim here that your findings are resolved — that is your call. This comment only
records what changed since you looked.

serina-mcfall added a commit that referenced this pull request Aug 26, 2026
* docs(corpus): plan for issue #636 -- launchpad/docs/corpus/AGENTS.md

Reviewed by an independent cross-model review-plan pass (Codex, gpt-5.6-sol,
high effort): eleven findings, all applied. Three more were found afterwards,
the load-bearing one being that the plan blocked its own STEP 2 on an OPEN
item -- an OPEN item a step must consume is a missing input, not an open
question.

Refs #636

Signed-off-by: Serina Mcfall <serina.mcfall@gmail.com>

* feat(corpus): AGENTS.md frontmatter and skeleton (#636 STEPs 2-3)

The corpus's first hand-authored node. Frontmatter validates against
node.schema.json with zero errors; 11 evidence entries; no relationships,
because no sibling node exists to target and an unresolved target is a hard
validator error. The repository revision is recorded in the evidence ledger --
the schema calls that array the provenance ledger and permits no other field,
so it is the only schema-legal place for it.

STEP 3 (RUNS HERE) proved the validator actually scans the node rather than
skipping it: mutating type: to a value outside the enum produced
'FAIL corpus-agents: schema violation at type' and exit 1, naming the node's
id; restoring it returned exit 0.

Also replaces test_real_corpus_root_currently_has_no_content_outside_schema,
which asserted the corpus root is EMPTY and therefore could not survive the
first authored node. Authorised by Serina as a deliberate widening of the
plan's LEFT OUT scope, recorded in the plan. The replacement asserts what the
original's own comment said it was for -- content present AND nothing from
schema/ -- and both halves were negative-controlled before being trusted.

Refs #636

Signed-off-by: Serina Mcfall <serina.mcfall@gmail.com>

* docs(corpus): AGENTS.md node-contract section (#636 STEP 4)

Adds the lookup table of authoritative sources and the 'what a corpus node
is' section: one file per node, one idea per node, where nodes go, id
permanence, relationship resolution, and the authored-vs-generated boundary.

Reproduces no enum member list and no field-combination matrix -- those stay
canonical in node.schema.json and schema/README.md and are linked instead.
The checker never reads body prose, so a copy here would stay green while
drifting. Verified: no type-enum member and none of entry_class/provided_by/
confidence appear in the body.

Refs #636

Signed-off-by: Serina Mcfall <serina.mcfall@gmail.com>

* docs(corpus): AGENTS.md evidence and citation section (#636 STEP 5)

The part that lives nowhere else: what the checker actually does with each of
CONTRACT.md's six citation shapes, and three things a passing run does not
mean -- that a citation supports its claim (checking is structural, the file
is never opened and compared), that UNVERIFIED is a pass, or that a line
number was verified (#1459).

Names the three evidence classes and what each is FOR, but not which fields
the schema requires per class -- that matrix stays in node.schema.json.
Verified absent from the body: entry_class, provided_by, confidence.

Refs #636

Signed-off-by: Serina Mcfall <serina.mcfall@gmail.com>

* docs(corpus): AGENTS.md create/update/retire procedures (#636 STEP 6)

This is #605's acceptance criterion for the file -- a cold-start agent
creating, updating or retiring one node without oral guidance. Three numbered
procedures, each ending in the same check command.

Adds a 'Running the check' section giving the interpreter form (works without
Hermit; the just recipe needs activation first) and documenting --root, since
without it the command always validates the real corpus whatever directory
you are standing in.

Retire covers the three things most likely to be got wrong: inbound
relationships resolved before retirement, status set rather than the file
deleted, and the id never reused or renamed.

Refs #636

Signed-off-by: Serina Mcfall <serina.mcfall@gmail.com>

* docs(corpus): AGENTS.md scope and omissions (#636 STEP 7)

Names each deferred area with its owning issue rather than leaving a reader
unable to tell a gap from an omission: the standards and templates
(#1307-#1351, none merged), generated-artifact provenance (#1316), claim-type
and flagged state (#1410), the human entry point (#639), and unverified line
numbers in citations (#1459).

Records why this node carries no relationships -- at its recorded revision it
was the only authored node, and a target no node carries is a hard error --
so the absence reads as deliberate rather than forgotten.

Every repo path cited in the body verified to resolve to a real file.

Refs #636

Signed-off-by: Serina Mcfall <serina.mcfall@gmail.com>

* docs(corpus): audit AGENTS.md against its own ledger (#636 STEP 8)

The audit found two things the earlier steps had missed.

The document's own rule -- 'a FACT resting only on UNVERIFIED citations has
not been checked by anything' -- condemned the node's own ledger entry 1, the
provenance record, whose only citation is a commit reference. Resolved by
stating the one carve-out narrowly and giving it a real check
(git cat-file -e <sha>, run, exit 0) rather than by quietly exempting it. A
commit citation on a claim about repository CONTENT is explicitly still not
covered.

Ledger entry 11 overreached: 'every change under launchpad/docs/corpus is
gated in CI' where the workflow triggers on pull requests and pushes to
launchpad. Narrowed to what the workflow actually does.

Also records a scope judgement the DoD asks for: the citation-shape table is
reference material rather than instruction and belongs in the evidence
standard (#1314) when that lands. Noted in place rather than folded in
silently.

Verified: all 11 ledger entries resolve, every body path is a real file,
merge-base equals the recorded revision, and exactly one hand-authored .md
was added under the corpus root.

Refs #636

Signed-off-by: Serina Mcfall <serina.mcfall@gmail.com>

* test(corpus): strengthen real-root discovery test (#636, review-tests finding)

An independent review-tests pass defeated the first replacement: patching
discover_markdown_files to a hardcoded 'return [root / "AGENTS.md"]' -- a
constant that never touches the filesystem -- satisfied both 'non-empty' and
'nothing from schema/' while proving neither discovery nor exclusion. My own
negative control had broken the exclusion set and emptied the corpus, but
never tried a constant. Showing that assertions CAN fail is not the same as
showing they can only pass for the right reason.

The expectation is now derived from an independent filesystem walk and
compared for equality, which catches partial walks and both exclusion
directions.

It does NOT catch the constant today, and the docstring says so rather than
claiming otherwise: the corpus holds exactly one node, so [root/'AGENTS.md']
IS the correct answer and no assertion against the real tree can tell a
constant from a real walk. That resolves itself at the second node.
test_sibling_discovered_schema_dir_excluded catches it now -- verified FAIL
under that mutant -- and is the real proof of discovery behaviour.

Measured, after fixing a mutation harness that had patched a different module
instance than the test uses and so reported every mutant as caught:
  returns nothing     -> FAIL (caught)
  exclusion disabled  -> FAIL (caught)
  constant return     -> PASS (not caught; sibling catches it)

Refs #636

Signed-off-by: Serina Mcfall <serina.mcfall@gmail.com>

* docs(corpus): address review-code findings on AGENTS.md (#636)

FINDING A -- the carve-out claimed enforcement it does not have. 'There is
exactly one carve-out, and widening it defeats the rule' read as though some
check held the line. Nothing does: a second, third or tenth commit-only FACT
produces extra non-fatal UNVERIFIED notices and still exits 0, which the
reviewer proved by adding one. Now says plainly that this is a convention a
REVIEWER holds, names the signal to look for (more than one commit-only FACT
in a ledger), and states that no check will raise it.

FINDING B -- create-node step 3 asked the author to record four categories of
evidence and gave a destination to exactly one of them. Adds the mapping:
revision to the ledger, sources to the citations of the claims they support,
and expected-but-unverified to the body's scope section. Plus the rule that
anything reaching none of the three was either a missing claim or a stale
note.

Applying B to this node exposed that it did not follow its own new rule --
the same self-contradiction class STEP 8 caught. Its scope section now names
the two things unverified when it was written: no agent harness was tested
reading it as a resolved AGENTS.md, and relationships.schema.json's enum was
never read directly.

Re-audited after the change: exactly one commit-only FACT remains, the
enforcement claim is gone, validator exits 0, 79 tests pass.

Refs #636

Signed-off-by: Serina Mcfall <serina.mcfall@gmail.com>

* docs(corpus): address the cross-model final review (#636)

Codex review-final returned REQUEST_CHANGES with seven conditions, then a
second pass confirmed five fixed and refused two. Both refusals were correct.
This commit is the whole of that work.

1. LEDGER COMPLETENESS. Seven entries added (11 -> 18) for claims the body
   taught with no classified entry behind them: the evidence classes, the
   "supersedes" vocabulary, the syntax-only GitHub check, the external-URL
   verdict, and the delete-vs-retire distinction.

   The recheck caught two of these still wrong. An entry said the schema
   "permits no field outside that set" after listing the six required fields
   -- it also permits "relationships", so the entry was wrong about the file
   it cited. And the "supersedes" entry cited node.schema.json, which carries
   only the enum; the directionality it asserts ("source replaces target;
   target becomes historical") lives in relationships.schema.json:30, a file
   this node had listed as read-but-unverified and cited anyway. Read it,
   cited it.

2. RETIREMENT WAS BUILT ON A FALSE PREMISE. It claimed inbound relationships
   "no longer resolve" after retirement. Measured: with the file retained --
   which the same procedure requires -- they resolve fine; only deletion
   breaks them. Rewritten so the reason matches the mechanism, the danger is
   stated correctly (edges keep pointing at a node that stopped being
   current, and nothing warns you), and a replacement declares "supersedes",
   which the procedure never mentioned.

3. CITATION TABLE OVERSTATED VERIFICATION. A GitHub link pinned to a real SHA
   naming a file that never existed returns ok -- the checker never contacts
   GitHub. The table now carries a "does it prove the target exists?" column,
   gains the external-URL row it was missing, and names the trap. All seven
   rows independently confirmed against the classifier.

4. PROVENANCE SEMANTICS -- and the defect the recheck was right to refuse.

   The update procedure said to bump the revision on every edit; this node
   kept its original across seven. The first attempt defined the field as an
   evidence snapshot and classified that as INFERENCE citing node.schema.json.
   That schema says nothing whatever about revisions, so the citation could
   not support the statement: a policy choice had been given a class that made
   it look derived. It is now TEAM_KNOWLEDGE attributed to #636's own
   definition of done -- "the draft is checked against the repository revision
   recorded in provenance" -- which is where the meaning comes from. ADR-0029
   requires exactly that: history and team knowledge stay attributed rather
   than being promoted to fact.

   The node also claimed it was "the only authored node in the corpus" at the
   recorded revision. It did not exist at that revision and neither did any
   other node: git cat-file -e on that path at that sha exits 128, and the
   corpus held zero .md files outside schema/. Corrected.

   The revision still does not move, and the reason is checkable rather than
   asserted: every cited source is byte-identical between that revision and
   now, so verifying a claim at HEAD and at the recorded revision were the
   same act. git diff --name-only <sha> -- <cited paths> returns nothing.

5. AUDIENCE. Added "reviewer": the document assigns reviewers an enforcement
   duty no tool performs, so they are addressed by it. The adjudicator had
   declined this finding (nothing consumes the field today); the final pass
   argued audience describes who is addressed, not who parses it. Went with
   the latter. Whether "developer" also belongs is left undecided.

6. The test module docstring claimed absolute fixture isolation that was false
   before this branch and more visibly false after it. States the rule with
   its one named exception instead. Flagged independently by the adjudicator
   and the final pass.

   Pre-existing, fixed while in the file: the same docstring advertised
   "python3 -m unittest launchpad.project_intelligence.corpus.tests.test_validate",
   which has never worked -- the directory is "project-intelligence" with a
   hyphen, not a legal package name. Confirmed ModuleNotFoundError, removed.

7. Full suites re-run in a writable environment: 79 validator tests OK, 27
   schema tests OK, real corpus exit 0. The plan's two-node create/update/
   retire charter ran with its negative control -- a deliberately invalid node
   failed and named itself, proving the scratch root was the one scanned;
   retirement kept beta's edge resolving; deleting alpha instead broke it
   exactly as the corrected text now says.

Final state: 18 ledger entries, schema errors 0, every citation resolves, no
cited source changed since the recorded revision, exactly one commit-only
FACT, 79 + 27 tests green, real corpus exit 0.

Refs #636

Signed-off-by: Serina Mcfall <serina.mcfall@gmail.com>

* docs(corpus): standard for the confidence field (#1309)

Adds launchpad/docs/corpus/standards/confidence.md, the canonical policy node
for the confidence key on an evidence entry.

The field is a number in a closed interval, required for INFERENCE only and
forbidden on FACT and TEAM_KNOWLEDGE. The schema holds that much. What it
cannot hold is what the number means, and an uninterpreted number between 0
and 1 is decoration -- an author picks 0.8 because it feels about right and a
reader treats it as calibrated. The standard states that no calibration exists
or is collected, that values are not comparable between authors, and that no
confidence however high promotes an INFERENCE to a FACT.

Its load-bearing section separates reasoning from evidence from dressing up a
decision: an entry whose citation supports the subject of a claim but not the
choice the claim makes is a decision in disguise and belongs in
TEAM_KNOWLEDGE. That move was caught by cross-model review on #636 and no
check can catch it, so it is documented with both worked examples.

Records a measured divergence: a NaN confidence satisfies node.schema.json and
passes corpus validation while memory.py rejects the identical value, because
every comparison against NaN is false. Filed as #1463; the standard carries a
MUST for finite values to cover it until that lands.

Declares no relationships -- every sibling standard is unmerged, and a target
naming an id no loaded node carries is a hard validation error.

Signed-off-by: Serina Mcfall <serina.mcfall@gmail.com>

* docs(corpus): stop asserting provenance policy this node cannot source (#636)

Third cross-model pass, third refusal on the same condition. It was right
three times, and the third refusal finally identified why the first two fixes
failed: the document was writing corpus-wide provenance policy it had no
authority to write. Reclassifying it (INFERENCE, then TEAM_KNOWLEDGE) only
changed the label on an unsourced decision.

WHAT ACTUALLY CHANGED: the document stops making the claim.

#1321 ("document corpus standard for provenance", unlanded) owns the question
of whether a recorded revision may stay put across edits. This node now says
so, states its own approach as working practice pending that standard rather
than as a rule others must follow, and defers. The ledger entry no longer
asserts the policy -- it records only what #636's definition of done actually
requires, which is a fact about the issue and is attributed to it.

Also fixes a self-contradiction the last fix opened, exactly the failure shape
this repository keeps producing. The previous commit added a FACT citing
relationships.schema.json for the supersedes directionality. The same node
still said, in its unverified list, that relationships.schema.json had never
been read. Both could not stand. The file WAS read; the stale disclaimer is
now narrowed to what remains genuinely unchecked -- that the two enum lists
agree, which rests on a test that was not run.

Ledger completeness, third sweep: entries added for the generated-artifact
rejection, the non-enforcement of the one-commit-only-FACT convention, and
positional citations not being bounds-checked. Prose that asserted this
node's own history -- node counts at the recorded revision, byte-identity of
cited sources -- is removed rather than given entries: it told the reader to
trust the document about a fact they can check themselves in one command, so
the document now gives them the command instead.

TEAM_KNOWLEDGE's description corrected. It said "a person told you", which the
schema does not require (it asks who or what told the corpus) and which made
the class look unavailable for an attributed issue or decision record. It now
also says outright what it is not: a place to park a decision you made
yourself. Attributing an extrapolation to the thing it started from does not
make it something you were told.

Updating step 4 restated as four explicit branches including the
byte-identical case, which the earlier "bump on every edit" wording omitted
while this very node relied on it -- the inconsistency the pass named.

Verified: 21 ledger entries, schema errors 0, every citation resolves, exactly
one commit-only FACT, no cited source changed since the recorded revision, 79
validator tests OK, real corpus exit 0.

Refs #636

Signed-off-by: Serina Mcfall <serina.mcfall@gmail.com>

* docs(corpus): make the confidence standard obey itself (#1309)

Two review findings, both adjudicated Medium, both defects in this document
rather than in anything it describes. A standard that contradicts itself
teaches the wrong lesson to every sibling that copies it, so both are fixed
here rather than deferred.

review-code, confirmed by review-adjudicate: the Guidance section said to pick
one number per band and reuse it, but never said which number belonged to
which band -- and then shipped two INFERENCE entries at two different values
with no band label and no stated reason. The bands now carry explicit pegs of
0.8, 0.6 and 0.4, the criterial column is named so the illustrations stop
reading as tests, and this node's own two entries are labelled to their bands
as the nearest worked example. The scope-and-omissions entry now disclaims the
specific values, not just the idea of banding: they are agreed pegs so two
authors mean the same thing by the same numeral, and they measure nothing.

review-plan, surviving into the document and caught by review-adjudicate: the
header claimed the field-combination rules and enum members were deliberately
not copied, while Requirement 1 restates the confidence row and names all
three classes. A standard about this field cannot omit the field's own rule,
so the claim was the wrong half to keep. The header now names that one
restatement as a deliberate drift surface and points at the two places that
must change if the schema's rule ever does.

One neighbouring hole opened by the first fix and closed here: Requirement 6
forbids moving a number, and the second entry moved from 0.7 to 0.6. Snapping
an unchanged assessment onto a band peg is not the move that rule prohibits,
but from a diff the two are indistinguishable, so Requirement 6 now says so
and requires the reason be recorded. This message is that record.

Signed-off-by: Serina Mcfall <serina.mcfall@gmail.com>

* docs(corpus): fix the standard's claims about itself (#1309)

review-final found nine findings, and the cluster that mattered was not about
confidence at all -- it was the document's claims about its own contents. For a
node whose whole subject is not asserting more than the evidence supports, and
which roughly 45 siblings are meant to copy, that is the report. All nine are
defects in this document, all small, all inside the issue's scope, so they are
fixed here rather than deferred.

High. The stated reason for declaring no relationships was false. It said there
was nothing this node could legally point at, and the next sentence named
AGENTS.md. AGENTS.md carries id corpus-agents, is a loaded node, and a
references edge to it validates today. The convention of declaring none is
settled and unchanged; the justification is now the true one -- add the edge set
in one pass once the siblings land -- and names corpus-agents as the first edge
to make. The same false premise was in the plan's LEFT OUT and is corrected
there too.

Medium, four of them. The Requirements preamble claimed the first three MUSTs
were machine-enforced while Requirement 3 said the schema does not reach it; it
now says two. The header named two places tracking the schema when three do, and
is now a table. The peg rationale asserted cross-author shared meaning that the
reader rules and this node's own 0.8 INFERENCE both deny; adopting pegs is now
stated as a hope about future practice, never as something a reader may sort on.
The only-INFERENCE-in-the-corpus FACT cited AGENTS.md alone, which cannot
establish a quantifier -- it now also cites validate.py for the schema/
exclusion and the fixture that carries the other one, which is exactly the
merely-on-topic citation this document calls the tell.

Medium, and the sharpest. Requirement 6's carve-out claimed re-encoding means
the judgement is identical and only its expression changed. Measured against the
document's own rules that is false: 0.7 to 0.6 moved the entry a full band from
its neighbour, and ranking within a class is the one thing the number
legitimately does. The carve-out now says re-encoding preserves the assessment
but can change the rank, requires the record to name the Means row it matches,
and admits there is no tie-break for a midpoint.

Low, two. A number between 0 and 1 restated bounds the header said were left to
the schema. And MUST be finite was overbroad -- .inf and -.inf are both caught
on range, so NaN is the single value that escapes, because every comparison
against it is false and the assertions never fire. Requirement 3 and the
enforcement table both say so now.

Per Requirement 6 as amended: the 0.6 entry matches the Medium band's Means row,
an absence-of-evidence argument over a scope that was checked, because its scope
is three files rather than the repository.

Not fixed here: the NaN divergence itself stays filed as #1463, since the schema
sits outside this issue's scope.

Signed-off-by: Serina Mcfall <serina.mcfall@gmail.com>

* docs(corpus): fix four AGENTS.md defects found by authoring against it (#636)

Three sibling agents authored corpus nodes following this document literally, and
reported what broke. These four are the defects that produced real false claims in
their work, not stylistic complaints. Filed as #1471; fixed here because #1462 is
still open and 45 further nodes would inherit them.

1. THE ONLY-NODE JUSTIFICATION WENT FALSE, AND TWO AGENTS COPIED IT.
   "A node with no sibling to point at correctly declares none" read as a general
   rule. It was a fact about one moment -- when this was the corpus's only node --
   and stopped holding as soon as a second existed. Both the #1309 and #1307 agents
   reproduced the reasoning and wrote a justification that was false at the time
   they wrote it; their adjudicators caught it, not their own reviews. Now says to
   enumerate what exists and give the real reason, and records why.

2. AN ISSUE-ONLY SOURCE HAD NO HONEST CLASS. The citation table lists tool results as
   legitimate while "Three things a passing run does not mean" says a FACT resting
   only on UNVERIFIED citations "has not been checked by anything", with one named
   exception. A claim whose only source is a GitHub issue has no openable form and
   cannot be SHA-pinned, so both passages applied and disagreed. The #1307 agent hit
   this with nothing honest to choose. Now states it: TEAM_KNOWLEDGE with provided_by
   naming the issue, which is what ADR-0029 requires of GitHub history anyway.

3. A RANGE WAS READ AS A MAPPING. The not-covered table paired eight subjects with
   "#1307-#1351", which is 45 issues and no subject-to-issue pairing at all. The
   #1307 agent cited this table for nine invented mappings -- exactly the "FACT cited
   to a file that does not discuss the claim" failure the document warns about two
   sections earlier. Now says outright that it is a range, not a mapping, and gives
   the search command.

4. CREATE STEP 3 ROUTED SOMETHING STEP 8 DID NOT ACCEPT. Step 3 sends
   expected-but-unverified items to the scope section; step 8 said only "state what
   the node does not cover". A boundary and a confidence disclosure are different
   things, so working the steps in order left that category homeless. Both the #1307
   and #1309 agents reported it independently. Step 8 now names both.

Verified: schema errors 0, 21 ledger entries, validator exit 0, 79 tests OK.

Refs #636 #1471

Signed-off-by: Serina Mcfall <serina.mcfall@gmail.com>

* docs(corpus): fix a false attribution and the relationship-target trap (#636)

Two more defects reported by the #1308 agent after authoring against this document.
Both are mine, and the first is embarrassing in context.

1. THE CITATION TABLE MISATTRIBUTED ITSELF, IN THE SECTION ABOUT NOT DOING THAT.
   It said "CONTRACT.md §3 defines the six shapes" above a SEVEN-row table whose
   GitHub-link and external-URL rows appear nowhere in §3. Measured: grepping that
   section for http/url/github returns zero hits. So the sentence cited a file that
   does not discuss two of the rows beneath it -- the exact failure this document
   warns about, committed by this document. The #1308 agent took the table as an
   accurate summary and built a scope argument on "four of six shapes name code"
   before their plan review caught it. Now states what §3 actually enumerates, that
   the two URL forms are the validator's and not §3's, and that the table is seven
   rows and not a summary.

2. "ADD RELATIONSHIPS ONLY TO NODES THAT EXIST" WAS UNFOLLOWABLE, AND DANGEROUSLY SO.
   "Exist" was ambiguous between the branch you are working on and the branch you
   merge into, and the checker loads whatever is present where it runs. An agent
   branched off this unmerged node could therefore target `corpus-agents`, validate
   clean locally, and break CI on `launchpad`, where the node is absent. The #1308
   agent hit exactly that and their review-code raised it as a Blocker. Step 9 now
   says "exists on the branch you are merging INTO", explains why the distinction is
   the trap, and gives the command to check the merge base rather than the local tree.

This is the second round of AGENTS.md fixes sourced from agents authoring against it,
and the pattern is worth naming: every defect found this way was invisible to three
review passes over the document itself, and obvious within minutes to someone trying
to follow it. Reviewing a procedure is not the same as executing one.

Verified: validator exit 0, 79 tests OK, schema clean.

Refs #636 #1471 #1478

Signed-off-by: Serina Mcfall <serina.mcfall@gmail.com>

---------

Signed-off-by: Serina Mcfall <serina.mcfall@gmail.com>
serina-mcfall added a commit that referenced this pull request Aug 26, 2026
* docs(corpus): plan for issue #636 -- launchpad/docs/corpus/AGENTS.md

Reviewed by an independent cross-model review-plan pass (Codex, gpt-5.6-sol,
high effort): eleven findings, all applied. Three more were found afterwards,
the load-bearing one being that the plan blocked its own STEP 2 on an OPEN
item -- an OPEN item a step must consume is a missing input, not an open
question.

Refs #636

Signed-off-by: Serina Mcfall <serina.mcfall@gmail.com>

* feat(corpus): AGENTS.md frontmatter and skeleton (#636 STEPs 2-3)

The corpus's first hand-authored node. Frontmatter validates against
node.schema.json with zero errors; 11 evidence entries; no relationships,
because no sibling node exists to target and an unresolved target is a hard
validator error. The repository revision is recorded in the evidence ledger --
the schema calls that array the provenance ledger and permits no other field,
so it is the only schema-legal place for it.

STEP 3 (RUNS HERE) proved the validator actually scans the node rather than
skipping it: mutating type: to a value outside the enum produced
'FAIL corpus-agents: schema violation at type' and exit 1, naming the node's
id; restoring it returned exit 0.

Also replaces test_real_corpus_root_currently_has_no_content_outside_schema,
which asserted the corpus root is EMPTY and therefore could not survive the
first authored node. Authorised by Serina as a deliberate widening of the
plan's LEFT OUT scope, recorded in the plan. The replacement asserts what the
original's own comment said it was for -- content present AND nothing from
schema/ -- and both halves were negative-controlled before being trusted.

Refs #636

Signed-off-by: Serina Mcfall <serina.mcfall@gmail.com>

* docs(corpus): AGENTS.md node-contract section (#636 STEP 4)

Adds the lookup table of authoritative sources and the 'what a corpus node
is' section: one file per node, one idea per node, where nodes go, id
permanence, relationship resolution, and the authored-vs-generated boundary.

Reproduces no enum member list and no field-combination matrix -- those stay
canonical in node.schema.json and schema/README.md and are linked instead.
The checker never reads body prose, so a copy here would stay green while
drifting. Verified: no type-enum member and none of entry_class/provided_by/
confidence appear in the body.

Refs #636

Signed-off-by: Serina Mcfall <serina.mcfall@gmail.com>

* docs(corpus): AGENTS.md evidence and citation section (#636 STEP 5)

The part that lives nowhere else: what the checker actually does with each of
CONTRACT.md's six citation shapes, and three things a passing run does not
mean -- that a citation supports its claim (checking is structural, the file
is never opened and compared), that UNVERIFIED is a pass, or that a line
number was verified (#1459).

Names the three evidence classes and what each is FOR, but not which fields
the schema requires per class -- that matrix stays in node.schema.json.
Verified absent from the body: entry_class, provided_by, confidence.

Refs #636

Signed-off-by: Serina Mcfall <serina.mcfall@gmail.com>

* docs(corpus): AGENTS.md create/update/retire procedures (#636 STEP 6)

This is #605's acceptance criterion for the file -- a cold-start agent
creating, updating or retiring one node without oral guidance. Three numbered
procedures, each ending in the same check command.

Adds a 'Running the check' section giving the interpreter form (works without
Hermit; the just recipe needs activation first) and documenting --root, since
without it the command always validates the real corpus whatever directory
you are standing in.

Retire covers the three things most likely to be got wrong: inbound
relationships resolved before retirement, status set rather than the file
deleted, and the id never reused or renamed.

Refs #636

Signed-off-by: Serina Mcfall <serina.mcfall@gmail.com>

* docs(corpus): AGENTS.md scope and omissions (#636 STEP 7)

Names each deferred area with its owning issue rather than leaving a reader
unable to tell a gap from an omission: the standards and templates
(#1307-#1351, none merged), generated-artifact provenance (#1316), claim-type
and flagged state (#1410), the human entry point (#639), and unverified line
numbers in citations (#1459).

Records why this node carries no relationships -- at its recorded revision it
was the only authored node, and a target no node carries is a hard error --
so the absence reads as deliberate rather than forgotten.

Every repo path cited in the body verified to resolve to a real file.

Refs #636

Signed-off-by: Serina Mcfall <serina.mcfall@gmail.com>

* docs(corpus): audit AGENTS.md against its own ledger (#636 STEP 8)

The audit found two things the earlier steps had missed.

The document's own rule -- 'a FACT resting only on UNVERIFIED citations has
not been checked by anything' -- condemned the node's own ledger entry 1, the
provenance record, whose only citation is a commit reference. Resolved by
stating the one carve-out narrowly and giving it a real check
(git cat-file -e <sha>, run, exit 0) rather than by quietly exempting it. A
commit citation on a claim about repository CONTENT is explicitly still not
covered.

Ledger entry 11 overreached: 'every change under launchpad/docs/corpus is
gated in CI' where the workflow triggers on pull requests and pushes to
launchpad. Narrowed to what the workflow actually does.

Also records a scope judgement the DoD asks for: the citation-shape table is
reference material rather than instruction and belongs in the evidence
standard (#1314) when that lands. Noted in place rather than folded in
silently.

Verified: all 11 ledger entries resolve, every body path is a real file,
merge-base equals the recorded revision, and exactly one hand-authored .md
was added under the corpus root.

Refs #636

Signed-off-by: Serina Mcfall <serina.mcfall@gmail.com>

* test(corpus): strengthen real-root discovery test (#636, review-tests finding)

An independent review-tests pass defeated the first replacement: patching
discover_markdown_files to a hardcoded 'return [root / "AGENTS.md"]' -- a
constant that never touches the filesystem -- satisfied both 'non-empty' and
'nothing from schema/' while proving neither discovery nor exclusion. My own
negative control had broken the exclusion set and emptied the corpus, but
never tried a constant. Showing that assertions CAN fail is not the same as
showing they can only pass for the right reason.

The expectation is now derived from an independent filesystem walk and
compared for equality, which catches partial walks and both exclusion
directions.

It does NOT catch the constant today, and the docstring says so rather than
claiming otherwise: the corpus holds exactly one node, so [root/'AGENTS.md']
IS the correct answer and no assertion against the real tree can tell a
constant from a real walk. That resolves itself at the second node.
test_sibling_discovered_schema_dir_excluded catches it now -- verified FAIL
under that mutant -- and is the real proof of discovery behaviour.

Measured, after fixing a mutation harness that had patched a different module
instance than the test uses and so reported every mutant as caught:
  returns nothing     -> FAIL (caught)
  exclusion disabled  -> FAIL (caught)
  constant return     -> PASS (not caught; sibling catches it)

Refs #636

Signed-off-by: Serina Mcfall <serina.mcfall@gmail.com>

* docs(corpus): address review-code findings on AGENTS.md (#636)

FINDING A -- the carve-out claimed enforcement it does not have. 'There is
exactly one carve-out, and widening it defeats the rule' read as though some
check held the line. Nothing does: a second, third or tenth commit-only FACT
produces extra non-fatal UNVERIFIED notices and still exits 0, which the
reviewer proved by adding one. Now says plainly that this is a convention a
REVIEWER holds, names the signal to look for (more than one commit-only FACT
in a ledger), and states that no check will raise it.

FINDING B -- create-node step 3 asked the author to record four categories of
evidence and gave a destination to exactly one of them. Adds the mapping:
revision to the ledger, sources to the citations of the claims they support,
and expected-but-unverified to the body's scope section. Plus the rule that
anything reaching none of the three was either a missing claim or a stale
note.

Applying B to this node exposed that it did not follow its own new rule --
the same self-contradiction class STEP 8 caught. Its scope section now names
the two things unverified when it was written: no agent harness was tested
reading it as a resolved AGENTS.md, and relationships.schema.json's enum was
never read directly.

Re-audited after the change: exactly one commit-only FACT remains, the
enforcement claim is gone, validator exits 0, 79 tests pass.

Refs #636

Signed-off-by: Serina Mcfall <serina.mcfall@gmail.com>

* docs(corpus): address the cross-model final review (#636)

Codex review-final returned REQUEST_CHANGES with seven conditions, then a
second pass confirmed five fixed and refused two. Both refusals were correct.
This commit is the whole of that work.

1. LEDGER COMPLETENESS. Seven entries added (11 -> 18) for claims the body
   taught with no classified entry behind them: the evidence classes, the
   "supersedes" vocabulary, the syntax-only GitHub check, the external-URL
   verdict, and the delete-vs-retire distinction.

   The recheck caught two of these still wrong. An entry said the schema
   "permits no field outside that set" after listing the six required fields
   -- it also permits "relationships", so the entry was wrong about the file
   it cited. And the "supersedes" entry cited node.schema.json, which carries
   only the enum; the directionality it asserts ("source replaces target;
   target becomes historical") lives in relationships.schema.json:30, a file
   this node had listed as read-but-unverified and cited anyway. Read it,
   cited it.

2. RETIREMENT WAS BUILT ON A FALSE PREMISE. It claimed inbound relationships
   "no longer resolve" after retirement. Measured: with the file retained --
   which the same procedure requires -- they resolve fine; only deletion
   breaks them. Rewritten so the reason matches the mechanism, the danger is
   stated correctly (edges keep pointing at a node that stopped being
   current, and nothing warns you), and a replacement declares "supersedes",
   which the procedure never mentioned.

3. CITATION TABLE OVERSTATED VERIFICATION. A GitHub link pinned to a real SHA
   naming a file that never existed returns ok -- the checker never contacts
   GitHub. The table now carries a "does it prove the target exists?" column,
   gains the external-URL row it was missing, and names the trap. All seven
   rows independently confirmed against the classifier.

4. PROVENANCE SEMANTICS -- and the defect the recheck was right to refuse.

   The update procedure said to bump the revision on every edit; this node
   kept its original across seven. The first attempt defined the field as an
   evidence snapshot and classified that as INFERENCE citing node.schema.json.
   That schema says nothing whatever about revisions, so the citation could
   not support the statement: a policy choice had been given a class that made
   it look derived. It is now TEAM_KNOWLEDGE attributed to #636's own
   definition of done -- "the draft is checked against the repository revision
   recorded in provenance" -- which is where the meaning comes from. ADR-0029
   requires exactly that: history and team knowledge stay attributed rather
   than being promoted to fact.

   The node also claimed it was "the only authored node in the corpus" at the
   recorded revision. It did not exist at that revision and neither did any
   other node: git cat-file -e on that path at that sha exits 128, and the
   corpus held zero .md files outside schema/. Corrected.

   The revision still does not move, and the reason is checkable rather than
   asserted: every cited source is byte-identical between that revision and
   now, so verifying a claim at HEAD and at the recorded revision were the
   same act. git diff --name-only <sha> -- <cited paths> returns nothing.

5. AUDIENCE. Added "reviewer": the document assigns reviewers an enforcement
   duty no tool performs, so they are addressed by it. The adjudicator had
   declined this finding (nothing consumes the field today); the final pass
   argued audience describes who is addressed, not who parses it. Went with
   the latter. Whether "developer" also belongs is left undecided.

6. The test module docstring claimed absolute fixture isolation that was false
   before this branch and more visibly false after it. States the rule with
   its one named exception instead. Flagged independently by the adjudicator
   and the final pass.

   Pre-existing, fixed while in the file: the same docstring advertised
   "python3 -m unittest launchpad.project_intelligence.corpus.tests.test_validate",
   which has never worked -- the directory is "project-intelligence" with a
   hyphen, not a legal package name. Confirmed ModuleNotFoundError, removed.

7. Full suites re-run in a writable environment: 79 validator tests OK, 27
   schema tests OK, real corpus exit 0. The plan's two-node create/update/
   retire charter ran with its negative control -- a deliberately invalid node
   failed and named itself, proving the scratch root was the one scanned;
   retirement kept beta's edge resolving; deleting alpha instead broke it
   exactly as the corrected text now says.

Final state: 18 ledger entries, schema errors 0, every citation resolves, no
cited source changed since the recorded revision, exactly one commit-only
FACT, 79 + 27 tests green, real corpus exit 0.

Refs #636

Signed-off-by: Serina Mcfall <serina.mcfall@gmail.com>

* docs(corpus): stop asserting provenance policy this node cannot source (#636)

Third cross-model pass, third refusal on the same condition. It was right
three times, and the third refusal finally identified why the first two fixes
failed: the document was writing corpus-wide provenance policy it had no
authority to write. Reclassifying it (INFERENCE, then TEAM_KNOWLEDGE) only
changed the label on an unsourced decision.

WHAT ACTUALLY CHANGED: the document stops making the claim.

#1321 ("document corpus standard for provenance", unlanded) owns the question
of whether a recorded revision may stay put across edits. This node now says
so, states its own approach as working practice pending that standard rather
than as a rule others must follow, and defers. The ledger entry no longer
asserts the policy -- it records only what #636's definition of done actually
requires, which is a fact about the issue and is attributed to it.

Also fixes a self-contradiction the last fix opened, exactly the failure shape
this repository keeps producing. The previous commit added a FACT citing
relationships.schema.json for the supersedes directionality. The same node
still said, in its unverified list, that relationships.schema.json had never
been read. Both could not stand. The file WAS read; the stale disclaimer is
now narrowed to what remains genuinely unchecked -- that the two enum lists
agree, which rests on a test that was not run.

Ledger completeness, third sweep: entries added for the generated-artifact
rejection, the non-enforcement of the one-commit-only-FACT convention, and
positional citations not being bounds-checked. Prose that asserted this
node's own history -- node counts at the recorded revision, byte-identity of
cited sources -- is removed rather than given entries: it told the reader to
trust the document about a fact they can check themselves in one command, so
the document now gives them the command instead.

TEAM_KNOWLEDGE's description corrected. It said "a person told you", which the
schema does not require (it asks who or what told the corpus) and which made
the class look unavailable for an attributed issue or decision record. It now
also says outright what it is not: a place to park a decision you made
yourself. Attributing an extrapolation to the thing it started from does not
make it something you were told.

Updating step 4 restated as four explicit branches including the
byte-identical case, which the earlier "bump on every edit" wording omitted
while this very node relied on it -- the inconsistency the pass named.

Verified: 21 ledger entries, schema errors 0, every citation resolves, exactly
one commit-only FACT, no cited source changed since the recorded revision, 79
validator tests OK, real corpus exit 0.

Refs #636

Signed-off-by: Serina Mcfall <serina.mcfall@gmail.com>

* docs(corpus): README.md front matter and what-is-here-today (#639 STEP 1)

Signed-off-by: Serina Mcfall <serina.mcfall@gmail.com>

* docs(corpus): README.md routing table to the authoritative sources (#639 STEP 2)

Signed-off-by: Serina Mcfall <serina.mcfall@gmail.com>

* docs(corpus): README.md how the corpus is checked, and what a pass does not establish (#639 STEP 3)

Signed-off-by: Serina Mcfall <serina.mcfall@gmail.com>

* docs(corpus): README.md scope, omissions, and the audiences and type decisions (#639 STEP 4)

Signed-off-by: Serina Mcfall <serina.mcfall@gmail.com>

* docs(corpus): audit README.md against its own ledger (#639 STEP 5)

Signed-off-by: Serina Mcfall <serina.mcfall@gmail.com>

* docs(corpus): address review-code and adjudication findings (#639)

Rebased onto a1e8bbc first. The branch was cut from 60d4947, which was the
tip of task/636-corpus-agents-md at the time; #636 advanced one commit 38
minutes later and that commit is the precedent for the Blocker below. The
recorded revision moves to a1e8bbc because every claim was re-verified
against it, not because the file was edited.

BLOCKER (confirmed by adjudication). The TEAM_KNOWLEDGE entry attributed
"declare no relationships" to a shared task brief. No such convention is
recorded anywhere -- the corpus-plan issue template says the opposite, asking
for "typed relationships appropriate to the node". Following a1e8bbc's fix
for the identical defect on #636: the document stops making the claim. The
entry is removed and the body now states the absence as this node's own
decision, explicitly not a rule for the corpus, and defers the question to
the per-type standards that own it.

HIGH (confirmed). A FACT claimed #1307-#1351 were #605's forty-five children
with none merged, citing schema/README.md and AGENTS.md. Both were checked:
schema/README.md names no issue in that range and says nothing about merge
status; AGENTS.md never mentions #605 at all. The claim is true but was
sourced to files that do not establish it -- the #636 defect exactly. Split
into two entries that say only what each cited file says, and the count is
dropped from both ledger and body.

MEDIUM (confirmed, relocated by adjudication). The page promised "Nothing
here restates a rule" and then restated two, and said three limits were "not
repeated here" three lines after summarising them. The restatements are
legitimate orientation for a door; the absolute promises were false. The
stated policy now matches what the document does: no rule is owned here,
every summary names its owner, and the owner wins.

Also, following a1e8bbc's model: prose asserting how many nodes the corpus
holds is replaced by the command that lists them. That removes a count which
goes stale silently, and removes the one tool-result citation with it -- the
ledger's only remaining UNVERIFIED entry is now the provenance commit.

A fix opened its neighbour, as this repository keeps doing. Adding the
enumeration command gave the body a second fenced block, which desynchronised
the STEP 2 gate's inline-backtick pairing and collapsed its path count from
13 to 1. The >=6 floor added after review-plan is the only reason that
surfaced instead of passing quietly. The gate now strips fenced blocks before
scanning; commands belong in fences, citations belong in prose.

Verified: 21 ledger entries, exactly one commit-only FACT, no FACT without
evidence, 15 backticked paths and 18 link targets all resolve, no cited
source has changed since a1e8bbc, validator exit 0, 79 validator tests OK,
27 schema tests OK.

Refs #639

Signed-off-by: Serina Mcfall <serina.mcfall@gmail.com>

* docs(corpus): address the final-review findings on README.md (#639)

Six findings from the final pass, all fixed. The Blocker was process, not
content; the rest were the correspondence invariant breaking again in the
commit that fixed the previous round.

BLOCKER -- no step ledger existed. Diagnosed rather than patched: the
installed serina-skills plugin cache is pinned at 0.11.0 (dated Aug 21) whose
build-change ships SKILL.md alone and never mentions a ledger, while the
marketplace copy (Aug 25) carries record-step.sh and names it nine times. The
skill as served has no ledger step, which is why sibling #636 has none either.
The ledger is now recorded from this session's own knowledge of each step's
verified done-when, not inferred from commit messages -- the distinction
record-step.sh's own header insists on. It lands in .superpowers/, which is
git-excluded, so it is a local artefact and not part of this diff.

MEDIUM -- evidence entry 11 was orphaned by the previous commit. Splitting the
#605 FACT in two while deleting the body's only #605 sentence left the
#622/#605/#602 lineage entry with no body claim. Exactly the neighbour-hole
shape this repository keeps producing, opened by the fix for the last round.
The lineage is now stated in the body, where a human arriving at a
half-built corpus needs it.

MEDIUM -- "first developer audience, first deliberate exclusion" was a
substantive claim with no ledger entry, and merge-order-fragile: any sibling
landing first with developer falsifies it and nothing checks. Now pinned to
the recorded revision, ledgered as a FACT against AGENTS.md's own audiences,
and it says outright that a later sibling may get there first.

MEDIUM -- the rewritten policy promised "every one of them names the file that
owns it" and then the very next section named none. The same absolute-promise
defect the rewrite existed to fix, reintroduced one paragraph lower. "What a
node is" now names ADR-0028 and AGENTS.md as its owners.

LOW -- entry 12 said AGENTS.md names three further gaps; it names four. The
fourth is #639, this node -- so the undercount hid the fact that AGENTS.md
declares this page as one of its own gaps. Corrected, and the body now says
the two nodes are a declared pair rather than overlapping documents.

LOW -- the published enumeration command is cwd-sensitive; its section now
says to run it from the repository root, as the validator's section already did.

LOW -- "parent feature #605" did not match the cited source's "parent PRD
#605". Reworded to what schema/README.md actually says.

Verified after the fixes: 22 ledger entries, all 22 corresponding to a body
claim and no body claim unledgered (checked entry by entry), exactly one
commit-only FACT, no FACT without evidence, 17 backticked paths and 20 link
targets resolve, no cited source changed since a1e8bbc, the published
command runs and prints both nodes, validator exit 0, 79 validator tests OK,
27 schema tests OK.

Refs #639

Signed-off-by: Serina Mcfall <serina.mcfall@gmail.com>

* docs(corpus): fix four AGENTS.md defects found by authoring against it (#636)

Three sibling agents authored corpus nodes following this document literally, and
reported what broke. These four are the defects that produced real false claims in
their work, not stylistic complaints. Filed as #1471; fixed here because #1462 is
still open and 45 further nodes would inherit them.

1. THE ONLY-NODE JUSTIFICATION WENT FALSE, AND TWO AGENTS COPIED IT.
   "A node with no sibling to point at correctly declares none" read as a general
   rule. It was a fact about one moment -- when this was the corpus's only node --
   and stopped holding as soon as a second existed. Both the #1309 and #1307 agents
   reproduced the reasoning and wrote a justification that was false at the time
   they wrote it; their adjudicators caught it, not their own reviews. Now says to
   enumerate what exists and give the real reason, and records why.

2. AN ISSUE-ONLY SOURCE HAD NO HONEST CLASS. The citation table lists tool results as
   legitimate while "Three things a passing run does not mean" says a FACT resting
   only on UNVERIFIED citations "has not been checked by anything", with one named
   exception. A claim whose only source is a GitHub issue has no openable form and
   cannot be SHA-pinned, so both passages applied and disagreed. The #1307 agent hit
   this with nothing honest to choose. Now states it: TEAM_KNOWLEDGE with provided_by
   naming the issue, which is what ADR-0029 requires of GitHub history anyway.

3. A RANGE WAS READ AS A MAPPING. The not-covered table paired eight subjects with
   "#1307-#1351", which is 45 issues and no subject-to-issue pairing at all. The
   #1307 agent cited this table for nine invented mappings -- exactly the "FACT cited
   to a file that does not discuss the claim" failure the document warns about two
   sections earlier. Now says outright that it is a range, not a mapping, and gives
   the search command.

4. CREATE STEP 3 ROUTED SOMETHING STEP 8 DID NOT ACCEPT. Step 3 sends
   expected-but-unverified items to the scope section; step 8 said only "state what
   the node does not cover". A boundary and a confidence disclosure are different
   things, so working the steps in order left that category homeless. Both the #1307
   and #1309 agents reported it independently. Step 8 now names both.

Verified: schema errors 0, 21 ledger entries, validator exit 0, 79 tests OK.

Refs #636 #1471

Signed-off-by: Serina Mcfall <serina.mcfall@gmail.com>

* docs(corpus): fix a false attribution and the relationship-target trap (#636)

Two more defects reported by the #1308 agent after authoring against this document.
Both are mine, and the first is embarrassing in context.

1. THE CITATION TABLE MISATTRIBUTED ITSELF, IN THE SECTION ABOUT NOT DOING THAT.
   It said "CONTRACT.md §3 defines the six shapes" above a SEVEN-row table whose
   GitHub-link and external-URL rows appear nowhere in §3. Measured: grepping that
   section for http/url/github returns zero hits. So the sentence cited a file that
   does not discuss two of the rows beneath it -- the exact failure this document
   warns about, committed by this document. The #1308 agent took the table as an
   accurate summary and built a scope argument on "four of six shapes name code"
   before their plan review caught it. Now states what §3 actually enumerates, that
   the two URL forms are the validator's and not §3's, and that the table is seven
   rows and not a summary.

2. "ADD RELATIONSHIPS ONLY TO NODES THAT EXIST" WAS UNFOLLOWABLE, AND DANGEROUSLY SO.
   "Exist" was ambiguous between the branch you are working on and the branch you
   merge into, and the checker loads whatever is present where it runs. An agent
   branched off this unmerged node could therefore target `corpus-agents`, validate
   clean locally, and break CI on `launchpad`, where the node is absent. The #1308
   agent hit exactly that and their review-code raised it as a Blocker. Step 9 now
   says "exists on the branch you are merging INTO", explains why the distinction is
   the trap, and gives the command to check the merge base rather than the local tree.

This is the second round of AGENTS.md fixes sourced from agents authoring against it,
and the pattern is worth naming: every defect found this way was invisible to three
review passes over the document itself, and obvious within minutes to someone trying
to follow it. Reviewing a procedure is not the same as executing one.

Verified: validator exit 0, 79 tests OK, schema clean.

Refs #636 #1471 #1478

Signed-off-by: Serina Mcfall <serina.mcfall@gmail.com>

---------

Signed-off-by: Serina Mcfall <serina.mcfall@gmail.com>
serina-mcfall added a commit that referenced this pull request Aug 26, 2026
* docs(corpus): plan for issue #636 -- launchpad/docs/corpus/AGENTS.md

Reviewed by an independent cross-model review-plan pass (Codex, gpt-5.6-sol,
high effort): eleven findings, all applied. Three more were found afterwards,
the load-bearing one being that the plan blocked its own STEP 2 on an OPEN
item -- an OPEN item a step must consume is a missing input, not an open
question.

Refs #636

Signed-off-by: Serina Mcfall <serina.mcfall@gmail.com>

* feat(corpus): AGENTS.md frontmatter and skeleton (#636 STEPs 2-3)

The corpus's first hand-authored node. Frontmatter validates against
node.schema.json with zero errors; 11 evidence entries; no relationships,
because no sibling node exists to target and an unresolved target is a hard
validator error. The repository revision is recorded in the evidence ledger --
the schema calls that array the provenance ledger and permits no other field,
so it is the only schema-legal place for it.

STEP 3 (RUNS HERE) proved the validator actually scans the node rather than
skipping it: mutating type: to a value outside the enum produced
'FAIL corpus-agents: schema violation at type' and exit 1, naming the node's
id; restoring it returned exit 0.

Also replaces test_real_corpus_root_currently_has_no_content_outside_schema,
which asserted the corpus root is EMPTY and therefore could not survive the
first authored node. Authorised by Serina as a deliberate widening of the
plan's LEFT OUT scope, recorded in the plan. The replacement asserts what the
original's own comment said it was for -- content present AND nothing from
schema/ -- and both halves were negative-controlled before being trusted.

Refs #636

Signed-off-by: Serina Mcfall <serina.mcfall@gmail.com>

* docs(corpus): AGENTS.md node-contract section (#636 STEP 4)

Adds the lookup table of authoritative sources and the 'what a corpus node
is' section: one file per node, one idea per node, where nodes go, id
permanence, relationship resolution, and the authored-vs-generated boundary.

Reproduces no enum member list and no field-combination matrix -- those stay
canonical in node.schema.json and schema/README.md and are linked instead.
The checker never reads body prose, so a copy here would stay green while
drifting. Verified: no type-enum member and none of entry_class/provided_by/
confidence appear in the body.

Refs #636

Signed-off-by: Serina Mcfall <serina.mcfall@gmail.com>

* docs(corpus): AGENTS.md evidence and citation section (#636 STEP 5)

The part that lives nowhere else: what the checker actually does with each of
CONTRACT.md's six citation shapes, and three things a passing run does not
mean -- that a citation supports its claim (checking is structural, the file
is never opened and compared), that UNVERIFIED is a pass, or that a line
number was verified (#1459).

Names the three evidence classes and what each is FOR, but not which fields
the schema requires per class -- that matrix stays in node.schema.json.
Verified absent from the body: entry_class, provided_by, confidence.

Refs #636

Signed-off-by: Serina Mcfall <serina.mcfall@gmail.com>

* docs(corpus): AGENTS.md create/update/retire procedures (#636 STEP 6)

This is #605's acceptance criterion for the file -- a cold-start agent
creating, updating or retiring one node without oral guidance. Three numbered
procedures, each ending in the same check command.

Adds a 'Running the check' section giving the interpreter form (works without
Hermit; the just recipe needs activation first) and documenting --root, since
without it the command always validates the real corpus whatever directory
you are standing in.

Retire covers the three things most likely to be got wrong: inbound
relationships resolved before retirement, status set rather than the file
deleted, and the id never reused or renamed.

Refs #636

Signed-off-by: Serina Mcfall <serina.mcfall@gmail.com>

* docs(corpus): AGENTS.md scope and omissions (#636 STEP 7)

Names each deferred area with its owning issue rather than leaving a reader
unable to tell a gap from an omission: the standards and templates
(#1307-#1351, none merged), generated-artifact provenance (#1316), claim-type
and flagged state (#1410), the human entry point (#639), and unverified line
numbers in citations (#1459).

Records why this node carries no relationships -- at its recorded revision it
was the only authored node, and a target no node carries is a hard error --
so the absence reads as deliberate rather than forgotten.

Every repo path cited in the body verified to resolve to a real file.

Refs #636

Signed-off-by: Serina Mcfall <serina.mcfall@gmail.com>

* docs(corpus): audit AGENTS.md against its own ledger (#636 STEP 8)

The audit found two things the earlier steps had missed.

The document's own rule -- 'a FACT resting only on UNVERIFIED citations has
not been checked by anything' -- condemned the node's own ledger entry 1, the
provenance record, whose only citation is a commit reference. Resolved by
stating the one carve-out narrowly and giving it a real check
(git cat-file -e <sha>, run, exit 0) rather than by quietly exempting it. A
commit citation on a claim about repository CONTENT is explicitly still not
covered.

Ledger entry 11 overreached: 'every change under launchpad/docs/corpus is
gated in CI' where the workflow triggers on pull requests and pushes to
launchpad. Narrowed to what the workflow actually does.

Also records a scope judgement the DoD asks for: the citation-shape table is
reference material rather than instruction and belongs in the evidence
standard (#1314) when that lands. Noted in place rather than folded in
silently.

Verified: all 11 ledger entries resolve, every body path is a real file,
merge-base equals the recorded revision, and exactly one hand-authored .md
was added under the corpus root.

Refs #636

Signed-off-by: Serina Mcfall <serina.mcfall@gmail.com>

* test(corpus): strengthen real-root discovery test (#636, review-tests finding)

An independent review-tests pass defeated the first replacement: patching
discover_markdown_files to a hardcoded 'return [root / "AGENTS.md"]' -- a
constant that never touches the filesystem -- satisfied both 'non-empty' and
'nothing from schema/' while proving neither discovery nor exclusion. My own
negative control had broken the exclusion set and emptied the corpus, but
never tried a constant. Showing that assertions CAN fail is not the same as
showing they can only pass for the right reason.

The expectation is now derived from an independent filesystem walk and
compared for equality, which catches partial walks and both exclusion
directions.

It does NOT catch the constant today, and the docstring says so rather than
claiming otherwise: the corpus holds exactly one node, so [root/'AGENTS.md']
IS the correct answer and no assertion against the real tree can tell a
constant from a real walk. That resolves itself at the second node.
test_sibling_discovered_schema_dir_excluded catches it now -- verified FAIL
under that mutant -- and is the real proof of discovery behaviour.

Measured, after fixing a mutation harness that had patched a different module
instance than the test uses and so reported every mutant as caught:
  returns nothing     -> FAIL (caught)
  exclusion disabled  -> FAIL (caught)
  constant return     -> PASS (not caught; sibling catches it)

Refs #636

Signed-off-by: Serina Mcfall <serina.mcfall@gmail.com>

* docs(corpus): address review-code findings on AGENTS.md (#636)

FINDING A -- the carve-out claimed enforcement it does not have. 'There is
exactly one carve-out, and widening it defeats the rule' read as though some
check held the line. Nothing does: a second, third or tenth commit-only FACT
produces extra non-fatal UNVERIFIED notices and still exits 0, which the
reviewer proved by adding one. Now says plainly that this is a convention a
REVIEWER holds, names the signal to look for (more than one commit-only FACT
in a ledger), and states that no check will raise it.

FINDING B -- create-node step 3 asked the author to record four categories of
evidence and gave a destination to exactly one of them. Adds the mapping:
revision to the ledger, sources to the citations of the claims they support,
and expected-but-unverified to the body's scope section. Plus the rule that
anything reaching none of the three was either a missing claim or a stale
note.

Applying B to this node exposed that it did not follow its own new rule --
the same self-contradiction class STEP 8 caught. Its scope section now names
the two things unverified when it was written: no agent harness was tested
reading it as a resolved AGENTS.md, and relationships.schema.json's enum was
never read directly.

Re-audited after the change: exactly one commit-only FACT remains, the
enforcement claim is gone, validator exits 0, 79 tests pass.

Refs #636

Signed-off-by: Serina Mcfall <serina.mcfall@gmail.com>

* docs(corpus): address the cross-model final review (#636)

Codex review-final returned REQUEST_CHANGES with seven conditions, then a
second pass confirmed five fixed and refused two. Both refusals were correct.
This commit is the whole of that work.

1. LEDGER COMPLETENESS. Seven entries added (11 -> 18) for claims the body
   taught with no classified entry behind them: the evidence classes, the
   "supersedes" vocabulary, the syntax-only GitHub check, the external-URL
   verdict, and the delete-vs-retire distinction.

   The recheck caught two of these still wrong. An entry said the schema
   "permits no field outside that set" after listing the six required fields
   -- it also permits "relationships", so the entry was wrong about the file
   it cited. And the "supersedes" entry cited node.schema.json, which carries
   only the enum; the directionality it asserts ("source replaces target;
   target becomes historical") lives in relationships.schema.json:30, a file
   this node had listed as read-but-unverified and cited anyway. Read it,
   cited it.

2. RETIREMENT WAS BUILT ON A FALSE PREMISE. It claimed inbound relationships
   "no longer resolve" after retirement. Measured: with the file retained --
   which the same procedure requires -- they resolve fine; only deletion
   breaks them. Rewritten so the reason matches the mechanism, the danger is
   stated correctly (edges keep pointing at a node that stopped being
   current, and nothing warns you), and a replacement declares "supersedes",
   which the procedure never mentioned.

3. CITATION TABLE OVERSTATED VERIFICATION. A GitHub link pinned to a real SHA
   naming a file that never existed returns ok -- the checker never contacts
   GitHub. The table now carries a "does it prove the target exists?" column,
   gains the external-URL row it was missing, and names the trap. All seven
   rows independently confirmed against the classifier.

4. PROVENANCE SEMANTICS -- and the defect the recheck was right to refuse.

   The update procedure said to bump the revision on every edit; this node
   kept its original across seven. The first attempt defined the field as an
   evidence snapshot and classified that as INFERENCE citing node.schema.json.
   That schema says nothing whatever about revisions, so the citation could
   not support the statement: a policy choice had been given a class that made
   it look derived. It is now TEAM_KNOWLEDGE attributed to #636's own
   definition of done -- "the draft is checked against the repository revision
   recorded in provenance" -- which is where the meaning comes from. ADR-0029
   requires exactly that: history and team knowledge stay attributed rather
   than being promoted to fact.

   The node also claimed it was "the only authored node in the corpus" at the
   recorded revision. It did not exist at that revision and neither did any
   other node: git cat-file -e on that path at that sha exits 128, and the
   corpus held zero .md files outside schema/. Corrected.

   The revision still does not move, and the reason is checkable rather than
   asserted: every cited source is byte-identical between that revision and
   now, so verifying a claim at HEAD and at the recorded revision were the
   same act. git diff --name-only <sha> -- <cited paths> returns nothing.

5. AUDIENCE. Added "reviewer": the document assigns reviewers an enforcement
   duty no tool performs, so they are addressed by it. The adjudicator had
   declined this finding (nothing consumes the field today); the final pass
   argued audience describes who is addressed, not who parses it. Went with
   the latter. Whether "developer" also belongs is left undecided.

6. The test module docstring claimed absolute fixture isolation that was false
   before this branch and more visibly false after it. States the rule with
   its one named exception instead. Flagged independently by the adjudicator
   and the final pass.

   Pre-existing, fixed while in the file: the same docstring advertised
   "python3 -m unittest launchpad.project_intelligence.corpus.tests.test_validate",
   which has never worked -- the directory is "project-intelligence" with a
   hyphen, not a legal package name. Confirmed ModuleNotFoundError, removed.

7. Full suites re-run in a writable environment: 79 validator tests OK, 27
   schema tests OK, real corpus exit 0. The plan's two-node create/update/
   retire charter ran with its negative control -- a deliberately invalid node
   failed and named itself, proving the scratch root was the one scanned;
   retirement kept beta's edge resolving; deleting alpha instead broke it
   exactly as the corrected text now says.

Final state: 18 ledger entries, schema errors 0, every citation resolves, no
cited source changed since the recorded revision, exactly one commit-only
FACT, 79 + 27 tests green, real corpus exit 0.

Refs #636

Signed-off-by: Serina Mcfall <serina.mcfall@gmail.com>

* docs(corpus): stop asserting provenance policy this node cannot source (#636)

Third cross-model pass, third refusal on the same condition. It was right
three times, and the third refusal finally identified why the first two fixes
failed: the document was writing corpus-wide provenance policy it had no
authority to write. Reclassifying it (INFERENCE, then TEAM_KNOWLEDGE) only
changed the label on an unsourced decision.

WHAT ACTUALLY CHANGED: the document stops making the claim.

#1321 ("document corpus standard for provenance", unlanded) owns the question
of whether a recorded revision may stay put across edits. This node now says
so, states its own approach as working practice pending that standard rather
than as a rule others must follow, and defers. The ledger entry no longer
asserts the policy -- it records only what #636's definition of done actually
requires, which is a fact about the issue and is attributed to it.

Also fixes a self-contradiction the last fix opened, exactly the failure shape
this repository keeps producing. The previous commit added a FACT citing
relationships.schema.json for the supersedes directionality. The same node
still said, in its unverified list, that relationships.schema.json had never
been read. Both could not stand. The file WAS read; the stale disclaimer is
now narrowed to what remains genuinely unchecked -- that the two enum lists
agree, which rests on a test that was not run.

Ledger completeness, third sweep: entries added for the generated-artifact
rejection, the non-enforcement of the one-commit-only-FACT convention, and
positional citations not being bounds-checked. Prose that asserted this
node's own history -- node counts at the recorded revision, byte-identity of
cited sources -- is removed rather than given entries: it told the reader to
trust the document about a fact they can check themselves in one command, so
the document now gives them the command instead.

TEAM_KNOWLEDGE's description corrected. It said "a person told you", which the
schema does not require (it asks who or what told the corpus) and which made
the class look unavailable for an attributed issue or decision record. It now
also says outright what it is not: a place to park a decision you made
yourself. Attributing an extrapolation to the thing it started from does not
make it something you were told.

Updating step 4 restated as four explicit branches including the
byte-identical case, which the earlier "bump on every edit" wording omitted
while this very node relied on it -- the inconsistency the pass named.

Verified: 21 ledger entries, schema errors 0, every citation resolves, exactly
one commit-only FACT, no cited source changed since the recorded revision, 79
validator tests OK, real corpus exit 0.

Refs #636

Signed-off-by: Serina Mcfall <serina.mcfall@gmail.com>

* docs(corpus): add the decision-references standard (#1310)

Adds launchpad/docs/corpus/standards/decision-references.md, the canonical
policy node for how a corpus node cites an accepted decision -- an ADR or a
ratified specification -- and when a decision is the wrong citation entirely.

The pivot the standard has to make decidable is ADR-0029's: the same record is
the right citation for an intent/authorization claim and the wrong one for a
behaviour claim. The node gives an author a single question that decides it,
three worked examples from this repository, and separated MUST/SHOULD sections.

Also covers what an author does when two accepted decisions of the same claim
type conflict -- record both, flag, escalate as a type:adr issue, never pick a
side -- and what happens to a citation whose record is later superseded: the
path still resolves, so validation stays green and the citation goes quietly
wrong.

Enforcement of the flagged state in the schema and checker is #1410's work and
is named as deferred, not implemented here. No relationships are declared: no
sibling standard node exists yet and an unresolvable target is a hard error.

Refs: ADR-0029, ADR-0028, launchpad/decisions/README.md
Signed-off-by: Serina Mcfall <serina.mcfall@gmail.com>

* docs(corpus): fix MUST 3's false claim about the checker (#1310)

review-code found, and review-adjudicate confirmed and promoted to Blocker, that
MUST 3 asserted a bare repository path is "the one citation shape checked against
the filesystem". That is false. _classify_citation routes a path:line citation
through _FILE_POSITION_RE and then into _classify_repo_path -- the same filesystem
check a bare path gets. Only the line number goes unchecked, which is what
AGENTS.md's own citation table has said all along.

The false rationale also produced a second verdict on one input: MUST 3 forbade a
line position outright while the SHOULD section only "preferred" a bare path, so a
reviewer enforcing the MUST and an author following the SHOULD were both correctly
following this file.

MUST 3 now states the real reason -- the path is checked either way, and a line
number is an unverified precision that drifts silently -- and keeps the
prohibition. The overlapping SHOULD bullet is replaced with one that does not
restate the rule at a weaker strength. A ledger entry records the path:line
behaviour, and the reviewer checklist now says MUST 3 is unenforced too.

Also narrows the "cite the record, not the issue" SHOULD so it cannot read as
permitting an authorization claim to cite an issue, which MUST 1 forbids.

Signed-off-by: Serina Mcfall <serina.mcfall@gmail.com>

* docs(corpus): address the final review; rebase onto the moved base (#1310)

The base branch advanced while this was being built. review-final caught it:
origin/task/636-corpus-agents-md moved to a1e8bbc, which rewrote AGENTS.md --
the only cited source that moved -- and broke this node two ways.

REBASE AND REVISION. Rebased onto a1e8bbc and moved the recorded revision
there after re-verifying every claim at that revision. Verified:
git diff --name-only a1e8bbc HEAD -- <every ledger path> is empty, so no cited
source has moved since.

TWO ENTRIES THE BASE COMMIT INVALIDATED.
a1e8bbc deleted the AGENTS.md sentence "it is still the only one" that a FACT
quoted -- a FACT citing a file that no longer says it, which the validator
cannot see. That entry is gone, replaced by a FACT the checker's source does
support, and the body now gives the reader a command instead of asking them to
take the node's word for repository state.

The same commit narrowed TEAM_KNOWLEDGE: it is now explicitly "not a place to
park a decision you made yourself". One entry did exactly that. #636 spent a
whole commit learning that reclassifying such a claim only relabels it, so this
takes #636's remedy -- the entry now records a fact ABOUT the issue, attributed
to it, and the body carries the policy in its own voice. Both TEAM_KNOWLEDGE
entries now match the sibling node's provided_by shape.

SELF-CONTRADICTIONS. The node broke three of its own rules.
- MUST 7 was unconditional, so the ledger's two ADR-0001 citations violated it.
  Scoped to intent claims, which is the rule it always meant.
- MUST 5 named three ADR sections; README lists five and this node's own
  security-implications FACT cites a sixth. Now covers every section.
- Three behaviour claims about the checker had no ledger entry, including the
  load-bearing "a flagged node validates green" -- which MUST 2 requires
  executable evidence for. Added, and verified: validate.py never reads status.

NEIGHBOUR HOLE FROM THE LAST FIX. The previous commit corrected MUST 3 and the
reviewer checklist but left "What the checks establish" describing bare paths
only and counting three consequences, so that section still implied a path:line
citation would fail. Now states both shapes, four consequences, and the fourth
is the conflict blindness the enforcement discussion depends on.

ALSO. "the whole of ADR-0029" understated the ADR by half and would let a
section-at-a-time reader miss the escalation duty. The reviewer checklist
claimed to hold every MUST while omitting MUST 6, the one nothing else catches.
The conflict procedure withheld the status value the same body names three
times. The omissions table now discloses that this node settles a slice of
#1459's and #1314's territory rather than implying it defers both wholly.

Cross-model note: the mandatory Codex pass could not run -- workspace out of
credits, no other external CLI installed -- so review-final stood in and is
same-model. Recorded in the PR as a gap, not as a pass.

Signed-off-by: Serina Mcfall <serina.mcfall@gmail.com>

* docs(corpus): fix four AGENTS.md defects found by authoring against it (#636)

Three sibling agents authored corpus nodes following this document literally, and
reported what broke. These four are the defects that produced real false claims in
their work, not stylistic complaints. Filed as #1471; fixed here because #1462 is
still open and 45 further nodes would inherit them.

1. THE ONLY-NODE JUSTIFICATION WENT FALSE, AND TWO AGENTS COPIED IT.
   "A node with no sibling to point at correctly declares none" read as a general
   rule. It was a fact about one moment -- when this was the corpus's only node --
   and stopped holding as soon as a second existed. Both the #1309 and #1307 agents
   reproduced the reasoning and wrote a justification that was false at the time
   they wrote it; their adjudicators caught it, not their own reviews. Now says to
   enumerate what exists and give the real reason, and records why.

2. AN ISSUE-ONLY SOURCE HAD NO HONEST CLASS. The citation table lists tool results as
   legitimate while "Three things a passing run does not mean" says a FACT resting
   only on UNVERIFIED citations "has not been checked by anything", with one named
   exception. A claim whose only source is a GitHub issue has no openable form and
   cannot be SHA-pinned, so both passages applied and disagreed. The #1307 agent hit
   this with nothing honest to choose. Now states it: TEAM_KNOWLEDGE with provided_by
   naming the issue, which is what ADR-0029 requires of GitHub history anyway.

3. A RANGE WAS READ AS A MAPPING. The not-covered table paired eight subjects with
   "#1307-#1351", which is 45 issues and no subject-to-issue pairing at all. The
   #1307 agent cited this table for nine invented mappings -- exactly the "FACT cited
   to a file that does not discuss the claim" failure the document warns about two
   sections earlier. Now says outright that it is a range, not a mapping, and gives
   the search command.

4. CREATE STEP 3 ROUTED SOMETHING STEP 8 DID NOT ACCEPT. Step 3 sends
   expected-but-unverified items to the scope section; step 8 said only "state what
   the node does not cover". A boundary and a confidence disclosure are different
   things, so working the steps in order left that category homeless. Both the #1307
   and #1309 agents reported it independently. Step 8 now names both.

Verified: schema errors 0, 21 ledger entries, validator exit 0, 79 tests OK.

Refs #636 #1471

Signed-off-by: Serina Mcfall <serina.mcfall@gmail.com>

* docs(corpus): fix a false attribution and the relationship-target trap (#636)

Two more defects reported by the #1308 agent after authoring against this document.
Both are mine, and the first is embarrassing in context.

1. THE CITATION TABLE MISATTRIBUTED ITSELF, IN THE SECTION ABOUT NOT DOING THAT.
   It said "CONTRACT.md §3 defines the six shapes" above a SEVEN-row table whose
   GitHub-link and external-URL rows appear nowhere in §3. Measured: grepping that
   section for http/url/github returns zero hits. So the sentence cited a file that
   does not discuss two of the rows beneath it -- the exact failure this document
   warns about, committed by this document. The #1308 agent took the table as an
   accurate summary and built a scope argument on "four of six shapes name code"
   before their plan review caught it. Now states what §3 actually enumerates, that
   the two URL forms are the validator's and not §3's, and that the table is seven
   rows and not a summary.

2. "ADD RELATIONSHIPS ONLY TO NODES THAT EXIST" WAS UNFOLLOWABLE, AND DANGEROUSLY SO.
   "Exist" was ambiguous between the branch you are working on and the branch you
   merge into, and the checker loads whatever is present where it runs. An agent
   branched off this unmerged node could therefore target `corpus-agents`, validate
   clean locally, and break CI on `launchpad`, where the node is absent. The #1308
   agent hit exactly that and their review-code raised it as a Blocker. Step 9 now
   says "exists on the branch you are merging INTO", explains why the distinction is
   the trap, and gives the command to check the merge base rather than the local tree.

This is the second round of AGENTS.md fixes sourced from agents authoring against it,
and the pattern is worth naming: every defect found this way was invisible to three
review passes over the document itself, and obvious within minutes to someone trying
to follow it. Reviewing a procedure is not the same as executing one.

Verified: validator exit 0, 79 tests OK, schema clean.

Refs #636 #1471 #1478

Signed-off-by: Serina Mcfall <serina.mcfall@gmail.com>

---------

Signed-off-by: Serina Mcfall <serina.mcfall@gmail.com>
…ed at (#636)

Rebased onto `launchpad` and reduced to one file. #1468, #1469 and #1477 merged
while this PR sat on CHANGES_REQUESTED, and each carried the shared #636 base,
so two of this branch's three files -- the #636 plan and test_validate.py --
are now byte-identical on `launchpad` and no longer belong in this diff. What
is left is the fix itself.

The consequence worth stating plainly: those merges landed the UNFIXED corpus
AGENTS.md on `launchpad`, including the line this commit removes -- "Do not
take that on this document's word -- the command is the check". The High
finding is on the default branch now, which is why this is a defect fix rather
than a pre-merge correction.

HIGH -- the provenance-update shortcut could certify stale evidence. Two
passages said an empty `git diff --name-only <recorded-sha> -- <ledger paths>`
established that every cited source was unchanged and every claim still stood.
Both are false for citation forms this document itself permits.

Measured, not argued. A file that DOES differ reports as changed with a bare
path and reports empty output with exit status 0 once `:127` or `:219-221` is
appended -- no error, no warning. So an unnormalized `path:line` citation makes
a changed file indistinguishable from an unchanged one, which is the worst
available failure shape for a provenance check. Recorded as a new FACT entry
with the tool-result citation that demonstrates it.

The rule now states its reach: normalize positions off the path first; only
bare path, file line and file range are in scope; graph edge, tool result,
commit and the two URL forms are excluded explicitly, so the branch is simply
unavailable when a ledger contains one. The conclusion is narrowed to what a
diff can support -- those files are unchanged -- with re-verification left as
the only thing that establishes a FACT still holds.

MEDIUM -- the one-node rule had no supporting ledger entry. "One node is one
independently maintainable idea" is a substantive instruction and the ledger's
only #636 entry backed the recorded-revision requirement instead. Added as
TEAM_KNOWLEDGE attributed to #636's definition of done, which states it
near-verbatim, matching the existing entry's classification for the same source.

Also corrected a wrong-direction cross-reference: "*Updating a node* below" in
Scope and omissions, where that section is above it.

The recorded revision stays at 0052f5a, re-checked against the NEW base after
the rebase rather than assumed to still hold: the two non-file citations were
excluded, the nine normalized file paths were passed to `git diff` against
0052f5a from `launchpad` tip 0ffc1c9, and the result was empty.

The second Medium is not addressed because it is stale -- the citation table
was already corrected by ebe2daf and says outright that it is seven rows and
is not a summary of CONTRACT.md section 3. The remaining cross-document
disagreement is tracked as #1478. Evidence is in the PR thread.

Validated against the full four-node corpus now on `launchpad`, not just this
node: PASS, 5 non-fatal UNVERIFIED notices, all commit or tool-result
citations that name no openable file.

Signed-off-by: Serina Mcfall <serina.mcfall@gmail.com>
serina-mcfall added a commit that referenced this pull request Aug 26, 2026
…d FACT (#1307)

Addresses the Medium finding from the review panel on PR #1470.

The ledger recorded "One node carries one recorded revision covering its whole ledger,
and moving that revision makes a statement about every claim the node holds" as a FACT
cited to AGENTS.md, and section 3's maintenance-clock test rested on it as though it
were settled. AGENTS.md says the opposite about its own status: whether a recorded
revision may stay put while a node is edited is #1321's to decide, is unlanded, and
what AGENTS.md states is that document's working practice rather than a corpus-wide
rule -- three review passes having already rejected attempts to present it as one.

The entry now states what AGENTS.md actually says, including that it is working
practice pending #1321. That is a FACT about a document this branch carries, which is
checkable; the previous entry asserted the underlying rule, which is not settled.

Section 3 now names the premise as working practice, defers to #1321 explicitly, and
says what survives if #1321 rules the other way: two halves on different maintenance
clocks still force a choice about what the recorded revision covers, so the test is
worth running either way and only its sharpness depends on the ruling. The section 6
checklist item is scoped the same way.

Also merges the current head of task/636-corpus-agents-md. The branch was carrying
AGENTS.md at e422283 while the base had advanced to 8c6c21a, which rewrote the
revision-shortcut procedure this finding is about -- fixing the claim against the stale
copy would have described text that had already changed.

Verification, after the merge and the edits:

  python3 launchpad/project-intelligence/corpus/validate.py
  PASS  corpus validation found no errors; 4 item(s) reported unverified

  python3 -m unittest discover -s launchpad/docs/corpus/schema/tests -p "test_*.py"
  Ran 27 tests -- OK

  python3 -m unittest discover -s launchpad/project-intelligence/corpus/tests -p "test_*.py"
  Ran 79 tests -- OK

The review's second finding, on merge order, is not addressed here: it asks that #1462
merge first so this PR carries only the atomicity standard. That is a sequencing
decision, not a change to this branch.

Signed-off-by: Serina Mcfall <serina.mcfall@gmail.com>
serina-mcfall added a commit that referenced this pull request Aug 26, 2026
launchpad gained launchpad/docs/corpus/AGENTS.md while this branch was open, via
PRs #1468, #1469 and #1477 merging -- each carried the shared #636 base. That made
an add/add conflict against the copy this branch had merged from
task/636-corpus-agents-md.

Resolved in favour of launchpad's copy (e422283), NOT this branch's newer one
(8c6c21a). The newer copy carries #1462's fixes for its outstanding review
findings. Those fixes belong to #1462 and are still under review; landing them
through this PR would merge unreviewed changes to another PR's file under cover of
an atomicity-standard change.

This branch's own fix is unaffected either way: the "What the corpus has NOT settled
about revisions" passage that the recast ledger entry cites is present in both
copies, so the claim is true against whichever version ends up on launchpad.

Signed-off-by: Serina Mcfall <serina.mcfall@gmail.com>
@serina-mcfall
serina-mcfall force-pushed the task/636-corpus-agents-md branch from eb8de57 to 919886b Compare August 26, 2026 21:38
@serina-mcfall

Copy link
Copy Markdown
Author

Rebased onto launchpad — history rewritten, diff now one file

eb8de57b5919886b41 (forced update, authorised by @serina-mcfall). Flagging it because the review thread above references a three-file diff that no longer exists.

Why. #1468, #1469 and #1477 merged while this PR sat on CHANGES_REQUESTED, and each carried the shared #636 base. Two of this branch's three files are now byte-identical on launchpad, so they no longer belong in the diff:

File On launchpad This branch
launchpad/plans/2026-08-26-issue-636-corpus-agents-md.md 65d3570b82 same → dropped
launchpad/project-intelligence/corpus/tests/test_validate.py ffdff6ffc0 same → dropped
launchpad/docs/corpus/AGENTS.md e42228305d the fix → kept
launchpad/docs/corpus/AGENTS.md | 59 +++++++++-----
1 file changed, 48 insertions(+), 11 deletions(-)

The consequence worth stating plainly. Those merges landed the unfixed AGENTS.md on launchpad, including the line this PR removes:

Do not take that on this document's word — the command is the check.

So the High finding is on the default branch now, and this is a defect fix rather than a pre-merge correction. Nothing about the fix changed in the rebase — the same two findings are addressed, the same way, with the same evidence as in my earlier comment.

Re-verified after the rebase, not carried over

The base moved from 0052f5a7 to 0ffc1c9e4, so everything base-dependent was re-run rather than assumed.

$ python3 launchpad/project-intelligence/corpus/validate.py
UNVERIFIED  corpus-agents: evidence entry 1, citation 1: is a commit reference, which names no openable file
UNVERIFIED  corpus-agents: evidence entry 20, citation 1: is a graph-edge or tool-result citation, which names no openable file
UNVERIFIED  corpus-readme: evidence entry 1, citation 1: is a commit reference, which names no openable file
UNVERIFIED  corpus-standard-confidence: evidence entry 1, citation 1: is a commit reference, which names no openable file
UNVERIFIED  corpus-standard-decision-references: evidence entry 1, citation 1: is a commit reference, which names no openable file
PASS  corpus validation found no errors; 5 item(s) reported unverified

$ python3 -m unittest discover -s launchpad/project-intelligence/corpus/tests -p "test_*.py"
Ran 79 tests in 0.318s
OK

That is the full four-node corpus now on launchpad, not just this node — corpus-readme, corpus-standard-confidence and corpus-standard-decision-references are the three that landed. All five notices are commit or tool-result citations that name no openable file, which is the honest outcome for those forms.

Recorded revision stays at 0052f5a7, re-checked against the new base using the corrected procedure this PR introduces: the two non-file citations excluded, the nine normalized file paths passed to git diff against 0052f5a7 from tip 0ffc1c9e4, result empty.

Still needs

A re-review to clear CHANGES_REQUESTED. mergeable: MERGEABLE, all seven checks SUCCESS including the corpus validate job.

Not verified

  • I did not re-check whether the three merged nodes' own claims survived their merge. This PR touches only AGENTS.md; the validator passing says their citations resolve structurally, not that their statements are still true against the current tree.
  • The two dropped files were compared by blob hash, not by reading them. Identical hashes are conclusive for "no content difference", but I did not review what those files say now that they are on launchpad.

serina-mcfall added a commit that referenced this pull request Aug 26, 2026
Addresses all three findings from the review panel on PR #1480. Each verdict below
was measured by calling validate._classify_citation directly rather than read off
the document.

High -- entry 15 and section 8 asserted as FACT that AGENTS.md "introduces its
citation table as CONTRACT.md section 3's six shapes and then lists seven rows".
The AGENTS.md this branch carries says the opposite in as many words: the table
"is not a summary of section 3", because its two URL rows are forms validate.py
recognises and section 3 does not enumerate. That correction landed in ebe2daf,
after this node recorded its claim against 60d4947. The entry and section 8 now
describe the AGENTS.md that actually ships beside them.

Section 8 also now records what happened, because the node is a standard about
citation discipline and this was a live instance of the failure it teaches against:
a claim recorded against one revision of a moving document is not a claim about the
document that finally ships. AGENTS.md records the same incident from its own side.
The open CONTRACT.md-versus-validator divergence is unchanged and still points at
#1478.

Medium -- the ledger said a bare repository path "is opened on disk" while a later
entry said a citation resolving to a real file "is never opened". Both cannot hold.
Measured: a bare path returns ok on resolution alone and the file's contents are
never read. The first entry now says resolved, and says explicitly that resolution
establishes only that the file is there.

Medium -- the ledger scoped the UNVERIFIED channel to "non-GitHub URLs", while the
node's own table records GitHub issue and pull-request URLs as unverified. Measured:

  #1459 -> unverified
  #1462   -> unverified
  https://example.com/spec                          -> unverified
  .../blob/<full-40-sha>/path                       -> ok
  .../blob/launchpad/path                           -> error (mutable ref, ADR-0003)

The entry now scopes the channel to external URLs that are not pinned repository
links, and names GitHub issue and pull-request URLs as included -- which matches the
table and the measured behaviour.

Also merges origin/launchpad, which gained the corpus tree when #1468, #1469 and
#1477 merged. No conflict: this branch already carried the same AGENTS.md blob.

Verification:

  python3 launchpad/project-intelligence/corpus/validate.py
  PASS  corpus validation found no errors; 5 item(s) reported unverified

Signed-off-by: Serina Mcfall <serina.mcfall@gmail.com>

@tucktuck101 tucktuck101 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

am approving after verbal discussions.

@tucktuck101
tucktuck101 merged commit a44cf52 into launchpad Aug 27, 2026
23 checks passed
@serina-mcfall

Copy link
Copy Markdown
Author

Post-merge cross-model (Codex) review found one real finding on this content — filed as #1526 (linked under #605), not fixed here since this PR is already approved and merged.

@serina-mcfall
serina-mcfall deleted the task/636-corpus-agents-md branch August 31, 2026 20:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

by:agent Filed or authored by an AI agent, not a human

Projects

None yet

Development

Successfully merging this pull request may close these issues.

task: document AGENTS.md

2 participants