Skip to content

fix(secondmate): account for every registered secondmate at session start - #2526

Open
IanQiu979 wants to merge 3 commits into
kunchenguid:mainfrom
IanQiu979:fm/firstmate-secondmate-liveness-r1
Open

fix(secondmate): account for every registered secondmate at session start#2526
IanQiu979 wants to merge 3 commits into
kunchenguid:mainfrom
IanQiu979:fm/firstmate-secondmate-liveness-r1

Conversation

@IanQiu979

Copy link
Copy Markdown

Fixes two high-severity defects the 2026-08-16 drift audit found in firstmate's own always-loaded contract (findings D1 and D2). Both are cases where the documentation and the code disagreed; the point of this change is that they agree when it lands.

D2 - the sweep now keeps the promise AGENTS.md makes

AGENTS.md:159 tells every firstmate session that the session-start secondmate liveness sweep "deterministically accounts for every registered secondmate". It did not. secondmate_liveness_sweep only iterated state/*.meta records carrying kind=secondmate and never opened data/secondmates.md.

Live evidence, not a hypothetical. The captain's home was in exactly the failing state: one registered secondmate (bubble-bobble-sm, a real seeded home with its own charter and an active backlog) and zero secondmate metadata records. That secondmate was therefore invisible to the sweep rather than skipped with a reason, and no SECONDMATE_LIVENESS: line was ever printed to say the guarantee had not been met. Because AGENTS.md states the guarantee, no session had cause to go looking.

The consequence generalizes: a registered secondmate that loses its runtime metadata - a crash, a partial teardown, a home restored from backup - is silently never relaunched and never reported.

The sweep now enumerates the registry as well and names every registered id the metadata scan did not cover:

SECONDMATE_LIVENESS: secondmate <id>: skipped: no runtime record

Narrowing the doc to "every secondmate with a runtime record" was considered and rejected: it is honest, but it leaves the recovery hole open.

Design constraints held throughout:

  • The registry pass reports, never relaunches. Relaunch needs a recorded endpoint to act on, and inventing one would be guessing.
  • Every pre-existing guarantee is unchanged: recovery still fires only from the recovery-grade dead or missing states, and ambiguous, unreadable, and unreachable remote targets are still preserved.
  • The primary-only no-op inside a secondmate home stays structural rather than flag-gated - such a home holds neither a kind=secondmate meta nor a data/secondmates.md of its own. This is the same reasoning already recorded at bin/fm-bootstrap.sh:585-586.
  • A registry that cannot be parsed, or cannot be read at all, is reported too, since a registry the sweep cannot read is one it cannot account for either. Those cases report under a literal <unknown> id so every emitted line still matches the one documented shape; a real registry id is a plain [A-Za-z0-9._-]+ slug and cannot collide with it.

AGENTS.md:159 itself is deliberately not edited - the code now satisfies it as written. Per .agents/skills/firstmate-coding-guidelines the always-loaded contract is kept lean, so the new skip reasons were routed to their owner, .agents/skills/bootstrap-diagnostics, instead of being added inline.

D1 - fm-home-seed.sh validate refused the entire live registry

Two separate defects, both fixed:

  1. The contract produced records its own parser rejects. bin/fm-secondmate-registry-lib.sh anchors the generated (home: ...; added ...) suffix to end-of-line, so any character after the closing ) fails the parse - but secondmate-provisioning/SKILL.md never said the suffix must come last. The prose that broke the live registry was written by firstmate itself on 2026-08-08 while repairing a lost entry, i.e. following the skill as written produced an unparseable record. The skill now states that the generated suffix must be the last thing on the line and any extra prose goes before it.

  2. Validation aborted on the first malformed line, so one annotated entry disabled validation for every other secondmate in the home, and secondmate_registry_field returned 1 for every field lookup on that id. Validation now collects and reports all malformed records, implemented as a separate parse pass so the binding checks still run only on a registry known to parse end to end. Each reported line carries its own prefix.

The captain's live data/secondmates.md was repaired separately by moving the bracketed note ahead of the suffix with its wording unchanged; that file is captain-private, gitignored, and not part of this diff.

Tests

Five new regressions, all confirmed failing against the pre-change code:

  • a registry entry whose extra prose precedes the suffix parses;
  • a genuinely malformed line is reported without suppressing the others;
  • a registry-only secondmate with no metadata produces the skipped line rather than silence;
  • a record with no readable id, and a registry that cannot be opened at all, both report under <unknown> and never kill or respawn anything.

Every one drives a real executable - bin/fm-home-seed.sh validate and bin/fm-bootstrap.sh - rather than re-describing the behaviour, per the "exercise behavior through an executable interface" rule in firstmate-coding-guidelines.

Validation

Shipped through this repo's own no-mistakes pipeline: intent, rebase, review, test, document, lint all green (outcome: passed). The review round surfaced and fixed four issues the original change missed, most importantly that the new registry read could go silent on a present-but-unreadable registry - which would have re-opened the exact blindness this change exists to close. bin/fm-lint.sh and bin/fm-doc-audience-check.sh are clean; tests/fm-secondmate-liveness.test.sh, tests/fm-secondmate-safety.test.sh, tests/fm-secondmate-sync.test.sh, tests/fm-secondmate-harness.test.sh, and tests/fm-bootstrap.test.sh all pass.

…tart

AGENTS.md promises the session-start liveness sweep "deterministically
accounts for every registered secondmate", but the sweep only iterated
state/*.meta records carrying kind=secondmate and never opened
data/secondmates.md. A registered secondmate whose runtime record was
lost - a crash, a partial teardown, a home restored from backup - was
therefore invisible to the sweep rather than skipped with a reason, and
no SECONDMATE_LIVENESS: line was printed to say so.

The sweep now enumerates the registry as well and names every registered
id the metadata scan did not cover:

  SECONDMATE_LIVENESS: secondmate <id>: skipped: no runtime record

Relaunch needs a recorded endpoint to act on, so the registry pass
reports rather than guessing at one; every existing guarantee is
unchanged. Recovery still runs only from the recovery-grade dead or
missing states, ambiguous, unreadable and unreachable remote targets are
still preserved, and both passes stay a primary-only no-op inside a
secondmate home, which holds neither a kind=secondmate meta nor a
registry of its own.

Two registry-parsing defects made that guarantee unreachable in practice
and are fixed alongside it:

- The record regex anchors the generated (home: ...; added ...) suffix to
  end-of-line, so any character after the closing parenthesis fails the
  parse - but secondmate-provisioning did not say the suffix must come
  last, and following it as written produced an unparseable record. The
  skill now states that extra prose goes before the suffix.
- secondmate_registry_validate_bindings aborted on the first malformed
  line, so one annotated entry disabled validation for every other
  secondmate in the home. It now collects and reports all of them, and
  fm-home-seed.sh validate prefixes each reported line.

Regression coverage drives the real bin/fm-home-seed.sh validate command
and the real sweep through bin/fm-bootstrap.sh.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant