voidmachine 5.0.5 → 5.0.7

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.5",
3
+ "version": "5.0.7",
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:56012433234f5e7d537066868543df205e92f817003384489c7e698778a49ab2"
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
  },
@@ -4677,7 +4677,7 @@
4677
4677
  }
4678
4678
  ],
4679
4679
  "id": "skill:void-orchestrate",
4680
- "lines": 327,
4680
+ "lines": 328,
4681
4681
  "name": "void-orchestrate",
4682
4682
  "owner": "folpe",
4683
4683
  "pack": null,
@@ -4686,7 +4686,7 @@
4686
4686
  "codex"
4687
4687
  ],
4688
4688
  "source": "packages/core/skills/void-orchestrate/SKILL.md",
4689
- "staticTokens": 4673,
4689
+ "staticTokens": 4684,
4690
4690
  "type": "skill"
4691
4691
  }
4692
4692
  },
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "harnessVersion": "5.0.5",
3
+ "harnessVersion": "5.0.7",
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
  {
@@ -2415,7 +2415,7 @@
2415
2415
  }
2416
2416
  ],
2417
2417
  "id": "skill:void-orchestrate",
2418
- "lines": 327,
2418
+ "lines": 328,
2419
2419
  "name": "void-orchestrate",
2420
2420
  "owner": "folpe",
2421
2421
  "pack": null,
@@ -2424,7 +2424,7 @@
2424
2424
  "codex"
2425
2425
  ],
2426
2426
  "source": "packages/core/skills/void-orchestrate/SKILL.md",
2427
- "staticTokens": 4673,
2427
+ "staticTokens": 4684,
2428
2428
  "type": "skill"
2429
2429
  },
2430
2430
  {
@@ -13035,7 +13035,7 @@ var selectionSchema = strictObject({
13035
13035
  mission: identifier,
13036
13036
  coordinator: strictObject({ label: identifier, worktree: path })
13037
13037
  });
13038
- var instant = iso_exports.datetime();
13038
+ var instant = iso_exports.datetime({ offset: true });
13039
13039
  var status = _enum(["planned", "running", "blocked", "done", "failed"]);
13040
13040
  var workerSchema = strictObject({
13041
13041
  label: identifier,
@@ -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.
@@ -161,7 +161,8 @@ mission bytes; exceeding a bound is incomplete, never a unique match.
161
161
  See the shipped technical contract `docs/ORCHESTRA.md` in the harness source for
162
162
  schemas; the operational fields are listed here so an installed skill is sufficient.
163
163
 
164
- - mission.md: schema, mission, project, status, updated_at (UTC ISO), repository
164
+ - mission.md: schema, mission, project, status, updated_at
165
+ (ISO with `Z` or `+HH:MM`/`-HH:MM`; prefer UTC `Z`), repository
165
166
  (canonical Git common path), coordinator {label, worktree, workspace?}, workers.
166
167
  - Worker entries: label, agent (codex or claude), worktree, branch, ticket, status,
167
168
  attempt; optional pane_id and session are hints only. At most four workers.
package/dist/main.js CHANGED
@@ -35813,7 +35813,19 @@ function collectCompletions(events, stage, expectedSource, stageStartSeqExclusiv
35813
35813
  const contextIds = /* @__PURE__ */ new Set();
35814
35814
  const currentFailedRounds = [];
35815
35815
  let historicalHighestRound = 0;
35816
- const inStageWindow = (event) => (afterSeqExclusive === void 0 || event.seq > afterSeqExclusive) && (beforeSeqExclusive === void 0 || event.seq < beforeSeqExclusive);
35816
+ const inStageWindow = (event) => {
35817
+ let seq = event.seq;
35818
+ if (stage === "pre-implementation" && event.kind === "specialist.completed") {
35819
+ const payload = record7(event.payload);
35820
+ const mission2 = events.find((candidate) => candidate.kind === "mission.started" && candidate.missionId === event.missionId && candidate.seq < event.seq);
35821
+ const planHash = record7(mission2?.payload)?.["planHash"];
35822
+ const matches = (candidate) => candidate.missionId === event.missionId && candidate.subject === event.subject && ["stage", "reviewRound", "inputHash", "contractVersion"].every((key) => record7(candidate.payload)?.[key] === payload?.[key]);
35823
+ 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);
35824
+ 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)))
35825
+ seq = invocation.seq;
35826
+ }
35827
+ return (afterSeqExclusive === void 0 || seq > afterSeqExclusive) && (beforeSeqExclusive === void 0 || seq < beforeSeqExclusive);
35828
+ };
35817
35829
  for (const event of events) {
35818
35830
  const payload = record7(event.payload);
35819
35831
  if (event.kind === "specialist.failed") {
@@ -38739,10 +38751,10 @@ function lifecycleField(event, key) {
38739
38751
  function sameSpecialistDispatch(left, right) {
38740
38752
  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
38753
  }
38742
- function unboundCompletionReasons(input2, runtime2) {
38754
+ function unboundCompletionReasons(input2, runtime2, completions2 = input2.stream.events.filter((event) => event.kind === "specialist.completed"), invocationBeforeSeq) {
38743
38755
  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));
38756
+ for (const completion of completions2) {
38757
+ 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
38758
  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
38759
  if (requested === void 0 || started === void 0) {
38748
38760
  reasons.push(`specialist lifecycle is unbound: ${completion.subject}`);
@@ -38816,7 +38828,7 @@ function decideReviewPhase(start, review, baseVerdict, stage) {
38816
38828
  action: stage === "pre-implementation" ? {
38817
38829
  kind: "run-preparation-correction",
38818
38830
  writerId: start.leadWriterId,
38819
- findingIds: review.findings.filter((finding) => !start.boundedReview || finding.classification === "blocking").map((finding) => finding.findingId)
38831
+ findingIds: review.findings.filter((finding) => finding.classification !== "advisory").map((finding) => finding.findingId)
38820
38832
  } : {
38821
38833
  kind: "run-correction",
38822
38834
  writerId: start.leadWriterId,
@@ -38897,15 +38909,6 @@ function retainedPreparationSpecialists(input2, events, boundary, targets) {
38897
38909
  return result?.["verdict"] === "pass" && result["contractVersion"] === contractVersions(input2.plan)[id3] && lifecycleField(latest, "inputHash") === input2.currentInputHashesByStage["pre-implementation"][id3];
38898
38910
  });
38899
38911
  }
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
38912
  function pendingRecoveredCorrectionFindingIds(events) {
38910
38913
  const recovery = events.filter((event) => event.kind === "mission.recovered").at(-1);
38911
38914
  if (recovery === void 0 || events.some((event) => event.seq > recovery.seq && event.kind === "lead-writer.completed"))
@@ -38962,14 +38965,18 @@ function orchestrateMissionTeam(originalInput) {
38962
38965
  const firstWriterSeq = completions2.length === 0 ? void 0 : Math.min(...completions2.map((event) => event.seq));
38963
38966
  const implementationCompletions = completions2.filter((event) => leadWriterActionKind(event) !== "run-preparation-correction");
38964
38967
  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");
38968
+ const preparationFollowupRequired = preparationRecovery !== void 0;
38968
38969
  const lastWriterSeq = completions2.length === 0 ? void 0 : Math.max(...completions2.map((event) => event.seq));
38969
38970
  const missionStartSeq = input2.stream.events.find((event) => event.kind === "mission.started")?.seq;
38970
38971
  const expectedSource = start.runtime === "claude" ? "runtime:claude" : "runtime:codex";
38971
38972
  const obligations = evidenceObligations(input2, expectedSource, firstImplementationSeq === void 0 ? "pre-implementation" : "post-implementation");
38972
38973
  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));
38974
+ const preparationBoundary = Math.max(lastPreparationSeq ?? 0, ...completions2.filter((event) => leadWriterActionKind(event) === "run-correction").map((event) => event.seq));
38975
+ const observedPreparation = reviewEvents.filter((event) => event.kind === "specialist.completed" && lifecycleField(event, "stage") === "pre-implementation");
38976
+ const unresolvedPreparation = [...new Map(observedPreparation.filter((event) => event.seq > preparationBoundary).map((event) => [event.subject, event])).values()].filter((event) => {
38977
+ const completion = parseSpecialistCompletionValue(lifecycleField(event, "completion"));
38978
+ return completion !== void 0 && completion.verdict !== "pass" && (completion.verdict !== "changes-requested" || completion.findings.length === 0 || completion.findings.some((finding) => finding.classification !== "advisory"));
38979
+ });
38973
38980
  const preReview = reduceReviewLoop({
38974
38981
  stage: "pre-implementation",
38975
38982
  expectedSource,
@@ -38977,9 +38984,10 @@ function orchestrateMissionTeam(originalInput) {
38977
38984
  stageStartSeqExclusive: missionStartSeq,
38978
38985
  afterSeqExclusive: lastPreparationSeq
38979
38986
  },
38980
- ...firstWriterSeq === void 0 ? {} : { beforeSeqExclusive: firstWriterSeq },
38981
38987
  events: reviewEvents,
38982
- requiredSpecialists: requiredSpecialists(input2.plan, "pre-implementation"),
38988
+ // Historical advice is inspected, but the frozen plan is no longer a
38989
+ // compulsory preparation quorum. Explicit admitted recoveries retain theirs.
38990
+ requiredSpecialists: requiredSpecialists(input2.plan, "pre-implementation").filter((id3) => preparationRecovery !== void 0 || (unresolvedPreparation.length > 0 ? unresolvedPreparation : observedPreparation).some((event) => event.subject === id3)),
38983
38991
  contractVersions: contractVersions(input2.plan),
38984
38992
  currentInputHashes: input2.currentInputHashesByStage["pre-implementation"],
38985
38993
  maxRounds: input2.maxReviewRounds,
@@ -38987,11 +38995,17 @@ function orchestrateMissionTeam(originalInput) {
38987
38995
  ...obligations,
38988
38996
  blockingObligationIds: obligations.blockingObligationIds.filter((id3) => preparationObligationIds.has(id3))
38989
38997
  },
38998
+ ...preparationRecovery === void 0 && lastPreparationSeq !== void 0 && missionStartSeq !== void 0 ? {
38999
+ retainedSpecialists: requiredSpecialists(input2.plan, "pre-implementation").filter((id3) => unresolvedPreparation.some((event) => event.subject === id3))
39000
+ } : {},
38990
39001
  ...!preparationFollowupRequired || lastPreparationSeq === void 0 || preparationRecovery !== void 0 && lastPreparationSeq <= preparationRecovery.recoverySeq ? {} : {
38991
39002
  retainedSpecialists: retainedPreparationSpecialists(input2, reviewEvents, lastPreparationSeq, preparationRecovery?.specialistIds ?? [])
38992
39003
  }
38993
39004
  });
38994
39005
  const baseVerdict = deriveMissionVerdict(input2.stream, input2.evidenceContext);
39006
+ if (baseVerdict.status === "degraded") {
39007
+ return stopped("degraded", preReview, baseVerdict, baseVerdict.reasons);
39008
+ }
38995
39009
  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
39010
  if (invalidRuntime) {
38997
39011
  return stopped("degraded", preReview, baseVerdict, [
@@ -39042,8 +39056,17 @@ function orchestrateMissionTeam(originalInput) {
39042
39056
  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
39057
  return stopped("blocked", preReview, baseVerdict, ["Preparation replacement inputs or contracts changed; reconcile the admitted subject before dispatch"]);
39044
39058
  }
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"]);
39059
+ const implementationBoundary = input2.stream.events.find((event) => event.kind === "lead-writer.requested" && leadWriterActionKind(event) === "run-lead-writer")?.seq ?? firstImplementationSeq;
39060
+ if (implementationBoundary !== void 0) {
39061
+ const latePreparation = observedPreparation.filter((event) => event.seq > implementationBoundary);
39062
+ const lifecycleReasons = unboundCompletionReasons(input2, start.runtime, latePreparation, implementationBoundary);
39063
+ const replayed = input2.stream.events.some((event) => event.seq > implementationBoundary && event.kind === "specialist.started" && lifecycleField(event, "stage") === "pre-implementation");
39064
+ if (replayed || lifecycleReasons.length > 0) {
39065
+ return stopped("blocked", preReview, baseVerdict, ["pre-implementation review was replayed after implementation", ...lifecycleReasons]);
39066
+ }
39067
+ }
39068
+ if (preReview.issues.length > 0) {
39069
+ return decideReviewPhase(start, preReview, baseVerdict, "pre-implementation");
39047
39070
  }
39048
39071
  const pendingEvidence = obligationStop(obligations, preReview, baseVerdict);
39049
39072
  const migration = migrated.migration;
@@ -39054,7 +39077,7 @@ function orchestrateMissionTeam(originalInput) {
39054
39077
  }
39055
39078
  const terminal = input2.stream.events.some((event) => event.seq > migration.seq && event.subject === receipt2.specialistId && ["specialist.completed", "specialist.failed"].includes(event.kind));
39056
39079
  const onlyAuthorObligations = obligations.issues.length === 0 && obligations.blockingObligationIds.every((id3) => receipt2.pendingAuthorObligationIds.includes(id3));
39057
- const preparationAdmitted = preReview.readyForVerdict || preparationCorrectionCompleted && !preparationFollowupRequired && firstImplementationSeq !== void 0;
39080
+ const preparationAdmitted = firstImplementationSeq !== void 0;
39058
39081
  if (!terminal && onlyAuthorObligations && preparationAdmitted) {
39059
39082
  const reasons = ["Explicit contract migration requires one fresh visual assessment; historical evidence obligations remain due"];
39060
39083
  return applyRuntimeCertification({
@@ -39120,22 +39143,30 @@ function orchestrateMissionTeam(originalInput) {
39120
39143
  reasons
39121
39144
  }, input2.specialistRuntime);
39122
39145
  }
39123
- if (!preparationFollowupRequired && preparationCorrectionCompleted && latePreparationCompletion) {
39124
- return applyRuntimeCertification(stopped("blocked", preReview, baseVerdict, ["pre-implementation review was replayed after preparation correction"]), input2.specialistRuntime);
39146
+ if (preparationFollowupRequired && !preReview.readyForVerdict) {
39147
+ return applyRuntimeCertification(decideReviewPhase(start, preReview, baseVerdict, "pre-implementation"), input2.specialistRuntime);
39125
39148
  }
39126
- if (!preparationFollowupRequired && preparationCorrectionCompleted && firstImplementationSeq === void 0) {
39127
- const reasons = ["preparation correction completed; implementation is pending"];
39149
+ if (unresolvedPreparation.length > 0) {
39150
+ const reasons = unresolvedPreparation.flatMap((event) => {
39151
+ const completion = parseSpecialistCompletionValue(lifecycleField(event, "completion"));
39152
+ return completion === void 0 ? [] : [
39153
+ ...completion.findings.map((finding) => finding.summary),
39154
+ ...completion.limitations
39155
+ ];
39156
+ });
39157
+ if (preReview.staleSpecialists.some((id3) => unresolvedPreparation.some((event) => event.subject === id3))) {
39158
+ return stopped("blocked", preReview, baseVerdict, ["Existing preparation findings require reconciliation against changed inputs.", ...reasons]);
39159
+ }
39160
+ const decision = decideReviewPhase(start, preReview, baseVerdict, "pre-implementation");
39161
+ if (decision.action.kind === "run-preparation-correction" && decision.action.findingIds.length === 0) {
39162
+ return stopped("blocked", preReview, baseVerdict, ["Preparation requested changes without an actionable finding.", ...reasons]);
39163
+ }
39128
39164
  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
39165
+ ...decision,
39166
+ reasons: [...decision.reasons, ...reasons],
39167
+ ...firstImplementationSeq !== void 0 && decision.action.kind === "run-preparation-correction" ? { action: { ...decision.action, kind: "run-correction" } } : {}
39134
39168
  }, input2.specialistRuntime);
39135
39169
  }
39136
- if (!preReview.readyForVerdict && (!preparationCorrectionCompleted || preparationFollowupRequired)) {
39137
- return applyRuntimeCertification(decideReviewPhase(start, preReview, baseVerdict, "pre-implementation"), input2.specialistRuntime);
39138
- }
39139
39170
  if (completions2.length === 0) {
39140
39171
  const reasons = ["lead writer implementation is incomplete"];
39141
39172
  return applyRuntimeCertification({
@@ -56469,7 +56500,7 @@ async function missionReviewBase(root) {
56469
56500
  throw new Error("MISSION_REVIEW_BASE_INVALID: a committed Git baseline is required");
56470
56501
  }
56471
56502
  }
56472
- async function captureMissionReviewSubject(root, baseCommit, committed = false, hashFormat = committed ? "committed" : "legacy") {
56503
+ async function validateMissionReviewBase(root, baseCommit) {
56473
56504
  if (baseCommit === void 0) {
56474
56505
  throw new Error(
56475
56506
  "MISSION_REVIEW_BASE_MISSING: legacy mission has no review baseline; preserve its history and start a new mission"
@@ -56479,8 +56510,13 @@ async function captureMissionReviewSubject(root, baseCommit, committed = false,
56479
56510
  try {
56480
56511
  await execFile2("git", ["merge-base", "--is-ancestor", baseCommit, "HEAD"], options2);
56481
56512
  } catch {
56482
- throw new Error("MISSION_REVIEW_BASE_INVALID: baseline is missing or is not an ancestor of HEAD");
56513
+ 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
56514
  }
56515
+ return baseCommit;
56516
+ }
56517
+ async function captureMissionReviewSubject(root, baseCommit, committed = false, hashFormat = committed ? "committed" : "legacy") {
56518
+ const validatedBase = await validateMissionReviewBase(root, baseCommit);
56519
+ const options2 = { cwd: root, encoding: "utf8", timeout: 1e4, maxBuffer: 4e6 };
56484
56520
  try {
56485
56521
  const paths = ["--", ".", ":(exclude).void/machine/**"];
56486
56522
  const reviewedCommit = committed ? await missionReviewBase(root) : void 0;
@@ -56497,7 +56533,7 @@ async function captureMissionReviewSubject(root, baseCommit, committed = false,
56497
56533
  throw new Error("MISSION_REVIEW_UNCOMMITTED: commit the implementation before independent review");
56498
56534
  }
56499
56535
  }
56500
- const range = reviewedCommit === void 0 ? [baseCommit] : [baseCommit, reviewedCommit];
56536
+ const range = reviewedCommit === void 0 ? [validatedBase] : [validatedBase, reviewedCommit];
56501
56537
  const [patch, names, untracked] = await Promise.all([
56502
56538
  execFile2("git", [
56503
56539
  "diff",
@@ -56547,11 +56583,11 @@ async function captureMissionReviewSubject(root, baseCommit, committed = false,
56547
56583
  throw new Error("MISSION_REVIEW_CHANGED: HEAD changed while capturing the review subject");
56548
56584
  }
56549
56585
  return Object.freeze({
56550
- ...reviewedCommit === void 0 ? {} : { baseCommit, reviewedCommit },
56586
+ ...reviewedCommit === void 0 ? {} : { baseCommit: validatedBase, reviewedCommit },
56551
56587
  diff: patch.stdout,
56552
56588
  files,
56553
56589
  hash: canonicalJsonHash({
56554
- baseCommit,
56590
+ baseCommit: validatedBase,
56555
56591
  ...hashFormat === "legacy" ? {} : { reviewedCommit },
56556
56592
  diff: patch.stdout,
56557
56593
  files: files.files
@@ -68026,7 +68062,7 @@ async function recordSpecialistRequests(root, missionId, envelopes, planHash) {
68026
68062
  init_esm_shims();
68027
68063
 
68028
68064
  // package.json
68029
- var version2 = "5.0.5";
68065
+ var version2 = "5.0.7";
68030
68066
 
68031
68067
  // src/lib/runs/verify.ts
68032
68068
  var MAX_CAPTURE_BYTES = 64 * 1024;
@@ -68725,6 +68761,9 @@ async function dispatchMissionSpecialists(roots, input2, generatedAt = (/* @__PU
68725
68761
  const inspected = current?.inspected ?? initialInspection;
68726
68762
  requireOpenMission(inspected.stream.events);
68727
68763
  const implemented = inspected.stream.events.some((event) => event.kind === "lead-writer.completed" && isUnknownRecord(event.payload) && event.payload.actionKind !== "run-preparation-correction");
68764
+ if (!implemented && stored.baseCommit !== void 0) {
68765
+ await validateMissionReviewBase(workRoot, stored.baseCommit);
68766
+ }
68728
68767
  const reviewSubject = implemented ? await captureControllerReviewSubject(workRoot, stored.baseCommit, inspected.stream.events) : void 0;
68729
68768
  const live = await planBoundMission(
68730
68769
  workRoot,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "voidmachine",
3
- "version": "5.0.5",
3
+ "version": "5.0.7",
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",
@@ -60,8 +60,8 @@
60
60
  "zod": "^4.6.5",
61
61
  "@voidcorp/hook-runner": "0.0.0",
62
62
  "@voidcorp/mission-engine": "0.0.0",
63
- "@voidcorp/void-machine": "0.0.0",
64
- "@voidcorp/harness-graph": "5.0.5"
63
+ "@voidcorp/harness-graph": "5.0.7",
64
+ "@voidcorp/void-machine": "0.0.0"
65
65
  },
66
66
  "scripts": {
67
67
  "build": "tsup",