feat(bin): split task identity into role/deliverable/stage axes and rename promote to reflag - #65
Conversation
Check state after the first CI pass: 12 passed, 2 failedBoth failures are accounted for, and neither is a defect in this change. 1. The change itself was validated by the pipeline, on the upstream contribution PR: intent, review, test, document, lint, push, and pr all completed with zero remaining findings. Three of the commits on this branch ( 2. Verified not to be caused by this change: extracting the fork trunk ( Everything else is green, including all four other behavior shards, Herdr behavior tests, lint, the coverage guard, repo invariants, the macOS stock-Bash snapshot, and the Windows launcher bridge. What this change was verified against directly: 309 passing assertions with zero failures across |
fcd046f to
db80f18
Compare
The fleet resolved name collisions wherever they surfaced, so a ruling was only ever findable by whoever remembered making it, and the platform's own Register 3 had no counterpart on this side. Open docs/vocabulary-collisions.md as the single owner of every word carrying more than one meaning across the fleet, the platform, and the vendor tools both depend on. It ships seeded with the ruled dispositions rather than empty: axi and execution keep their names with the evidence recorded, skill and watch and lifecycle take mandated qualified forms, promotion splits with the fleet verb becoming reflag, and kind splits into three axes. A rename or a split row also states the obsolete name's retirement condition, so no superseded path is left with an open-ended life. This lands before any rename it governs: the map exists first, then the moves it records.
One kind= field carried three independent facts at once: who the worker is, what the task produces, and where the task stands in its life. Every consumer reconstructed the axis it cared about from a value that also encoded the two it did not, and the scout-to-ship operation expressed a lifecycle transition by rewriting a deliverable type - which is why a reflagged ship and a commissioned one were indistinguishable, and why a requested agent role had nowhere to land that would not have made a fourth conflated dimension. bin/fm-task-axis-lib.sh becomes the single owner of role=, deliverable=, and stage=, of their values, and of the total derivation from the retired field, so no consumer spells that mapping itself. Migration follows the ordered contract: - Dual-write first. Every writer emits the axes beside kind=, which keeps reading unchanged while records converge. - Backfill by derivation, forward-only and idempotent, in a startup sweep that runs only under the fleet lock and leaves a converged home byte-identical. Stage is deliberately NOT derived: the old field could not distinguish a reflagged ship from a commissioned one, so backfill records the lower-information value rather than inventing a fact. - Migrate readers one axis at a time - role across the secondmate-membership consumers, then deliverable across the pipeline and teardown-protection consumers - splitting the conditions that had mixed both. kind= stays dual-written for now; the registry owns its retirement condition. While it stays, a record whose alias contradicts its axes is REFUSED rather than resolved, because either side could be the stale one and teardown choosing between a protected ship worktree and a scratch scout worktree by luck is how unlanded work gets discarded. The scout-to-ship operation becomes bin/fm-reflag.sh in the same change, since it is what makes the stage axis true. The old name stays only as a bounded shim that forwards, warns, and records each use, so its own retirement is settled by evidence rather than by memory. Coverage is red-capable: each guard was witnessed failing with its behavior removed before being trusted green - the derivation table, dual-write, backfill idempotence, the shim's evidence, and the refusal in the library, in reflagging, and in teardown.
The rename and the axis split are only real once the instructions that drive them say so, so this carries the fleet's own vocabulary across: the scout outcome section reflags rather than promotes, the metadata field list names the three axes and the deprecated field they replace, and the captain-facing do-not-expose list drops a word the fleet no longer uses internally. Knowledge routing gains one line: a word with a second live meaning goes to the collision registry, never settled locally in whichever file it surfaced in. That is the rule that keeps the registry from going stale the first time someone is in a hurry. Also moves teardown's admission release reminder into the admission library, which already owns that policy. Its test previously reconstructed the function by parsing teardown's source and eval-ing the fragment, so it broke the moment the function read a variable defined outside it - and a test that reads implementation source is exactly what CONTRIBUTING forbids. The reminder now takes its inputs as arguments and the test calls it directly.
Two consumers still read the deprecated field: the watcher classified every supervised window by it, and teardown's nested-home check read it on a parent record. Both only ever ask whether they are looking at a persistent direct report, so both are the role axis and neither needed what the work produces. The watcher's helper is renamed to say what it answers, which is what made the one remaining stale reference visible - a variable read with no assignment left, caught at runtime by the triage suite rather than by lint.
The old-versus-new teardown conformance case builds a mixed tree: the entry points come from the baseline commit while their siblings are copied from the working tree. Several of those copied siblings now source the axis library, so the fixture needs it present or the baseline entry point dies looking for it.
The recovery and secondmate procedures still told an agent to look for the retired single field, so the instructions that decide which playbook applies would have gone on naming a field the fleet is removing. Each of these asks only who the worker is, so each now says role.
The ruled value set includes delivered, and the spawn and the reflag write the other two. Nothing writes delivered: the honest place to stamp it is after a confirmed landing, inside the merge path's own private metadata rewrite, whose ordering, device, and single-link invariants that path owns - so the writer is a deliberate follow-up rather than something bolted on beside this split. Say so in both the library and the registry, because a declared value that never appears is otherwise read as evidence that the task did not land.
Reads in the order it acts: the refusal and why it exists, then the axes it protects. Also drops a local the remote path no longer reads and closes the gap the moved admission reminder left.
Every teardown branch reads an axis now, and the ledger record carries role and deliverable, so nothing consulted the alias teardown still parsed on the way in. With that gone no consumer anywhere branches on the deprecated field, which changes what its retirement is waiting for: not a reader migration, but a full task cycle on the axes across every home. The registry says so precisely, because a retirement condition nobody can evaluate is how an alias becomes permanent.
The view's type column was the last thing reading the deprecated field, and it could only ever show one of the three facts a row carries. It now renders the role and deliverable together and appends the stage when it is not the spawn default, so a reflagged ship is visible as one at a glance instead of being indistinguishable from a commissioned one - which is the whole reason the lifecycle axis exists.
Both fake roots symlink the real teardown and each sibling it sources, so the new axis library has to be linked in beside them or teardown dies looking for it before it reaches the behavior these cases assert.
A task's metadata doubles as PR identity, and its parser refuses any unrecognized key that appears after the pr= line - the rule that stops a tampered record from smuggling a second identity past an armed merge poll. Backfill appended, so the first startup sweep over a home with a live merge watch invalidated exactly the records that had one, and the watcher stopped honoring those polls. Nothing reported it, because a poll that is refused looks the same as a poll with nothing to say. Axis writes now land before the pr= line, through one helper both the backfill and the reflag use, and the regression pins the position rather than only the parse so a future writer cannot reintroduce it by appending again. Found by the PR-check security suite, which was green on the base and red here.
db80f18 to
afec7d7
Compare
Rebased onto the current fork trunkRebased onto Conflicts and how they were resolvedEleven conflicts across three of the fifteen commits, in
The Verifying the ported behavior, not assuming it
That was not trusted on a pass alone. Removing the ported lines and re-running produced exactly the expected failure - Test results, disclosed in full
All twelve failures are pre-existing and unrelated, reproduced on unmodified
Every suite this branch actually owns is green:
|
…ename promote to reflag (#65) * docs: open the fleet's vocabulary-collision registry The fleet resolved name collisions wherever they surfaced, so a ruling was only ever findable by whoever remembered making it, and the platform's own Register 3 had no counterpart on this side. Open docs/vocabulary-collisions.md as the single owner of every word carrying more than one meaning across the fleet, the platform, and the vendor tools both depend on. It ships seeded with the ruled dispositions rather than empty: axi and execution keep their names with the evidence recorded, skill and watch and lifecycle take mandated qualified forms, promotion splits with the fleet verb becoming reflag, and kind splits into three axes. A rename or a split row also states the obsolete name's retirement condition, so no superseded path is left with an open-ended life. This lands before any rename it governs: the map exists first, then the moves it records. * feat(bin): split task identity into role, deliverable, and stage axes One kind= field carried three independent facts at once: who the worker is, what the task produces, and where the task stands in its life. Every consumer reconstructed the axis it cared about from a value that also encoded the two it did not, and the scout-to-ship operation expressed a lifecycle transition by rewriting a deliverable type - which is why a reflagged ship and a commissioned one were indistinguishable, and why a requested agent role had nowhere to land that would not have made a fourth conflated dimension. bin/fm-task-axis-lib.sh becomes the single owner of role=, deliverable=, and stage=, of their values, and of the total derivation from the retired field, so no consumer spells that mapping itself. Migration follows the ordered contract: - Dual-write first. Every writer emits the axes beside kind=, which keeps reading unchanged while records converge. - Backfill by derivation, forward-only and idempotent, in a startup sweep that runs only under the fleet lock and leaves a converged home byte-identical. Stage is deliberately NOT derived: the old field could not distinguish a reflagged ship from a commissioned one, so backfill records the lower-information value rather than inventing a fact. - Migrate readers one axis at a time - role across the secondmate-membership consumers, then deliverable across the pipeline and teardown-protection consumers - splitting the conditions that had mixed both. kind= stays dual-written for now; the registry owns its retirement condition. While it stays, a record whose alias contradicts its axes is REFUSED rather than resolved, because either side could be the stale one and teardown choosing between a protected ship worktree and a scratch scout worktree by luck is how unlanded work gets discarded. The scout-to-ship operation becomes bin/fm-reflag.sh in the same change, since it is what makes the stage axis true. The old name stays only as a bounded shim that forwards, warns, and records each use, so its own retirement is settled by evidence rather than by memory. Coverage is red-capable: each guard was witnessed failing with its behavior removed before being trusted green - the derivation table, dual-write, backfill idempotence, the shim's evidence, and the refusal in the library, in reflagging, and in teardown. * docs: move the fleet's instructions onto the new names and axes The rename and the axis split are only real once the instructions that drive them say so, so this carries the fleet's own vocabulary across: the scout outcome section reflags rather than promotes, the metadata field list names the three axes and the deprecated field they replace, and the captain-facing do-not-expose list drops a word the fleet no longer uses internally. Knowledge routing gains one line: a word with a second live meaning goes to the collision registry, never settled locally in whichever file it surfaced in. That is the rule that keeps the registry from going stale the first time someone is in a hurry. Also moves teardown's admission release reminder into the admission library, which already owns that policy. Its test previously reconstructed the function by parsing teardown's source and eval-ing the fragment, so it broke the moment the function read a variable defined outside it - and a test that reads implementation source is exactly what CONTRIBUTING forbids. The reminder now takes its inputs as arguments and the test calls it directly. * fix(bin): move the watcher and nested-home checks onto the role axis Two consumers still read the deprecated field: the watcher classified every supervised window by it, and teardown's nested-home check read it on a parent record. Both only ever ask whether they are looking at a persistent direct report, so both are the role axis and neither needed what the work produces. The watcher's helper is renamed to say what it answers, which is what made the one remaining stale reference visible - a variable read with no assignment left, caught at runtime by the triage suite rather than by lint. * test: carry the axis library into the old-bin conformance fixture The old-versus-new teardown conformance case builds a mixed tree: the entry points come from the baseline commit while their siblings are copied from the working tree. Several of those copied siblings now source the axis library, so the fixture needs it present or the baseline entry point dies looking for it. * docs: teach recovery and provisioning the identity axes The recovery and secondmate procedures still told an agent to look for the retired single field, so the instructions that decide which playbook applies would have gone on naming a field the fleet is removing. Each of these asks only who the worker is, so each now says role. * docs: state plainly that the stage axis has one value nothing writes yet The ruled value set includes delivered, and the spawn and the reflag write the other two. Nothing writes delivered: the honest place to stamp it is after a confirmed landing, inside the merge path's own private metadata rewrite, whose ordering, device, and single-link invariants that path owns - so the writer is a deliberate follow-up rather than something bolted on beside this split. Say so in both the library and the registry, because a declared value that never appears is otherwise read as evidence that the task did not land. * refactor(bin): tidy the teardown identity block Reads in the order it acts: the refusal and why it exists, then the axes it protects. Also drops a local the remote path no longer reads and closes the gap the moved admission reminder left. * refactor(bin): drop teardown's now-unread copy of the deprecated field Every teardown branch reads an axis now, and the ledger record carries role and deliverable, so nothing consulted the alias teardown still parsed on the way in. With that gone no consumer anywhere branches on the deprecated field, which changes what its retirement is waiting for: not a reader migration, but a full task cycle on the axes across every home. The registry says so precisely, because a retirement condition nobody can evaluate is how an alias becomes permanent. * feat(bin): render the identity axes in the fleet view The view's type column was the last thing reading the deprecated field, and it could only ever show one of the three facts a row carries. It now renders the role and deliverable together and appends the stage when it is not the spawn default, so a reflagged ship is visible as one at a glance instead of being indistinguishable from a commissioned one - which is the whole reason the lifecycle axis exists. * test: carry the axis library into the gotmp fake roots Both fake roots symlink the real teardown and each sibling it sources, so the new axis library has to be linked in beside them or teardown dies looking for it before it reaches the behavior these cases assert. * fix(bin): keep a polled record's PR identity intact when writing an axis A task's metadata doubles as PR identity, and its parser refuses any unrecognized key that appears after the pr= line - the rule that stops a tampered record from smuggling a second identity past an armed merge poll. Backfill appended, so the first startup sweep over a home with a live merge watch invalidated exactly the records that had one, and the watcher stopped honoring those polls. Nothing reported it, because a poll that is refused looks the same as a poll with nothing to say. Axis writes now land before the pr= line, through one helper both the backfill and the reflag use, and the regression pins the position rather than only the parse so a future writer cannot reintroduce it by appending again. Found by the PR-check security suite, which was green on the base and red here. * no-mistakes(review): make reflag atomic, dual-carry kind on v1 wire surfaces * no-mistakes(document): migrate leftover scout-promotion wording to reflag * no-mistakes(document): reflag retired promote verb in scout brief output
…ename promote to reflag (#65) * docs: open the fleet's vocabulary-collision registry The fleet resolved name collisions wherever they surfaced, so a ruling was only ever findable by whoever remembered making it, and the platform's own Register 3 had no counterpart on this side. Open docs/vocabulary-collisions.md as the single owner of every word carrying more than one meaning across the fleet, the platform, and the vendor tools both depend on. It ships seeded with the ruled dispositions rather than empty: axi and execution keep their names with the evidence recorded, skill and watch and lifecycle take mandated qualified forms, promotion splits with the fleet verb becoming reflag, and kind splits into three axes. A rename or a split row also states the obsolete name's retirement condition, so no superseded path is left with an open-ended life. This lands before any rename it governs: the map exists first, then the moves it records. * feat(bin): split task identity into role, deliverable, and stage axes One kind= field carried three independent facts at once: who the worker is, what the task produces, and where the task stands in its life. Every consumer reconstructed the axis it cared about from a value that also encoded the two it did not, and the scout-to-ship operation expressed a lifecycle transition by rewriting a deliverable type - which is why a reflagged ship and a commissioned one were indistinguishable, and why a requested agent role had nowhere to land that would not have made a fourth conflated dimension. bin/fm-task-axis-lib.sh becomes the single owner of role=, deliverable=, and stage=, of their values, and of the total derivation from the retired field, so no consumer spells that mapping itself. Migration follows the ordered contract: - Dual-write first. Every writer emits the axes beside kind=, which keeps reading unchanged while records converge. - Backfill by derivation, forward-only and idempotent, in a startup sweep that runs only under the fleet lock and leaves a converged home byte-identical. Stage is deliberately NOT derived: the old field could not distinguish a reflagged ship from a commissioned one, so backfill records the lower-information value rather than inventing a fact. - Migrate readers one axis at a time - role across the secondmate-membership consumers, then deliverable across the pipeline and teardown-protection consumers - splitting the conditions that had mixed both. kind= stays dual-written for now; the registry owns its retirement condition. While it stays, a record whose alias contradicts its axes is REFUSED rather than resolved, because either side could be the stale one and teardown choosing between a protected ship worktree and a scratch scout worktree by luck is how unlanded work gets discarded. The scout-to-ship operation becomes bin/fm-reflag.sh in the same change, since it is what makes the stage axis true. The old name stays only as a bounded shim that forwards, warns, and records each use, so its own retirement is settled by evidence rather than by memory. Coverage is red-capable: each guard was witnessed failing with its behavior removed before being trusted green - the derivation table, dual-write, backfill idempotence, the shim's evidence, and the refusal in the library, in reflagging, and in teardown. * docs: move the fleet's instructions onto the new names and axes The rename and the axis split are only real once the instructions that drive them say so, so this carries the fleet's own vocabulary across: the scout outcome section reflags rather than promotes, the metadata field list names the three axes and the deprecated field they replace, and the captain-facing do-not-expose list drops a word the fleet no longer uses internally. Knowledge routing gains one line: a word with a second live meaning goes to the collision registry, never settled locally in whichever file it surfaced in. That is the rule that keeps the registry from going stale the first time someone is in a hurry. Also moves teardown's admission release reminder into the admission library, which already owns that policy. Its test previously reconstructed the function by parsing teardown's source and eval-ing the fragment, so it broke the moment the function read a variable defined outside it - and a test that reads implementation source is exactly what CONTRIBUTING forbids. The reminder now takes its inputs as arguments and the test calls it directly. * fix(bin): move the watcher and nested-home checks onto the role axis Two consumers still read the deprecated field: the watcher classified every supervised window by it, and teardown's nested-home check read it on a parent record. Both only ever ask whether they are looking at a persistent direct report, so both are the role axis and neither needed what the work produces. The watcher's helper is renamed to say what it answers, which is what made the one remaining stale reference visible - a variable read with no assignment left, caught at runtime by the triage suite rather than by lint. * test: carry the axis library into the old-bin conformance fixture The old-versus-new teardown conformance case builds a mixed tree: the entry points come from the baseline commit while their siblings are copied from the working tree. Several of those copied siblings now source the axis library, so the fixture needs it present or the baseline entry point dies looking for it. * docs: teach recovery and provisioning the identity axes The recovery and secondmate procedures still told an agent to look for the retired single field, so the instructions that decide which playbook applies would have gone on naming a field the fleet is removing. Each of these asks only who the worker is, so each now says role. * docs: state plainly that the stage axis has one value nothing writes yet The ruled value set includes delivered, and the spawn and the reflag write the other two. Nothing writes delivered: the honest place to stamp it is after a confirmed landing, inside the merge path's own private metadata rewrite, whose ordering, device, and single-link invariants that path owns - so the writer is a deliberate follow-up rather than something bolted on beside this split. Say so in both the library and the registry, because a declared value that never appears is otherwise read as evidence that the task did not land. * refactor(bin): tidy the teardown identity block Reads in the order it acts: the refusal and why it exists, then the axes it protects. Also drops a local the remote path no longer reads and closes the gap the moved admission reminder left. * refactor(bin): drop teardown's now-unread copy of the deprecated field Every teardown branch reads an axis now, and the ledger record carries role and deliverable, so nothing consulted the alias teardown still parsed on the way in. With that gone no consumer anywhere branches on the deprecated field, which changes what its retirement is waiting for: not a reader migration, but a full task cycle on the axes across every home. The registry says so precisely, because a retirement condition nobody can evaluate is how an alias becomes permanent. * feat(bin): render the identity axes in the fleet view The view's type column was the last thing reading the deprecated field, and it could only ever show one of the three facts a row carries. It now renders the role and deliverable together and appends the stage when it is not the spawn default, so a reflagged ship is visible as one at a glance instead of being indistinguishable from a commissioned one - which is the whole reason the lifecycle axis exists. * test: carry the axis library into the gotmp fake roots Both fake roots symlink the real teardown and each sibling it sources, so the new axis library has to be linked in beside them or teardown dies looking for it before it reaches the behavior these cases assert. * fix(bin): keep a polled record's PR identity intact when writing an axis A task's metadata doubles as PR identity, and its parser refuses any unrecognized key that appears after the pr= line - the rule that stops a tampered record from smuggling a second identity past an armed merge poll. Backfill appended, so the first startup sweep over a home with a live merge watch invalidated exactly the records that had one, and the watcher stopped honoring those polls. Nothing reported it, because a poll that is refused looks the same as a poll with nothing to say. Axis writes now land before the pr= line, through one helper both the backfill and the reflag use, and the regression pins the position rather than only the parse so a future writer cannot reintroduce it by appending again. Found by the PR-check security suite, which was green on the base and red here. * no-mistakes(review): make reflag atomic, dual-carry kind on v1 wire surfaces * no-mistakes(document): migrate leftover scout-promotion wording to reflag * no-mistakes(document): reflag retired promote verb in scout brief output
…ename promote to reflag (#65) * docs: open the fleet's vocabulary-collision registry The fleet resolved name collisions wherever they surfaced, so a ruling was only ever findable by whoever remembered making it, and the platform's own Register 3 had no counterpart on this side. Open docs/vocabulary-collisions.md as the single owner of every word carrying more than one meaning across the fleet, the platform, and the vendor tools both depend on. It ships seeded with the ruled dispositions rather than empty: axi and execution keep their names with the evidence recorded, skill and watch and lifecycle take mandated qualified forms, promotion splits with the fleet verb becoming reflag, and kind splits into three axes. A rename or a split row also states the obsolete name's retirement condition, so no superseded path is left with an open-ended life. This lands before any rename it governs: the map exists first, then the moves it records. * feat(bin): split task identity into role, deliverable, and stage axes One kind= field carried three independent facts at once: who the worker is, what the task produces, and where the task stands in its life. Every consumer reconstructed the axis it cared about from a value that also encoded the two it did not, and the scout-to-ship operation expressed a lifecycle transition by rewriting a deliverable type - which is why a reflagged ship and a commissioned one were indistinguishable, and why a requested agent role had nowhere to land that would not have made a fourth conflated dimension. bin/fm-task-axis-lib.sh becomes the single owner of role=, deliverable=, and stage=, of their values, and of the total derivation from the retired field, so no consumer spells that mapping itself. Migration follows the ordered contract: - Dual-write first. Every writer emits the axes beside kind=, which keeps reading unchanged while records converge. - Backfill by derivation, forward-only and idempotent, in a startup sweep that runs only under the fleet lock and leaves a converged home byte-identical. Stage is deliberately NOT derived: the old field could not distinguish a reflagged ship from a commissioned one, so backfill records the lower-information value rather than inventing a fact. - Migrate readers one axis at a time - role across the secondmate-membership consumers, then deliverable across the pipeline and teardown-protection consumers - splitting the conditions that had mixed both. kind= stays dual-written for now; the registry owns its retirement condition. While it stays, a record whose alias contradicts its axes is REFUSED rather than resolved, because either side could be the stale one and teardown choosing between a protected ship worktree and a scratch scout worktree by luck is how unlanded work gets discarded. The scout-to-ship operation becomes bin/fm-reflag.sh in the same change, since it is what makes the stage axis true. The old name stays only as a bounded shim that forwards, warns, and records each use, so its own retirement is settled by evidence rather than by memory. Coverage is red-capable: each guard was witnessed failing with its behavior removed before being trusted green - the derivation table, dual-write, backfill idempotence, the shim's evidence, and the refusal in the library, in reflagging, and in teardown. * docs: move the fleet's instructions onto the new names and axes The rename and the axis split are only real once the instructions that drive them say so, so this carries the fleet's own vocabulary across: the scout outcome section reflags rather than promotes, the metadata field list names the three axes and the deprecated field they replace, and the captain-facing do-not-expose list drops a word the fleet no longer uses internally. Knowledge routing gains one line: a word with a second live meaning goes to the collision registry, never settled locally in whichever file it surfaced in. That is the rule that keeps the registry from going stale the first time someone is in a hurry. Also moves teardown's admission release reminder into the admission library, which already owns that policy. Its test previously reconstructed the function by parsing teardown's source and eval-ing the fragment, so it broke the moment the function read a variable defined outside it - and a test that reads implementation source is exactly what CONTRIBUTING forbids. The reminder now takes its inputs as arguments and the test calls it directly. * fix(bin): move the watcher and nested-home checks onto the role axis Two consumers still read the deprecated field: the watcher classified every supervised window by it, and teardown's nested-home check read it on a parent record. Both only ever ask whether they are looking at a persistent direct report, so both are the role axis and neither needed what the work produces. The watcher's helper is renamed to say what it answers, which is what made the one remaining stale reference visible - a variable read with no assignment left, caught at runtime by the triage suite rather than by lint. * test: carry the axis library into the old-bin conformance fixture The old-versus-new teardown conformance case builds a mixed tree: the entry points come from the baseline commit while their siblings are copied from the working tree. Several of those copied siblings now source the axis library, so the fixture needs it present or the baseline entry point dies looking for it. * docs: teach recovery and provisioning the identity axes The recovery and secondmate procedures still told an agent to look for the retired single field, so the instructions that decide which playbook applies would have gone on naming a field the fleet is removing. Each of these asks only who the worker is, so each now says role. * docs: state plainly that the stage axis has one value nothing writes yet The ruled value set includes delivered, and the spawn and the reflag write the other two. Nothing writes delivered: the honest place to stamp it is after a confirmed landing, inside the merge path's own private metadata rewrite, whose ordering, device, and single-link invariants that path owns - so the writer is a deliberate follow-up rather than something bolted on beside this split. Say so in both the library and the registry, because a declared value that never appears is otherwise read as evidence that the task did not land. * refactor(bin): tidy the teardown identity block Reads in the order it acts: the refusal and why it exists, then the axes it protects. Also drops a local the remote path no longer reads and closes the gap the moved admission reminder left. * refactor(bin): drop teardown's now-unread copy of the deprecated field Every teardown branch reads an axis now, and the ledger record carries role and deliverable, so nothing consulted the alias teardown still parsed on the way in. With that gone no consumer anywhere branches on the deprecated field, which changes what its retirement is waiting for: not a reader migration, but a full task cycle on the axes across every home. The registry says so precisely, because a retirement condition nobody can evaluate is how an alias becomes permanent. * feat(bin): render the identity axes in the fleet view The view's type column was the last thing reading the deprecated field, and it could only ever show one of the three facts a row carries. It now renders the role and deliverable together and appends the stage when it is not the spawn default, so a reflagged ship is visible as one at a glance instead of being indistinguishable from a commissioned one - which is the whole reason the lifecycle axis exists. * test: carry the axis library into the gotmp fake roots Both fake roots symlink the real teardown and each sibling it sources, so the new axis library has to be linked in beside them or teardown dies looking for it before it reaches the behavior these cases assert. * fix(bin): keep a polled record's PR identity intact when writing an axis A task's metadata doubles as PR identity, and its parser refuses any unrecognized key that appears after the pr= line - the rule that stops a tampered record from smuggling a second identity past an armed merge poll. Backfill appended, so the first startup sweep over a home with a live merge watch invalidated exactly the records that had one, and the watcher stopped honoring those polls. Nothing reported it, because a poll that is refused looks the same as a poll with nothing to say. Axis writes now land before the pr= line, through one helper both the backfill and the reflag use, and the regression pins the position rather than only the parse so a future writer cannot reintroduce it by appending again. Found by the PR-check security suite, which was green on the base and red here. * no-mistakes(review): make reflag atomic, dual-carry kind on v1 wire surfaces * no-mistakes(document): migrate leftover scout-promotion wording to reflag * no-mistakes(document): reflag retired promote verb in scout brief output
…ename promote to reflag (#65) * docs: open the fleet's vocabulary-collision registry The fleet resolved name collisions wherever they surfaced, so a ruling was only ever findable by whoever remembered making it, and the platform's own Register 3 had no counterpart on this side. Open docs/vocabulary-collisions.md as the single owner of every word carrying more than one meaning across the fleet, the platform, and the vendor tools both depend on. It ships seeded with the ruled dispositions rather than empty: axi and execution keep their names with the evidence recorded, skill and watch and lifecycle take mandated qualified forms, promotion splits with the fleet verb becoming reflag, and kind splits into three axes. A rename or a split row also states the obsolete name's retirement condition, so no superseded path is left with an open-ended life. This lands before any rename it governs: the map exists first, then the moves it records. * feat(bin): split task identity into role, deliverable, and stage axes One kind= field carried three independent facts at once: who the worker is, what the task produces, and where the task stands in its life. Every consumer reconstructed the axis it cared about from a value that also encoded the two it did not, and the scout-to-ship operation expressed a lifecycle transition by rewriting a deliverable type - which is why a reflagged ship and a commissioned one were indistinguishable, and why a requested agent role had nowhere to land that would not have made a fourth conflated dimension. bin/fm-task-axis-lib.sh becomes the single owner of role=, deliverable=, and stage=, of their values, and of the total derivation from the retired field, so no consumer spells that mapping itself. Migration follows the ordered contract: - Dual-write first. Every writer emits the axes beside kind=, which keeps reading unchanged while records converge. - Backfill by derivation, forward-only and idempotent, in a startup sweep that runs only under the fleet lock and leaves a converged home byte-identical. Stage is deliberately NOT derived: the old field could not distinguish a reflagged ship from a commissioned one, so backfill records the lower-information value rather than inventing a fact. - Migrate readers one axis at a time - role across the secondmate-membership consumers, then deliverable across the pipeline and teardown-protection consumers - splitting the conditions that had mixed both. kind= stays dual-written for now; the registry owns its retirement condition. While it stays, a record whose alias contradicts its axes is REFUSED rather than resolved, because either side could be the stale one and teardown choosing between a protected ship worktree and a scratch scout worktree by luck is how unlanded work gets discarded. The scout-to-ship operation becomes bin/fm-reflag.sh in the same change, since it is what makes the stage axis true. The old name stays only as a bounded shim that forwards, warns, and records each use, so its own retirement is settled by evidence rather than by memory. Coverage is red-capable: each guard was witnessed failing with its behavior removed before being trusted green - the derivation table, dual-write, backfill idempotence, the shim's evidence, and the refusal in the library, in reflagging, and in teardown. * docs: move the fleet's instructions onto the new names and axes The rename and the axis split are only real once the instructions that drive them say so, so this carries the fleet's own vocabulary across: the scout outcome section reflags rather than promotes, the metadata field list names the three axes and the deprecated field they replace, and the captain-facing do-not-expose list drops a word the fleet no longer uses internally. Knowledge routing gains one line: a word with a second live meaning goes to the collision registry, never settled locally in whichever file it surfaced in. That is the rule that keeps the registry from going stale the first time someone is in a hurry. Also moves teardown's admission release reminder into the admission library, which already owns that policy. Its test previously reconstructed the function by parsing teardown's source and eval-ing the fragment, so it broke the moment the function read a variable defined outside it - and a test that reads implementation source is exactly what CONTRIBUTING forbids. The reminder now takes its inputs as arguments and the test calls it directly. * fix(bin): move the watcher and nested-home checks onto the role axis Two consumers still read the deprecated field: the watcher classified every supervised window by it, and teardown's nested-home check read it on a parent record. Both only ever ask whether they are looking at a persistent direct report, so both are the role axis and neither needed what the work produces. The watcher's helper is renamed to say what it answers, which is what made the one remaining stale reference visible - a variable read with no assignment left, caught at runtime by the triage suite rather than by lint. * test: carry the axis library into the old-bin conformance fixture The old-versus-new teardown conformance case builds a mixed tree: the entry points come from the baseline commit while their siblings are copied from the working tree. Several of those copied siblings now source the axis library, so the fixture needs it present or the baseline entry point dies looking for it. * docs: teach recovery and provisioning the identity axes The recovery and secondmate procedures still told an agent to look for the retired single field, so the instructions that decide which playbook applies would have gone on naming a field the fleet is removing. Each of these asks only who the worker is, so each now says role. * docs: state plainly that the stage axis has one value nothing writes yet The ruled value set includes delivered, and the spawn and the reflag write the other two. Nothing writes delivered: the honest place to stamp it is after a confirmed landing, inside the merge path's own private metadata rewrite, whose ordering, device, and single-link invariants that path owns - so the writer is a deliberate follow-up rather than something bolted on beside this split. Say so in both the library and the registry, because a declared value that never appears is otherwise read as evidence that the task did not land. * refactor(bin): tidy the teardown identity block Reads in the order it acts: the refusal and why it exists, then the axes it protects. Also drops a local the remote path no longer reads and closes the gap the moved admission reminder left. * refactor(bin): drop teardown's now-unread copy of the deprecated field Every teardown branch reads an axis now, and the ledger record carries role and deliverable, so nothing consulted the alias teardown still parsed on the way in. With that gone no consumer anywhere branches on the deprecated field, which changes what its retirement is waiting for: not a reader migration, but a full task cycle on the axes across every home. The registry says so precisely, because a retirement condition nobody can evaluate is how an alias becomes permanent. * feat(bin): render the identity axes in the fleet view The view's type column was the last thing reading the deprecated field, and it could only ever show one of the three facts a row carries. It now renders the role and deliverable together and appends the stage when it is not the spawn default, so a reflagged ship is visible as one at a glance instead of being indistinguishable from a commissioned one - which is the whole reason the lifecycle axis exists. * test: carry the axis library into the gotmp fake roots Both fake roots symlink the real teardown and each sibling it sources, so the new axis library has to be linked in beside them or teardown dies looking for it before it reaches the behavior these cases assert. * fix(bin): keep a polled record's PR identity intact when writing an axis A task's metadata doubles as PR identity, and its parser refuses any unrecognized key that appears after the pr= line - the rule that stops a tampered record from smuggling a second identity past an armed merge poll. Backfill appended, so the first startup sweep over a home with a live merge watch invalidated exactly the records that had one, and the watcher stopped honoring those polls. Nothing reported it, because a poll that is refused looks the same as a poll with nothing to say. Axis writes now land before the pr= line, through one helper both the backfill and the reflag use, and the regression pins the position rather than only the parse so a future writer cannot reintroduce it by appending again. Found by the PR-check security suite, which was green on the base and red here. * no-mistakes(review): make reflag atomic, dual-carry kind on v1 wire surfaces * no-mistakes(document): migrate leftover scout-promotion wording to reflag * no-mistakes(document): reflag retired promote verb in scout brief output
What this is
CFVC-16 fleet-side implementation, executing the captain's 2026-08-07 naming ruling. The captain framed it as cohesion work, not cosmetic terminology work.
docs/vocabulary-collisions.md, the fleet's first vocabulary-collision registry, seeded with every ruled disposition (AXI not renamed, the fiveskillsenses, the qualifiedwatcher/Watch Registerforms,promotion's senses,executionNO-CONTACT, the qualifiedlifecycleforms) and with each obsolete name's retirement condition recorded.bin/fm-promote.sh->bin/fm-reflag.sh. The old name remains only as a bounded shim that forwards, warns, and records each use tostate/.reflag-shim-used, so its retirement is settled by evidence rather than memory.kind=into three orthogonal fields:role=crew|secondmate,deliverable=scout|ship,stage=commissioned|reflagged|delivered, owned by the newbin/fm-task-axis-lib.sh. Migration follows the ruled sequence: dual-write, deterministic idempotent backfill (a locked startup sweep), then readers migrated one axis at a time.kind=stays dual-written exactly as the ruling requires; no reader consults it any more.requested_agent_rolelands on it instead of adding a fourth conflated dimension.Deliberate non-goals
stage=deliveredis part of the ruled value set but is declared and not yet written by any path. Stamping it belongs after a confirmed landing, insidebin/fm-pr-merge.sh's private metadata rewrite, whose ordering, device, and single-link invariants that path owns. Both the library header and the registry say so explicitly, so its absence is not misread as "this task did not land".kind=is not removed. The ruling permits retiring it only after a full proven cycle; the registry records the exact retirement condition.Safety property worth review attention
A task's metadata doubles as PR identity, and its parser refuses any unrecognized key appearing after the
pr=line - the rule that stops a tampered record from smuggling a second identity past an armed merge poll. An early version of the backfill appended, which would have invalidated exactly the records carrying a live merge watch, silently stopping the watcher from honoring those polls. Axis writes now land beforepr=through one shared helper used by both the backfill and the reflag, and the regression pins the position, not just the parse.Relatedly, a record whose deprecated
kind=contradicts its explicit axes is refused rather than silently resolved, in the library, in reflagging, and in teardown - because either side could be the stale one, and letting teardown choose between a protected ship worktree and a scratch scout worktree by luck is how unlanded work gets discarded.Verification
bin/fm-lint.shclean (ShellCheck 0.11.0, the pinned version).bin/fm-doc-audience-check.shok - 74 surfaces, 235 local links.bin/fm-test-run.sh --check-coverageok.fm-task-axis,fm-task-delivery,fm-teardown,fm-gotmp,fm-backend,fm-brief,fm-admission,fm-crew-state,fm-bootstrap,fm-fleet-snapshot-view,fm-bearings-snapshot,fm-wake-ledger..tsloader, tmux unavailability, a session-startMISSINGdiagnostic, a tmux agent-liveness classification, a launch-template assertion, a watcher-lock restart attach, and a suite that emits the worktree-tangle banner whenever run from any task branch).tests/fm-task-axis.test.shcovers dual-write, backfill determinism and idempotence, PR-identity preservation, each reader migration, and the alias refusing stale writers. Each guard was witnessed failing with its behavior removed before being trusted green.Check state - please read before merging
This PR has no CI checks configured, so nothing here has been verified green by CI. The validation above is local evidence only. The fleet's own merge guard refuses merges without verified green checks, and that refusal is correct for this PR.
Provenance of this branch
This branch was cut fresh from the current fork trunk and carries only this change: 15 commits, 39 files, zero replayed fork-landing commits, with the fork trunk as a strict ancestor (clean fast-forward, nothing behind).
It exists because a validation pipeline run rebased the original branch onto the upstream trunk while resolving conflicts, which replayed the fork's landing queue under new SHAs and left that head 107 commits / 272 files divergent from the fork trunk. That head and its upstream PR are deliberately untouched. Three of these commits (
no-mistakes(review)andno-mistakes(document)) are fixes that pipeline applied and are carried across intact.One concrete casualty of that rebase is fixed here: it silently dropped the change adding
fm-task-axis-lib.shto the old-bin conformance fixture's sibling list, resolving the conflict by taking the base side. This branch keeps both the base's addition and this change's.