feat(worker): CCM-v0 lane B — economics-summary.json on every worker terminal - #49
Conversation
…Summary, economicsSha256 Add vinci/worker/economics.mjs exporting three functions for CCM-V0: - canonicalJson(obj): deterministic sorted-key JSON, never throws - buildEconomicsSummary(input): produces WorkOrderEconomicsSummary v1 with usage rollup/dedup, never throws - economicsSha256(canonicalString): SHA-256 hex digest Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Expose which fallback provided the cost: 'outcome', 'usage_entries', or 'message_fallback'. This is used by economics.mjs to fill cost_reconstruction field. Existing callers unaffected (they ignore the new field). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… to ledger - worker.mjs: Build economics summary before evidence upload, add to extraFiles - evidence.mjs: Include economics_summary and economics_sha256 in POST /v1/evidence for ledger refs - Terminal seam emits summary on all paths: DONE, BLOCKED, FAILED, UNVERIFIED, budget/deadline trip, harness stop Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Add vinci/worker/test/economics.test.mjs with: - 3 canonicalJson tests (determinism, sorting, robustness) - 2 economicsSha256 tests (format, determinism) - 2 summary structure tests (schema, error handling) - 7 terminal state tests (DONE, BLOCKED, FAILED, limit_tripped, harness_stop, killed_before_outcome) - 3 cost reconstruction tests (outcome, usage_entries, message_fallback) - 2 lease tests (with_lease, no_lease) - 3 usage rollup tests (dedup, accumulation, cost_basis) - 1 malformed entry test - 2 work field tests - 1 complete scenario test - 1 packaging test (skipped citing #48) - 1 dedup mutation control Uses Node's built-in test framework. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Add test that verifies two entries with same responseId count as 1 model_call. Mutation control: disable dedup logic, test fails with 2 != 1, restore via cp. Proves the dedup mechanism works as intended. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Add helper function to emit economics-summary.json into attempt dir. Wiring to actual early terminal paths (contract error, deadline-in-past, clean-room) needs to be completed with coordinator guidance on exact locations. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…cal_result, crew flag, digest in bus body - session-read: usageEntries mapped from vinci-task-usage entries (was left uncommitted by the previous pass); crewRan when vinci-crew-helper/result entries exist - economics: killed_before_outcome keyed to the vinci-task-outcome receipt, verification_state from the receipt, crew_unattributed when crew ran, responseId dedup counts a duplicated response once - worker: emitEconomics on contract-error, invalid-bounds, past-deadline, provider-not-allowed, base_ref and clean-room terminals (written under <stateDir>/economics/<taskId>/ when no attempt dir exists); economics_sha256 token appended to every terminal bus body Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VVK5S9ssoXtZYBhwcoWWT6
…roll-up to skip the whole entry The new session test caught a real defect: a duplicated responseKey was counted once for model_calls but its tokens and cost were summed twice. The roll-up now skips the entire duplicate entry. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VVK5S9ssoXtZYBhwcoWWT6
…t, checkout-blocked and catch-all FAILED terminals; session lookup by attempt session id Review finding (Claude, self-review of lane B): six terminal sites still returned without a summary, including the catch-all FAILED path where a session may already have spent. emitEconomics also looked the session up by task id; the session file is keyed by the attempt's session id. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VVK5S9ssoXtZYBhwcoWWT6
…h, closed harness_stop token, caller-supplied session_id, receipt-only cost carried, no_session/none on pre-session terminals, per-response dedup, estimated cost_basis Review (Claude, fresh agent) NO-GO findings 1-9 addressed: - postFinal: BLOCKED/FAILED/harness-stop/UNVERIFIED branches now carry economics_sha256= - harness_stop is instrument_stop:<count>, never the blocked tool call's text (R3) - session_id is attempt.sessionId, not the session file name - digest-path registry refusal emits a summary - receipt-only or message-fallback cost becomes one estimated usage row plus usage_persistence_failed, never omitted as zero spend - a terminal before any session ran reports no_session / cost_reconstruction none instead of a fallback that never ran or a kill that never happened - main path also writes economics-summary.json into the attempt dir - cost_basis/cost_confidence default to estimated; dedup is per response across rows; an over-long model name is malformed_entries Tests updated to the new semantics with negative controls; 33+6 pass, 1 skip (#48). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VVK5S9ssoXtZYBhwcoWWT6
…tegration test for a blocked terminal - economics.mjs emits lineage (backlog_row_id from a bk_ ref), execution_world_ref, capacity_events, decision_refs, measurement_cost — nullable, each null carrying its closed incomplete[] code per charter §8.3 - early terminals read limit_tripped/exit_code from the lifecycle (run result is absent) - vinci/test/worker-economics-terminal.mjs drives worker.mjs start --once through the fixture bus to a past-deadline BLOCKED terminal and asserts the on-disk summary (BLOCKED, limit deadline, no_session, no_lease, cost_reconstruction none, no usage) and that the bus body carries exactly its sha256 and no summary content Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VVK5S9ssoXtZYBhwcoWWT6
|
Independent review at exact head 066f2d5 (fresh Claude reviewer spawned by projects-11, neither asker nor builder): BLOCK. Full envelope on the bus thread under msg_72983480. Blocking: (1) usage[] rolls only the supplemental vinci-task-usage entries, so every main-loop model call and its cost is omitted (task-outcome.ts:576-579 places those in the assistant-message stream); (2) crew helper usage is summed into usage[] instead of being flagged; (3) local_result.harness_stop carries raw tool-result text across the R3 boundary instead of the closed instrument_stop: token; (4) the two failing offline CI jobs are caused by this PR — the appended economics_sha256= token breaks the end-anchored tail regex in vinci/test/worker-governor-fail-closed.mjs:107 (14 of 23 fail). Seven HIGHs (worker tests not wired into CI; main-path summary never written locally; cost_reconstruction mislabels its source; session_id is a filename; two terminal posts bypass the seam; packaging test is a skip, not red; no test reaches worker.mjs). The duplicate-responseKey fix is present and mutation-verified. §8.3 Revision-1 delta enumerated on the bus. Please hold un-draft; fixes are being prepared on a separate branch. |
|
Independent review (Claude, fresh agent): NO-GO at 066f2d5 — ten findings, addressed in f4149a2 and de30cc4.
Also emits the charter §8.3 (Revision 1) fields with their codes. Tests: 34 + 6 unit, 7 worker integration files pass, 1 skip (#48). Fixes are builder-authored; the integration test is the discriminating control for the terminal-path claims. |
…opy for standard runs Found by the local end-to-end run against the lane A server (real worker, real POST /v1/evidence): started_at was null on the main path (the run result carries no timestamps; the lifecycle does), and the standard (non-clean-room) run has no attempt dir so the local copy was never written. Both fixed; the summary for job_e2e4 was ECONOMICS_ACCEPTED. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ules hold CI caught it: worker-governor-fail-closed asserts BOTH that a terminal body ends with ' worker_build=<x> vinci_binary=<y>' AND that several blocker reasons match $-anchored against the body with that tail stripped. A token appended after the reason broke the second; after the stamps it would break the first. The digest now leads: economics_sha256=<64hex> <reason> <stamps>. worker-economics-terminal additionally asserts the build tail still closes the body. 23/23 fail-closed, 6 worker integration files, 40 unit tests pass. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… a null ref Found by projects-11 (bus msg_9438fe86), confirmed against this tree with their reproduction and a mutation control. task.mjs:433 hard-codes `ref: undefined` for a CONTRACT envelope and carries the real id in contract.work_order_id; economics.mjs read only envelope.ref, so every governed attempt emitted work_order_id: null plus incomplete ['missing'] — on exactly the field vinci-gpu-control #288 keys acceptance on. Both PRs stayed green because each is self-consistent alone and every existing test drove a PROSE envelope; the failure exists only at the join. The emitter now takes workOrderId (contract first, envelope ref second) on the main path and in emitEconomics, and the degraded catch path uses the same precedence. A contract-envelope test asserts it; the mutation (revert to the ref-only read) fails that test and passes with the fix. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
vinci/package.sh line 30 states that "tests, docs, infrastructure state, and release tooling must never enter the public archive", but the only test-shaped exclusion in the packaging path was a hardcoded node_modules/ssh2/test, and package-entries.mjs tars first-party release roots (vinci/worker, vinci/extensions, vinci/updater, vinci/themes, vinci/assets, packages/*/dist) wholesale. Any first-party test directory added under them shipped. Reported by projects-9f, routed via the coordinator from projects-5a. Latent, not live: no first-party test path ships today. `tar -tzf` of the artifact built at this head matches no test-shaped first-party path, and no such file exists on disk under any release root. PR #49 adds vinci/worker/test/**, which would have been the first. Fixed as a class rather than per path. A per-directory exclusion would leave every future first-party test directory shipping while still reading as closed — the same "enumeration wearing a policy's comment" shape as the workspace gate repaired in the previous commit. vinci/scripts/ first-party-test-paths.mjs keys off the shape of the path instead: any test/tests/__tests__/__mocks__/spec/specs segment, or a *.test.* / *.spec.* basename. Scope is first-party only; paths inside node_modules stay governed by the production dependency closure plus the ssh2 carve-out, because pruning a dependency by name would change which bytes of a third-party package the artifact carries. The rule is applied on BOTH sides from that one predicate, which is not optional: the checker requires every authority entry under vinci/worker, so a producer-only exclusion would make every artifact fail as "required by the trusted package layout is missing" (the pairwise failure this shared module exists to prevent). - package-entries.mjs excluded(): the entry list tar is given. - packaged-artifact-check.mjs excludedFromReleaseAuthority(): the trusted release surface, so producer and verifier agree. - packaged-artifact-check.mjs compareAuthorityDirectory(): a test path that did reach the archive is refused in its own words rather than as a generic authority mismatch, since that means the packaging rule regressed. Controls (verbatim outputs in the PR body): with vinci/worker/test/ ws-c2-probe.test.mjs and vinci/updater/__tests__/ws-c2-probe.test.mjs planted, `bash vinci/package.sh` before this change listed all four entries in the tarball and after it lists none, while vinci/worker keeps its 21 shipped entries and the artifact still certifies. Both new tests fail under the per-path mutation named in their docstring; they are pinned separately because the producer and the artifact-direction refusal are different guards and one mutation does not fail both.
projects-11 (bus msg_9438fe86): the emit helper's catch returned a stub with no work_order_id, so #288's validator records boundary:missing_key -> ECONOMICS_REFUSED against nothing, and the attempt's spend leaves the denominator entirely (charter §8.1) — while looking like poster fault. The degraded summary now carries the resolved work_order_id, lease_id, fencing_generation and a real digest, and says malformed_entries. Also de-aliases the contract-envelope fixture: it now sets envelope.ref to a DIFFERENT value than workOrderId, so the test can actually tell the two sources apart instead of passing because they coincide. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Wave 1 lane B of Cognitive Capital Measurement v0. Charter:
docs/economics/CCM-V0-PROTOCOL.mdon vinci-gpu-control main (#287). Ledger side: vinci-gpu-control #288.What this does
vinci/worker/economics.mjs:buildEconomicsSummary,canonicalJson(sorted keys, integer micro-USD),economicsSha256. Never throws; missing inputs land in a closedincomplete[]vocabulary.session-read.mjs: surfaces the persisted per-callvinci-task-usageentries, which of the three cost fallbacks answered, and whether crew helpers ran. Existing callers unchanged.worker.mjs: a summary is built at every terminal — into the evidence bundle on the main path, and under<stateDir>/economics/<taskId>/on early blockers, governor and branch-lease refusals, authority-lost, checkout-blocked and the catch-all FAILED.economics_sha256travels as a structured field besideevidence_sha256, never spliced into blocker prose.evidence.mjs:economics_summaryandeconomics_sha256added to the POST /v1/evidence body.Known limitation, stated plainly
On the governed (CONTRACT-envelope) path the summary does not reach the ledger.
evidence.mjsposts only whenisLedgerRef(envelope.ref), andtask.mjssetsref: undefinedfor a contract envelope, so a governed attempt writes its summary to disk and never POSTs it. This PR does not change that gate:isLedgerRefgoverns all evidence posting, not just economics, and it deserves its own PR and its own adversarial review. Do not read this PR as "the ledger now receives worker economics" — it receives them on the prose/ledger-ref path only. The CCM Wave-1 integration gate stays open on this and on the packaged worker (#48).Defects found and fixed during review
Two independent Claude reviews plus a live cross-lane review from projects-11 (bus msg_9438fe86):
work_order_id: null— the emitter readenvelope.ref, which isundefinedfor contract envelopes while the real id sits incontract.work_order_id. That is exactly the field #288 keys acceptance on. Both PRs stayed green in isolation because every existing test drove a prose envelope. Fixed with contract-first precedence, a contract-envelope test whose fixture deliberately sets a differentenvelope.refso the two sources can be told apart, and a mutation control.boundary:missing_key→ REFUSED against nothing, dropping that attempt's spend out of the denominator. The degraded summary now carries the resolved work order, lease, generation and a real digest.harness_stopcarried raw tool output past the metadata-only boundary,session_idwas a filename, and receipt-only cost silently became zero spend.^- and$-anchored, so the digest moved into the structured field list.Tests: 40 unit plus 7 worker integration files, including
vinci/test/worker-economics-terminal.mjs, which drivesworker.mjs start --oncethrough the fixture bus to a real blocked terminal and checks the on-disk summary and the bus body. One skip with reason: the packaging test, blocked on #48.Authorship: Vinci Code (module, wiring, first tests) and Claude (terminal coverage, receipt-keyed fields, dedup fix, governed-path fix, degraded path, reviews). Codex and OpenRouter lanes were unavailable.
🤖 Generated with Claude Code