Skip to content

docs(corpus): add the invariant template node - #1549

Merged
tucktuck101 merged 1 commit into
launchpadfrom
task/1343-corpus-template-invariant
Aug 28, 2026
Merged

docs(corpus): add the invariant template node#1549
tucktuck101 merged 1 commit into
launchpadfrom
task/1343-corpus-template-invariant

Conversation

@serina-mcfall

Copy link
Copy Markdown

Summary

Adds launchpad/docs/corpus/templates/invariant.md, a corpus template node telling a future author how to document one Buzz system invariant: required sections, evidence expectations (FACT only when a source actually enforces the claim, not merely mentions it), and Design by Contract (Bertrand Meyer / Eiffel) as the industry model adapted rather than adopted wholesale. States an explicit boundary against #1320's normative-language standard (a rule about corpus prose wording, not system behavior) and the not-yet-written policy template #1344 (what participants must do, vs. what always holds).

Related issue

Closes #1343

Issue type

Task


Agent provenance

Field Value
Harness / provider Claude Code
Model claude-sonnet-5
Session reference N/A
Initiating human @serina (batch dispatch, corpus templates batch 4: #1329/#1333/#1334/#1343/#1348)

Objective

A single corpus node, corpus-template-invariant, that governs the shape, evidence expectations and adapted industry model for any future corpus node documenting one Buzz system invariant.

Impacted components

  • launchpad/docs/corpus/templates/invariant.md (new file)
  • launchpad/plans/2026-08-27-issue-1343-corpus-template-invariant.md (new file, the plan this PR was built from)

Approach and rejected alternatives

Adopted Bertrand Meyer's Design by Contract vocabulary (class invariant / precondition / postcondition) as the template's industry model, per the batch brief's own steer, but adapted rather than adopted it wholesale: Meyer's invariant is checked automatically by the Eiffel runtime on every routine boundary, and this repository has no such uniform mechanism. The six repo-native invariants surveyed while drafting (crates/buzz-core/src/tenant.rs, crates/buzz-db/src/thread.rs, crates/buzz-audit/src/hash.rs, crates/buzz-relay/src/state.rs, crates/buzz-db/src/push.rs, crates/buzz-acp/src/pool.rs) are each held by a different mechanism (type system, structural code design, a unit test, a SQL predicate, an ordering convention, or nothing but caller discipline), so the template's "Enforcement today" required section asks an author to name the weakest true tier honestly, rather than implying Eiffel-style automatic checking that does not exist here.

Considered citing Meyer's original 1992 IEEE Computer paper ("Applying Design by Contract") directly, since it is the usual primary reference the batch brief pointed at. Rejected as impossible in this environment: no pdftotext/poppler-utils, no importable pypdf/PyPDF2, and automated web-fetch summarization of the PDF returned only a binary-content notice rather than extracted text (three separate attempts, two different PDF mirrors). Used Meyer's own ETH Zurich-hosted HTML page and Eiffel Software's own definitional page instead — both fetched and read directly, and both primary in the sense of being Meyer's/his company's own words — and recorded the paper-text gap explicitly in the node's "Expected but not verified" section rather than silently citing the PDF as read.

Considered whether references should run from the invariant node toward an interface node it underpins. Drafted it that way first, then an independent review-code pass (below) caught that this contradicted the template's own "Boundary" section, which frames an invariant as "the deeper claim an interface's contract can point at" — i.e. the interface should be the references source, not the invariant. Fixed by reversing the Relationships section's guidance and the template skeleton to match, and re-validated.

Verification

Command run:

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

Raw output:

test_duplicate_audiences_rejected (test_schema.InvalidFixtureTest.test_duplicate_audiences_rejected) ... ok
test_fact_with_forbidden_fields_rejected (test_schema.InvalidFixtureTest.test_fact_with_forbidden_fields_rejected) ... ok
test_inference_missing_confidence_rejected (test_schema.InvalidFixtureTest.test_inference_missing_confidence_rejected) ... ok
test_malformed_id_rejected (test_schema.InvalidFixtureTest.test_malformed_id_rejected) ... ok
test_missing_audiences_rejected (test_schema.InvalidFixtureTest.test_missing_audiences_rejected) ... ok
test_missing_evidence_field_rejected (test_schema.InvalidFixtureTest.test_missing_evidence_field_rejected) ... ok
test_missing_evidence_for_fact_rejected (test_schema.InvalidFixtureTest.test_missing_evidence_for_fact_rejected) ... ok
test_missing_evidence_for_inference_rejected (test_schema.InvalidFixtureTest.test_missing_evidence_for_inference_rejected) ... ok
test_missing_identity_rejected (test_schema.InvalidFixtureTest.test_missing_identity_rejected) ... ok
test_missing_origin_rejected (test_schema.InvalidFixtureTest.test_missing_origin_rejected) ... ok
test_missing_status_rejected (test_schema.InvalidFixtureTest.test_missing_status_rejected) ... ok
test_missing_type_rejected (test_schema.InvalidFixtureTest.test_missing_type_rejected) ... ok
test_team_knowledge_missing_provided_by_rejected (test_schema.InvalidFixtureTest.test_team_knowledge_missing_provided_by_rejected) ... ok
test_unknown_audience_value_rejected (test_schema.InvalidFixtureTest.test_unknown_audience_value_rejected) ... ok
test_unknown_origin_rejected (test_schema.InvalidFixtureTest.test_unknown_origin_rejected) ... ok
test_unknown_relationship_type_rejected (test_schema.InvalidFixtureTest.test_unknown_relationship_type_rejected) ... ok
test_unknown_status_rejected (test_schema.InvalidFixtureTest.test_unknown_status_rejected) ... ok
test_unknown_type_rejected (test_schema.InvalidFixtureTest.test_unknown_type_rejected) ... ok
test_unrecognized_field_rejected (test_schema.InvalidFixtureTest.test_unrecognized_field_rejected) ... ok
test_wrong_direction_relationship_rejected (test_schema.InvalidFixtureTest.test_wrong_direction_relationship_rejected) ... ok
test_every_enum_member_has_metadata (test_schema.RelationshipEnumMetadataTest.test_every_enum_member_has_metadata) ... ok
test_every_metadata_entry_has_directionality_and_inverse (test_schema.RelationshipEnumMetadataTest.test_every_metadata_entry_has_directionality_and_inverse) ... ok
test_relationship_enum_matches_node_schemas_inlined_copy (test_schema.RelationshipEnumMetadataTest.test_relationship_enum_matches_node_schemas_inlined_copy) ... ok
test_node_schema_is_valid_json_schema (test_schema.SchemaMetaValidityTest.test_node_schema_is_valid_json_schema) ... ok
test_relationships_schema_is_valid_json_schema (test_schema.SchemaMetaValidityTest.test_relationships_schema_is_valid_json_schema) ... ok
test_full_fixture_passes (test_schema.ValidFixtureTest.test_full_fixture_passes) ... ok
test_minimal_fixture_passes (test_schema.ValidFixtureTest.test_minimal_fixture_passes) ... ok

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

OK

Command run:

python3 launchpad/project-intelligence/corpus/validate.py

Raw output:

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
UNVERIFIED  corpus-template-invariant: evidence entry 1, citation 1: is a commit reference, which names no openable file
UNVERIFIED  corpus-template-invariant: evidence entry 5, citation 1: is a graph-edge or tool-result citation, which names no openable file
UNVERIFIED  corpus-template-invariant: evidence entry 15, citation 1: is an external URL this validator can neither pin nor open
UNVERIFIED  corpus-template-invariant: evidence entry 16, citation 1: is an external URL this validator can neither pin nor open
UNVERIFIED  corpus-template-invariant: evidence entry 18, citation 1: is a graph-edge or tool-result citation, which names no openable file
UNVERIFIED  corpus-template-invariant: evidence entry 21, citation 2: is an external URL this validator can neither pin nor open
PASS  corpus validation found no errors; 11 item(s) reported unverified
  • 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

  • Bertrand Meyer's original 1992 IEEE Computer paper, "Applying Design by Contract," was not read as text. No PDF-to-text tool was available in this environment (pdftotext/poppler-utils absent, pypdf/PyPDF2 not importable, pip3 unavailable to install one), and automated web-fetch summarization of two different PDF mirrors returned only binary-content notices, not extracted text. The node cites Meyer's own ETH Zurich-hosted HTML page and Eiffel Software's own definitional page instead (both fetched and read directly this session) and records this gap explicitly in its own "Expected but not verified" section.
  • No corpus node instance has yet been drafted from this template. Every required section is validated only against this repository's own code comments naming invariants and against Design by Contract's own vocabulary, not against a real instance node passing validate.py end to end.
  • Whether an invariant spanning many crates (like tenant.rs's fence, which affects buzz-db, buzz-auth, buzz-pubsub, buzz-search, buzz-audit and buzz-media) should be one corpus node or split per-layer under AGENTS.md's "one node is one independently maintainable idea" rule is not settled by this template.
  • Whether implements or references is the corpus-wide convention for a node's optional self-link back to its own template remains unsettled, matching the same open question the sibling interface template (#1342, PR task: define the interface corpus template #1541) already noted for itself.

Security implications

None. This is a new Markdown documentation file under launchpad/docs/corpus/templates/; it adds no code path, no dependency, and no runtime behavior.

Escalations

None. Two findings surfaced during review (see Approach section and pipeline note below) were both in-scope defects in this same artefact and were fixed directly, not deferred: (1) review-code found the Relationships section declaring the invariant→interface references edge in the opposite direction from the Boundary section's own stated reasoning — fixed by reversing the guidance and the skeleton to have the interface node declare the edge instead. (2) The same pass flagged the sole INFERENCE's confidence value (0.8) as better justified at 0.6 under corpus-standard-confidence.md's own rubric, since one premise in the reasoning ("the ordinary meaning of 'policy'") is supplied by the author rather than cited — fixed. Both were re-validated after the fix (validate.py exit 0, schema tests 27/27). Searched for duplicate escalation issues per the batch brief; none applied since nothing here needed escalating.

Pipeline note. Followed plan-issue → review-plan → build-change → review-code → review-adjudicate → cross-model-substitute final pass, per the batch brief. Codex (the usual cross-model check) is down per issue #1467, so the final pass was a same-model adversarial self-review reading the finished node cold against parent PRD #605's acceptance sentence and against its own Boundary claims, in addition to the independent review-code subagent pass. review-tests was skipped as N/A: this issue adds no test code, only a documentation node validated by the existing schema tests.

Create launchpad/docs/corpus/templates/invariant.md so a future author has
a schema-valid shape, evidence expectations and an adapted Design-by-Contract
model to write a corpus node documenting one system invariant, distinct from
the normative-language standard (#1320) and the not-yet-written policy
template (#1344).

Closes #1343

Signed-off-by: Serina Mcfall <serina.mcfall@gmail.com>
@serina-mcfall serina-mcfall added the by:agent Filed or authored by an AI agent, not a human label Aug 27, 2026
@serina-mcfall
serina-mcfall marked this pull request as ready for review August 27, 2026 05:27
@tucktuck101 tucktuck101 self-assigned this Aug 27, 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.

Review — invariant template node

Strong template — the Design-by-Contract grounding (Meyer, Eiffel) checks out, the five
enforcement tiers are a genuinely useful frame, and letting an instance pick type by subject
matter rather than fixing one is the right call. But the flagship worked example inverts the
tier it exists to illustrate.

High — the thread.rs example files a transaction-enforced invariant under "convention only", and its cited functions are dead code

The FACT entry claims buzz-db's thread.rs "implements that gotcha as two functions,
increment_reply_count and decrement_reply_count ... nothing in the type system stops a
future call site ... from skipping these functions — the invariant holds only if every such
call site actually calls them
", citing thread.rs:250-287 and :289-327.

Verified in launchpad:

  1. thread.rs:250 is #[allow(dead_code)], and its doc comment at :246-249 reads: "NOTE:
    The primary increment path is inlined inside [insert_thread_metadata]'s transaction. This
    standalone version exists for future use cases..." — no insert path calls it.
  2. The live path is insert_thread_metadata, whose doc at :112-114 reads: "The INSERT and all
    counter UPDATEs are wrapped in a single transaction so a crash between them cannot leave
    reply_count / descendant_count inconsistent with the actual number of reply rows (F9)." I
    confirmed pool.begin() at :129 and tx.commit() at :236, with both counter UPDATEs on
    the same &mut *tx.

Two consequences. Required section 3 says "Naming the weakest true tier is the point of this
section" — yet the example files thread.rs under "nothing but the discipline of every caller
remembering to call the right function" when the live path is transactionally enforced and the
cited functions are unreachable. And the DbC illustration at :182-184 ("momentarily
inconsistent between the two UPDATE statements") holds only for the two dead-code functions,
which take &PgPool and issue independent execute(pool) calls; in the live path both UPDATEs
sit in one transaction precisely so that intermediate state is never observable.

This also trips the template's own rule at :337-343: an enforcement claim is a FACT "only if
the cited source actually enforces the invariant, not merely mentions it."

Re-cite thread.rs:112-114 and reclassify as transaction-enforced within one path plus
convention-and-review across future call sites, and re-ground the point-2 illustration.

Medium — the boundary against the test-contract template is unnamed

See my review on #1540 for the full analysis and the ownership ruling. Short form: your
flagship example ("kind 40002 replies must bump reply_count") is exactly the case
test-contract.md:132 also claims, and neither Boundary section mentions the other template.
One bullet each; both PRs are in this wave.

Low — an INFERENCE names the wrong file as its premise

The closing INFERENCE says "AGENTS.md's actual text states it as a property of which
CommunityId values can come to exist". Grepping the root AGENTS.md for
resolved from the connection host, never supplied, client-supplied community and
the fence returns nothing. The text described is crates/buzz-core/src/tenant.rs's module
doc — which is what the entry's own evidence array cites, and which the body attributes
correctly at :239-242. Reasoning, confidence and citation are all sound; only the prose names
the wrong file. It matters because this entry is the template's central
invariant-versus-policy discriminator.

Low — the skeleton omits frontmatter

:292-297 opens the fenced skeleton at # [Invariant name]: invariant with no --- block,
while both siblings in this wave ship a complete copyable one. That asymmetry bites hardest
here, because this is the one template that deliberately does not fix a single instance type
— so the author gets variable type guidance and no worked example of the field it applies to.

Not findings, ruled out on evidence

  • The launchpad/plans/ file: 19 established siblings with the identical per-issue shape,
    and the content matches this PR. Consistent with repo practice, not a roadmap-file violation.
  • Index registration: the corpus AGENTS.md forbids a hand-authored node index.
  • CI green at head (latest run per check); node validates clean.

Reviewed by tucktuck101's review lane. Every failing claim reported above was reproduced by me against this PR head before posting.

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: define the invariant corpus template

2 participants