muse-crew 0.14.4 → 0.14.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +1 -1
- package/API.md +61 -7
- package/docs/decisions/AGENTS.md +3 -0
- package/docs/decisions/publish-path.md +244 -1
- package/docs/guide.md +42 -6
- package/docs/publish-unknown-recovery.md +28 -113
- package/docs/publish-verification.md +148 -445
- package/docs/release-integrity.md +5 -3
- package/docs/reviews/critic-0145.md +106 -0
- package/lib/AGENTS.md +4 -8
- package/lib/advance-publish-base.js +3 -3
- package/lib/compose-evidence-caption.js +1 -2
- package/lib/compute-publish-diff.js +113 -15
- package/lib/crew-api.js +855 -725
- package/lib/publish-note-vocabulary.js +137 -24
- package/lib/schema.sql +19 -0
- package/package.json +1 -1
- package/seed/AGENTS.md +1 -0
- package/seed/cron-body-ack-scan.md +44 -0
- package/seed/cron-body-template.md +65 -65
- package/seed/crons.json +12 -0
- package/workflows/bugfix.js +146 -740
- package/workflows/chore.js +146 -740
- package/workflows/crew-dispatch.js +36 -0
- package/workflows/crew-init.js +23 -0
- package/workflows/crew-uninstall.js +7 -4
- package/workflows/standard.js +146 -763
- package/lib/build-readback-request.js +0 -140
- package/lib/classify-publish-absence.js +0 -462
- package/lib/publish-content.js +0 -154
- package/lib/readback-disk.js +0 -195
- package/lib/retry-publish.js +0 -405
- package/lib/verify-publish.js +0 -414
package/AGENTS.md
CHANGED
|
@@ -18,4 +18,4 @@ Muse Crew source repository. The repo is the product; the personal instance (`$C
|
|
|
18
18
|
- Git source is authoritative.
|
|
19
19
|
- Never expose this repo publicly.
|
|
20
20
|
- Ship implementation and documentation together.
|
|
21
|
-
- After editing `workflows/*.js`, run `bash tests/run.sh` — `node --check` does NOT catch syntax errors inside function bodies (V8 lazy preparsing), so it cannot validate workflow edits alone. Worse (2026-09-19): raw `node --check` on the unstripped file is a FALSE NEGATIVE — the file parses as a module (top-level `export`), which masks breakage the release gate's export-strip + async-wrap transform exposes (module/script goal confusion). An unterminated string passed raw `node --check` and was caught only by `publish-verdict-first.test.js`
|
|
21
|
+
- After editing `workflows/*.js`, run `bash tests/run.sh` — `node --check` does NOT catch syntax errors inside function bodies (V8 lazy preparsing), so it cannot validate workflow edits alone. Worse (2026-09-19): raw `node --check` on the unstripped file is a FALSE NEGATIVE — the file parses as a module (top-level `export`), which masks breakage the release gate's export-strip + async-wrap transform exposes (module/script goal confusion). An unterminated string passed raw `node --check` and was caught only by the suite's loader emulation (then `publish-verdict-first.test.js`, now the `_validate_workflows` behavioral drive in `tests/workflow-size.test.js`). After any workflow edit, verify with the true gate: `{ echo "async function __crew_workflow__(args) {"; sed 's/^export //' workflows/<f>.js; echo "}"; } > /tmp/w.js && node --check /tmp/w.js` — or just run the suite.
|
package/API.md
CHANGED
|
@@ -94,15 +94,68 @@ Atomically park a task for human attention: sets `state` to `parked`, writes the
|
|
|
94
94
|
|
|
95
95
|
Returns `{ "ok": true, "task": {...}, "settled_sessions": <n> }` with the updated task record and the count of sessions settled (`0` when none were running).
|
|
96
96
|
|
|
97
|
-
### `
|
|
97
|
+
### `scan-publish-intent`
|
|
98
98
|
|
|
99
|
-
|
|
99
|
+
Claims parked publish intents for the tick worker's direct issuance (0.14.6). For each task parked with `publish: publish-requested <commit> attempt=N`, re-derives the version deterministically (never trusts the ledger's), verifies the staged diff's sha256, checks the intent base against stamped provenance, and claims with a 1-hour lease (CAS on `claim_expiry`). Skips: no intent entry, unparseable note, terminal note, unexpired claim (another tick owns it), base-stale (re-queues to the workflow's Publish), backoff not elapsed.
|
|
100
|
+
|
|
101
|
+
Returns `{ "intent": [...], "skipped": [...], "requeued": [...] }`. Each intent entry carries `task_id`, `commit`, `attempt` (numeric `publish_attempt`), `version` (`<commit>:<attempt>`), `claim_expiry`, `diff_path`, `diff_sha256`, `ledger_path`.
|
|
102
|
+
|
|
103
|
+
### `record-intent-issuance`
|
|
104
|
+
|
|
105
|
+
Records the tick worker's direct issuance outcome (0.14.6). Compare-and-swap on `claim_expiry` — a stale tick writes nothing.
|
|
106
|
+
|
|
107
|
+
| Field | Type | Required | Notes |
|
|
108
|
+
|-------|------|----------|-------|
|
|
109
|
+
| `task_id` | uuid | yes | Must have an unexpired intent claim |
|
|
110
|
+
| `claim_expiry` | ISO-8601 | yes | Must match the claim's expiry (CAS) |
|
|
111
|
+
| `edit_outcome` | `accepted`\|`refused` | yes | The direct `artifact_edit` outcome |
|
|
112
|
+
| `refusal_text` | string | no | Required when `refused` |
|
|
113
|
+
|
|
114
|
+
`accepted` writes the issuer-stamped `submitted` ledger entry + `publish: edit-issued` note. `refused` writes `rejected` + terminal `publish: publish-refused`. Returns `{ "recorded": true, "edit_outcome" }` or `{ "recorded": false }` on CAS mismatch.
|
|
115
|
+
|
|
116
|
+
### `record-builder-report`
|
|
117
|
+
|
|
118
|
+
Records a builder's refusal report (0.14.6). Mechanically requires the EXACT version in `report_text` — a report that does not echo the version is rejected.
|
|
119
|
+
|
|
120
|
+
| Field | Type | Required | Notes |
|
|
121
|
+
|-------|------|----------|-------|
|
|
122
|
+
| `task_id` | uuid | yes | |
|
|
123
|
+
| `version` | `<commit>:<attempt>` | yes | Must name an issued attempt |
|
|
124
|
+
| `outcome` | `refused` | yes | (2026-09-20 REVIEW) `acknowledged` is rejected — the positive builder-report path was circular; disk is the sole positive evidence |
|
|
125
|
+
| `report_text` | string | yes | Must contain the exact version |
|
|
126
|
+
|
|
127
|
+
Returns `{ "ok": true, "publish_attempt" }`. The ack scan consumes refusal reports (terminal `publish: publish-refused`); positive acknowledgements come only from the on-disk version receipt.
|
|
128
|
+
|
|
129
|
+
### `record-version-ack`
|
|
130
|
+
|
|
131
|
+
Stamps a version acknowledgement (0.14.6). The version must name an issued attempt for the task/commit.
|
|
132
|
+
|
|
133
|
+
| Field | Type | Required | Notes |
|
|
134
|
+
|-------|------|----------|-------|
|
|
135
|
+
| `task_id` | uuid | yes | |
|
|
136
|
+
| `version` | `<commit>:<attempt>` | yes | Must match an issued attempt's derived version |
|
|
137
|
+
| `via` | string | yes | `disk` only (2026-09-20 REVIEW) |
|
|
138
|
+
|
|
139
|
+
Stamps provenance, writes terminal `publish: version-acknowledged`, re-queues the task to `in_progress`. Returns `{ "ok": true, "commit", "publish_attempt", "via" }`. The reclaim path accepts the exact disk version for the currently claimed intent when no `submitted` entry exists (dead tick issued but died before recording).
|
|
140
|
+
|
|
141
|
+
### `scan-ack-pending`
|
|
142
|
+
|
|
143
|
+
Evaluates version acknowledgements (0.14.6). For each `edit-issued` task: checks the on-disk `.crew-publish-version-<task_id>` receipt (per-task scoped; the SOLE positive evidence — 2026-09-20 REVIEW removed the circular builder-report positive path). Any exact on-disk acknowledgement for an issued version on the task/commit counts (late earlier attempts included). On ack: provenance + terminal `publish: version-acknowledged` + requeue. On explicit refusal (via builder report): terminal `publish: publish-refused`. On 30-minute window expiry (per attempt, anchored at the LATEST issuance): re-issues with a FRESH version, immediately claimable (no backoff). When the 2-hour total budget from the FIRST issuance is exhausted: terminal `publish: version-timeout` (unobserved within budget — the edit may still have landed).
|
|
144
|
+
|
|
145
|
+
Returns `{ "acknowledged": [...], "refused": [...], "reissued": [...], "timeouts": [...], "skipped": [...] }`.
|
|
146
|
+
|
|
147
|
+
A dedicated 5-minute `crew-ack-scan` cron (seed/crons.json) runs this command on an observation-only tick so verdicts land quickly; it never issues edits — issuance stays in the 15-minute poll tick's claim-and-issue path.
|
|
148
|
+
|
|
149
|
+
### `record-publish-note`
|
|
150
|
+
|
|
151
|
+
The closed publish-note writer (0.14.6 §1.5). Asserts the note's verb against the writable registry before writing — prose cannot mint new states.
|
|
100
152
|
|
|
101
153
|
| Field | Type | Required | Notes |
|
|
102
154
|
|-------|------|----------|-------|
|
|
103
|
-
| `task_id` | uuid | yes |
|
|
155
|
+
| `task_id` | uuid | yes | |
|
|
156
|
+
| `note` | string | yes | Must start with a registered `publish: <verb>` |
|
|
104
157
|
|
|
105
|
-
|
|
158
|
+
Writable verbs (tick prose via `record-publish-note`): `publish-requested`, `publish-intent-claimed`, `publish-base-stale`, `edit-issued` (transitional only — prose can never mint a terminal). Deterministic code writes terminals via the code-writable registry: `version-acknowledged`, `publish-refused`, `version-timeout`, `publish-unissuable`. Legacy terminals are recognized but NOT writable by anyone. Returns `{ "ok": true, "verb" }` or a usage error.
|
|
106
159
|
|
|
107
160
|
---
|
|
108
161
|
|
|
@@ -483,9 +536,10 @@ project row carries its own publish base (`provenance_source_commit`,
|
|
|
483
536
|
a crew home with N artifact projects carries N independent bases. There are
|
|
484
537
|
two stamp call paths, and they are different operations on the same row:
|
|
485
538
|
|
|
486
|
-
- **Full stamp** — called by the
|
|
487
|
-
|
|
488
|
-
|
|
539
|
+
- **Full stamp** — called by the ack scan's `record-version-ack` (the 0.14.6
|
|
540
|
+
version machine) after the exact per-attempt version is observed on the
|
|
541
|
+
artifact's disk: `source_commit` is the acknowledged build commit,
|
|
542
|
+
`crew_release` the active release at publish time.
|
|
489
543
|
- **Refresh** — called by the release installer (`lib/advance-publish-base.js`)
|
|
490
544
|
after installing/activating a crew release: `crew_release` is refreshed to
|
|
491
545
|
the live release identity while preserving the existing `source_commit`.
|
package/docs/decisions/AGENTS.md
CHANGED
|
@@ -57,6 +57,9 @@ re-verified that every `docs/decisions/*.md#anchor` reference in
|
|
|
57
57
|
- `#d1-issuance-time` — D1 issuance-time field (`issued_at`; `entry_kind` cut 2026-09-20)
|
|
58
58
|
- `#detached-head-audit` — Detached-HEAD audit (0.14.2)
|
|
59
59
|
- `#detached-head-audit-redo` — Detached-HEAD audit REDO (0.14.3, supersedes findings 3/5)
|
|
60
|
+
- `#version-acknowledgement` — Version acknowledgement replaces content verification (0.14.6)
|
|
61
|
+
- `#version-review-dispositions` — 2026-09-20 amended REVIEW dispositions (14 mechanical fixes)
|
|
62
|
+
- `#version-not-hash` — 2026-09-20: version number replaces the sha256 nonce
|
|
60
63
|
|
|
61
64
|
## qa-reproduce.md — Decision history: qa reproduce
|
|
62
65
|
|
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# Decision history: publish path
|
|
2
2
|
|
|
3
|
+
> **SUPERSESSION NOTICE (2026-09-20, 0.14.6 version machine REVIEW):** This
|
|
4
|
+
> document records the decision history of the RETIRED publish machinery
|
|
5
|
+
> (two-party trigger, fire-and-forget observation, content verification,
|
|
6
|
+
> pre-trigger baselines, artifact toolcheck). The sections below describe
|
|
7
|
+
> that machinery in present tense as "Invariants" — they are HISTORICAL,
|
|
8
|
+
> not current. The current publish path is the one-party worker-owned version
|
|
9
|
+
> acknowledgement machine: the workflow stages a checksummed diff and
|
|
10
|
+
> parks with `publish: publish-requested`; the session-carrying tick
|
|
11
|
+
> worker claims the intent, issues the edit via `artifact_edit`, and the
|
|
12
|
+
> ack scan observes the exact per-attempt version on the artifact's disk as
|
|
13
|
+
> the sole positive evidence. See `docs/publish-verification.md` for the
|
|
14
|
+
> current contract.
|
|
15
|
+
|
|
3
16
|
Relocated from workflow source comments during H5 (2026-09-18). The workflows keep only the relied-upon invariant inline; the full decision history lives here.
|
|
4
17
|
|
|
5
18
|
<a id="fire-and-forget-trigger"></a>
|
|
@@ -1361,7 +1374,7 @@ Consumers (one anchor, shared): `lib/verify-publish.js`,
|
|
|
1361
1374
|
`lib/crew-api.js::findTriggerEntry`, the classifier
|
|
1362
1375
|
(`lib/classify-publish-absence.js`), and the retry writer
|
|
1363
1376
|
(`lib/retry-publish.js`) all take the oldest matching "submitted" by `ts`
|
|
1364
|
-
and bind `issued_at || ts`. Platform
|
|
1377
|
+
and bind `issued_at || ts`. Platform version: deferred, not filed in this
|
|
1365
1378
|
change.
|
|
1366
1379
|
|
|
1367
1380
|
Edge cases:
|
|
@@ -1507,3 +1520,233 @@ push-destination, do_push refspec), lib/publish-npm.sh
|
|
|
1507
1520
|
(PUSH-TARGET-ANCHOR refspec), workflows/{standard,bugfix,chore}.js
|
|
1508
1521
|
(STEP-2/R5-PUSH wording), lib/test-detached-integrate.sh (fixture H),
|
|
1509
1522
|
lib/test-publish-preflight.sh (cases 6/7).
|
|
1523
|
+
|
|
1524
|
+
## One-party worker-owned publish (0.14.5, room #24 → blockers 22/23)
|
|
1525
|
+
|
|
1526
|
+
**Problem.** Room #24's four journeys all died at Publish on one mechanical
|
|
1527
|
+
fact: workflow children cannot call `artifact_edit`. The tool requires a
|
|
1528
|
+
parent-conversation session ID that `agent()` children do not have — every
|
|
1529
|
+
trigger-child attempt failed with `missing session id`, and the runtime's
|
|
1530
|
+
JSON-candidate scan threw on the refusal before the workflow could parse it.
|
|
1531
|
+
A second, independent defect: the retry harness (`lib/retry-publish.js`)
|
|
1532
|
+
imported the unavailable `better-sqlite3` and pointed at `crew.db` instead
|
|
1533
|
+
of `crew-state.db`, so no retry could ever run.
|
|
1534
|
+
|
|
1535
|
+
**Decision.** One party owns issuance: the session-carrying tick worker.
|
|
1536
|
+
The workflow prepares (preflight, provenance base, checksummed diff via
|
|
1537
|
+
`lib/compute-publish-diff.js`, toolcheck, pre-trigger manifest baseline),
|
|
1538
|
+
records ONE issuer-stamped ledger entry (`outcome: "publish-intent"`,
|
|
1539
|
+
`issuer: "workflow"`, carrying `diff_path`/`diff_sha256`), and parks with
|
|
1540
|
+
`publish: publish-requested <commit> <attempt>`. The tick claims the intent
|
|
1541
|
+
(`publish: publish-intent-claimed <expiry>`, 1-hour lease, via
|
|
1542
|
+
`scan-publish-unknown`'s `intent` bucket), verifies the staged diff's sha256
|
|
1543
|
+
(regenerating deterministically from `base..commit` when the file is
|
|
1544
|
+
missing), runs the manifest-freshness check on re-claimed intents (the dead
|
|
1545
|
+
tick may have issued and died before recording — never blindly re-issue),
|
|
1546
|
+
calls `artifact_edit` directly in its own turn, and records the outcome
|
|
1547
|
+
through `record-intent-issuance` (CAS on the claim): `accepted`/`recovered`
|
|
1548
|
+
→ issuer-stamped `submitted` (`issuer: "tick-worker"`) + mirrored
|
|
1549
|
+
`publish: verification-requested`; `refused` → `rejected` + terminal
|
|
1550
|
+
`publish: publish-refused`.
|
|
1551
|
+
|
|
1552
|
+
**Retired, not reverted.** The trigger child, build-state observation
|
|
1553
|
+
machinery, refusal parser, phantom workflow-owned `submitted`, and the
|
|
1554
|
+
receipt-less unknown machinery's issuance side are deleted. The
|
|
1555
|
+
unknown-recovery classifier survives for legacy parks; its trigger anchor
|
|
1556
|
+
is now issuer-bound (only `issuer: "tick-worker"` binds). The retry
|
|
1557
|
+
protocol survives ported to `node:sqlite` + `crew-state.db` with the
|
|
1558
|
+
missing `CREW_REPO` for the merge lock supplied, and its issuance step is
|
|
1559
|
+
the tick worker's direct call. Verified re-entry: a `submitted` without a
|
|
1560
|
+
mirrored request mirrors `verification-requested` — never re-issues.
|
|
1561
|
+
|
|
1562
|
+
**What it is not.** Not a ledger-as-request queue (no new queue
|
|
1563
|
+
abstraction — the intent IS the ledger entry plus the park note). Not a
|
|
1564
|
+
revert of room #22's trigger-timing work (that machinery was deleted, and
|
|
1565
|
+
room #24 disproved its premise). The new path is proven in parts; the next
|
|
1566
|
+
clean room proves it end-to-end.
|
|
1567
|
+
|
|
1568
|
+
Applies to: workflows/{standard,bugfix,chore}.js (Publish exits at intent),
|
|
1569
|
+
lib/crew-api.js (scan-publish-unknown intent bucket, record-intent-issuance,
|
|
1570
|
+
issuer-bound findTriggerEntry), lib/retry-publish.js (node:sqlite port,
|
|
1571
|
+
CREW_REPO, direct tick issuance), lib/verify-publish.js (issuer-bound
|
|
1572
|
+
trigger anchor), lib/publish-note-vocabulary.js (publish-requested,
|
|
1573
|
+
publish-intent-claimed, publish-refused), seed/cron-body-template.md
|
|
1574
|
+
(steps 4.4b, 4.4 retry direct-issuance), docs/publish-verification.md,
|
|
1575
|
+
docs/publish-unknown-recovery.md.
|
|
1576
|
+
|
|
1577
|
+
<a id="version-acknowledgement"></a>
|
|
1578
|
+
## Version acknowledgement replaces content verification (0.14.6, 2026-09-20)
|
|
1579
|
+
|
|
1580
|
+
**Eric's contract:** "If we hear that the artifact acknowledges our version,
|
|
1581
|
+
that's it. We don't verify against content."
|
|
1582
|
+
|
|
1583
|
+
The content-verdict machine (read-back, `verify-publish.js`, the unknown-
|
|
1584
|
+
recovery classifier, the retry harness) is deleted. Exact per-attempt version
|
|
1585
|
+
acknowledgement is the sole positive completion criterion.
|
|
1586
|
+
|
|
1587
|
+
**The version:** `sha256("publish-version:v1:" + task_id + ":" + commit + ":" +
|
|
1588
|
+
publish_attempt)` — deterministic, 64 hex. Synthesized into the staged diff
|
|
1589
|
+
as `.crew-publish-version` (checksum covers it; never committed). The workflow
|
|
1590
|
+
replay key (`attempt`, a string) is separate from the numeric
|
|
1591
|
+
`publish_attempt`.
|
|
1592
|
+
|
|
1593
|
+
**The loop:** workflow stages intent (attempt 1) → tick worker claims via
|
|
1594
|
+
`scan-publish-intent` → issues directly → `record-intent-issuance` writes
|
|
1595
|
+
`submitted` + `publish: edit-issued` → `scan-ack-pending` watches for the
|
|
1596
|
+
exact version on disk or in a builder report. Any issued version for the
|
|
1597
|
+
task/commit counts (late earlier attempts included). Refusal is terminal.
|
|
1598
|
+
45-min window per attempt, 3 attempts max, 15/45-min backoff. Third expiry
|
|
1599
|
+
→ `publish: version-timeout`.
|
|
1600
|
+
|
|
1601
|
+
**Provenance certifies request acknowledgement, not byte equality.**
|
|
1602
|
+
|
|
1603
|
+
Applies to: lib/compute-publish-diff.js (--task-id/--attempt, version hunk),
|
|
1604
|
+
lib/crew-api.js (scan-publish-intent, record-intent-issuance,
|
|
1605
|
+
record-builder-report, record-version-ack, scan-ack-pending,
|
|
1606
|
+
record-publish-note), lib/publish-note-vocabulary.js (WRITABLE split:
|
|
1607
|
+
legacy terminals recognized, not writable), workflows/{standard,bugfix,
|
|
1608
|
+
chore}.js (Publish exits at intent with `attempt=1`), seed/cron-body-
|
|
1609
|
+
template.md (tick protocol), docs/publish-verification.md,
|
|
1610
|
+
docs/publish-unknown-recovery.md (retired).
|
|
1611
|
+
|
|
1612
|
+
## 2026-09-20 (post-REVIEW amendment) — bounds re-set, observation split from mutation
|
|
1613
|
+
|
|
1614
|
+
Before PUBLISH, Eric challenged the bounds as unmeasured placeholders and
|
|
1615
|
+
approved a replacement:
|
|
1616
|
+
|
|
1617
|
+
- **Observation is cheap and read-only; mutation is not.** The old bounds
|
|
1618
|
+
coupled them: the 45-minute window + 15/45-minute backoffs + 3-attempt cap
|
|
1619
|
+
were all guesses, and the scan ran inside the 15-minute poll tick, so a
|
|
1620
|
+
verdict could never land sooner than the tick cadence.
|
|
1621
|
+
- **New shape:** the ack scan runs on its own 5-minute `crew-ack-scan` cron
|
|
1622
|
+
(observation-only — it never issues edits); the 15-minute poll tick keeps
|
|
1623
|
+
the claim-and-issue path. The scan cadence no longer constrains verdict
|
|
1624
|
+
latency: with 6 scans per window, the verdict lands within one cadence of
|
|
1625
|
+
the ack or the expiry.
|
|
1626
|
+
- **New bounds:** 30-minute acknowledgement window per attempt (anchored at
|
|
1627
|
+
the LATEST issuance — evidence-set: room #25's one healthy ack landed
|
|
1628
|
+
~15-17 minutes after issuance, so the window is ~2x that plus margin),
|
|
1629
|
+
immediate reissue at expiry (`not_before: null` — no backoff), and a
|
|
1630
|
+
2-hour total budget from the FIRST issuance, after which the scan parks
|
|
1631
|
+
terminal `publish: version-timeout`. The fixed 3-attempt cap is gone — a
|
|
1632
|
+
slow-but-healthy artifact no longer false-timeouts, and a dead one still
|
|
1633
|
+
terminates on the budget. The ledger records issued_at and first-seen ack
|
|
1634
|
+
per attempt, so the window is tunable from measured data.
|
|
1635
|
+
|
|
1636
|
+
<a id="version-review-dispositions"></a>
|
|
1637
|
+
## 2026-09-20 (amended REVIEW dispositions) — 14 mechanical fixes
|
|
1638
|
+
|
|
1639
|
+
A five-critic read-only review (Architect, Subtractor, Reliability, Room
|
|
1640
|
+
Operator + step-back) of the amended 0.14.6 tree returned
|
|
1641
|
+
ACCEPT-WITH-CONCERNS, no REJECTs. All 14 dispositions applied before the
|
|
1642
|
+
amended BUILD commit:
|
|
1643
|
+
|
|
1644
|
+
1. **Torn reissue staging is note-keyed.** The ack scan's reissue
|
|
1645
|
+
idempotency guard is keyed on the `publish: publish-requested <commit>
|
|
1646
|
+
attempt=N` note — the same anchor `scan-publish-intent` claims on —
|
|
1647
|
+
not the ledger line. A crash between the ledger append and the note
|
|
1648
|
+
write (or a rolled-back COMMIT) leaves an orphan line; the note-keyed
|
|
1649
|
+
guard self-heals by re-appending the content-identical line. Write
|
|
1650
|
+
order stays line-then-note: note-first would park permanently on a
|
|
1651
|
+
tear.
|
|
1652
|
+
2. **`publish: version-timeout` vocabulary** now reads the 2-hour
|
|
1653
|
+
acknowledgement-budget meaning, not "three issuance attempts
|
|
1654
|
+
exhausted".
|
|
1655
|
+
3. **Dead backoff guard deleted** from `scan-publish-intent` (the amended
|
|
1656
|
+
design never writes `not_before`).
|
|
1657
|
+
4. **`not_before` plumbing removed**: no longer on claimed intents,
|
|
1658
|
+
reissue results, or ledger lines — the field is gone entirely, not
|
|
1659
|
+
even null-inline (nothing reads it; a dead field is the opposite of
|
|
1660
|
+
the disposition).
|
|
1661
|
+
5. **Version-mismatch is per-entry**: a corrupt submitted entry is
|
|
1662
|
+
quarantined; valid derivation-verified entries still drive the windows
|
|
1663
|
+
and the budget. Only when NO entry verifies does the record park as
|
|
1664
|
+
`version-mismatch`.
|
|
1665
|
+
6. **Unparseable latest issuance is an unknown window**: never reported
|
|
1666
|
+
as "window-open"; the budget check still runs (a corrupt latest
|
|
1667
|
+
timestamp cannot defer the terminal timeout), but reissue is withheld
|
|
1668
|
+
until the budget decides.
|
|
1669
|
+
7. **Ack-scan failure is mechanical**: nonzero exit → report stderr
|
|
1670
|
+
verbatim, take no other action, no rerun, no other Crew API command,
|
|
1671
|
+
stop.
|
|
1672
|
+
8. **Tests extended** in the existing `tests/scan-publish-intent.test.js`
|
|
1673
|
+
(no new file): reissue idempotency across repeated scans, exact `>=`
|
|
1674
|
+
budget boundary (inside reissues, at-boundary times out), unparseable
|
|
1675
|
+
first issuance (`first-issuance-unparseable` park), unparseable latest
|
|
1676
|
+
issuance (reissue withheld), duplicate submitted entries (deduped),
|
|
1677
|
+
corrupt version beside a valid entry (quarantined), all-invalid
|
|
1678
|
+
versions (`version-mismatch` park), on-disk version naming no issued
|
|
1679
|
+
attempt (ignored), per-task receipts never cross-attributed, torn
|
|
1680
|
+
reissue prepare self-heals via the note-keyed guard.
|
|
1681
|
+
9. **Receipt scoped per task** (verified possible): per-project
|
|
1682
|
+
simultaneity defaults to 2 and parked tasks don't count as in-flight,
|
|
1683
|
+
so two same-project tasks CAN sit in `edit-issued` concurrently — a
|
|
1684
|
+
shared per-slug `.crew-publish-version` would let each issuance
|
|
1685
|
+
overwrite the other's acknowledgement (receipt ping-pong). The
|
|
1686
|
+
receipt is now `.crew-publish-version-<task_id>` in the staged diff
|
|
1687
|
+
hunk and on disk.
|
|
1688
|
+
10. **Cron id overrides validated at init**: an override must start with
|
|
1689
|
+
`<manifest-id>-`, so uninstall's prefix-based discovery fallback can
|
|
1690
|
+
always find the job even if `.cron-registry.json` is lost.
|
|
1691
|
+
11. **`crew-burn-room.sh`** (outside the repo): `--crons-removed` caller
|
|
1692
|
+
attestation is REQUIRED — the script refuses to burn without it
|
|
1693
|
+
(fail-closed; it cannot reach the scheduler, so the contract is
|
|
1694
|
+
honest: the caller removes poll/ack-scan/update-watch/journey-watch
|
|
1695
|
+
via cron.remove first and attests the ids). `roomNN-journey-watch`
|
|
1696
|
+
added to the convention list. `--ts-spaces <slugs>` moves the room's
|
|
1697
|
+
artifact records into the trash (already-gone is fine).
|
|
1698
|
+
12. **Stuck-publish runbook** in `docs/guide.md`: the ack scan stages
|
|
1699
|
+
reissues, the poll tick issues them — if the poll tick is down,
|
|
1700
|
+
reissues stay staged and the budget eventually parks
|
|
1701
|
+
`publish: version-timeout`.
|
|
1702
|
+
13. **Receipt scope documented**: the version proves adoption into the
|
|
1703
|
+
artifact's on-disk source tree — not a successful rebuild or
|
|
1704
|
+
redeployment.
|
|
1705
|
+
14. **Result taxonomy fixed**: `timeouts[]` is terminal-budget exhaustion
|
|
1706
|
+
only; window expiries are `reissued[]`.
|
|
1707
|
+
|
|
1708
|
+
**Follow-up fix found in final pre-commit inspection (same day):** the
|
|
1709
|
+
version-not-hash cut left the old 64-hex nonce regex in the Publish
|
|
1710
|
+
phase of `workflows/standard.js`, `bugfix.js`, and `chore.js` — the
|
|
1711
|
+
format gate would have rejected every `<commit>:<attempt>` version and
|
|
1712
|
+
parked every publish. All three now mirror `isPublishVersion()` from
|
|
1713
|
+
`lib/crew-api.js`, and `tests/compute-publish-diff.test.js` carries a
|
|
1714
|
+
mechanical guard asserting the workflow regex matches the library's
|
|
1715
|
+
(and the stale 64-hex shape is gone). The full suite passed before this
|
|
1716
|
+
was found because no test executes the workflow's Publish gate — the
|
|
1717
|
+
guard closes that hole.
|
|
1718
|
+
|
|
1719
|
+
Step-back conclusions recorded: the five-minute observer costs ~192
|
|
1720
|
+
marginal task-mode turns/day/room (accepted for current VERIFY
|
|
1721
|
+
turnaround); the machine is a compensating control for the platform's
|
|
1722
|
+
missing real completion receipt and should be deleted the day the
|
|
1723
|
+
platform issues one (see `docs/publish-verification.md`
|
|
1724
|
+
§ Decommissioning). Non-blocking backlog: at final timeout, a
|
|
1725
|
+
machine-owned audit-directory check could split the park note into
|
|
1726
|
+
"landed-but-receiptless" vs "platform-silent".
|
|
1727
|
+
|
|
1728
|
+
<a id="version-not-hash"></a>
|
|
1729
|
+
## 2026-09-20 — Version number replaces the sha256 nonce
|
|
1730
|
+
|
|
1731
|
+
Eric: a nonce is a hash plus a version number — why not just the version
|
|
1732
|
+
number? He was right. The sha256 defended against a forger that doesn't
|
|
1733
|
+
exist: the builder's failure mode is omission (zero-tool runs), never
|
|
1734
|
+
forging our receipts. The artifact's real failure mode — an unfaithful
|
|
1735
|
+
carrier that mangles bytes or ignores the request — defeats a hash and a
|
|
1736
|
+
plain counter identically. We own the code, so we count our own attempts;
|
|
1737
|
+
we don't own the artifact, so verification stays observation of its state,
|
|
1738
|
+
never its attestation. The hash pretended to buy attestation.
|
|
1739
|
+
|
|
1740
|
+
The per-attempt version is now `<commit>:<attempt>` (e.g.
|
|
1741
|
+
`4b825dc6…:2`) — no hash, no task_id (the receipt path
|
|
1742
|
+
`.crew-publish-version-<task_id>` and the per-task ledger scoping already
|
|
1743
|
+
carry the task). The commit binds the version to the exact code, so a stale
|
|
1744
|
+
receipt from an earlier commit can never equal a fresh version — the binding
|
|
1745
|
+
the hash used to provide, kept without the cryptography. Derivation is a
|
|
1746
|
+
plain concatenation, byte-identical between `compute-publish-diff.js` and
|
|
1747
|
+
`derivePublishVersion()` in `crew-api.js`; well-formedness is
|
|
1748
|
+
`/^[0-9a-f]{40}:[1-9][0-9]*$/` (`isPublishVersion`). The retry machine,
|
|
1749
|
+
windows, budget, and terminal vocabulary are unchanged — only the token got
|
|
1750
|
+
honest. Side benefit, and Eric's original motive: the version is
|
|
1751
|
+
human-readable observability ("roughly what's deployed") where the hash was
|
|
1752
|
+
opaque.
|
package/docs/guide.md
CHANGED
|
@@ -17,7 +17,7 @@ This is the full setup and operations reference. If you're new, start with the [
|
|
|
17
17
|
- A **release** — the first immutable snapshot of the crew's runtime code.
|
|
18
18
|
- An **`.orchestration/` directory** in the crew home with identities, personas, workflow docs, and feedback conventions.
|
|
19
19
|
- A **project registration** — the task service registered as its own first project.
|
|
20
|
-
- **Cron jobs** — the scheduler state declared in `seed/crons.json`: a polling loop (every 15 minutes via Muse's scheduling, even when nobody's in the conversation). The scheduler identity is dashboard-independent — chosen once at first init (no owner; the platform rejects `cli:` owners) — so deleting the dashboard artifact does not stop the crew. Removing a crew entirely is `crew-uninstall`'s job.
|
|
20
|
+
- **Cron jobs** — the scheduler state declared in `seed/crons.json`: a polling loop (every 15 minutes via Muse's scheduling, even when nobody's in the conversation), a 5-minute acknowledgement scan (observation-only — runs `scan-ack-pending` so publish verdicts land quickly; it never issues edits), and a daily update watcher. The scheduler identity is dashboard-independent — chosen once at first init (no owner; the platform rejects `cli:` owners) — so deleting the dashboard artifact does not stop the crew. Removing a crew entirely is `crew-uninstall`'s job.
|
|
21
21
|
|
|
22
22
|
The agent running in the main chat receives the dispatcher's claims and launches each task workflow. Workflows can't launch workflows, so this handoff is structural.
|
|
23
23
|
|
|
@@ -126,7 +126,7 @@ The run has four phases:
|
|
|
126
126
|
|
|
127
127
|
1. **Gates** — the crew home must be inside the workspace (uninstall refuses to delete anything outside it), and `confirm: true` must be present. Either missing blocks the run before anything is touched.
|
|
128
128
|
2. **Safety** — if any task is `in_progress`, the run blocks unless `force: true`. Uninstalling under live workflows would strand them.
|
|
129
|
-
3. **Crons** — removes the crew's scheduler jobs, and only those: the exact ids from `$CREW_HOME/.cron-registry.json` (written by init; covers id overrides), plus discovery of `crew-poll-*` jobs whose body contains the crew home path (covers installs from before the registry existed). Anything not on that union is never touched — shared crons are safe by construction, and a missing home still gets its orphaned crons removed.
|
|
129
|
+
3. **Crons** — removes the crew's scheduler jobs, and only those: the exact ids from `$CREW_HOME/.cron-registry.json` (written by init; covers id overrides), plus discovery of `crew-poll-*` / `crew-ack-scan-*` / `crew-update-watch-*` jobs whose body contains the crew home path (covers installs from before the registry existed). Anything not on that union is never touched — shared crons are safe by construction, and a missing home still gets its orphaned crons removed.
|
|
130
130
|
4. **Home** — best-effort `git worktree prune` on the crew's project repos, then deletes the crew home directory and verifies it is gone.
|
|
131
131
|
|
|
132
132
|
Dashboard artifacts are left untouched — uninstall removes the crew instance (its crons and its home), never the user's artifacts.
|
|
@@ -230,7 +230,10 @@ A task in `todo` state becomes eligible for dispatch on the next polling tick. A
|
|
|
230
230
|
|
|
231
231
|
## How the loop works
|
|
232
232
|
|
|
233
|
-
Every 15 minutes, the `crew-poll` cron fires
|
|
233
|
+
Every 15 minutes, the `crew-poll` cron fires. (Publish verdicts don't wait
|
|
234
|
+
for it: a separate 5-minute `crew-ack-scan` cron runs the observation-only
|
|
235
|
+
`scan-ack-pending` pass so acknowledgements, refusals, and timeouts land
|
|
236
|
+
within one scan cadence — issuance stays in the poll tick below.)
|
|
234
237
|
|
|
235
238
|
1. **Cron runs the dispatcher** — `crew-dispatch.js` with the task service slug and `crewHome` as arguments.
|
|
236
239
|
|
|
@@ -364,9 +367,42 @@ Bugfix tasks reproduce at the layer where the bug lives — not always in the br
|
|
|
364
367
|
|
|
365
368
|
The workflow dispatches the Reproduce strategy mechanically on the classification. A garbled or missing marker degrades to `artifact` (the old behavior) — a task is never parked on it. The experiential loop is forbidden for engine/docs tasks: if the reproducer drives the browser anyway, the closeout fails the phase loudly for retry, so wrong-layer verdicts never reach Map.
|
|
366
369
|
|
|
367
|
-
### Publish
|
|
370
|
+
### Publish acknowledgement
|
|
368
371
|
|
|
369
|
-
|
|
372
|
+
Eric's publication contract (2026-09-20): "If we hear that the artifact acknowledges our version, that's it. We don't verify against content."
|
|
373
|
+
|
|
374
|
+
The workflow never stamps provenance itself, and it doesn't issue the edit: workflow children cannot call `artifact_edit` (the tool requires a parent-conversation session id they don't have), so publish is one-party. The workflow prepares — preflight, provenance base, checksummed diff with the deterministic per-attempt version — writes one issuer-stamped `publish-intent` ledger entry, and parks with `publish: publish-requested <commit> attempt=N`. The session-carrying tick worker (the only caller class that can reach `artifact_edit`) claims the intent via `scan-publish-intent`, re-derives the version, verifies the diff's sha256, issues the edit directly in its own turn, and records the issuer-stamped `submitted` entry plus `publish: edit-issued` via `record-intent-issuance`.
|
|
375
|
+
|
|
376
|
+
The ack scan (`scan-ack-pending`) owns the verdict from there: the exact per-attempt version on the artifact's disk is the sole positive completion criterion (2026-09-20 REVIEW removed the circular builder-report positive path; builder reports carry explicit refusals only). Bounds: 30-minute window per attempt, 2-hour total budget from first issuance, no backoff (reissue immediately claimable), fresh version per attempt. Explicit refusal parks terminally (`publish: publish-refused`); budget exhaustion parks `publish: version-timeout`. Silence is unknown and fails closed. See `docs/publish-verification.md` for the full protocol. QA's provenance check then enforces the stamp mechanically, so an unacknowledged publish fails loudly in QA instead of passing silently.
|
|
377
|
+
|
|
378
|
+
### When publish is stuck
|
|
379
|
+
|
|
380
|
+
Two different workers move a publish forward, and they own different halves:
|
|
381
|
+
|
|
382
|
+
- The **5-minute `crew-ack-scan`** is observation-only: it watches the
|
|
383
|
+
on-disk version receipt and, when a 30-minute window expires, stages the
|
|
384
|
+
next attempt's intent (fresh version, immediately claimable). It never
|
|
385
|
+
issues an edit.
|
|
386
|
+
- The **15-minute poll tick** is the only half that issues: its
|
|
387
|
+
`scan-publish-intent` claims staged intents and its session-carrying
|
|
388
|
+
worker calls `artifact_edit`.
|
|
389
|
+
|
|
390
|
+
If the poll tick is down, reissues stay staged and unclaimed — the task
|
|
391
|
+
sits at `publish: edit-issued` (or `publish: publish-requested` for a
|
|
392
|
+
staged reissue) with no new issuance, and eventually the 2-hour total
|
|
393
|
+
budget expires and the ack scan writes the terminal
|
|
394
|
+
`publish: version-timeout`. The timeout is honest: the budget is measured
|
|
395
|
+
from the first issuance, not from the last successful observation, so a
|
|
396
|
+
dead issuer cannot stretch it.
|
|
397
|
+
|
|
398
|
+
**Runbook:** check the poll tick first — is `crew-poll-*` enabled and
|
|
399
|
+
producing recent tick reports? If yes, the stuck task is a publish-path
|
|
400
|
+
problem (diagnose via `docs/publish-verification.md`'s Operator recovery).
|
|
401
|
+
If no, the ack scan's staged reissues are waiting on an issuer that isn't
|
|
402
|
+
running — fix the poll tick, not the publish machine. In either case, do
|
|
403
|
+
not hand-issue the edit: issuance belongs to the tick worker's
|
|
404
|
+
claim-and-issue path, and an out-of-band edit has no ledger anchor for
|
|
405
|
+
the ack scan to verify against.
|
|
370
406
|
|
|
371
407
|
## Identities
|
|
372
408
|
|
|
@@ -380,7 +416,7 @@ Each phase has an assigned identity — a character with a defined personality:
|
|
|
380
416
|
| Build | **Wren** | Quietest one, trusts the plan |
|
|
381
417
|
| Review | **Cass** | Fair but exacting — holds the spec as the contract |
|
|
382
418
|
| Integrate | **Wren** | Merges the work into the integration target and pushes it (succeeds vacuously when the task branch is empty — runtime-state deliverable) |
|
|
383
|
-
| Publish | **Wren** | Ships the merged code to the publish target (skipped when none). The workflow
|
|
419
|
+
| Publish | **Wren** | Ships the merged code to the publish target (skipped when none). The workflow prepares the publish but never issues it — artifact issuance is tick-worker-owned (one-party, blocker 22); npm via registry version; artifact via the tick worker's direct edit plus an independent content read-back before the parent stamps provenance (see `docs/publish-verification.md`) — and fails closed on any unverified outcome |
|
|
384
420
|
| QA | **Hazel** | Code-blind, persistent, wears persona costumes |
|
|
385
421
|
| Reproduce | **Hazel** | Reproduces bugs before fixing |
|
|
386
422
|
| Write | **Tate** | Docs writer, observational voice |
|
|
@@ -1,113 +1,28 @@
|
|
|
1
|
-
# Publish-unknown recovery
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
│ ledger: unknown-resolved)
|
|
30
|
-
├─ provably-dropped ──> publish: dropped <commit>
|
|
31
|
-
│ (queues the retry protocol, next tick)
|
|
32
|
-
├─ applied-not-built / ambiguous ──> publish: ambiguous <commit>
|
|
33
|
-
│ (terminal; ledger: unknown-classified)
|
|
34
|
-
├─ superseded ────────> publish: superseded <commit> (terminal)
|
|
35
|
-
└─ deferred ──────────> (no note; the claim expires; the next scan
|
|
36
|
-
re-claims and re-classifies)
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
Retry protocol (Step 4.4, for `publish: dropped` with a free budget):
|
|
40
|
-
|
|
41
|
-
```
|
|
42
|
-
publish: dropped
|
|
43
|
-
── tick: HEAD == commit? no ──> publish: retry-superseded (terminal)
|
|
44
|
-
── tick: acquire merge lock publish-retry:<task> (600s); held ──> stop, retry next tick
|
|
45
|
-
── tick: re-read content in the lock (classifier)
|
|
46
|
-
├─ not provably-dropped ──> record-retry-recheck routes it
|
|
47
|
-
│ (verified → verification-requested; ambiguous → terminal;
|
|
48
|
-
│ superseded → terminal; deferred → no-op)
|
|
49
|
-
└─ provably-dropped ──> publish: retry-intended <commit> <ts>
|
|
50
|
-
── trigger (same child shape as the first attempt)
|
|
51
|
-
├─ ARTIFACT_EDIT_REFUSED ──> publish: retry-refused (terminal)
|
|
52
|
-
└─ no refusal ──> publish: retry-issued <commit>
|
|
53
|
-
──> publish: verification-requested <commit> not-before=<ts+20m>
|
|
54
|
-
(Step 4.5 skips not-before entries until the window passes)
|
|
55
|
-
──> one -retry1 ledger entry (best-effort)
|
|
56
|
-
── release the merge lock (every path)
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
Crash recovery (fail closed, never re-trigger blind):
|
|
60
|
-
|
|
61
|
-
- `publish: retry-intended` without `retry-issued` → the next scan mirrors
|
|
62
|
-
`verification-requested` with `not-before=intended+20m`. The trigger may
|
|
63
|
-
or may not have gone out; content verification is the arbiter.
|
|
64
|
-
- `publish: retry-issued` without a mirrored request → the next scan writes
|
|
65
|
-
the missing mirror.
|
|
66
|
-
- Exactly one retry per task, enforced from note history: the scan emits
|
|
67
|
-
`retry_due` only when no `publish: retry-issued` exists in the task's
|
|
68
|
-
history — including a retry for an earlier unknown attempt on a reworked
|
|
69
|
-
task. When the budget is spent, the drop is terminal: `publish: ambiguous`.
|
|
70
|
-
|
|
71
|
-
## Commands
|
|
72
|
-
|
|
73
|
-
- `scan-publish-unknown` — the cron scan (Step 4.5 of the old numbering).
|
|
74
|
-
Returns `{ waiting, due, retry_due, mirrored, skipped }`. `due` entries
|
|
75
|
-
ferry the classifier inputs: `commit`, `trigger_ts` (from the `submitted`
|
|
76
|
-
ledger entry — never a time window), `park_ts`, `slug`, `repo_path`,
|
|
77
|
-
`base` (provenance `source_commit`, else the empty tree), and
|
|
78
|
-
`claim_expiry` for the record CAS. `retry_due` entries ferry the retry
|
|
79
|
-
inputs (`commit`, `attempt`, `slug`, `repo_path`, `base`, `ledger_path`).
|
|
80
|
-
- `record-unknown-classification --json '{task_id, claim_expiry, decision}'`
|
|
81
|
-
— routes the classifier's decision; CAS on the claim expiry (a stale tick
|
|
82
|
-
records nothing).
|
|
83
|
-
- `record-retry-recheck --json '{task_id, decision}'` — routes the retry
|
|
84
|
-
protocol's in-lock content re-read; CAS on latest being `publish: dropped`.
|
|
85
|
-
- `resolve-publish-unknown` — the manual one-shot for a single parked task
|
|
86
|
-
(unchanged; the audit-window contract, not the classifier).
|
|
87
|
-
|
|
88
|
-
## Classifier (lib/classify-publish-absence.js)
|
|
89
|
-
|
|
90
|
-
Decides, from the task's repo and the platform's on-disk state, whether the
|
|
91
|
-
dropped edit is proven:
|
|
92
|
-
|
|
93
|
-
- `verified` — the content IS present (the platform applied it; the receipt
|
|
94
|
-
was the only thing lost). Never re-issue.
|
|
95
|
-
- `provably-dropped` — the old source is live AND the manifest shows no
|
|
96
|
-
build since the trigger. Only this decision may retry.
|
|
97
|
-
- `applied-not-built` — the new source is live but no build ran (the
|
|
98
|
-
trigger reached the platform but the build didn't). Ambiguous outcome,
|
|
99
|
-
terminal: a retry would double-apply.
|
|
100
|
-
- `ambiguous` — the content check is inconclusive. Never retry blind.
|
|
101
|
-
- `deferred` — not yet quiesced; re-check next tick.
|
|
102
|
-
- `superseded` — HEAD moved past the attempt's commit. Terminal.
|
|
103
|
-
|
|
104
|
-
The classifier's verified path requires the CURRENT manifest to be a new
|
|
105
|
-
build identity: `built_at` advanced past the trigger AND `content_sha256`
|
|
106
|
-
differs from the pre-trigger baseline the workflow snapshots into the
|
|
107
|
-
submitted ledger entry (design §1.9 — defeats a replayed manifest). When
|
|
108
|
-
the baseline is unavailable the classifier falls back to the time-based
|
|
109
|
-
advance check and notes it; the Step 4.5 verifier (`lib/verify-publish.js`)
|
|
110
|
-
is the strict gate and fails closed without a baseline before stamping.
|
|
111
|
-
Build-in-flight is checked twice bracketing the content read via manifest
|
|
112
|
-
state change (no mtime heuristics), and the suite carries add-only and
|
|
113
|
-
removal-only regression fixtures.
|
|
1
|
+
# Publish-unknown recovery — RETIRED (0.14.6)
|
|
2
|
+
|
|
3
|
+
The unknown-recovery machinery (the `publish: unknown-*` note state machine,
|
|
4
|
+
`lib/classify-publish-absence.js`, `lib/retry-publish.js`,
|
|
5
|
+
`lib/resolve-publish-unknown` flow) is RETIRED as of 0.14.6 (2026-09-20).
|
|
6
|
+
|
|
7
|
+
It is subsumed by the version attempt loop: a 30-minute acknowledgement
|
|
8
|
+
window per attempt, fresh per-attempt versions with immediate reissue (no
|
|
9
|
+
backoff), a 2-hour total budget from first issuance, and `scan-ack-pending`
|
|
10
|
+
as the sole verdict. There is no separate unknown state — silence within
|
|
11
|
+
the window is unknown and fails closed; expiry re-issues with a fresh version;
|
|
12
|
+
budget exhaustion parks `publish: version-timeout`.
|
|
13
|
+
|
|
14
|
+
See `docs/publish-verification.md` for the current protocol.
|
|
15
|
+
|
|
16
|
+
## Historical note
|
|
17
|
+
|
|
18
|
+
The unknown path existed because the old two-party publish fired the
|
|
19
|
+
artifact edit through a trigger child and got no receipt back — async was
|
|
20
|
+
planned for, receipt-less was not. The one-party publish (2026-09-20,
|
|
21
|
+
blocker 22) retired the trigger child: the workflow parks at intent, the
|
|
22
|
+
session-carrying tick worker issues directly, and the ack scan attributes
|
|
23
|
+
by exact version. The `publish: ambiguous`, `publish: retry-superseded`,
|
|
24
|
+
`publish: retry-refused`, `publish: verified`, `publish: superseded`,
|
|
25
|
+
`publish: verification-failed`, and `publish: intent-unverifiable` verbs
|
|
26
|
+
remain in the vocabulary's legacy registry — the exact list in
|
|
27
|
+
`lib/publish-note-vocabulary.js` (no wildcard families). Recognized so old
|
|
28
|
+
note history skips as terminal, never written anew.
|