muse-crew 0.12.0 → 0.13.1
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/API.md +38 -4
- package/docs/guide.md +1 -1
- package/docs/publish-verification.md +17 -5
- package/lib/AGENTS.md +4 -4
- package/lib/advance-publish-base.js +36 -15
- package/lib/compose-evidence-caption.js +4 -3
- package/lib/crew-api.js +303 -52
- package/lib/schema.sql +25 -1
- package/lib/test-worktree-backend.sh +29 -0
- package/lib/verify-publish.js +4 -3
- package/lib/worktree-lifecycle.sh +9 -3
- package/package.json +1 -1
- package/seed/cron-body-template.md +5 -3
- package/workflows/bugfix.js +184 -36
- package/workflows/chore.js +183 -35
- package/workflows/crew-init.js +59 -4
- package/workflows/standard.js +184 -36
- package/workflows/upgrade.js +8 -3
package/API.md
CHANGED
|
@@ -124,6 +124,9 @@ Create or update an agent session record. Sessions track which identity is worki
|
|
|
124
124
|
| `notes` | string (≤ 3000) | no | |
|
|
125
125
|
| `failure_reason` | string (≤ 3000) or null | no | |
|
|
126
126
|
| `caveats` | array of `{text, severity}` | no | |
|
|
127
|
+
| `already_merged_sha` | 7–40 lowercase hex or null | no | **Do not set this yourself.** The workflow sets it automatically when it verifies that a task's work is already on the main branch (e.g., landed by a prior run). Written by workflow code (not by the agent's text output) after the `rev-parse` + `merge-base --is-ancestor` check passes; a bad value is a usage error. Review loads it from the field instead of parsing the notes. Null semantics: omitting the field preserves the existing value; passing explicit `null` clears it. `record-phase` always preserves (it never clears). |
|
|
128
|
+
|
|
129
|
+
The `already_merged_sha` field exists because session `notes` are hard-capped at 3000 chars: a truthful already-merged declaration at the tail of a long Build report was silently truncated, and Review could not recover it. The sha is managed by the workflow — the workflow writes it after verification, and downstream phases read the field, never the agent's text.
|
|
127
130
|
|
|
128
131
|
### `heartbeat-session`
|
|
129
132
|
|
|
@@ -474,18 +477,49 @@ Set a global configuration value.
|
|
|
474
477
|
|
|
475
478
|
### `setprovenance`
|
|
476
479
|
|
|
477
|
-
Stamp publication provenance.
|
|
480
|
+
Stamp publication provenance. Provenance is per-project (2026-09-18): each
|
|
481
|
+
project row carries its own publish base (`provenance_source_commit`,
|
|
482
|
+
`provenance_crew_release`, `provenance_published_at`, `provenance_task_id`);
|
|
483
|
+
a crew home with N artifact projects carries N independent bases. There are
|
|
484
|
+
two stamp call paths, and they are different operations on the same row:
|
|
485
|
+
|
|
486
|
+
- **Full stamp** — called by the parent's publish verification (`lib/verify-publish.js`)
|
|
487
|
+
after independently verifying the rebuilt artifact: `source_commit` is the
|
|
488
|
+
verified build commit, `crew_release` the active release at publish time.
|
|
489
|
+
- **Refresh** — called by the release installer (`lib/advance-publish-base.js`)
|
|
490
|
+
after installing/activating a crew release: `crew_release` is refreshed to
|
|
491
|
+
the live release identity while preserving the existing `source_commit`.
|
|
492
|
+
A base-pointer move is not a publish.
|
|
493
|
+
|
|
494
|
+
Both paths flow through the single writer in `lib/crew-api.js`; an unknown
|
|
495
|
+
`project_id` fails closed (exit 3), and omitting `project_id` is a usage
|
|
496
|
+
error (exit 2) — there is no silent global fallback.
|
|
478
497
|
|
|
479
498
|
| Field | Type | Required | Notes |
|
|
480
499
|
|-------|------|----------|-------|
|
|
500
|
+
| `project_id` | string | yes | The project whose publish base is stamped |
|
|
481
501
|
| `source_commit` | 40-char hex | yes | Repo commit the artifact was built from |
|
|
482
|
-
| `crew_release` | 40-char hex | yes | Active crew release (`$CREW_HOME/current`) at
|
|
483
|
-
| `published_at` | ISO 8601 datetime (UTC) | yes | |
|
|
502
|
+
| `crew_release` | 40-char hex, or `pkg-<semver>` | yes | Active crew release (`$CREW_HOME/current`) at stamp time |
|
|
503
|
+
| `published_at` | ISO 8601 datetime (UTC) | yes | Defaults to now when omitted |
|
|
484
504
|
| `task_id` | string | no | Publishing task, for traceability |
|
|
485
505
|
|
|
506
|
+
A one-time migration attributes a pre-per-project legacy `config.provenance.*`
|
|
507
|
+
triple to a project row only when its `source_commit` is an ancestor of
|
|
508
|
+
exactly one project's repo HEAD (verified with git); zero or ambiguous
|
|
509
|
+
matches are recorded as `deferred:<reason>` and never attributed. A triple
|
|
510
|
+
that matches exactly one project but violates the writer's shape rules
|
|
511
|
+
(e.g. a hand-written abbreviated sha, which git resolves for the ancestor
|
|
512
|
+
check but which fails the 40-char storage rule) is recorded as
|
|
513
|
+
`deferred:stamp-failed:<reason>` — the migration never throws, so a bad
|
|
514
|
+
triple can never brick the home. A `deferred:*` watermark is re-evaluated
|
|
515
|
+
when a project is created (the only event that changes the match set), so
|
|
516
|
+
a triple that predates the home's first project is attributed once a
|
|
517
|
+
descendant project exists. The legacy keys are inert dead storage after a
|
|
518
|
+
successful migration — new code never reads them.
|
|
519
|
+
|
|
486
520
|
### `getprovenance`
|
|
487
521
|
|
|
488
|
-
Read
|
|
522
|
+
Read a project's publication provenance. Returns `{ "provenance": { "source_commit", "crew_release", "published_at", "task_id" } }`, or `{ "provenance": null }` when nothing has been published yet. A project that was never stamped reads `null` — another project's stamp is never visible here. `task_id` is the publishing task, for traceability — present when `setprovenance` was called with one, absent otherwise. `project_id` is required (exit 2 when omitted); an unknown `project_id` fails closed (exit 3).
|
|
489
523
|
|
|
490
524
|
---
|
|
491
525
|
|
package/docs/guide.md
CHANGED
|
@@ -34,7 +34,7 @@ When this guide says "call an action," it means calling an action defined in the
|
|
|
34
34
|
- A Muse account with access to workflows, crons, and artifacts.
|
|
35
35
|
- The `muse-crew` package installed via npm, or the repo cloned locally.
|
|
36
36
|
- A directory chosen for the crew home (e.g., `~/workspace/.jarvis`).
|
|
37
|
-
- A task service artifact already created. The [Orchestra Dashboard](https://github.com/emojimanegg1/orchestra-dashboard) is the reference implementation — clone its repo, create the
|
|
37
|
+
- A task service artifact already created. (An "artifact" is a Muse platform primitive — a hosted web app created through the Muse UI. The crew never creates artifacts; it only edits existing ones.) The [Orchestra Dashboard](https://github.com/emojimanegg1/orchestra-dashboard) is the reference implementation — clone its repo, create an artifact from it via the Muse UI, and note its slug. **Rule: every `deploy_slug` must point to an artifact that already exists.** Init checks that `~/workspace/ts-spaces/<slug>/` exists and will stop with an error if it doesn't — without this check, the first task would reach Publish and fail with a confusing error message. Publish edits an existing artifact; it never creates one.
|
|
38
38
|
|
|
39
39
|
### Running init
|
|
40
40
|
|
|
@@ -46,8 +46,12 @@ The workflow dropped the report entirely on 2026-09-16 (clean-room task
|
|
|
46
46
|
`e2a8d9f8`): the trigger's JSON closeout contract traveled over the
|
|
47
47
|
stochastic text channel and the runtime's JSON-candidate heuristic misfired
|
|
48
48
|
on its prose ("workflow agent output was not JSON"), parking a task whose
|
|
49
|
-
edit may have gone through. The trigger is now awaited
|
|
50
|
-
|
|
49
|
+
edit may have gone through. The trigger is now awaited and scanned for a
|
|
50
|
+
single explicit refusal signal — `ARTIFACT_EDIT_REFUSED: <text>` as the
|
|
51
|
+
entire trimmed turn output — and nothing else is consumed from the return.
|
|
52
|
+
An exact refusal is conclusive negative evidence (parks `rejected`, skips
|
|
53
|
+
observation polling); any other output (including prose quoting the signal)
|
|
54
|
+
is inconclusive and follows the existing fail-closed observation path.
|
|
51
55
|
`applied_report` is `missing-report` on ledger lines for issued triggers
|
|
52
56
|
(pre-trigger parks and unattributed-unknown parks write null — no trigger
|
|
53
57
|
was observed, so there is nothing to report). The
|
|
@@ -223,7 +227,9 @@ For a task parked with `publish: verification-requested <commit>`:
|
|
|
223
227
|
slug (the project's publish target).
|
|
224
228
|
2. **Expected change.** The publish delta is `git diff <base> <commit>`
|
|
225
229
|
in `repo_path`, where `<base>` is the previously-stamped provenance
|
|
226
|
-
`source_commit` (read it via
|
|
230
|
+
`source_commit` for this project (read it via
|
|
231
|
+
`get-provenance --json '{"project_id":"<id>"}'` — provenance is
|
|
232
|
+
per-project; without `project_id` the call exits 2; use the empty-tree sha
|
|
227
233
|
`4b825dc642cb6eb9a060e54bf8d69288fbee4904` when no provenance is
|
|
228
234
|
stamped yet — a first publish). Never use `commit^1` as the base and
|
|
229
235
|
never take the expected change from the builder's report: push-time
|
|
@@ -293,9 +299,15 @@ For a task parked with `publish: verification-requested <commit>`:
|
|
|
293
299
|
(the crew-owned store). Do NOT use the artifact's `setprovenance`
|
|
294
300
|
action — it writes a different, non-authoritative store that QA never
|
|
295
301
|
reads, so the stamp would be invisible to every gate:
|
|
296
|
-
`set-provenance --json '{"source_commit":"<commit>","crew_release":"<release>","task_id":"<task>"}'`
|
|
302
|
+
`set-provenance --json '{"project_id":"<id>","source_commit":"<commit>","crew_release":"<release>","task_id":"<task>"}'`
|
|
297
303
|
(crew_release is the basename of the active release, e.g.
|
|
298
|
-
`pkg-0.7.10`).
|
|
304
|
+
`pkg-0.7.10`). This is the full publish-verification stamp — it
|
|
305
|
+
records a verified publish. Do not confuse it with the
|
|
306
|
+
provenance *refresh* (re-pointing `crew_release` at the active
|
|
307
|
+
release after a crew upgrade without a new publish), which also goes
|
|
308
|
+
through `set-provenance` but keeps the existing `source_commit`.
|
|
309
|
+
Then read the stamp back with
|
|
310
|
+
`get-provenance --json '{"project_id":"<id>"}'`
|
|
299
311
|
and confirm source_commit, crew_release, and task_id match exactly
|
|
300
312
|
what was sent — a stamp that cannot be read back is not a stamp. Only
|
|
301
313
|
then log the task note event
|
package/lib/AGENTS.md
CHANGED
|
@@ -3,18 +3,18 @@
|
|
|
3
3
|
Shell scripts for the crew's infrastructure. Called by workflow scripts, cron, and the release system.
|
|
4
4
|
|
|
5
5
|
- `build-registry.js` — deterministic extractor that generates `workflows/registry.json` (workflow step registry) from the workflow files' `meta` blocks at release time; invoked by `crew-release.sh` deploy
|
|
6
|
-
- `crew-api.js` — the crew-owned task-service API (dependency inversion, 2026-09-11): a zero-dependency Node CLI implementing the API.md contract against `$CREW_HOME/crew-state.db` (schema in `schema.sql`). Workflows call it through their agents' shell; the dashboard delegates to it. All state-machine invariants live as CHECK constraints in the schema, never in client prose. Includes the `record-phase` composite (session + event in one transaction) and a one-time `migrate` import from a dashboard app.db.
|
|
6
|
+
- `crew-api.js` — the crew-owned task-service API (dependency inversion, 2026-09-11): a zero-dependency Node CLI implementing the API.md contract against `$CREW_HOME/crew-state.db` (schema in `schema.sql`). Workflows call it through their agents' shell; the dashboard delegates to it. All state-machine invariants live as CHECK constraints in the schema, never in client prose. Includes the `record-phase` composite (session + event in one transaction) and a one-time `migrate` import from a dashboard app.db. Active-release resolution is split in two (room #15): `resolveActiveReleaseName` (symlink-only — writers like the initial-provenance stamp record the active release without proving they are it) and `resolveActiveRelease` (symlink + self-path cross-check — verifiers like `scan-verification-pending` refuse to stamp claims when the running code isn't the active release's own). Provenance is per-project (2026-09-18, room #15 blocker 8): nullable `provenance_*` columns on the projects row, a single `stampProvenance()` writer, `set-provenance`/`get-provenance` require `project_id` (no silent global fallback), and a watermarked openDb backfill that attributes the legacy `config.provenance.*` triple to exactly-one ancestor match — never fabricated, otherwise deferred.
|
|
7
7
|
- `schema.sql` — the crew-owned state schema: projects, tasks, poll_state, config, agent_sessions, events. Vocabularies enforced by CHECK constraints; `rejected` is a valid event type (the 2026-09-11 crash was a stored session whose event was rejected). Column names match the historical dashboard tables for a verbatim migration.
|
|
8
8
|
- `crew-release.sh` — immutable release manager: deploy, rollback, prune
|
|
9
9
|
- `merge-lock.sh` — serialized merge lock for concurrent agents: time-based holder lease (bug 2fc8f52f — an unexpired lease is held regardless of process liveness; only an expired lease may be broken). Requires both `CREW_REPO` and `CREW_HOME` (fail closed: BLOCKED, exit 2 when either is unset). Lock file is key=value: task_id, opaque holder identity (never a PID), acquired_at epoch, lease_seconds (default 600, override via MERGE_LOCK_LEASE_SECONDS). acquire/refresh/release/status/force-release; holder-only refresh and release; every op appends to $CREW_HOME/.merge-lock.log
|
|
10
|
-
- `worktree-lifecycle.sh` — the worktree lifecycle seam: prepare/cleanup/inspect/integrate/verify-merge/status/post-deploy/terminal-cleanup/refresh-lock/lock-status over git worktrees (`integrate` reconciles `origin/main` under the merge lock after the task merge — canary `a6d8b0c8`, 2026-09-11 — so the push is a fast-forward; fail-soft with no remote, fail closed on conflict) (`.worktrees/<id>`, branch `task/<id>`). Branch resolution order: the crew registry (`<repo>/.worktrees/.registry/<id>`), then the canonical `task/<full-id>` ref, then a `task/<id-prefix>` ref matched by strict prefix enumeration (task 4e1a1bba — a Build agent may create the branch with raw git from an abbreviated id, bypassing prepare; prefix names are tolerated, never created; ambiguous prefixes fail closed). `resolve-branch` prints the resolved branch for agent-side one-liners. Nothing reconstructs the branch name — every command resolves it. Prepare fails closed on dirty `main`; cleanup is forgiving. `terminal-cleanup` is the run's last act at every park/fail boundary (called from `parkTask` in standard/bugfix/chore): releases the merge lock unconditionally and reclaims the worktree+branch only when the task branch is fully merged into main — unmerged work is preserved for the human by design, and a dirty worktree is reported, never force-removed. `post-deploy` reports worktree removal honestly (a lying "removed" echo hid real leftovers — canary run 9, 2026-09-12). Requires both `CREW_REPO` and `CREW_HOME` (fail closed: BLOCKED, exit 2 when either is unset); `CREW_REPO` is exported so internal merge-lock.sh calls inherit the repo being worked on; `LIB_DIR` defaults to `$CREW_HOME/lib` (`CREW_LIB` override). `lock-status` reports the merge-lock state explicitly (`UNLOCKED`, or key=value: locked=true, task_id, holder, acquired_at, lease_seconds, age_seconds, remaining_seconds — always exit 0) so Publish can distinguish an empty-diff Integrate (no lock taken) from a refresh failure.
|
|
10
|
+
- `worktree-lifecycle.sh` — the worktree lifecycle seam: prepare/cleanup/inspect/integrate/verify-merge/status/post-deploy/terminal-cleanup/refresh-lock/lock-status over git worktrees (`integrate` reconciles `origin/main` under the merge lock after the task merge — canary `a6d8b0c8`, 2026-09-11 — so the push is a fast-forward; fail-soft with no remote, fail closed on conflict) (`.worktrees/<id>`, branch `task/<id>`). Branch resolution order: the crew registry (`<repo>/.worktrees/.registry/<id>`), then the canonical `task/<full-id>` ref, then a `task/<id-prefix>` ref matched by strict prefix enumeration (task 4e1a1bba — a Build agent may create the branch with raw git from an abbreviated id, bypassing prepare; prefix names are tolerated, never created; ambiguous prefixes fail closed). `resolve-branch` prints the resolved branch for agent-side one-liners. Nothing reconstructs the branch name — every command resolves it. Prepare fails closed on dirty `main`; cleanup is forgiving. `terminal-cleanup` is the run's last act at every park/fail boundary (called from `parkTask` in standard/bugfix/chore): releases the merge lock unconditionally and reclaims the worktree+branch only when the task branch is fully merged into main — unmerged work is preserved for the human by design, and a dirty worktree is reported, never force-removed. `post-deploy` reports worktree removal honestly (a lying "removed" echo hid real leftovers — canary run 9, 2026-09-12). Requires both `CREW_REPO` and `CREW_HOME` (fail closed: BLOCKED, exit 2 when either is unset); `CREW_REPO` is exported so internal merge-lock.sh calls inherit the repo being worked on; `LIB_DIR` defaults to `$CREW_HOME/lib` (`CREW_LIB` override). `lock-status` reports the merge-lock state explicitly (`UNLOCKED`, or key=value: locked=true, task_id, holder, acquired_at, lease_seconds, age_seconds, remaining_seconds — always exit 0) so Publish can distinguish an empty-diff Integrate (no lock taken) from a refresh failure. post-deploy's `.worktrees/` guard is add-then-reset, never an all-negative pathspec: `git add -A -- ':!.worktrees/'` still exits 1 on git 2.43.0 against the crew's own gitignored non-empty `.worktrees/` (room #15 J3), killing post-deploy under `set -e` — a plain `add -A` never errors on ignored paths and the follow-up `git reset -q -- .worktrees/` keeps stray lock files out of main even on pre-gitignore-entry repos.
|
|
11
11
|
- `test-worktree-backend.sh` — regression tests for the lifecycle script (validate, prepare/reuse, inspect, status, cleanup, idempotent cleanup, dirty-main preflight, integrate remote-reconcile with fixture sensitivity + no-remote fail-soft) on scratch repos
|
|
12
12
|
- `test-version-write.sh` — regression tests for the escape-preserving step-8 version write in publish-npm.sh (fixture: current package.json with the \\u2014 escape; extracts the shipped block by anchor)
|
|
13
13
|
- `test-publish-verify.sh` — regression tests for the retry-tolerant step-12 verification in publish-npm.sh (canary 5a027278): extracts the shipped block by anchor and runs it against a fake npm whose read replica lags (non-zero exits, then the old version, then the target) — requires convergence on success, fail-closed `PUBLISH_FAILED=verify` on exhaustion, and `--prefer-online` on every read
|
|
14
14
|
- `test-publish-skip.sh` — regression tests for the no-lock graceful publish skip (park 2026-09-11): `lock-status` returns exact `UNLOCKED`/`LOCKED by <holder> since <ts> (pid <pid>)`; a fake empty-diff integrate takes the MERGED_EMPTY branch and never takes the lock; the shipped step-5 block from publish-npm.sh (extracted by anchor) skips gracefully on UNLOCKED (no park, no `PUBLISH_COMPLETE`), follows the refresh path when a lock is held, and fails closed on a `lock-status` query failure; the gate sits inside the ALREADY_PUBLISHED=0 branch and mutation steps are gated on NO_LOCK_HELD=0
|
|
15
15
|
- `publish-npm.sh` — deterministic npm publish: lock refresh, release install, version write/commit, pack, registry publish, verify, push, post-deploy. Takes TARGET_VERSION as input; idempotent on retry/resume.
|
|
16
16
|
- `compose-evidence.py` — deterministic visual-evidence compositor (Pillow): pairs identical PNG stems from baseline/ and postchange/ dirs, emits `<stem>-sidebyside.png` and amplified-difference `<stem>-overlay.png` into composites/, prints `PAIR`/`SKIP` manifests. Byte-deterministic; nonzero exit on errors.
|
|
17
|
-
- `compose-evidence-caption.js` — deterministic QA-evidence caption composer (2026-09-14): given `--crew-home`, `--task-id`, and the resolved `--audit-dir` name, extracts the task title/description, the QA verdict, the `publish: verified` commit (or the
|
|
17
|
+
- `compose-evidence-caption.js` — deterministic QA-evidence caption composer (2026-09-14): given `--crew-home`, `--task-id`, and the resolved `--audit-dir` name, extracts the task title/description, the QA verdict, the `publish: verified` commit (or the task's project-scoped provenance record when it belongs to this task), and the merge commit's subject line from the project repo — and prints the out-of-context transition Eric asked for (problem → solution → evidence handoff) to stdout. The caller appends the desktop/mobile attachment lines after this caption. Verdict source (2026-09-16): Hazel's `task-evidence/<taskId>/postchange/verdict.json` is authoritative, then the last line of the append-only `verdicts.jsonl` ledger, then the newest legacy `visual_verdict:` note (the parent visual-verdict protocol was retired 2026-09-15). Honest labels: "provenance not yet stamped" when no commit is known, "no QA verdict recorded" when none exists, and the visual-protocol-unavailable note only when no verdict evidence exists at all, the newest `baseline:` note is `baseline: none`, AND captures exist (a `baseline: none` note means no baseline captures to compare against — it never overrules a real QA verdict; the note's premise is the post-deploy capture, so without captures it would fabricate evidence again). Capture guard (2026-09-17, room #14): the composer resolves the audit dir itself (`<home>/workspace/ts-spaces/<deploy_slug>/audits/<auditDir>/`, the same root crew-api.js and readback-disk.js resolve) and verifies `screenshot.png` + `screenshot-mobile.png` are files before claiming "desktop + mobile captures below" — a bare `--audit-dir` name is never trusted. Missing deploy_slug, missing dir, or missing PNGs gets the honest no-captures opener and Evidence line (terminal surfaces named explicitly: "terminal surface — QA judges the CLI, not pixels"); the caller only appends attachment lines when its own freshness check passes. "Latest" is by event timestamp, sorted explicitly. Exits non-zero on missing input.
|
|
18
18
|
- `edit-image.py` — deterministic pixel-level evidence editing (2026-09-15, Pillow): `crop --region x,y,w,h` (pixel-exact, rejects out-of-bounds), `zoom --factor <f> [--center x,y]` (nearest-neighbor, returns to source frame size), `label --text <caption>` (caption bar), `nup --cols <n> --in <a.png> --in <b.png> [--labels "a|b"]` (side-by-side grid). Byte-deterministic PNG output; no network, no time, no randomness. Derivatives supplement raw frames — the source frame stays archived.
|
|
19
19
|
- `render-html.js` — deterministic HTML evidence composition (2026-09-15, headless Chromium): renders a local HTML layout to PNG (fixed viewport width, device scale 1, full-page screenshot). Hermetic: remote HTTP(S) assets are blocked and fail the render loudly; relative image paths resolve against the HTML file; local/system fonts only. Exit 3 reports NOT POSSIBLE when Chromium is unavailable. The composition layer above edit-image.py's pixel layer.
|
|
20
20
|
- `see-act.js` — single-step browser driver for experiential QA (2026-09-14): one browser action per invocation (`aria`, `shot`, `click`, `scroll`, `type`), one JSON line on stdout, exit 0/2/3 (3 = NOT POSSIBLE: missing playwright-core or Chromium). Spawns its own loopback forward proxy on an ephemeral port (Chromium blocks direct loopback). The QA work agent closes the OODA loop: run a step, read the screenshot/aria, decide the next. Determinism: no wall-clock reads, no randomness. `SEE_ACT_ARCHIVE_DIR=<phase-dir>` (2026-09-14): every screenshot is archived automatically as `001-shot-desktop.png`, `002-click-mobile.png`, ... (counter in `<dir>/.seq`); `--out` becomes optional; JSON carries `screenshot` + `archived`; explicit `--out` + archive copies into the archive; unusable dir is NOT POSSIBLE exit 3.
|
|
@@ -34,4 +34,4 @@ Shell scripts for the crew's infrastructure. Called by workflow scripts, cron, a
|
|
|
34
34
|
- `sample-project.js` — optional sample-project setup (2026-09-17): extracts `seed/sample-project.tar.gz` (stripping the `sample-project/` prefix), `git init`, initial commit. No remote required. Registration happens via the existing `create-project` action — no sample-specific endpoint. CLI: `--tarball <path> --dest <dir> --name <project-name>`.
|
|
35
35
|
- `compute-publish-diff.js` — deterministic publish-diff computer (2026-09-18, room #14): replaces the agent-ferried `git diff` JSON string field (the JSON ferry dropped a valid 700-line diff — "Publish diff parsed to zero files"). Runs `git diff <base> <HEAD>` via argv (no shell), writes the raw diff bytes to `--out`, and prints only a small JSON summary (`commit`, `base`, `diff_file`, `sha256`, `bytes`, `file_count`, `changed_lines` = added+removed, never raw diff output lines, `files`, `has_binary`, `has_rename`). Empty-tree base is the first-publish ancestor (hardcoded, never `merge-base` on the empty tree); non-ancestor is semantic JSON (`ok:false, error:"not-ancestor"`, exit 0); usage errors exit 2 with nothing on stdout. Pinned per-run alongside the other lifecycle files (`COMPUTE_DIFF` in `PIN_BASENAMES`) so a mid-run release swap cannot change it under the workflow.
|
|
36
36
|
- `classify-surface.js` — deterministic project-surface classifier (2026-09-18, room #14 defect): a pure, never-throwing scan of the registered repo — `space.json` present → `artifact` surface (deploy_slug suggestion = `space.json`'s valid slug, verbatim — presence alone still means "web app"), `package.json` with a non-empty `bin` → `terminal` surface, otherwise null (unclassified); artifact wins conflicts (the deployed artifact is what users see, same rule as workflows/standard.js). CLI `--repo-path` prints one JSON object (`{surface, deploy_slug, signals}`), always exit 0 (classification failure is a null result, exit 2 only for usage). `create-project` fills omitted `environment_type`/`deploy_type`/`deploy_slug` from the classification; explicit values (including explicit null) always win and drive the effective surface.
|
|
37
|
-
- `advance-publish-base.js` — publish-base advance past the setup scaffold (2026-09-18, room #14): `create-project` stamps provenance at the pre-scaffold repo HEAD, then crew-init commits `.orchestration/` (+ the consented `.gitignore`) on top — the first task's publish diff would carry the whole scaffold (517 insertions) and blow the 200-line diff-transport budget. The script moves `source_commit` to the scaffold commit only when `--project-created` (the stamp is this init's, never a real publish's), the scaffold commit's parent is exactly the stamped base, and the scaffold touches only crew-owned paths (`.orchestration/`, `.gitignore`); no provenance + root scaffold commit stamps fresh. Every other case skips without touching provenance; `published_at` is preserved on advance. Called from crew-init.js (best-effort, non-fatal). CLI: `--crew-home <path> --repo <path> --scaffold-sha <sha> [--project-created]`.
|
|
37
|
+
- `advance-publish-base.js` — publish-base advance past the setup scaffold (2026-09-18, room #14): `create-project` stamps provenance at the pre-scaffold repo HEAD, then crew-init commits `.orchestration/` (+ the consented `.gitignore`) on top — the first task's publish diff would carry the whole scaffold (517 insertions) and blow the 200-line diff-transport budget. The script moves `source_commit` to the scaffold commit only when `--project-created` (the stamp is this init's, never a real publish's), the scaffold commit's parent is exactly the stamped base, and the scaffold touches only crew-owned paths (`.orchestration/`, `.gitignore`); no provenance + root scaffold commit stamps fresh. Every other case skips without touching provenance; `published_at` is preserved on advance. Called from crew-init.js (best-effort, non-fatal). Provenance is per-project (room #15 blocker 8): `--project-id` is required and every read/stamp is project-scoped. CLI: `--crew-home <path> --repo <path> --scaffold-sha <sha> --project-id <id> [--project-created]`.
|
|
@@ -39,7 +39,7 @@ const RELEASE_RE = /^([0-9a-f]{40}|pkg-\d+\.\d+\.\d+)$/;
|
|
|
39
39
|
|
|
40
40
|
function usage() {
|
|
41
41
|
console.error(
|
|
42
|
-
"Usage: node lib/advance-publish-base.js --crew-home <path> --repo <path> --scaffold-sha <sha> --project-created"
|
|
42
|
+
"Usage: node lib/advance-publish-base.js --crew-home <path> --repo <path> --scaffold-sha <sha> --project-id <id> --project-created"
|
|
43
43
|
);
|
|
44
44
|
}
|
|
45
45
|
|
|
@@ -50,6 +50,7 @@ function parseArgv(argv) {
|
|
|
50
50
|
if (a === "--crew-home") out.crewHome = argv[++i];
|
|
51
51
|
else if (a === "--repo") out.repo = argv[++i];
|
|
52
52
|
else if (a === "--scaffold-sha") out.scaffoldSha = argv[++i];
|
|
53
|
+
else if (a === "--project-id") out.projectId = argv[++i];
|
|
53
54
|
else if (a === "--project-created") out.projectCreated = true;
|
|
54
55
|
else {
|
|
55
56
|
console.error("unknown argument: " + a);
|
|
@@ -94,10 +95,10 @@ function skip(reason) {
|
|
|
94
95
|
}
|
|
95
96
|
|
|
96
97
|
function main() {
|
|
97
|
-
const { crewHome, repo, scaffoldSha, projectCreated } = parseArgv(
|
|
98
|
+
const { crewHome, repo, scaffoldSha, projectId, projectCreated } = parseArgv(
|
|
98
99
|
process.argv.slice(2)
|
|
99
100
|
);
|
|
100
|
-
if (!crewHome || !repo || !scaffoldSha) {
|
|
101
|
+
if (!crewHome || !repo || !scaffoldSha || !projectId) {
|
|
101
102
|
usage();
|
|
102
103
|
process.exit(2);
|
|
103
104
|
}
|
|
@@ -112,7 +113,7 @@ function main() {
|
|
|
112
113
|
|
|
113
114
|
let provenance;
|
|
114
115
|
try {
|
|
115
|
-
provenance = crewApi(crewHome, "get-provenance").provenance;
|
|
116
|
+
provenance = crewApi(crewHome, "get-provenance", JSON.stringify({ project_id: projectId })).provenance;
|
|
116
117
|
} catch (e) {
|
|
117
118
|
console.error("get-provenance failed: " + (e.message || e));
|
|
118
119
|
process.exit(1);
|
|
@@ -167,10 +168,17 @@ function main() {
|
|
|
167
168
|
skip("no provenance stamped and the active release name is not a valid crew_release (" + crewRelease + ")");
|
|
168
169
|
return;
|
|
169
170
|
}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
171
|
+
let stamped;
|
|
172
|
+
try {
|
|
173
|
+
stamped = crewApi(crewHome, "set-provenance", JSON.stringify({
|
|
174
|
+
project_id: projectId,
|
|
175
|
+
source_commit: scaffoldSha,
|
|
176
|
+
crew_release: crewRelease,
|
|
177
|
+
}));
|
|
178
|
+
} catch (e) {
|
|
179
|
+
console.error("set-provenance failed: " + (e.message || e));
|
|
180
|
+
process.exit(1);
|
|
181
|
+
}
|
|
174
182
|
if (!stamped || stamped.ok !== true) {
|
|
175
183
|
console.error("set-provenance did not return ok");
|
|
176
184
|
process.exit(1);
|
|
@@ -215,19 +223,32 @@ function main() {
|
|
|
215
223
|
return;
|
|
216
224
|
}
|
|
217
225
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
226
|
+
let stamped;
|
|
227
|
+
try {
|
|
228
|
+
stamped = crewApi(crewHome, "set-provenance", JSON.stringify({
|
|
229
|
+
project_id: projectId,
|
|
230
|
+
source_commit: scaffoldSha,
|
|
231
|
+
crew_release: crewRelease,
|
|
232
|
+
// A base-pointer move is not a publish: keep the existing published_at.
|
|
233
|
+
...(provenance.published_at ? { published_at: provenance.published_at } : {}),
|
|
234
|
+
}));
|
|
235
|
+
} catch (e) {
|
|
236
|
+
console.error("set-provenance failed: " + (e.message || e));
|
|
237
|
+
process.exit(1);
|
|
238
|
+
}
|
|
224
239
|
if (!stamped || stamped.ok !== true) {
|
|
225
240
|
console.error("set-provenance did not return ok");
|
|
226
241
|
process.exit(1);
|
|
227
242
|
}
|
|
228
243
|
// Exact read-back: the stamp must match what we intended, or it is fiction
|
|
229
244
|
// (same discipline as lib/verify-publish.js).
|
|
230
|
-
|
|
245
|
+
let recheck;
|
|
246
|
+
try {
|
|
247
|
+
recheck = crewApi(crewHome, "get-provenance", JSON.stringify({ project_id: projectId })).provenance;
|
|
248
|
+
} catch (e) {
|
|
249
|
+
console.error("post-stamp get-provenance failed: " + (e.message || e));
|
|
250
|
+
process.exit(1);
|
|
251
|
+
}
|
|
231
252
|
if (!recheck || recheck.source_commit !== scaffoldSha) {
|
|
232
253
|
console.error("post-stamp read-back mismatch: provenance does not point at the scaffold commit");
|
|
233
254
|
process.exit(1);
|
|
@@ -154,7 +154,8 @@ if (!verdict) {
|
|
|
154
154
|
}
|
|
155
155
|
|
|
156
156
|
// Provenance: this task's own newest `publish: verified` event first; the
|
|
157
|
-
//
|
|
157
|
+
// project-scoped provenance record only when it belongs to this task
|
|
158
|
+
// (provenance is per-project — room #15 blocker 8).
|
|
158
159
|
let commit = null;
|
|
159
160
|
const verifiedNote = notesNewest
|
|
160
161
|
.find((e) => (e.message || "").includes("publish: verified"));
|
|
@@ -162,9 +163,9 @@ if (verifiedNote) {
|
|
|
162
163
|
const m = /\b([0-9a-f]{40})\b/.exec(verifiedNote.message);
|
|
163
164
|
if (m) commit = m[1];
|
|
164
165
|
}
|
|
165
|
-
if (!commit) {
|
|
166
|
+
if (!commit && project) {
|
|
166
167
|
try {
|
|
167
|
-
const prov = api("get-provenance", {}).provenance;
|
|
168
|
+
const prov = api("get-provenance", { project_id: project.id }).provenance;
|
|
168
169
|
if (prov && prov.task_id === taskId && /^[0-9a-f]{40}$/.test(prov.source_commit || "")) {
|
|
169
170
|
commit = prov.source_commit;
|
|
170
171
|
}
|