Skip to content

fix: ship the record table shape in the templates and the declared gate commands - #6

Merged
ahrazzle merged 1 commit into
mainfrom
fix/record-template-table-shape
Sep 19, 2026
Merged

ahrazzle merged 1 commit into
mainfrom
fix/record-template-table-shape

Conversation

@ahrazzle

Copy link
Copy Markdown
Contributor

Closes #5.

Problem

The documented first step for a new user of this ingredient does not work, and it
does not heal when the roles do their part.

$ cp records/ROTATION-STATE.md.tmpl records/ROTATION-STATE.md
$ python3 scripts/protean-ops/check-rotation.py records/ROTATION-STATE.md
FAIL: no '## Rows' table parsed in records/ROTATION-STATE.md      # exit 2

The same for records/INFLIGHT.md (no '## Lease rows' table parsed) and
records/LEARNINGS.md (no '## Rows' table parsed). Appending a fully formed row to
the copied record still fails, because the gates' parse_section_table consumes the
first pipe-delimited line as the table header, so with only a data row present the
record can never become parseable.

Cause: records/ROTATION-STATE.md.tmpl, records/LEARNINGS.md.tmpl, and
records/INFLIGHT.md.tmpl ended at a bare ## Rows / ## Lease rows heading. Each
shipped its columns as a definition table under ## Columns (fixed), but not the
record's own table header, which is the part the gate parses.
records/CONTRIB-STATE.md.tmpl already ships that shape and is the model for this
change.

The same path also declared the rotation gate two different ways: the README named
tests/fixtures/valid/ROTATION-STATE.md, which cannot work after installation because
tests/ is not in the payload, while protean-ingredient.json names
records/examples/valid/ROTATION-STATE.md --roster records/examples/valid/ROSTER.txt.
The README's Use block was six <records> placeholder lines with no expansion rule,
and five of the six failed when run as shown.

What changed

  • records/ROTATION-STATE.md.tmpl: carries the 9-column table header its gate reads,
    in the gate's column order.
  • records/LEARNINGS.md.tmpl: carries the 9-column table header its gate reads.
  • records/INFLIGHT.md.tmpl: carries the 11-column table header and the explicit
    (empty state) row its gate skips, so a fresh in-flight record is checkable before
    any claim exists.
  • README.md: the Use block is now the copy step plus the four record gates as
    runnable commands, with the exit-2 semantics of a record that has no row yet stated
    plainly; the gate table's rotation row is the descriptor's command; the paragraph
    below the table now names protean-ingredient.json as authoritative and says why no
    declared command may point into tests/.
  • tests/test_gates.py: TestShippedTemplates pins each shipped template to its
    gate's column set and pins the copy path, so a template that loses its table turns
    the suite red.
  • CHANGELOG.md: the entry for this change, in the repository's existing format.

No gate, payload, install target, gate name, or descriptor changed; the change is a
compatible addition per CONTRIBUTING.md.

Verification

  • Sabotage run: python3 -m unittest discover -s tests -p 'test_gates.py' fails on
    the four new template cases against the previous templates
    (test_rotation_template_carries_the_gate_columns,
    test_learnings_template_carries_the_gate_columns,
    test_inflight_template_carries_the_gate_columns,
    test_copied_inflight_template_is_checkable), and passes after the change.
  • python3 -m unittest discover -s tests: 144 tests, OK (137 before).
  • Every gate declared in protean-ingredient.json exits 0 from a fresh clone and from
    an installed target.
  • README Use block run verbatim on the fixed tree: the in-flight and
    contribution-state copies pass immediately (scanned=0); the rotation and learnings
    copies report missing input (exit 2) until one row is appended, and pass with one
    appended row (scanned=1, violations=0, exit 0).
  • The Gates section's internal-name row and all other declared rows are unchanged.

Risk

Documentation and record templates only. No gate logic, no descriptor, no payload
contents. The three changed templates are copied verbatim by install.sh, so an
existing target that re-installs gains the table header and an in-flight empty-state
row; a target that already copied a template keeps its own record file.

Exclusions, reported in the issue rather than fixed here

  • check-contrib-state.py exits 0 with scanned=0 for an absent record when no
    write query is passed.
  • records/examples/ and tests/fixtures/ have drifted apart in two files.
  • The pinned locks held by the kit composer no longer match six ingredient
    repositories' published tags; that is not fixable in this repository and is filed
    against the composer.

The documented first step for an operator is to copy a schema template to the
record name its gate expects and then append rows. Three of the four record
templates ended at a bare "## Rows" / "## Lease rows" heading with no table under
it, so the copied record was rejected by its own gate with "no table parsed"
(exit 2) and no role had a table to append a row to. The in-flight template now
also carries the explicit empty-state row its gate skips, so a fresh in-flight
record is checkable before any claim exists.

The README's Use section now shows the copy step and the four record gates as
runnable commands, and its gate table names the same fixture and roster arguments
as protean-ingredient.json for the rotation gate, which the previous row could not
satisfy after installation because tests/ is not part of the payload.

Verified: python3 -m unittest discover -s tests passes (144 tests, up from 137);
the new tests.test_gates.TestShippedTemplates cases fail against the previous
templates and pass after the change; every gate declared in protean-ingredient.json
exits 0 from a fresh clone and from an installed target.
@ahrazzle
ahrazzle merged commit 2905fa5 into main Sep 19, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Record templates ship without their record table: a copied record cannot pass its own gate

1 participant