voidmachine 5.0.6 → 5.0.8

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "harness",
3
- "version": "5.0.6",
3
+ "version": "5.0.8",
4
4
  "description": "VoidCorp universal craftsman skills (void-tdd, void-typescript-strict, void-hexagonal-architecture, void-domain-driven-design, void-functional, void-code-review, void-api-and-interface-design, void-source-driven-development, void-context, void-learn, void-decide, void-claude-md, void-observability, void-security-guidance, ...) plus 12 PreToolUse enforcement hooks (tdd-guard, no-any, protect-sensitive-files, block-dangerous-bash, no-ai-design-slop, ...) and PostToolUse/SessionStart lifecycle hooks (incl. trim-large-output token frugality), five read-only critics including doctrine-critic, native architecture/security/QA specialists, and the void-graph, void-doctor and void-audit gestures, which are skills like everything else.",
5
5
  "author": {
6
6
  "name": "VoidCorp",
@@ -5,7 +5,7 @@
5
5
  "source": {
6
6
  "kind": "adapter",
7
7
  "version": "3.0.0-alpha.1",
8
- "rootHash": "sha256:e38546e5aa7fcb09bddd3c33954b340026d7d92ae9d74b0272191043b98cd706"
8
+ "rootHash": "sha256:7a824dcd695bae061ba5ee2b9e797032682dd8ace6a2957015c8a1949248d5d7"
9
9
  },
10
10
  "nodes": [
11
11
  {
@@ -4331,7 +4331,7 @@
4331
4331
  }
4332
4332
  ],
4333
4333
  "id": "skill:void-implement",
4334
- "lines": 192,
4334
+ "lines": 193,
4335
4335
  "name": "void-implement",
4336
4336
  "owner": "folpe",
4337
4337
  "pack": null,
@@ -4340,7 +4340,7 @@
4340
4340
  "codex"
4341
4341
  ],
4342
4342
  "source": "packages/core/skills/void-implement/SKILL.md",
4343
- "staticTokens": 5800,
4343
+ "staticTokens": 5864,
4344
4344
  "type": "skill"
4345
4345
  }
4346
4346
  },
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "harnessVersion": "5.0.6",
3
+ "harnessVersion": "5.0.8",
4
4
  "capabilities": [
5
5
  {
6
6
  "id": "skill:pack-mobile/void-eas-build-profile",
@@ -2195,7 +2195,7 @@
2195
2195
  }
2196
2196
  ],
2197
2197
  "id": "skill:void-implement",
2198
- "lines": 192,
2198
+ "lines": 193,
2199
2199
  "name": "void-implement",
2200
2200
  "owner": "folpe",
2201
2201
  "pack": null,
@@ -2204,7 +2204,7 @@
2204
2204
  "codex"
2205
2205
  ],
2206
2206
  "source": "packages/core/skills/void-implement/SKILL.md",
2207
- "staticTokens": 5800,
2207
+ "staticTokens": 5864,
2208
2208
  "type": "skill"
2209
2209
  },
2210
2210
  {
@@ -36,11 +36,12 @@ materializes its runtime-neutral specialist envelopes from the current plan.
36
36
  1. Keep one `leadWriterId` for implementation and every correction. The implementer owns
37
37
  the worktree and code; specialists and the independent reviewer never edit.
38
38
  2. Start with `mission start --ticket <ticket>` and follow `mission dispatch --id <mission>
39
- --json`. The frozen task and controller own routing, state and correction budget. Before
40
- implementation, launch only the risk-relevant preparation specialists returned by dispatch,
41
- each envelope through `void-orchestrate` using the native runtime (pass 5). Collect each result without
42
- inventing a completion. Record each actual start and completion with `mission specialist-event
43
- --id <mission>`. A missing response resumes the run it already has, never another cycle.
39
+ --json`. The frozen task and controller own routing, state and correction budget. Normal
40
+ dispatch starts the writer without a compulsory preparation panel. Consult a targeted native
41
+ specialist only when a concrete risk or unresolved question warrants advice (pass 5).
42
+ For canonical `invoke-specialists` actions, pass each envelope unchanged through
43
+ `void-orchestrate` and record actual starts/results with `mission specialist-event --id <mission>`.
44
+ Reconcile any existing invocation; a missing response resumes its run, never another cycle.
44
45
  3. The implementer implements and verifies, then commits the candidate. Record the pending
45
46
  writer receipt with `mission writer-event --id <mission>`. Review must bind an exact
46
47
  `reviewedCommit`, `baseCommit` and `acceptanceCriteriaHash`; a mutable worktree is insufficient.
@@ -101,7 +102,7 @@ Run in order. Each pass names the skill it composes and the predicate that fires
101
102
  2. **Architecture pass** (IF it touches structure, a module boundary, the data model, or public types). Compose `void-hexagonal-architecture`, `void-domain-driven-design`, agents `type-design-analyzer` + `doctrine-critic`. Confirm the applicable ADR is honored.
102
103
  3. **Source grounding** (ALWAYS; the source depends on what the change touches). Read the relevant sources before the first line, per the Anti-rustine section of `PHILOSOPHY.md`. For a third-party configuration, schema or call signature, compose `void-source-driven-development`: official documentation **of the installed version**, never an upgrade by default. For internal code, read the owning contract, types, source and project docs of what you are about to change. Assumed semantics produce bugs that cost hours to find, and the assumption is invisible in the diff — which is why this runs before the writing rather than at review. Cite a source briefly when it helps understand or record a choice; a missing citation never blocks on its own.
103
104
  4. **Migration safety** (IF it changes a DB schema or ships a migration). Compose `void-migrations` (and `void-drizzle-migration-safe` on a Drizzle/Postgres stack): zero-downtime, two-phase, batched backfill, locking analysis. **Once the migration is generated and safety-reviewed, apply it to the dev/local database before the TDD and E2E passes run** — otherwise those tests execute against a stale schema and prove nothing about the new shape. **This cycle only ever applies to dev/local; production migrations run through CI / GitHub Actions on merge, never from a worker or this session** (see the `void-migrations` anti-rule). A schema change must never reach the rest of the cycle without this pass.
104
- 5. **Convene the panel** (ALWAYS once, in `team` mode). With `panel.provider: orchestrator`, consume ORCH's supplied panel and never launch another. Otherwise call `mission dispatch --json`: its first `invoke-specialists` action is at `stage: 'pre-implementation'`, before code. Compose `void-orchestrate` for native invocation. Pass the envelope's runtime, agentName, missionId, contextPack, reviewSubject and reviewScope unchanged; record actual starts and complete results using `mission specialist-event`. Reconcile an existing invocation before dispatch; uncertain identity means no launch. Keep at most four simultaneous agents, wait in bounded slices, and never fail an agent merely because a wait timed out. Findings brief the single writer; a concrete blocked verdict is resolved before implementation. Later controller stages reuse this transport and never replay preparation. Missing required capability is diagnosed, never fabricated.
105
+ 5. **Targeted preparation advice** (IF a concrete risk or unresolved question warrants a specialist). Use the existing professional passes and `void-orchestrate` to consult only the relevant native specialist. Bind the question to the actual brief, cited artifacts, boundary or risk being examined; revisit only the affected advice when those materially change. Baseline/profile applicability alone does not require a panel, and advice is not an automatic canonical gate. With `panel.provider: orchestrator`, consume its supplied advice without launching a duplicate. For canonical `invoke-specialists` actions, preserve runtime, agentName, missionId, contextPack, reviewSubject and reviewScope; record actual starts/results, never invented receipts. Reconcile uncertain invocations before launching, keep at most four simultaneous agents, and never fail one on a wait timeout. Preserve real unresolved findings and author-owned evidence obligations. Already-started preparation may finish as advice after writer admission; it cannot satisfy final code review or authorize a new preparation invocation after implementation starts.
105
106
  6. **TDD implementation** (ALWAYS). During development, the coordinator may create or reuse a draft pull request for the branch. Keep work-in-progress PRs in draft and run focused local checks; do not wait for full remote CI per iteration. `void-verify` owns the transition to complete integration checks and its portable CI template. The single lead writer composes `void-tdd` + `void-testing`. Red, green, refactor. Unit tests for the behavior, green before moving on. Review specialists remain read-only.
106
107
  7. **Async + idempotency** (IF it sends email, calls an external side-effecting API, enqueues a job, or mints a single-use token). Compose `void-async-safety`: idempotency keys, replay/dedup window, bounded retries, single-use enforcement.
107
108
  8. **End-to-end tests** (IF it touches a user-facing flow). Write/extend the E2E suite (Playwright). The path a user actually walks, not just the unit.
package/dist/main.js CHANGED
@@ -35804,6 +35804,89 @@ function parseEnvelope(event) {
35804
35804
  completion
35805
35805
  };
35806
35806
  }
35807
+ function freshReviewProofIds(events, context2) {
35808
+ return events.flatMap((event) => {
35809
+ if (event.kind !== "evidence.recorded")
35810
+ return [];
35811
+ const parsed = parseEvidence(record7(event.payload)?.["evidence"]);
35812
+ return parsed.ok && parsed.value.evidenceId === event.subject && parsed.value.missionId === event.missionId && parsed.value.status === "passed" && assessEvidence(parsed.value, context2).status === "fresh" ? [parsed.value.evidenceId] : [];
35813
+ });
35814
+ }
35815
+ function reviewField(event, key) {
35816
+ return record7(event?.payload)?.[key];
35817
+ }
35818
+ function sameNativeDispatch(left, right) {
35819
+ return left.missionId === right.missionId && left.subject === right.subject && ["stage", "reviewRound", "inputHash", "contractVersion", "reviewSubject", "reviewScope"].every((key) => reviewField(left, key) !== void 0 && reviewField(right, key) !== void 0 && canonicalJsonHash(reviewField(left, key)) === canonicalJsonHash(reviewField(right, key)));
35820
+ }
35821
+ function boundNativeRequest(events, envelope) {
35822
+ const { event, completion, contextId } = envelope;
35823
+ const receipt2 = completion.review;
35824
+ const subject = reviewField(event, "reviewSubject");
35825
+ const scope = reviewField(event, "reviewScope");
35826
+ const start = events[0];
35827
+ if (!start || !receipt2 || !isReviewSubject(subject) || !isReviewScope(scope) || !sameReviewSubject(receipt2, subject) || canonicalJsonHash(receipt2.scope) !== canonicalJsonHash(scope) || receipt2.taskId !== event.missionId || receipt2.writerId !== reviewField(start, "leadWriterId") || receipt2.provenance.kind !== "native-context" || receipt2.provenance.contextId !== contextId || contextId === receipt2.writerId || event.source !== `runtime:${String(reviewField(start, "runtime"))}` || event.subject !== completion.specialistId || reviewField(event, "contractVersion") !== completion.contractVersion)
35828
+ return void 0;
35829
+ const requests = events.filter((candidate) => candidate.kind === "specialist.requested" && candidate.seq < event.seq && candidate.source === "void-harness:mission.dispatch" && sameNativeDispatch(candidate, event) && reviewField(candidate, "planHash") === reviewField(start, "planHash") && reviewField(candidate, "runtime") === reviewField(start, "runtime"));
35830
+ const request = requests[0];
35831
+ if (requests.length !== 1 || !request)
35832
+ return void 0;
35833
+ const started = events.filter((candidate) => candidate.kind === "specialist.started" && candidate.seq > request.seq && candidate.seq < event.seq && candidate.source === event.source && sameNativeDispatch(candidate, event) && reviewField(candidate, "contextId") === contextId && (candidate.causationId === void 0 || candidate.causationId === request.eventId));
35834
+ return started.length === 1 && (event.causationId === void 0 || event.causationId === request.eventId) && (reviewField(event, "reviewInvocationEventId") === void 0 || reviewField(event, "reviewInvocationEventId") === started[0]?.eventId) ? request : void 0;
35835
+ }
35836
+ function sameMembers(actual, expected) {
35837
+ return Array.isArray(actual) && actual.length === new Set(expected).size && new Set(actual).size === actual.length && actual.every((value) => expected.includes(value));
35838
+ }
35839
+ function authorizedTargetedRequest(events, request, history) {
35840
+ const original = history[0]?.completion.review;
35841
+ const previous = history.at(-1);
35842
+ const subject = reviewField(request, "reviewSubject");
35843
+ const scope = reviewField(request, "reviewScope");
35844
+ if (!original || !previous || !isReviewSubject(subject) || !isReviewScope(scope) || scope.kind !== "targeted" || subject.taskId !== original.taskId || subject.baseCommit !== original.baseCommit || subject.acceptanceCriteriaHash !== original.acceptanceCriteriaHash || reviewField(request, "contractVersion") !== previous.completion.contractVersion)
35845
+ return false;
35846
+ const blockers = history.flatMap((item) => item.completion.findings).filter((item) => item.classification === "blocking");
35847
+ if (blockers.length === 0 || !sameMembers(scope.findingIds, blockers.map((item) => item.id)) || !sameMembers(scope.affectedPaths, blockers.flatMap((item) => item.evidence.map((proof4) => proof4.path))))
35848
+ return false;
35849
+ const writers = events.filter((event) => event.kind === "lead-writer.completed" && event.seq > previous.event.seq && event.seq < request.seq);
35850
+ const writer = writers[0];
35851
+ const correction = events.find((event) => event.eventId === reviewField(writer ?? request, "requestEventId"));
35852
+ const count = events.filter((event) => event.kind === "lead-writer.completed" && event.seq < request.seq && reviewField(event, "actionKind") === "run-correction").length;
35853
+ if (writers.length !== 1 || !writer || !correction || count < 1 || count > 2 || reviewField(request, "reviewRound") !== count + 1 || correction.kind !== "lead-writer.requested" || correction.source !== "void-harness:mission.dispatch" || correction.seq <= previous.event.seq || correction.seq >= writer.seq || correction.subject !== original.writerId || writer.subject !== original.writerId || writer.source !== original.writerId || writer.causationId !== correction.eventId || reviewField(writer, "actionKind") !== "run-correction" || reviewField(writer, "planHash") !== reviewField(events[0], "planHash") || reviewField(writer, "implementationRound") !== count + 1 || !["actionKind", "planHash", "writerId", "implementationRound"].every((key) => reviewField(writer, key) === reviewField(correction, key)))
35854
+ return false;
35855
+ const open11 = reconcileReviewFindings(history, freshReviewProofIds(events.filter((event) => event.seq < correction.seq), { dependencies: {} })).filter((finding) => finding.classification === "blocking");
35856
+ return open11.length > 0 && sameMembers(reviewField(correction, "findingIds"), open11.map((finding) => finding.findingId));
35857
+ }
35858
+ function canContinueNativeReview(events, request, contextId) {
35859
+ const start = events[0];
35860
+ if (!start || start.kind !== "mission.started" || reviewField(request, "planHash") !== reviewField(start, "planHash") || reviewField(request, "runtime") !== reviewField(start, "runtime") || request.kind !== "specialist.requested" || request.source !== "void-harness:mission.dispatch" || request.subject !== "core:independent-code-reviewer" || reviewField(request, "stage") !== "post-implementation" || !validNativeContextId(contextId) || events.some((event) => event.missionId !== request.missionId))
35861
+ return false;
35862
+ const history = events.filter((event) => event.kind === "specialist.completed" && event.seq < request.seq && reviewField(event, "contextId") === contextId).map(parseEnvelope);
35863
+ if (history.length === 0 || history.some((item) => !item))
35864
+ return false;
35865
+ const prior = history.filter((item) => item !== void 0);
35866
+ const first = prior[0];
35867
+ if (!first || first.completion.review?.scope.kind !== "general" || first.completion.verdict !== "changes-requested" || new Set(prior.map((item) => item.completion.completionId)).size !== prior.length || events.some((event) => event.seq < request.seq && ["specialist.started", "specialist.completed"].includes(event.kind) && reviewField(event, "contextId") === contextId && (event.subject !== request.subject || reviewField(event, "stage") !== "post-implementation")) || events.filter((event) => event.kind === "specialist.completed" && event.subject === request.subject && reviewField(event, "stage") === "post-implementation" && event.seq < request.seq).at(-1)?.eventId !== prior.at(-1)?.event.eventId)
35868
+ return false;
35869
+ for (const [index, item] of prior.entries()) {
35870
+ const bound = boundNativeRequest(events, item);
35871
+ if (!bound || item.completion.review?.reviewerId !== first.completion.review.reviewerId || index > 0 && !authorizedTargetedRequest(events, bound, prior.slice(0, index)))
35872
+ return false;
35873
+ }
35874
+ return authorizedTargetedRequest(events, request, prior);
35875
+ }
35876
+ function validNativeReviewContinuation(events, completion) {
35877
+ const envelope = parseEnvelope(completion);
35878
+ if (!envelope || !envelope.contextId)
35879
+ return false;
35880
+ const request = boundNativeRequest(events, envelope);
35881
+ const receipt2 = envelope.completion.review;
35882
+ if (!request || !receipt2 || !canContinueNativeReview(events, request, envelope.contextId) || receipt2.scope.kind !== "targeted")
35883
+ return false;
35884
+ const previous = events.filter((event) => event.kind === "specialist.completed" && event.seq < request.seq && reviewField(event, "contextId") === envelope.contextId).at(-1);
35885
+ const previousReceipt = previous && parseEnvelope(previous)?.completion.review;
35886
+ const correction = events.filter((event) => event.kind === "lead-writer.requested" && event.seq < request.seq && reviewField(event, "actionKind") === "run-correction").at(-1);
35887
+ const historicalProofIds = freshReviewProofIds(events.filter((event) => event.seq < completion.seq), { dependencies: {} });
35888
+ return receipt2.reviewerId === previousReceipt?.reviewerId && correction !== void 0 && receipt2.resolutions.every((resolution) => receipt2.scope.kind === "targeted" && receipt2.scope.findingIds.includes(resolution.findingId) && (resolution.status !== "resolved" || resolution.proofIds.every((id3) => historicalProofIds.includes(id3) && receipt2.proofIds.includes(id3) && events.some((event) => event.kind === "evidence.recorded" && event.subject === id3 && event.seq > correction.seq && event.seq < completion.seq))));
35889
+ }
35807
35890
  function collectCompletions(events, stage, expectedSource, stageStartSeqExclusive, afterSeqExclusive, beforeSeqExclusive, maxRounds, retainedSpecialists, contractMigration, correctionBatches) {
35808
35891
  const accepted = [];
35809
35892
  const retained = [];
@@ -35813,7 +35896,19 @@ function collectCompletions(events, stage, expectedSource, stageStartSeqExclusiv
35813
35896
  const contextIds = /* @__PURE__ */ new Set();
35814
35897
  const currentFailedRounds = [];
35815
35898
  let historicalHighestRound = 0;
35816
- const inStageWindow = (event) => (afterSeqExclusive === void 0 || event.seq > afterSeqExclusive) && (beforeSeqExclusive === void 0 || event.seq < beforeSeqExclusive);
35899
+ const inStageWindow = (event) => {
35900
+ let seq = event.seq;
35901
+ if (stage === "pre-implementation" && event.kind === "specialist.completed") {
35902
+ const payload = record7(event.payload);
35903
+ const mission2 = events.find((candidate) => candidate.kind === "mission.started" && candidate.missionId === event.missionId && candidate.seq < event.seq);
35904
+ const planHash = record7(mission2?.payload)?.["planHash"];
35905
+ const matches = (candidate) => candidate.missionId === event.missionId && candidate.subject === event.subject && ["stage", "reviewRound", "inputHash", "contractVersion"].every((key) => record7(candidate.payload)?.[key] === payload?.[key]);
35906
+ const invocation = events.find((candidate) => candidate.seq < event.seq && candidate.kind === "specialist.started" && candidate.source === expectedSource && matches(candidate) && typeof payload?.contextId === "string" && record7(candidate.payload)?.contextId === payload.contextId);
35907
+ if (invocation !== void 0 && events.some((candidate) => candidate.seq < invocation.seq && candidate.kind === "specialist.requested" && candidate.source === "void-harness:mission.dispatch" && typeof planHash === "string" && SHA256.test(planHash) && record7(candidate.payload)?.["planHash"] === planHash && `runtime:${record7(candidate.payload)?.["runtime"]}` === expectedSource && matches(candidate)))
35908
+ seq = invocation.seq;
35909
+ }
35910
+ return (afterSeqExclusive === void 0 || seq > afterSeqExclusive) && (beforeSeqExclusive === void 0 || seq < beforeSeqExclusive);
35911
+ };
35817
35912
  for (const event of events) {
35818
35913
  const payload = record7(event.payload);
35819
35914
  if (event.kind === "specialist.failed") {
@@ -35861,7 +35956,7 @@ function collectCompletions(events, stage, expectedSource, stageStartSeqExclusiv
35861
35956
  }
35862
35957
  const duplicateCompletion = completionIds.has(envelope.completion.completionId);
35863
35958
  const contextKey = envelope.contextId ?? (envelope.completion.review?.provenance.kind === "review-artifact" ? `artifact:${envelope.completion.review.provenance.sha256}` : void 0);
35864
- const reusedContext = contextKey !== void 0 && contextIds.has(contextKey);
35959
+ const reusedContext = contextKey !== void 0 && contextIds.has(contextKey) && !(envelope.stage === "post-implementation" && validNativeReviewContinuation(events, event));
35865
35960
  if (duplicateCompletion) {
35866
35961
  issues.push({
35867
35962
  code: "duplicate-completion",
@@ -36580,6 +36675,17 @@ function opaqueNativeContextDefect(input2) {
36580
36675
  return starts.length === 1 && started !== void 0 && started.source === event.source && started.seq > requested.seq && started.seq < event.seq && ["stage", "reviewRound", "inputHash", "contractVersion", "contextId"].every((key) => field4(started, key) === field4(event, key));
36581
36676
  });
36582
36677
  }
36678
+ function targetedReviewContextDefect(input2) {
36679
+ const { events } = input2.stream;
36680
+ const { observation: observation2 } = input2;
36681
+ const start = events[0];
36682
+ const closure = events.at(-1);
36683
+ const current = completions(events).filter((item) => field4(item.event, "stage") === "post-implementation").at(-1);
36684
+ const receipt2 = current?.completion.review;
36685
+ if (!start || closure?.kind !== "mission.closed" || closure.source !== "void-harness:mission.dispatch" || start.kind !== "mission.started" || start.source !== "void-harness:mission" || field4(start, "runtimeAttested") !== true || observation2.stage !== "post-implementation" || observation2.reviewPolicy !== "bounded-corrections-v1" || !observation2.reviewSubject || !current || !receipt2 || !sameReviewSubject(receipt2, observation2.reviewSubject) || current.completion.verdict !== "pass" || current.event.source !== observation2.expectedSource || field4(current.event, "inputHash") !== observation2.currentInputHashes[current.event.subject] || current.completion.contractVersion !== observation2.contractVersions[current.event.subject])
36686
+ return false;
36687
+ return validNativeReviewContinuation(events, current.event);
36688
+ }
36583
36689
  function preparationInvocationReplacement(input2) {
36584
36690
  const { events } = input2.stream;
36585
36691
  const { observation: observation2, request } = input2;
@@ -36855,12 +36961,13 @@ function admitStoppedMission(input2, projectedHistory = input2.stream.events) {
36855
36961
  }
36856
36962
  const disposition = request.disposition;
36857
36963
  const replacement = disposition.kind === "preparation-invocation";
36964
+ const targetedContext = disposition.kind === "controller-defect" && disposition.defect === "targeted-review-context";
36858
36965
  if (replacement && !preparationInvocationReplacement(input2)) {
36859
36966
  return refuse2("invalid-preparation-invocation", "Name exactly the reused-context preparation completions with intact invocation bindings; replacement is one-time and retains round two");
36860
36967
  }
36861
- const roundCorrections = replacement ? [] : partialFanoutCorrections(projectedHistory);
36862
- const inadmissible = replacement ? disposition.completionEventIds : staleDispatchCompletions(projectedHistory);
36863
- if (disposition.kind === "controller-defect" && (disposition.defect === "partial-fanout-round" ? roundCorrections.length === 0 : disposition.defect === "stale-input-dispatch" ? inadmissible.length === 0 : !opaqueNativeContextDefect(input2))) {
36968
+ const roundCorrections = replacement || targetedContext ? [] : partialFanoutCorrections(projectedHistory);
36969
+ const inadmissible = replacement ? disposition.completionEventIds : targetedContext ? [] : staleDispatchCompletions(projectedHistory);
36970
+ if (disposition.kind === "controller-defect" && (disposition.defect === "partial-fanout-round" ? roundCorrections.length === 0 : disposition.defect === "stale-input-dispatch" ? inadmissible.length === 0 : targetedContext ? !targetedReviewContextDefect(input2) : !opaqueNativeContextDefect(input2))) {
36864
36971
  return refuse2("unproven-controller-defect", "The journal must prove the requested controller defect; do not reset its budget");
36865
36972
  }
36866
36973
  if (disposition.kind === "review-provenance" && !validProvenanceRecovery(input2, existing)) {
@@ -36932,6 +37039,9 @@ function admitStoppedMission(input2, projectedHistory = input2.stream.events) {
36932
37039
  requiredSpecialists: required2,
36933
37040
  contractVersions: observation2.contractVersions,
36934
37041
  currentInputHashes: observation2.currentInputHashes,
37042
+ ...targetedContext ? { validProofIds: freshReviewProofIds(projected, {
37043
+ dependencies: observation2.evidenceDependencies ?? {}
37044
+ }) } : {},
36935
37045
  ...observation2.reviewPolicy === "bounded-corrections-v1" ? BOUNDED_REVIEW_BUDGET : { maxRounds: observation2.maxRounds }
36936
37046
  };
36937
37047
  const originalReview = reduceReviewLoop({ ...reviewInput, events: projectedHistory });
@@ -36945,14 +37055,17 @@ function admitStoppedMission(input2, projectedHistory = input2.stream.events) {
36945
37055
  if (disposition.kind === "controller-defect" && disposition.defect === "opaque-native-context" && (!review.readyForVerdict || existing.length !== required2.length || required2.some((id3) => !existing.some((item) => item.event.subject === id3)))) {
36946
37056
  return refuse2("incomplete-required-panel", "Every required preparation specialist must have its original matching PASS triplet before recovery");
36947
37057
  }
37058
+ if (targetedContext && !review.readyForVerdict) {
37059
+ return refuse2("unproven-controller-defect", "The retained targeted review must resolve every blocking finding with current evidence before verification");
37060
+ }
36948
37061
  const consumedRounds = Math.max(0, ...(replacement ? projectedHistory : projected).filter((event) => ["specialist.completed", "specialist.failed"].includes(event.kind) && field4(event, "stage") === observation2.stage).map((event) => Number(field4(event, "reviewRound"))));
36949
- if (!replacement && disposition.kind !== "review-provenance" && consumedRounds >= reviewInput.maxRounds)
37062
+ if (!replacement && !targetedContext && disposition.kind !== "review-provenance" && consumedRounds >= reviewInput.maxRounds)
36950
37063
  return refuse2("review-budget-exhausted", "The real review budget is exhausted; recovery cannot reset it");
36951
37064
  const consumedCorrectionBatches = projected.filter((event) => event.kind === "lead-writer.completed" && field4(event, "actionKind") === "run-correction").length;
36952
- if (disposition.kind === "review-provenance" && consumedCorrectionBatches > 2) {
37065
+ if ((targetedContext || disposition.kind === "review-provenance") && consumedCorrectionBatches > 2) {
36953
37066
  return refuse2("review-budget-exhausted", "The real correction budget is exhausted; recovery cannot reset it");
36954
37067
  }
36955
- const preserved = existing.filter((item) => !inadmissible.includes(item.event.eventId) && item.completion.contractVersion === observation2.contractVersions[item.event.subject] && field4(item.event, "inputHash") === observation2.currentInputHashes[item.event.subject]);
37068
+ const preserved = targetedContext ? existing : existing.filter((item) => !inadmissible.includes(item.event.eventId) && item.completion.contractVersion === observation2.contractVersions[item.event.subject] && field4(item.event, "inputHash") === observation2.currentInputHashes[item.event.subject]);
36956
37069
  const preservedIds = new Set(preserved.map((item) => item.event.eventId));
36957
37070
  const receipt2 = {
36958
37071
  schemaVersion: 1,
@@ -36969,11 +37082,11 @@ function admitStoppedMission(input2, projectedHistory = input2.stream.events) {
36969
37082
  roundCorrections,
36970
37083
  consumedRounds,
36971
37084
  remainingRounds: Math.max(0, reviewInput.maxRounds - consumedRounds),
36972
- ...!replacement && disposition.kind !== "review-provenance" ? {} : {
37085
+ ...!replacement && !targetedContext && disposition.kind !== "review-provenance" ? {} : {
36973
37086
  consumedCorrectionBatches,
36974
37087
  remainingCorrectionBatches: 2 - consumedCorrectionBatches
36975
37088
  },
36976
- nextAction: replacement || disposition.kind === "evidence-disposition" || disposition.kind === "review-provenance" || disposition.kind === "command-correction" || disposition.kind === "controller-defect" && disposition.defect === "opaque-native-context" ? "verification" : disposition.kind === "review-blocker" ? "clarification" : "correction"
37089
+ nextAction: replacement || targetedContext || disposition.kind === "evidence-disposition" || disposition.kind === "review-provenance" || disposition.kind === "command-correction" || disposition.kind === "controller-defect" && disposition.defect === "opaque-native-context" ? "verification" : disposition.kind === "review-blocker" ? "clarification" : "correction"
36977
37090
  };
36978
37091
  if (new TextEncoder().encode(canonicalJson(receipt2)).length > 16384) {
36979
37092
  return refuse2("recovery-receipt-too-large", "Narrow the recovery scope to the supported bounded incident");
@@ -37000,7 +37113,7 @@ function recoveryRequest(value) {
37000
37113
  if (disposition["kind"] === "command-correction") {
37001
37114
  return exactKeys2(disposition, ["kind", "pairs", "resolutionArtifact"]) && isCommandEvidenceCorrections(disposition["pairs"]) && artifact(disposition["resolutionArtifact"]);
37002
37115
  }
37003
- return disposition["kind"] === "controller-defect" ? exactKeys2(disposition, ["kind", "defect"]) && ["partial-fanout-round", "stale-input-dispatch", "opaque-native-context"].includes(String(disposition["defect"])) : (disposition["kind"] === "review-blocker" || disposition["kind"] === "review-provenance") && exactKeys2(disposition, ["kind", "completionEventIds", "resolutionArtifact"]) && Array.isArray(disposition["completionEventIds"]) && disposition["completionEventIds"].length <= 64 && disposition["completionEventIds"].every((item) => typeof item === "string") && artifact(disposition["resolutionArtifact"]);
37116
+ return disposition["kind"] === "controller-defect" ? exactKeys2(disposition, ["kind", "defect"]) && ["partial-fanout-round", "stale-input-dispatch", "opaque-native-context", "targeted-review-context"].includes(String(disposition["defect"])) : (disposition["kind"] === "review-blocker" || disposition["kind"] === "review-provenance") && exactKeys2(disposition, ["kind", "completionEventIds", "resolutionArtifact"]) && Array.isArray(disposition["completionEventIds"]) && disposition["completionEventIds"].length <= 64 && disposition["completionEventIds"].every((item) => typeof item === "string") && artifact(disposition["resolutionArtifact"]);
37004
37117
  }
37005
37118
  function reviewBinding(value) {
37006
37119
  if (typeof value !== "object" || value === null || Array.isArray(value))
@@ -38739,10 +38852,10 @@ function lifecycleField(event, key) {
38739
38852
  function sameSpecialistDispatch(left, right) {
38740
38853
  return left.missionId === right.missionId && left.subject === right.subject && lifecycleField(left, "contractVersion") === lifecycleField(right, "contractVersion") && lifecycleField(left, "stage") === lifecycleField(right, "stage") && lifecycleField(left, "reviewRound") === lifecycleField(right, "reviewRound") && lifecycleField(left, "inputHash") === lifecycleField(right, "inputHash");
38741
38854
  }
38742
- function unboundCompletionReasons(input2, runtime2) {
38855
+ function unboundCompletionReasons(input2, runtime2, completions2 = input2.stream.events.filter((event) => event.kind === "specialist.completed"), invocationBeforeSeq) {
38743
38856
  const reasons = [];
38744
- for (const completion of input2.stream.events.filter((event) => event.kind === "specialist.completed")) {
38745
- const started = input2.stream.events.find((event) => event.seq < completion.seq && event.kind === "specialist.started" && event.source === `runtime:${runtime2 ?? "invalid"}` && sameSpecialistDispatch(event, completion) && (lifecycleField(completion, "reviewInvocationEventId") === void 0 ? lifecycleField(event, "contextId") === lifecycleField(completion, "contextId") : event.eventId === lifecycleField(completion, "reviewInvocationEventId") && lifecycleField(event, "reviewerId") === parseSpecialistCompletionValue(lifecycleField(completion, "completion"))?.review?.reviewerId));
38857
+ for (const completion of completions2) {
38858
+ const started = input2.stream.events.find((event) => event.seq < completion.seq && (invocationBeforeSeq === void 0 || event.seq < invocationBeforeSeq) && event.kind === "specialist.started" && event.source === `runtime:${runtime2 ?? "invalid"}` && sameSpecialistDispatch(event, completion) && (lifecycleField(completion, "reviewInvocationEventId") === void 0 ? lifecycleField(event, "contextId") === lifecycleField(completion, "contextId") : event.eventId === lifecycleField(completion, "reviewInvocationEventId") && lifecycleField(event, "reviewerId") === parseSpecialistCompletionValue(lifecycleField(completion, "completion"))?.review?.reviewerId));
38746
38859
  const requested = started === void 0 ? void 0 : input2.stream.events.find((event) => event.seq < started.seq && event.kind === "specialist.requested" && event.source === "void-harness:mission.dispatch" && sameSpecialistDispatch(event, completion) && lifecycleField(event, "planHash") === input2.plan.planHash && lifecycleField(event, "runtime") === runtime2);
38747
38860
  if (requested === void 0 || started === void 0) {
38748
38861
  reasons.push(`specialist lifecycle is unbound: ${completion.subject}`);
@@ -38816,7 +38929,7 @@ function decideReviewPhase(start, review, baseVerdict, stage) {
38816
38929
  action: stage === "pre-implementation" ? {
38817
38930
  kind: "run-preparation-correction",
38818
38931
  writerId: start.leadWriterId,
38819
- findingIds: review.findings.filter((finding) => !start.boundedReview || finding.classification === "blocking").map((finding) => finding.findingId)
38932
+ findingIds: review.findings.filter((finding) => finding.classification !== "advisory").map((finding) => finding.findingId)
38820
38933
  } : {
38821
38934
  kind: "run-correction",
38822
38935
  writerId: start.leadWriterId,
@@ -38897,15 +39010,6 @@ function retainedPreparationSpecialists(input2, events, boundary, targets) {
38897
39010
  return result?.["verdict"] === "pass" && result["contractVersion"] === contractVersions(input2.plan)[id3] && lifecycleField(latest, "inputHash") === input2.currentInputHashesByStage["pre-implementation"][id3];
38898
39011
  });
38899
39012
  }
38900
- function preparationHasMissingOrStaleReviews(input2, events, boundary) {
38901
- return requiredSpecialists(input2.plan, "pre-implementation").some((id3) => {
38902
- const latest = events.filter((item) => item.kind === "specialist.completed" && item.subject === id3 && item.seq < boundary && lifecycleField(item, "stage") === "pre-implementation").at(-1);
38903
- if (latest === void 0)
38904
- return true;
38905
- const result = record15(lifecycleField(latest, "completion") ?? null);
38906
- return result?.["contractVersion"] !== contractVersions(input2.plan)[id3] || lifecycleField(latest, "inputHash") !== input2.currentInputHashesByStage["pre-implementation"][id3];
38907
- });
38908
- }
38909
39013
  function pendingRecoveredCorrectionFindingIds(events) {
38910
39014
  const recovery = events.filter((event) => event.kind === "mission.recovered").at(-1);
38911
39015
  if (recovery === void 0 || events.some((event) => event.seq > recovery.seq && event.kind === "lead-writer.completed"))
@@ -38962,14 +39066,18 @@ function orchestrateMissionTeam(originalInput) {
38962
39066
  const firstWriterSeq = completions2.length === 0 ? void 0 : Math.min(...completions2.map((event) => event.seq));
38963
39067
  const implementationCompletions = completions2.filter((event) => leadWriterActionKind(event) !== "run-preparation-correction");
38964
39068
  const firstImplementationSeq = implementationCompletions.length === 0 ? void 0 : Math.min(...implementationCompletions.map((event) => event.seq));
38965
- const preparationCorrectionCompleted = preparationCorrections.length > 0;
38966
- const preparationFollowupRequired = preparationRecovery !== void 0 || lastPreparationSeq !== void 0 && preparationHasMissingOrStaleReviews(input2, reviewEvents, lastPreparationSeq);
38967
- const latePreparationCompletion = lastPreparationSeq !== void 0 && input2.stream.events.some((event) => event.seq > lastPreparationSeq && event.kind === "specialist.completed" && lifecycleField(event, "stage") === "pre-implementation");
39069
+ const preparationFollowupRequired = preparationRecovery !== void 0;
38968
39070
  const lastWriterSeq = completions2.length === 0 ? void 0 : Math.max(...completions2.map((event) => event.seq));
38969
39071
  const missionStartSeq = input2.stream.events.find((event) => event.kind === "mission.started")?.seq;
38970
39072
  const expectedSource = start.runtime === "claude" ? "runtime:claude" : "runtime:codex";
38971
39073
  const obligations = evidenceObligations(input2, expectedSource, firstImplementationSeq === void 0 ? "pre-implementation" : "post-implementation");
38972
39074
  const preparationObligationIds = new Set(obligations.obligations.filter((obligation) => input2.stream.events.some((event) => event.eventId === obligation.completionEventId && lifecycleField(event, "stage") === "pre-implementation")).map((obligation) => obligation.obligationId));
39075
+ const preparationBoundary = Math.max(lastPreparationSeq ?? 0, ...completions2.filter((event) => leadWriterActionKind(event) === "run-correction").map((event) => event.seq));
39076
+ const observedPreparation = reviewEvents.filter((event) => event.kind === "specialist.completed" && lifecycleField(event, "stage") === "pre-implementation");
39077
+ const unresolvedPreparation = [...new Map(observedPreparation.filter((event) => event.seq > preparationBoundary).map((event) => [event.subject, event])).values()].filter((event) => {
39078
+ const completion = parseSpecialistCompletionValue(lifecycleField(event, "completion"));
39079
+ return completion !== void 0 && completion.verdict !== "pass" && (completion.verdict !== "changes-requested" || completion.findings.length === 0 || completion.findings.some((finding) => finding.classification !== "advisory"));
39080
+ });
38973
39081
  const preReview = reduceReviewLoop({
38974
39082
  stage: "pre-implementation",
38975
39083
  expectedSource,
@@ -38977,21 +39085,29 @@ function orchestrateMissionTeam(originalInput) {
38977
39085
  stageStartSeqExclusive: missionStartSeq,
38978
39086
  afterSeqExclusive: lastPreparationSeq
38979
39087
  },
38980
- ...firstWriterSeq === void 0 ? {} : { beforeSeqExclusive: firstWriterSeq },
38981
39088
  events: reviewEvents,
38982
- requiredSpecialists: requiredSpecialists(input2.plan, "pre-implementation"),
39089
+ // Historical advice is inspected, but the frozen plan is no longer a
39090
+ // compulsory preparation quorum. Explicit admitted recoveries retain theirs.
39091
+ requiredSpecialists: requiredSpecialists(input2.plan, "pre-implementation").filter((id3) => preparationRecovery !== void 0 || (unresolvedPreparation.length > 0 ? unresolvedPreparation : observedPreparation).some((event) => event.subject === id3)),
38983
39092
  contractVersions: contractVersions(input2.plan),
38984
39093
  currentInputHashes: input2.currentInputHashesByStage["pre-implementation"],
38985
39094
  maxRounds: input2.maxReviewRounds,
39095
+ validProofIds: freshReviewProofIds(input2.stream.events, input2.evidenceContext),
38986
39096
  evidenceObligations: {
38987
39097
  ...obligations,
38988
39098
  blockingObligationIds: obligations.blockingObligationIds.filter((id3) => preparationObligationIds.has(id3))
38989
39099
  },
39100
+ ...preparationRecovery === void 0 && lastPreparationSeq !== void 0 && missionStartSeq !== void 0 ? {
39101
+ retainedSpecialists: requiredSpecialists(input2.plan, "pre-implementation").filter((id3) => unresolvedPreparation.some((event) => event.subject === id3))
39102
+ } : {},
38990
39103
  ...!preparationFollowupRequired || lastPreparationSeq === void 0 || preparationRecovery !== void 0 && lastPreparationSeq <= preparationRecovery.recoverySeq ? {} : {
38991
39104
  retainedSpecialists: retainedPreparationSpecialists(input2, reviewEvents, lastPreparationSeq, preparationRecovery?.specialistIds ?? [])
38992
39105
  }
38993
39106
  });
38994
39107
  const baseVerdict = deriveMissionVerdict(input2.stream, input2.evidenceContext);
39108
+ if (baseVerdict.status === "degraded") {
39109
+ return stopped("degraded", preReview, baseVerdict, baseVerdict.reasons);
39110
+ }
38995
39111
  const invalidRuntime = input2.specialistRuntime === void 0 || !Array.isArray(input2.specialistRuntime.limitations) || input2.specialistRuntime.limitations.some((item) => typeof item !== "string" || item.trim() === "") || !["available", "degraded", "unavailable"].includes(input2.specialistRuntime.status);
38996
39112
  if (invalidRuntime) {
38997
39113
  return stopped("degraded", preReview, baseVerdict, [
@@ -39042,8 +39158,17 @@ function orchestrateMissionTeam(originalInput) {
39042
39158
  if (preparationRecovery?.replacement && firstImplementationSeq === void 0 && requiredSpecialists(input2.plan, "pre-implementation").some((id3) => input2.currentInputHashesByStage["pre-implementation"][id3] !== preparationRecovery.inputHashes[id3] || contractVersions(input2.plan)[id3] !== preparationRecovery.contractVersions[id3])) {
39043
39159
  return stopped("blocked", preReview, baseVerdict, ["Preparation replacement inputs or contracts changed; reconcile the admitted subject before dispatch"]);
39044
39160
  }
39045
- if (firstImplementationSeq !== void 0 && input2.stream.events.some((event) => event.kind === "specialist.completed" && event.seq > firstImplementationSeq && lifecycleField(event, "stage") === "pre-implementation")) {
39046
- return stopped("blocked", preReview, baseVerdict, ["pre-implementation review was replayed after implementation"]);
39161
+ const implementationBoundary = input2.stream.events.find((event) => event.kind === "lead-writer.requested" && leadWriterActionKind(event) === "run-lead-writer")?.seq ?? firstImplementationSeq;
39162
+ if (implementationBoundary !== void 0) {
39163
+ const latePreparation = observedPreparation.filter((event) => event.seq > implementationBoundary);
39164
+ const lifecycleReasons = unboundCompletionReasons(input2, start.runtime, latePreparation, implementationBoundary);
39165
+ const replayed = input2.stream.events.some((event) => event.seq > implementationBoundary && event.kind === "specialist.started" && lifecycleField(event, "stage") === "pre-implementation");
39166
+ if (replayed || lifecycleReasons.length > 0) {
39167
+ return stopped("blocked", preReview, baseVerdict, ["pre-implementation review was replayed after implementation", ...lifecycleReasons]);
39168
+ }
39169
+ }
39170
+ if (preReview.issues.length > 0) {
39171
+ return decideReviewPhase(start, preReview, baseVerdict, "pre-implementation");
39047
39172
  }
39048
39173
  const pendingEvidence = obligationStop(obligations, preReview, baseVerdict);
39049
39174
  const migration = migrated.migration;
@@ -39054,7 +39179,7 @@ function orchestrateMissionTeam(originalInput) {
39054
39179
  }
39055
39180
  const terminal = input2.stream.events.some((event) => event.seq > migration.seq && event.subject === receipt2.specialistId && ["specialist.completed", "specialist.failed"].includes(event.kind));
39056
39181
  const onlyAuthorObligations = obligations.issues.length === 0 && obligations.blockingObligationIds.every((id3) => receipt2.pendingAuthorObligationIds.includes(id3));
39057
- const preparationAdmitted = preReview.readyForVerdict || preparationCorrectionCompleted && !preparationFollowupRequired && firstImplementationSeq !== void 0;
39182
+ const preparationAdmitted = firstImplementationSeq !== void 0;
39058
39183
  if (!terminal && onlyAuthorObligations && preparationAdmitted) {
39059
39184
  const reasons = ["Explicit contract migration requires one fresh visual assessment; historical evidence obligations remain due"];
39060
39185
  return applyRuntimeCertification({
@@ -39083,12 +39208,7 @@ function orchestrateMissionTeam(originalInput) {
39083
39208
  ...start.boundedReview ? BOUNDED_REVIEW_BUDGET : { maxRounds: input2.maxReviewRounds },
39084
39209
  // Pending proofs are handled by obligationStop; they must not mask failed reviews.
39085
39210
  evidenceObligations: { ...obligations, blockingObligationIds: [] },
39086
- validProofIds: input2.stream.events.flatMap((event) => {
39087
- if (event.kind !== "evidence.recorded")
39088
- return [];
39089
- const parsed = parseEvidence(lifecycleField(event, "evidence"));
39090
- return parsed.ok && parsed.value.evidenceId === event.subject && parsed.value.missionId === event.missionId && parsed.value.status === "passed" && assessEvidence(parsed.value, input2.evidenceContext).status === "fresh" ? [parsed.value.evidenceId] : [];
39091
- }),
39211
+ validProofIds: freshReviewProofIds(input2.stream.events, input2.evidenceContext),
39092
39212
  ...migration === void 0 ? {} : { contractMigration: {
39093
39213
  specialistId: migration.receipt.specialistId,
39094
39214
  afterSeq: migration.seq,
@@ -39120,22 +39240,30 @@ function orchestrateMissionTeam(originalInput) {
39120
39240
  reasons
39121
39241
  }, input2.specialistRuntime);
39122
39242
  }
39123
- if (!preparationFollowupRequired && preparationCorrectionCompleted && latePreparationCompletion) {
39124
- return applyRuntimeCertification(stopped("blocked", preReview, baseVerdict, ["pre-implementation review was replayed after preparation correction"]), input2.specialistRuntime);
39243
+ if (preparationFollowupRequired && !preReview.readyForVerdict) {
39244
+ return applyRuntimeCertification(decideReviewPhase(start, preReview, baseVerdict, "pre-implementation"), input2.specialistRuntime);
39125
39245
  }
39126
- if (!preparationFollowupRequired && preparationCorrectionCompleted && firstImplementationSeq === void 0) {
39127
- const reasons = ["preparation correction completed; implementation is pending"];
39246
+ if (unresolvedPreparation.length > 0) {
39247
+ const reasons = unresolvedPreparation.flatMap((event) => {
39248
+ const completion = parseSpecialistCompletionValue(lifecycleField(event, "completion"));
39249
+ return completion === void 0 ? [] : [
39250
+ ...completion.findings.map((finding) => finding.summary),
39251
+ ...completion.limitations
39252
+ ];
39253
+ });
39254
+ if (preReview.staleSpecialists.some((id3) => unresolvedPreparation.some((event) => event.subject === id3))) {
39255
+ return stopped("blocked", preReview, baseVerdict, ["Existing preparation findings require reconciliation against changed inputs.", ...reasons]);
39256
+ }
39257
+ const decision = decideReviewPhase(start, preReview, baseVerdict, "pre-implementation");
39258
+ if (decision.action.kind === "run-preparation-correction" && decision.action.findingIds.length === 0) {
39259
+ return stopped("blocked", preReview, baseVerdict, ["Preparation requested changes without an actionable finding.", ...reasons]);
39260
+ }
39128
39261
  return applyRuntimeCertification({
39129
- phase: "implementation",
39130
- action: { kind: "run-lead-writer", writerId: start.leadWriterId },
39131
- review: preReview,
39132
- verdict: overrideVerdict(baseVerdict, "unverified", reasons),
39133
- reasons
39262
+ ...decision,
39263
+ reasons: [...decision.reasons, ...reasons],
39264
+ ...firstImplementationSeq !== void 0 && decision.action.kind === "run-preparation-correction" ? { action: { ...decision.action, kind: "run-correction" } } : {}
39134
39265
  }, input2.specialistRuntime);
39135
39266
  }
39136
- if (!preReview.readyForVerdict && (!preparationCorrectionCompleted || preparationFollowupRequired)) {
39137
- return applyRuntimeCertification(decideReviewPhase(start, preReview, baseVerdict, "pre-implementation"), input2.specialistRuntime);
39138
- }
39139
39267
  if (completions2.length === 0) {
39140
39268
  const reasons = ["lead writer implementation is incomplete"];
39141
39269
  return applyRuntimeCertification({
@@ -56469,7 +56597,7 @@ async function missionReviewBase(root) {
56469
56597
  throw new Error("MISSION_REVIEW_BASE_INVALID: a committed Git baseline is required");
56470
56598
  }
56471
56599
  }
56472
- async function captureMissionReviewSubject(root, baseCommit, committed = false, hashFormat = committed ? "committed" : "legacy") {
56600
+ async function validateMissionReviewBase(root, baseCommit) {
56473
56601
  if (baseCommit === void 0) {
56474
56602
  throw new Error(
56475
56603
  "MISSION_REVIEW_BASE_MISSING: legacy mission has no review baseline; preserve its history and start a new mission"
@@ -56479,8 +56607,13 @@ async function captureMissionReviewSubject(root, baseCommit, committed = false,
56479
56607
  try {
56480
56608
  await execFile2("git", ["merge-base", "--is-ancestor", baseCommit, "HEAD"], options2);
56481
56609
  } catch {
56482
- throw new Error("MISSION_REVIEW_BASE_INVALID: baseline is missing or is not an ancestor of HEAD");
56610
+ throw new Error("MISSION_REVIEW_BASE_INVALID: baseline is missing or is not an ancestor of HEAD; return to the mission worktree with a checkout descended from its bound base; preserve the journal");
56483
56611
  }
56612
+ return baseCommit;
56613
+ }
56614
+ async function captureMissionReviewSubject(root, baseCommit, committed = false, hashFormat = committed ? "committed" : "legacy") {
56615
+ const validatedBase = await validateMissionReviewBase(root, baseCommit);
56616
+ const options2 = { cwd: root, encoding: "utf8", timeout: 1e4, maxBuffer: 4e6 };
56484
56617
  try {
56485
56618
  const paths = ["--", ".", ":(exclude).void/machine/**"];
56486
56619
  const reviewedCommit = committed ? await missionReviewBase(root) : void 0;
@@ -56497,7 +56630,7 @@ async function captureMissionReviewSubject(root, baseCommit, committed = false,
56497
56630
  throw new Error("MISSION_REVIEW_UNCOMMITTED: commit the implementation before independent review");
56498
56631
  }
56499
56632
  }
56500
- const range = reviewedCommit === void 0 ? [baseCommit] : [baseCommit, reviewedCommit];
56633
+ const range = reviewedCommit === void 0 ? [validatedBase] : [validatedBase, reviewedCommit];
56501
56634
  const [patch, names, untracked] = await Promise.all([
56502
56635
  execFile2("git", [
56503
56636
  "diff",
@@ -56547,11 +56680,11 @@ async function captureMissionReviewSubject(root, baseCommit, committed = false,
56547
56680
  throw new Error("MISSION_REVIEW_CHANGED: HEAD changed while capturing the review subject");
56548
56681
  }
56549
56682
  return Object.freeze({
56550
- ...reviewedCommit === void 0 ? {} : { baseCommit, reviewedCommit },
56683
+ ...reviewedCommit === void 0 ? {} : { baseCommit: validatedBase, reviewedCommit },
56551
56684
  diff: patch.stdout,
56552
56685
  files,
56553
56686
  hash: canonicalJsonHash({
56554
- baseCommit,
56687
+ baseCommit: validatedBase,
56555
56688
  ...hashFormat === "legacy" ? {} : { reviewedCommit },
56556
56689
  diff: patch.stdout,
56557
56690
  files: files.files
@@ -67223,7 +67356,7 @@ function parseMissionRecoveryRequest(value) {
67223
67356
  };
67224
67357
  if (disposition["kind"] === "controller-defect") {
67225
67358
  const defect = disposition["defect"];
67226
- if (!exact2(disposition, ["kind", "defect"]) || defect !== "partial-fanout-round" && defect !== "stale-input-dispatch" && defect !== "opaque-native-context") invalid11();
67359
+ if (!exact2(disposition, ["kind", "defect"]) || defect !== "partial-fanout-round" && defect !== "stale-input-dispatch" && defect !== "opaque-native-context" && defect !== "targeted-review-context") invalid11();
67227
67360
  return { ...base, disposition: { kind: "controller-defect", defect } };
67228
67361
  }
67229
67362
  if (disposition["kind"] === "evidence-disposition") {
@@ -67836,7 +67969,7 @@ async function recordSpecialistLifecycle(root, missionId, input2, artifactRoot =
67836
67969
  const failed2 = input2.envelope.reviewSubject !== void 0 && dispatchEvents.some((event) => event.kind === "specialist.failed" && event.seq > existing.seq && sameDispatch(event, input2.envelope) && field8(event.payload, "contextId") === field8(existing.payload, "contextId"));
67837
67970
  if (!failed2) invalid14("dispatch already started with another contextId");
67838
67971
  }
67839
- if (recovery !== void 0 && input2.contextId !== void 0 && events.some((event) => event.kind === "specialist.started" && field8(event.payload, "contextId") === input2.contextId)) {
67972
+ if (recovery !== void 0 && input2.contextId !== void 0 && events.some((event) => event.kind === "specialist.started" && field8(event.payload, "contextId") === input2.contextId) && !canContinueNativeReview(events, requested, input2.contextId)) {
67840
67973
  invalid14("contextId was already used by a previous dispatch");
67841
67974
  }
67842
67975
  } else {
@@ -67868,7 +68001,7 @@ async function recordSpecialistLifecycle(root, missionId, input2, artifactRoot =
67868
68001
  if (observedMissionLifecycle(current).episodeId !== lifecycle.episodeId) {
67869
68002
  invalid14("mission episode changed before recording lifecycle");
67870
68003
  }
67871
- if (recovery !== void 0 && input2.status === "started" && input2.contextId !== void 0 && current.some((event) => event.kind === "specialist.started" && field8(event.payload, "contextId") === input2.contextId && event.eventId !== eventId3)) {
68004
+ if (recovery !== void 0 && input2.status === "started" && input2.contextId !== void 0 && current.some((event) => event.kind === "specialist.started" && field8(event.payload, "contextId") === input2.contextId && event.eventId !== eventId3) && !canContinueNativeReview(current, requested, input2.contextId)) {
67872
68005
  invalid14("contextId was already used by a previous dispatch");
67873
68006
  }
67874
68007
  }
@@ -68026,7 +68159,7 @@ async function recordSpecialistRequests(root, missionId, envelopes, planHash) {
68026
68159
  init_esm_shims();
68027
68160
 
68028
68161
  // package.json
68029
- var version2 = "5.0.6";
68162
+ var version2 = "5.0.8";
68030
68163
 
68031
68164
  // src/lib/runs/verify.ts
68032
68165
  var MAX_CAPTURE_BYTES = 64 * 1024;
@@ -68597,6 +68730,7 @@ async function recoverStoppedMission(roots, missionId, request) {
68597
68730
  const identity2 = missionRuntimeIdentity(inspected.stream.events);
68598
68731
  const coordinator = coordinatorRuntimeIdentity(process.env);
68599
68732
  const provenanceRecovery = request.disposition.kind === "review-provenance";
68733
+ const targetedContextRecovery = request.disposition.kind === "controller-defect" && request.disposition.defect === "targeted-review-context";
68600
68734
  if (identity2 === void 0 || coordinator.attested && coordinator.runtime !== identity2.runtime || !provenanceRecovery && (!identity2.attested || !coordinator.attested)) {
68601
68735
  throw new Error("MISSION_RECOVERY_RUNTIME: observe the original native runtime before recovery");
68602
68736
  }
@@ -68650,10 +68784,10 @@ async function recoverStoppedMission(roots, missionId, request) {
68650
68784
  const provenance4 = request.disposition.kind !== "review-provenance" ? void 0 : await readRecoveryReviewBindings(workRoot, request.disposition.resolutionArtifact.path);
68651
68785
  const resolutionArtifact = provenance4?.artifact ?? (request.disposition.kind === "review-blocker" || request.disposition.kind === "command-correction" ? await recoveryResolutionArtifact(workRoot, request.disposition.resolutionArtifact.path) : void 0);
68652
68786
  const opaqueContextRecovery = request.disposition.kind === "controller-defect" && request.disposition.defect === "opaque-native-context";
68653
- const recoverySpecialists = opaqueContextRecovery || request.disposition.kind === "preparation-invocation" ? migration.plan.specialists.filter((value) => value.state === "applicable" && value.stages?.includes("pre-implementation")) : migration.plan.specialists;
68787
+ const recoverySpecialists = targetedContextRecovery ? migration.plan.specialists.filter((value) => value.specialistId === "core:independent-code-reviewer") : opaqueContextRecovery || request.disposition.kind === "preparation-invocation" ? migration.plan.specialists.filter((value) => value.state === "applicable" && value.stages?.includes("pre-implementation")) : migration.plan.specialists;
68654
68788
  const observation2 = {
68655
- ...provenance4 === void 0 || subject?.baseCommit === void 0 || subject.reviewedCommit === void 0 ? {} : {
68656
- reviewBindings: provenance4.bindings,
68789
+ ...provenance4 === void 0 && !targetedContextRecovery || subject?.baseCommit === void 0 || subject.reviewedCommit === void 0 ? {} : {
68790
+ ...provenance4 === void 0 ? {} : { reviewBindings: provenance4.bindings },
68657
68791
  reviewSubject: {
68658
68792
  taskId: missionId,
68659
68793
  baseCommit: subject.baseCommit,
@@ -68725,6 +68859,9 @@ async function dispatchMissionSpecialists(roots, input2, generatedAt = (/* @__PU
68725
68859
  const inspected = current?.inspected ?? initialInspection;
68726
68860
  requireOpenMission(inspected.stream.events);
68727
68861
  const implemented = inspected.stream.events.some((event) => event.kind === "lead-writer.completed" && isUnknownRecord(event.payload) && event.payload.actionKind !== "run-preparation-correction");
68862
+ if (!implemented && stored.baseCommit !== void 0) {
68863
+ await validateMissionReviewBase(workRoot, stored.baseCommit);
68864
+ }
68728
68865
  const reviewSubject = implemented ? await captureControllerReviewSubject(workRoot, stored.baseCommit, inspected.stream.events) : void 0;
68729
68866
  const live = await planBoundMission(
68730
68867
  workRoot,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "voidmachine",
3
- "version": "5.0.6",
3
+ "version": "5.0.8",
4
4
  "description": "void-machine CLI — install an opinionated engineering doctrine on any project; render deterministic project health. Run via `npx voidmachine`; command: void-machine (alias vm).",
5
5
  "license": "MIT",
6
6
  "author": "Folpe @ VoidCorp",
@@ -58,10 +58,10 @@
58
58
  "ws": "8.21.0",
59
59
  "yaml": "^2.5.0",
60
60
  "zod": "^4.6.5",
61
+ "@voidcorp/harness-graph": "5.0.8",
62
+ "@voidcorp/hook-runner": "0.0.0",
61
63
  "@voidcorp/mission-engine": "0.0.0",
62
- "@voidcorp/void-machine": "0.0.0",
63
- "@voidcorp/harness-graph": "5.0.6",
64
- "@voidcorp/hook-runner": "0.0.0"
64
+ "@voidcorp/void-machine": "0.0.0"
65
65
  },
66
66
  "scripts": {
67
67
  "build": "tsup",