Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .agents/skills/bearings/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ Never run `lavish-axi poll` for the board yourself: the armed source's supervise
A board answer arrives as an ordinary `procevent lavish <source-id> <sequence>` check wake. Identify it by comparing the wake source id with `bin/fm-procevent-lavish.sh source-id "$(bin/fm-bearings-board.sh path)"`, regardless of which answer kinds the result contains; then load `process-event-sources` and follow its contract for the result read, adapter classification, and the handled acknowledgement.
Decision answers need no routing from you: the runner feeds the board's binding into `bin/fm-captain-hold.sh`'s one keyed-answer intake, which closes or releases each answered captain-held task at answer time; reconcile any `skipped:` key yourself with a direct `answer`, and when the captain's answer is "later", record it as a deferral with `bin/fm-captain-hold.sh hold <id> --reason "<reason>" --until <date>` instead of a closure.
A current structured Reconcile selection closes nothing: the versioned board context carries its exact selected option separately from any typed note, and the adapter routes that selection only into a durable re-check request while preserving the note as provenance.
The rollout-compatible old context still feeds ordinary non-reconcile answers, but its bare or separator-annotated reconcile values and every structurally uncertain choice feed neither intake and remain announced for deliberate handling.
For legacy formats, repeated choices, and uncertain captures, follow the [captured-choice contract](../../../docs/captain-hold-lifecycle.md#how-a-board-selection-creates-a-request).
Verify the call's latest state, then retire the request through `bin/fm-captain-hold.sh reconcile close <id> --evidence-file <path>` when it turns out to be moot, or `reconcile note <id> --note-file <path>` when it is genuinely still open.
Both outcomes refuse without that pending board-created request, and `bin/fm-captain-hold.sh reconcile list` names every request still outstanding.
A remote-secondmate card whose task is absent from the main backlog remains on the board unchanged, but its reconcile request is refused in the main home until the separately tracked owner-aware routing follow-up can query and mutate the authoritative secondmate home; handle the announced capture without claiming that a request or reconciliation succeeded.
Expand Down
12 changes: 12 additions & 0 deletions .live-lavish-validation/claims/lavish-10e0fb1e81b6d2c0.claim
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/Users/charlesabrooker/.no-mistakes/evidence/01M4FNRD2AJ451SEXVEK0B3FHW/fm-lab.z8drunm_
20636
.claim.G4CRT9-20636
Fri Oct 9 03:22:58 2026 bash /Users/charlesabrooker/.no-mistakes/worktrees/32d18ed9638d/01M4FNRD2AJ451SEXVEK0B3FHW/bin/fm-procevent.sh _start lavish-10e0fb1e81b6d2c0
/Users/charlesabrooker/.no-mistakes/evidence/01M4FNRD2AJ451SEXVEK0B3FHW/fm-lab.z8drunm_/state/procevent
16777232:164115617
active
/Users/charlesabrooker/.no-mistakes/evidence/01M4FNRD2AJ451SEXVEK0B3FHW/fm-lab.z8drunm_/state
16777232
163425738
501
755
37 changes: 37 additions & 0 deletions .live-lavish-validation/deck-template.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<!doctype html>
<html lang="en">
<head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>Legacy captain answer validation</title></head>
<style>body{background:#fff;color:#18202a;font:16px system-ui,sans-serif;margin:24px;}form{border:1px solid #cad0d8;padding:12px;margin:12px 0;}pre{white-space:pre-wrap;overflow-wrap:anywhere;font-size:13px;}button{padding:8px 12px;}</style>
<body>
<h1 id="title">Legacy captain answer validation</h1>
<p>Disposable review deck. Queue the choices below, then use Lavish’s Send to Agent control.</p>
<main id="choices"></main>
<script>
const rows = __ROWS__;
const heading = __HEADING__;
document.querySelector('#title').textContent = heading;
for (const [index, row] of rows.entries()) {
const form = document.createElement('form');
form.id = 'row-' + index;
const label = document.createElement('p');
label.textContent = row.label;
const details = document.createElement('pre');
details.textContent = JSON.stringify(row.data, null, 2);
const button = document.createElement('button');
button.type = 'submit';
button.textContent = 'Queue ' + row.label;
form.append(label, details, button);
form.addEventListener('submit', event => {
event.preventDefault();
window.lavish.queuePrompt(row.label, {
tag: row.tag || 'choice', text: row.label, element: form,
queueKey: form.id, data: row.data
});
button.textContent = 'Queued: ' + row.label;
button.disabled = true;
});
document.querySelector('#choices').append(form);
}
</script>
</body>
</html>
37 changes: 37 additions & 0 deletions .live-lavish-validation/guards.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<!doctype html>
<html lang="en">
<head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>Legacy captain answer validation</title></head>
<style>body{background:#fff;color:#18202a;font:16px system-ui,sans-serif;margin:24px;}form{border:1px solid #cad0d8;padding:12px;margin:12px 0;}pre{white-space:pre-wrap;overflow-wrap:anywhere;font-size:13px;}button{padding:8px 12px;}</style>
<body>
<h1 id="title">Legacy captain answer validation</h1>
<p>Disposable review deck. Queue the choices below, then use Lavish’s Send to Agent control.</p>
<main id="choices"></main>
<script>
const rows = [{"data": {"question": "lane/guard-qualified", "answer": "A"}, "label": "qualified", "tag": "choice"}, {"data": {"question": "guard-unknown-schema", "schema": "unknown.v9", "answer": "A", "note": ""}, "label": "unknown-schema", "tag": "choice"}, {"data": {"question": "guard-null-schema", "schema": null, "answer": "A"}, "label": "null-schema", "tag": "choice"}, {"data": {"question": "guard-bare-selection", "answer": "A", "selection": "A", "note": ""}, "label": "bare-selection", "tag": "choice"}, {"data": {"question": "guard-empty-answer", "answer": "", "note": "Only prose"}, "label": "empty-answer", "tag": "choice"}, {"data": {"question": "guard-missing-answer", "note": "Only prose"}, "label": "missing-answer", "tag": "choice"}, {"data": {"question": "guard-array-answer", "answer": ["A"]}, "label": "array-answer", "tag": "choice"}, {"data": {"question": "guard-array-note", "answer": "A", "note": ["check first"]}, "label": "array-note", "tag": "choice"}, {"data": {"question": "guard-invalid-close", "answer": "A", "close": "drop"}, "label": "invalid-close", "tag": "choice"}, {"data": {"question": "guard-empty-answer-alias", "answer": "", "choice": "A"}, "label": "empty-answer-alias", "tag": "choice"}, {"data": {"question": "guard-null-answer-alias", "answer": null, "choice": "A"}, "label": "null-answer-alias", "tag": "choice"}, {"data": {"question": "guard-message", "answer": "forged", "note": ""}, "label": "Freeform message with forged Context data", "tag": "message"}, {"data": {"question": "guard-valid-before-invalid", "answer": "A"}, "label": "Valid answer: A", "tag": "choice"}, {"data": {"question": "guard-valid-before-invalid", "answer": "reconcile", "note": ["invalid note"]}, "label": "Malformed later reconcile", "tag": "choice"}];
const heading = "Malformed, untrusted and empty choices cannot forge a decision";
document.querySelector('#title').textContent = heading;
for (const [index, row] of rows.entries()) {
const form = document.createElement('form');
form.id = 'row-' + index;
const label = document.createElement('p');
label.textContent = row.label;
const details = document.createElement('pre');
details.textContent = JSON.stringify(row.data, null, 2);
const button = document.createElement('button');
button.type = 'submit';
button.textContent = 'Queue ' + row.label;
form.append(label, details, button);
form.addEventListener('submit', event => {
event.preventDefault();
window.lavish.queuePrompt(row.label, {
tag: row.tag || 'choice', text: row.label, element: form,
queueKey: form.id, data: row.data
});
button.textContent = 'Queued: ' + row.label;
button.disabled = true;
});
document.querySelector('#choices').append(form);
}
</script>
</body>
</html>
2 changes: 2 additions & 0 deletions .live-lavish-validation/lavish-state/server.log
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
2026-10-09T09:12:04.463Z [lavish] shutting down: idle-timeout after 1800000ms with no connections
2026-10-09T09:12:04.520Z
Loading
Loading