Skip to content

test(coven): drive the released v0.4.7 daemon through the SDK client - #338

Merged
BunsDev merged 2 commits into
mainfrom
test/80-automations-daemon-canary
Oct 3, 2026
Merged

BunsDev merged 2 commits into
mainfrom
test/80-automations-daemon-canary

Conversation

@BunsDev

@BunsDev BunsDev commented Oct 3, 2026 •

Copy link
Copy Markdown
Member

Refs #80, OpenCoven/coven#1054. This adds a canary that drives the released Coven v0.4.7 daemon through this SDK's built client. Until now, the SDK's command and replay behaviour was proven only against stubbed transports and the contract bundle's golden vectors.

What CI now does

The exact-runtime lane (Node 24.18.1), after pnpm verify has built the packages:

  1. Copies conformance/automations-v1-daemon/{package.json,package-lock.json} to runner temp storage.
  2. Runs npm ci --ignore-scripts, which installs @opencoven/cli@0.4.7 and its platform package at the locked sha512 integrity.
  3. Runs npm audit signatures, which checks the registry signatures and provenance attestations.
  4. Runs pnpm canary:automations-v1-daemon -- --coven …/bin/coven.js --expect-version 0.4.7.

The canary refuses a binary that reports any other version. It starts coven daemon serve through the npm wrapper users run, in an owned temporary COVEN_HOME with HOME, PATH (Node, /usr/bin and /bin) and NO_COLOR only. It discovers the daemon with discoverCovenEndpoint, then:

Step Required answer
capabilities() available, with the 11 actions the scenario uses
createDraft committed rev 1; the stored integrity equals computeDefinitionDigest()
same key, same body replayed rev 1 with firstCommittedAt
same key, changed body rejected ADOPTION_REPLAY_MISMATCH
revise at 1 committed rev 2
stale revise at 1 rejected REVISION_CONFLICT, currentRevision: 2
activate, pause committed rev 3, rev 4; get() reads back paused rev 4
SIGTERM, restart daemon removes coven.sock and daemon.json; rediscovery finds a new pid
the earlier activate key replayed rev 3 (adoption survives the restart)
disable committed rev 5
subscribe() from the pre-restart checkpoint exactly revised, activated, paused, disabled (sequences 1–4), then the final empty page at after = nextAfter = 4 with its checkpoint
events({ after: 2 }) sequences 3–4
list(), occurrenceHistory(), runHistory() rev 5; empty final pages

The schedule is set twelve hours from the activation hour, so nothing fires and the history assertions are deterministic. Output:

Automations v1 daemon verified: covenVersion=0.4.7 daemonStarts=2 commands=9 replays=2 rejections=2 events=5 subscribePages=2 definitionIntegrity=matches-sdk adoptionReplayAcrossRestart=passed checkpointResumeAcrossRestart=passed eventDefinitionDigest=differs-from-definition-integrity peerIdentity=harness-asserted

Finding: lifecycle events carry the routine-projection digest

v0.4.7 events have a different payload.definitionDigest from the definition's integrity. The definition.created event carries a different value. The definition.revised (rev 2) and definition.paused (rev 4) events carry the same value. The event digest is migration::definition_digest(definition_json), a hash of the legacy routine row, which has no revision. Occurrences copy that column (occurrences.rs), and runs and receipts carry it forward (receipts.rs).

So a caller who passes a definition's integrity to verifyReceipt() as the expected definitionDigest will get DEFINITION_DIGEST_MISMATCH against a v0.4.7 receipt. The contract's golden fixtures, by contrast, bind the receipt digest to the definition document's integrity. The canary reports this as eventDefinitionDigest=differs-from-definition-integrity rather than failing on it, so a Coven fix shows up as a changed summary. The README documents it, and the details are on Coven #1054.

Limits (also in the README)

  • Peer identity is harness-asserted. Node has no peer-credential API. The canary launched the daemon itself, and checks that the 0700 home and the socket belong to its uid before asserting that uid. It does not certify the SDK's Unix peer check.
  • No run executes. Runs, receipts and Runtime Authority (#857/#858) are out of scope.
  • get() returns the legacy routine projection, not the stored rich body.
  • Unix only.

Changes

  • scripts/verify-automations-v1-daemon.mjs + .d.mts: the canary. Failure output includes the daemon's own output. Cleanup sends SIGTERM first, then kills the native pid recorded in daemon.json, because the npm wrapper cannot forward SIGKILL. SIGINT and SIGTERM abort the run: every step races the abort, and a pending restart refuses to start a daemon. The one cleanup path then runs, and the process exits 130 or 143. The owned temp root is removed on every path.
  • conformance/automations-v1-daemon/: package.json + npm package-lock.json (v3) pinning @opencoven/cli@0.4.7 and its four platform packages. It sits outside the pnpm workspace and is not covered by Dependabot's root npm entry, so a version bump stays a deliberate change.
  • .github/workflows/ci.yml: the new step. This PR is pushed over SSH because it changes a workflow.
  • package.json: canary:automations-v1-daemon.
  • tests/automations-v1-daemon-canary.spec.ts, 26 tests:
    • the pin (lock, manifest, workflow version and steps agree, registry URLs, sha512 integrity);
    • argument refusals;
    • the scenario against an in-memory daemon, passing, and failing closed on nine deviations: a missing action, digest drift, a replay that commits, an accepted mismatch, an accepted stale revision, adoptions lost on restart, a rewinding checkpoint, a subscription that ends without its empty page, and non-empty history;
    • the security precheck against real sockets;
    • process handling with a fake coven: a version refusal, and a failing scenario that must stop the daemon and leave no temp root;
    • SIGTERM and SIGINT while a request is pending, against a fake daemon that never answers: the right exit code, no daemon left, and an empty TMPDIR. Both tests fail with the handlers removed;
    • a daemon that exits before it is ready.
  • README.md, docs/ROADMAP.md.

Verification

  • Against the real release: the canary passed against @opencoven/cli@0.4.7 from npm, both through the wrapper (bin/coven.js) and with the native cli-macos/bin/coven directly. A run takes about 1.8 s. Afterwards no cvn-* temp root and no canary daemon remained.
  • The CI step, run literally with a scratch directory standing in for runner.temp: npm ci and npm audit signatures passed ("2 packages have verified registry signatures", "2 packages have verified attestations"), and the canary passed.
  • Refusals: --expect-version 0.4.6 is refused with the reported version, as are a missing binary and bad arguments.
  • Interruption against the real daemon: SIGTERM sent 0.3, 0.6, 0.9 and 1.2 s into a run always exited 143, with no canary daemon or temp root left behind.
  • Suite: pnpm typecheck and pnpm lint are clean; the new spec passes 26/26; pnpm test passed on the first commit: 92 files, 3,147 passed, 2 skipped.
  • Linux CI on the first commit: the verify (24.18.1) log shows the signature and attestation checks, then the same summary line as above.

The second commit addresses both Copilot findings: the canary now requires the final empty page, and it cleans up on SIGINT and SIGTERM.

🤖 Generated with Claude Code

Adds a daemon canary that installs @opencoven/cli@0.4.7 from npm at a
locked integrity, starts `coven daemon serve` in an owned temporary
COVEN_HOME, and drives it through the built client: draft, replay and
mismatch refusal, revise and stale-revision refusal, activate, pause,
and disable, with adoption replay and checkpoint resume across a daemon
restart.

The canary reports, without failing, that v0.4.7 lifecycle events carry
the legacy routine-projection digest rather than the definition's
integrity. Peer identity is harness-asserted: the canary launched the
daemon under its own uid in a private home and checks that ownership.

Refs #80, OpenCoven/coven#1054.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Copilot AI balanced review requested due to automatic review settings October 3, 2026 06:41

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Interruption cleanup and final subscription-page validation need correction.

Review effort: Balanced
Findings: 2 Medium severity

Open (2)
What changed in this PR

Adds a released-daemon canary for the SDK’s Automations client, extending verification beyond stubbed transports and contract artifacts.

Changes:

  • Exercises lifecycle commands, adoption replay, and checkpoint resume across daemon restart.
  • Pins Coven v0.4.7 and verifies npm signatures before running the canary in CI.
  • Adds regression tests and documents verification limits.
File Description
tests/​automations-v1-daemon-canary.spec.ts Tests pins, scenarios, security checks, and process handling.
scripts/​verify-automations-v1-daemon.mjs Implements the daemon canary and lifecycle management.
scripts/​verify-automations-v1-daemon.d.mts Declares canary interfaces and results.
README.md Documents usage, coverage, and limitations.
package.json Adds the canary command.
docs/​ROADMAP.md Records daemon-canary coverage and digest observations.
conformance/​automations-v1-daemon/​package.json Pins the released CLI dependency.
conformance/​automations-v1-daemon/​package-lock.json Locks CLI and platform-package integrity.
.github/​workflows/​ci.yml Installs, authenticates, and runs the released daemon.
Files not reviewed (1)
  • conformance/automations-v1-daemon/package-lock.json: Generated file

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread scripts/verify-automations-v1-daemon.mjs
Comment thread scripts/verify-automations-v1-daemon.mjs
The daemon canary accepted a subscription that ended right after its
events. It now requires the final empty page, at `after` and `nextAfter`
4 with a checkpoint, as the subscribe() contract documents.

SIGINT or SIGTERM exited Node before the cleanup in `finally` ran,
leaving the daemon and its owned home behind. The canary now aborts on
either signal: every step races the abort, a pending restart refuses to
start a daemon, and the single cleanup path stops the daemon and removes
its home before exiting 130 or 143.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants