sortie-dogs 0.9.8 → 0.9.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -22,7 +22,7 @@ Requirements: Node.js 22.6 or newer, npm, and OpenCode.
22
22
 
23
23
  Guides: [日本語](docs/guide-ja.md) · [简体中文](docs/guide-zh-CN.md) · [CLI testing](docs/cli-testing.md)
24
24
 
25
- Release: [v0.9.8](https://github.com/zufall-upon/Sortie-dogs/releases/tag/v0.9.8)
25
+ Release: [v0.9.9](https://github.com/zufall-upon/Sortie-dogs/releases/tag/v0.9.9)
26
26
 
27
27
  ## Why Sortie-dogs?
28
28
 
@@ -2,5 +2,5 @@
2
2
  * Version of the installable runtime assets. Kept in its own module so the plugin can compare an
3
3
  * installed project marker without importing every asset body.
4
4
  */
5
- export declare const RUNTIME_ASSET_VERSION = "0.3.84-dispatch-recovery-v1";
5
+ export declare const RUNTIME_ASSET_VERSION = "0.3.86-codegen-proof-v1";
6
6
  export type RuntimeAssetVersion = typeof RUNTIME_ASSET_VERSION;
@@ -2,4 +2,4 @@
2
2
  * Version of the installable runtime assets. Kept in its own module so the plugin can compare an
3
3
  * installed project marker without importing every asset body.
4
4
  */
5
- export const RUNTIME_ASSET_VERSION = "0.3.84-dispatch-recovery-v1";
5
+ export const RUNTIME_ASSET_VERSION = "0.3.86-codegen-proof-v1";
@@ -221,7 +221,7 @@ export function reduceGoalFlight(records) {
221
221
  event.consumed >= 0 && event.consumed <= event.limit && event.operation_id.length > 0 && event.evidence_key.length > 0, "invalid", "Validation admission is malformed.");
222
222
  if (event.decision === "ALLOW") {
223
223
  requireState(event.scope !== null && event.consumed === state.validation_budget.consumed + 1 &&
224
- (state.validation_budget.limit === null || state.validation_budget.limit === event.limit) &&
224
+ (state.validation_budget.limit === null || event.limit >= state.validation_budget.limit) &&
225
225
  !state.validation_budget.evidence_keys.includes(event.evidence_key) &&
226
226
  !state.validation_budget.reservations.some((entry) => entry.reservation_id === event.reservation_id), "budget", "Validation admission is stale, duplicated, or exhausted.");
227
227
  state = { ...state, validation_budget: { consumed: event.consumed, limit: event.limit,
@@ -483,6 +483,8 @@ export function createContinuationHooks(client, directory, policySource, timings
483
483
  return false;
484
484
  if (terminalCheckpoint(text))
485
485
  return false;
486
+ if (topLevelProtocolLines(text).some(({ line }) => /^status\s*:\s*IN_PROGRESS(?:\s|$)/iu.test(line)))
487
+ return true;
486
488
  if (/➡️\s*(?:次action|next_action)\s*:\s*\S/iu.test(text))
487
489
  return true;
488
490
  if (checkpointStatus(text) === "BLOCKED" && !trueBlockerReport(text))
@@ -1009,6 +1011,7 @@ export function createContinuationHooks(client, directory, policySource, timings
1009
1011
  state.stepRecoveryActive = false;
1010
1012
  clearTimer(state.stepRecoveryTimer);
1011
1013
  state.stepRecoveryTimer = undefined;
1014
+ state.stepRecoveryDeferredReport = undefined;
1012
1015
  resetRecoveryStall(state);
1013
1016
  if (state.notRequiredRevision !== state.turnRevision &&
1014
1017
  !output.text.includes(ROLLOVER_MARKER) &&
@@ -1031,7 +1034,17 @@ export function createContinuationHooks(client, directory, policySource, timings
1031
1034
  }
1032
1035
  else if (nonTerminalProgress(state.latestCoordinatorReport) ||
1033
1036
  (state.stepRecoveryActive && !terminalCheckpoint(state.latestCoordinatorReport))) {
1034
- if (input.allowStepRecoveryFallback !== false) {
1037
+ if (input.allowStepRecoveryFallback === false) {
1038
+ state.stepRecoveryDeferredReport = state.latestCoordinatorReport;
1039
+ }
1040
+ else if (state.stepRecoveryDeferredReport === state.latestCoordinatorReport) {
1041
+ // A completed text-part event is not message completion. Release its deferred
1042
+ // recovery only when the persisted assistant message later confirms completion.
1043
+ state.stepRecoveryDeferredReport = undefined;
1044
+ state.idleDeferred = true;
1045
+ }
1046
+ else {
1047
+ state.stepRecoveryDeferredReport = undefined;
1035
1048
  scheduleStepRecovery(input.sessionID, state, state.latestCoordinatorReport);
1036
1049
  }
1037
1050
  }
@@ -1162,6 +1175,7 @@ export function createContinuationHooks(client, directory, policySource, timings
1162
1175
  state.attempts = 0;
1163
1176
  state.directUsed = false;
1164
1177
  state.latestCoordinatorReport = undefined;
1178
+ state.stepRecoveryDeferredReport = undefined;
1165
1179
  if (!synthetic) {
1166
1180
  state.lastStepContinueReport = undefined;
1167
1181
  state.lastStepContinueRevision = undefined;
@@ -1182,6 +1196,7 @@ export function createContinuationHooks(client, directory, policySource, timings
1182
1196
  state.stepRecoveryTimer = undefined;
1183
1197
  if (tool !== CONTINUATION_CAPABILITY) {
1184
1198
  state.latestCoordinatorReport = undefined;
1199
+ state.stepRecoveryDeferredReport = undefined;
1185
1200
  }
1186
1201
  },
1187
1202
  blocksTool(sessionID) {
@@ -1134,6 +1134,7 @@ export const SortieDogsPlugin = async (input, options) => {
1134
1134
  const hostGoalExecutions = new Map();
1135
1135
  const goalValidationDefects = new Set();
1136
1136
  const goalDeclarationAuthority = new Map();
1137
+ const explicitUserGoalUnitLimits = new Map();
1137
1138
  const pendingRealGoalTurns = new Map();
1138
1139
  const pendingGoalRecoveries = new Map();
1139
1140
  const scheduledGoalRecoveries = new Map();
@@ -1389,7 +1390,11 @@ export const SortieDogsPlugin = async (input, options) => {
1389
1390
  `unit:${unitID}`, "source_snapshot", "candidate", "command", "scope", "exit_code"]))], reason: "acceptance" };
1390
1391
  let reservation;
1391
1392
  try {
1392
- reservation = await ledger.reserveValidation(request, goal.budget?.max_units ?? 1);
1393
+ // A changed candidate produces a new evidence key and must not become a user-facing blocker
1394
+ // merely because earlier necessary validations consumed the initial estimate. Duplicate
1395
+ // evidence remains denied by reserveValidation before this limit is considered.
1396
+ const validationLimit = Math.max(goal.budget?.max_units ?? 1, goal.validation_budget.consumed + 1);
1397
+ reservation = await ledger.reserveValidation(request, validationLimit);
1393
1398
  }
1394
1399
  catch (error) {
1395
1400
  throw denyValidation(`authority-unavailable:${error instanceof Error ? error.name : "unknown"}`);
@@ -1895,8 +1900,14 @@ export const SortieDogsPlugin = async (input, options) => {
1895
1900
  }
1896
1901
  const declaration = validated.declaration;
1897
1902
  const units = Number(handoffValue(entries, ["goal_budget_units"]));
1898
- const maxUnits = Number.isSafeInteger(units) && units >= state.consumed_units && units > 0
1899
- ? units : state.budget?.max_units ?? 32;
1903
+ const declaredUnits = Number.isSafeInteger(units) && units >= state.consumed_units && units > 0
1904
+ ? units : undefined;
1905
+ // A model-authored Task declaration must not silently shrink the host's policy allowance.
1906
+ // It may request more capacity, while explicit later budget revisions remain cumulative.
1907
+ const explicitUserLimit = explicitUserGoalUnitLimits.get(sessionID);
1908
+ const maxUnits = declaredUnits === undefined ? state.budget?.max_units ?? 32 :
1909
+ state.budget?.source === "policy-default" && explicitUserLimit !== declaredUnits
1910
+ ? Math.max(state.budget.max_units, declaredUnits) : declaredUnits;
1900
1911
  const declaredTime = Number(handoffValue(entries, ["goal_budget_time_ms"]));
1901
1912
  const declaredCost = Number(handoffValue(entries, ["goal_budget_cost_usd"]));
1902
1913
  const timeBudget = Number.isFinite(declaredTime) && declaredTime > 0 ? declaredTime : state.budget?.time_ms ?? null;
@@ -1923,7 +1934,8 @@ export const SortieDogsPlugin = async (input, options) => {
1923
1934
  session_id: sessionID, selected_agent: state.selected_agent ?? COORDINATOR_AGENT,
1924
1935
  delivery: declaration.delivery, budget: { max_units: maxUnits,
1925
1936
  time_ms: timeBudget, cost_usd: costBudget,
1926
- source: Number.isSafeInteger(units) ? "accepted-plan" : state.budget?.source ?? "policy-default" },
1937
+ source: declaredUnits !== undefined && maxUnits !== state.budget?.max_units
1938
+ ? "accepted-plan" : state.budget?.source ?? "policy-default" },
1927
1939
  acceptance_contract: declaration.contract, reset_no_progress: true });
1928
1940
  goalDeclarationAuthority.delete(sessionID);
1929
1941
  return state;
@@ -2354,6 +2366,9 @@ export const SortieDogsPlugin = async (input, options) => {
2354
2366
  const bootstrapIdleWarnings = new Set();
2355
2367
  const coordinatorTaskCalls = new Map();
2356
2368
  const coordinatorTaskWatchdogs = new Map();
2369
+ // A deleted root is terminal for its current host lifetime. Retain only a bounded tombstone so
2370
+ // late generic events and already queued watchdog callbacks cannot recreate recovery state.
2371
+ const terminalCoordinatorTaskWatchdogs = new Set();
2357
2372
  const chatTransitions = new Map();
2358
2373
  const reflectionOwnedRoots = new Set();
2359
2374
  const reflectionClosingRoots = new Set();
@@ -3886,6 +3901,8 @@ export const SortieDogsPlugin = async (input, options) => {
3886
3901
  }
3887
3902
  }
3888
3903
  function beginCoordinatorTask(sessionID, callID) {
3904
+ if (terminalCoordinatorTaskWatchdogs.has(sessionID))
3905
+ return;
3889
3906
  const calls = coordinatorTaskCalls.get(sessionID) ?? new Set();
3890
3907
  calls.add(callID);
3891
3908
  coordinatorTaskCalls.set(sessionID, calls);
@@ -3921,7 +3938,7 @@ export const SortieDogsPlugin = async (input, options) => {
3921
3938
  coordinatorTaskWatchdogs.delete(sessionID);
3922
3939
  }
3923
3940
  function armCoordinatorTaskWatchdog(sessionID, activity) {
3924
- if (!coordinatorTaskCalls.has(sessionID))
3941
+ if (terminalCoordinatorTaskWatchdogs.has(sessionID) || !coordinatorTaskCalls.has(sessionID))
3925
3942
  return;
3926
3943
  const state = coordinatorTaskWatchdogs.get(sessionID) ?? {
3927
3944
  generation: 0,
@@ -3942,10 +3959,18 @@ export const SortieDogsPlugin = async (input, options) => {
3942
3959
  }
3943
3960
  function touchCoordinatorTaskWatchdog(sessionID) {
3944
3961
  const root = coordinatorRootForSession(sessionID);
3945
- if (root !== undefined && coordinatorTaskWatchdogs.has(root)) {
3962
+ if (root !== undefined && !terminalCoordinatorTaskWatchdogs.has(root) && coordinatorTaskWatchdogs.has(root)) {
3946
3963
  armCoordinatorTaskWatchdog(root, Date.now());
3947
3964
  }
3948
3965
  }
3966
+ function disarmDeletedCoordinatorTaskWatchdog(sessionID) {
3967
+ terminalCoordinatorTaskWatchdogs.delete(sessionID);
3968
+ terminalCoordinatorTaskWatchdogs.add(sessionID);
3969
+ while (terminalCoordinatorTaskWatchdogs.size > ACTIVE_SESSION_CACHE.maximum) {
3970
+ terminalCoordinatorTaskWatchdogs.delete(terminalCoordinatorTaskWatchdogs.values().next().value);
3971
+ }
3972
+ abortCoordinatorTasks(sessionID);
3973
+ }
3949
3974
  function sessionOwnedByRoot(sessionID, rootID) {
3950
3975
  return sessionID === rootID || sessionRoots.get(sessionID) === rootID || sessionParents.get(sessionID) === rootID;
3951
3976
  }
@@ -3974,6 +3999,8 @@ export const SortieDogsPlugin = async (input, options) => {
3974
3999
  return [...reasons].sort();
3975
4000
  }
3976
4001
  async function sweepCoordinatorTaskWatchdog(sessionID, generation) {
4002
+ if (terminalCoordinatorTaskWatchdogs.has(sessionID))
4003
+ return;
3977
4004
  const state = coordinatorTaskWatchdogs.get(sessionID);
3978
4005
  const calls = coordinatorTaskCalls.get(sessionID);
3979
4006
  if (state === undefined || calls === undefined || state.generation !== generation || state.recovering)
@@ -3990,7 +4017,8 @@ export const SortieDogsPlugin = async (input, options) => {
3990
4017
  return;
3991
4018
  }
3992
4019
  const reasons = await watchdogProtectedReasons(sessionID);
3993
- if (coordinatorTaskWatchdogs.get(sessionID) !== state || state.generation !== generation)
4020
+ if (terminalCoordinatorTaskWatchdogs.has(sessionID) ||
4021
+ coordinatorTaskWatchdogs.get(sessionID) !== state || state.generation !== generation)
3994
4022
  return;
3995
4023
  if (reasons.length > 0) {
3996
4024
  state.recovering = false;
@@ -4003,8 +4031,11 @@ export const SortieDogsPlugin = async (input, options) => {
4003
4031
  return;
4004
4032
  }
4005
4033
  const callIDs = [...calls];
4034
+ if (terminalCoordinatorTaskWatchdogs.has(sessionID))
4035
+ return;
4006
4036
  const result = await continuation.recoverStalledTask(sessionID, callIDs);
4007
- if (coordinatorTaskWatchdogs.get(sessionID) !== state || state.generation !== generation)
4037
+ if (terminalCoordinatorTaskWatchdogs.has(sessionID) ||
4038
+ coordinatorTaskWatchdogs.get(sessionID) !== state || state.generation !== generation)
4008
4039
  return;
4009
4040
  if (result === "recovered") {
4010
4041
  appLogInfo("batch-watchdog.recovered", sessionID, {
@@ -5630,11 +5661,11 @@ export const SortieDogsPlugin = async (input, options) => {
5630
5661
  await serializeChatTransition(chatInput.sessionID, async () => {
5631
5662
  const parentID = chatParentID(chatInput);
5632
5663
  const synthetic = output.parts.some((part) => isRecord(part) && part.synthetic === true);
5664
+ const selectedAgent = chatInput.agent ?? output.message.agent;
5633
5665
  if (parentID !== undefined)
5634
5666
  rememberParent(chatInput.sessionID, parentID);
5635
5667
  touchCoordinatorTaskWatchdog(chatInput.sessionID);
5636
5668
  const coordinatorRoot = isCoordinatorSession(chatInput.sessionID);
5637
- const selectedAgent = chatInput.agent ?? output.message.agent;
5638
5669
  if (chatInput.agent !== undefined && output.message.agent !== chatInput.agent) {
5639
5670
  output.message.agent = chatInput.agent;
5640
5671
  }
@@ -5662,6 +5693,10 @@ export const SortieDogsPlugin = async (input, options) => {
5662
5693
  }
5663
5694
  const coordinatorOrigin = parentID === undefined && requestedCoordinator;
5664
5695
  if (coordinatorOrigin) {
5696
+ // A proven explicit real root turn may reuse a host session identifier after deletion. Child
5697
+ // lineage rejection above runs first, so only a new root lifetime clears the tombstone.
5698
+ if (!synthetic)
5699
+ terminalCoordinatorTaskWatchdogs.delete(chatInput.sessionID);
5665
5700
  if (!synthetic)
5666
5701
  interruptedCoordinatorMessages.delete(chatInput.sessionID);
5667
5702
  if (synthetic) {
@@ -5672,6 +5707,12 @@ export const SortieDogsPlugin = async (input, options) => {
5672
5707
  else if (messageID !== undefined) {
5673
5708
  await acceptRealGoalTurn(chatInput.sessionID, messageID, selectedAgent, output.parts);
5674
5709
  goalDeclarationAuthority.set(chatInput.sessionID, messageID);
5710
+ const explicitUnits = /^\s*goal_budget_units:\s*([1-9][0-9]*)\s*$/imu
5711
+ .exec(output.parts.map(textPart).filter((text) => text !== undefined).join("\n"))?.[1];
5712
+ if (explicitUnits === undefined)
5713
+ explicitUserGoalUnitLimits.delete(chatInput.sessionID);
5714
+ else
5715
+ explicitUserGoalUnitLimits.set(chatInput.sessionID, Number(explicitUnits));
5675
5716
  }
5676
5717
  else if (selectedAgent !== undefined) {
5677
5718
  // Some native hosts persist the user message only after this hook returns. Defer to the
@@ -6630,6 +6671,10 @@ export const SortieDogsPlugin = async (input, options) => {
6630
6671
  }
6631
6672
  if (eventSessionID === undefined)
6632
6673
  return;
6674
+ // Deletion is terminal for watchdog recovery. Disarm synchronously before any generic event
6675
+ // processing can await, touch activity, or let a queued sweep recover the cancelled root.
6676
+ if (event.type === "session.deleted")
6677
+ disarmDeletedCoordinatorTaskWatchdog(eventSessionID);
6633
6678
  if (event.type === "message.updated" && info !== undefined) {
6634
6679
  rememberCoordinatorInterruption(eventSessionID, info);
6635
6680
  await acceptPersistedRealGoalEvent(eventSessionID, info);
@@ -6723,7 +6768,6 @@ export const SortieDogsPlugin = async (input, options) => {
6723
6768
  touchCoordinatorTaskWatchdog(eventSessionID);
6724
6769
  if (event.type === "session.deleted") {
6725
6770
  fastLane.forget(eventSessionID);
6726
- abortCoordinatorTasks(eventSessionID);
6727
6771
  if (reflectionStore !== undefined && reflectionConfiguration?.layers.run && reflectionOwnedRoots.has(eventSessionID)) {
6728
6772
  reflectionClosingRoots.add(eventSessionID);
6729
6773
  await waitForReflections(eventSessionID);
@@ -7,7 +7,7 @@ export interface RuntimeAsset {
7
7
  }
8
8
  export declare const runtimeAssets: readonly [{
9
9
  readonly name: "dog-coordinator";
10
- readonly version: "0.3.84-dispatch-recovery-v1";
10
+ readonly version: "0.3.86-codegen-proof-v1";
11
11
  readonly installPath: "agent/dog-coordinator.md";
12
12
  readonly content: `---
13
13
  description: Canonical MkII coordinator packaged by Sortie-dogs
@@ -1386,6 +1386,14 @@ explicitly record dog-reviewer skipped and permit staging. For a high-risk candi
1386
1386
  dog-reviewer only after canonical validation passes and require its PASS before the coordinator
1387
1387
  stages or commits. Return reviewer findings through dog-coordinator and fail closed while
1388
1388
  unreviewed. If dog-reviewer is unavailable or does not return PASS, fail closed before staging.
1389
+ Before risk classification or terminal DONE, require one criterion-level trace per accepted criterion:
1390
+ criterion -> changed or inspected implementation path -> concrete exercising test/input/branch -> PASS.
1391
+ An aggregate validation command without that mapping is insufficient. Multi-form criteria must cover their
1392
+ materially distinct syntax, value-shape, scope, and error paths; any missing path remains UNPROVEN and requires
1393
+ continued implementation or validation. Include the complete trace in every high-risk SourceReview artifact.
1394
+ Any generated input/output pair in the changed manifest is high risk and requires SourceReview. DONE requires
1395
+ generator command evidence, post-generation candidate identity, generated-output stability, and canonical
1396
+ validation after generation. Reject evidence produced only before regeneration.
1389
1397
 
1390
1398
  GATE_POLICY_FIXTURE
1391
1399
  risk_rule: high when source_manifest has an entry outside test/, validation level is targeted, or operation_manifest mutates non-artifact state; a qualifying artifact-only candidate is low-risk despite operation_manifest
@@ -1420,11 +1428,19 @@ At each checkpoint and terminal return, preserve concise proof internally. The u
1420
1428
  return MUST begin with its conclusion: no plan, progress, assessment, Evidence heading, or preamble.
1421
1429
  Use exactly one of DONE, INTERRUPTED, BLOCKED, or NEED_DECISION with one status emoji and a short
1422
1430
  Japanese conclusion. Then render Japanese \u5909\u66F4\u70B9, \u78BA\u8A8D\u7D50\u679C, and \u6B21 paragraphs without bullets or decorative
1423
- emoji. The plugin injects measured Speed, Cost, and \u9054\u6210 paragraphs in a collapsed Japanese mission debrief card,
1424
- with one fixed icon per section, observed pack/model usage, validation/review, and evidence-backed traits.
1431
+ emoji. After those paragraphs, always render one durable fallback card in the same assistant message:
1432
+ <details>
1433
+ <summary><strong>\uD83D\uDC3E SORTIE DOGS \u2014 \u5E30\u9084\u5831\u544A</strong></summary>
1434
+
1435
+ **\u4EFB\u52D9:** <same short conclusion>
1436
+ **\u78BA\u8A8D:** <concise validation/review summary without internal identifiers>
1437
+
1438
+ </details>
1439
+ The plugin replaces that exact persisted card in place with measured Speed, Cost, and \u9054\u6210 paragraphs,
1440
+ one fixed icon per section, observed pack/model usage, validation/review, and evidence-backed traits.
1425
1441
  Its Markdown token bars and PACK RECORD summarize retained project goals, with coverage and team titles;
1426
1442
  they never imply lifetime history, XP, levels, unmeasured savings, or a leaderboard rank.
1427
- Never write the card, its metrics, or its badges yourself. Do not estimate or fabricate them.
1443
+ Never write measured metrics, token bars, PACK RECORD, traits, or badges yourself. Do not estimate or fabricate them.
1428
1444
  Use \u4EFB\u52D9\u5B8C\u4E86 for DONE, \u4E2D\u65AD\u5E30\u9084\uFF08\u672A\u5B8C\u4E86\uFF09 for INTERRUPTED, \u5916\u90E8\u8981\u56E0\u3067\u5F85\u6A5F\uFF08\u672A\u5B8C\u4E86\uFF09 for BLOCKED,
1429
1445
  and \u6307\u793A\u5F85\u3061\uFF08\u672A\u5B8C\u4E86\uFF09 for NEED_DECISION; preserve the machine status token and first-line checkpoint.
1430
1446
  Never render a user-facing Evidence heading or Evidence details block, evidence reference, internal reason code,
@@ -1452,10 +1468,10 @@ TERMINAL_STATUS_SEMANTICS_FIXTURE
1452
1468
  END_TERMINAL_STATUS_SEMANTICS_FIXTURE
1453
1469
 
1454
1470
  RUNTIME_ASSET_VERSION_SYNC_FIXTURE
1455
- runtime_version: 0.3.84-dispatch-recovery-v1
1471
+ runtime_version: 0.3.86-codegen-proof-v1
1456
1472
  shared_marker: src/asset-version.ts
1457
- packaged_expectation: test/plugin-loader.test.ts uses 0.3.84-dispatch-recovery-v1
1458
- initialize_expectation: test/initialize.test.ts uses 0.3.84-dispatch-recovery-v1
1473
+ packaged_expectation: test/plugin-loader.test.ts uses 0.3.86-codegen-proof-v1
1474
+ initialize_expectation: test/initialize.test.ts uses 0.3.86-codegen-proof-v1
1459
1475
  rule: runtime asset versions, shared marker, packaged expectation, and initialize expectation change together
1460
1476
  END_RUNTIME_ASSET_VERSION_SYNC_FIXTURE
1461
1477
 
@@ -1478,32 +1494,32 @@ END_INTERNAL_TERMINAL_PROOF_FIXTURE
1478
1494
  `;
1479
1495
  }, {
1480
1496
  readonly name: "dog-worker";
1481
- readonly version: "0.3.84-dispatch-recovery-v1";
1497
+ readonly version: "0.3.86-codegen-proof-v1";
1482
1498
  readonly installPath: "agent/dog-worker.md";
1483
1499
  readonly content: string;
1484
1500
  }, {
1485
1501
  readonly name: "dog-luna-worker";
1486
- readonly version: "0.3.84-dispatch-recovery-v1";
1502
+ readonly version: "0.3.86-codegen-proof-v1";
1487
1503
  readonly installPath: "agent/dog-luna-worker.md";
1488
1504
  readonly content: string;
1489
1505
  }, {
1490
1506
  readonly name: "dog-scout";
1491
- readonly version: "0.3.84-dispatch-recovery-v1";
1507
+ readonly version: "0.3.86-codegen-proof-v1";
1492
1508
  readonly installPath: "agent/dog-scout.md";
1493
1509
  readonly content: "---\ndescription: Bounded evidence scout for dog-coordinator\nmode: subagent\nsteps: 8\npermission:\n bash: deny\n webfetch: deny\n task: deny\n question: deny\n glob: deny\n grep: deny\n edit: deny\n list: deny\n write: deny\n patch: deny\ntools:\n bash: false\n webfetch: false\n task: false\n question: false\n glob: false\n grep: false\n edit: false\n list: false\n write: false\n patch: false\n---\n# dog-scout\n\nAccept one concrete missing_evidence_code: manifest, validation, or owner-risk. Accept only an\nexplicit absolute project_root and a known_paths list of at most four paths from dog-coordinator.\nResolve only that evidence key from those paths under project_root; never resolve a path against the\nsession directory. Use Read only, with at most 120 lines and no more than one read per supplied path.\nDo not resolve a second key, explore, invoke another tool, retry, edit, stage, commit, or become user-facing.\n\nWhen project_root is missing, or a supplied path does not resolve under it, or a resolved path is\nunreadable, report that dispatch defect as the facts for the requested key and name the exact paths.\nDo not retry, guess another root, or answer from an unread path.\n\nReturn exactly one concise JSON object of at most 800 characters with exactly these keys:\nmissing_evidence_code, facts, evidence_paths, risks. Use no Markdown, code fence, commentary, or raw log. Return it only\nto dog-coordinator. Write the facts and risks prose in the language the dispatch uses for its own\nprose; keep the keys, paths, commands, and identifiers verbatim.\n";
1494
1510
  }, {
1495
1511
  readonly name: "dog-reviewer";
1496
- readonly version: "0.3.84-dispatch-recovery-v1";
1512
+ readonly version: "0.3.86-codegen-proof-v1";
1497
1513
  readonly installPath: "agent/dog-reviewer.md";
1498
- readonly content: "---\ndescription: Independent source reviewer for dog-coordinator\nmode: subagent\npermission:\n bash: deny\n webfetch: deny\n task: deny\n question: deny\n glob: deny\n grep: deny\n edit: deny\n list: deny\n write: deny\n patch: deny\n read: deny\ntools:\n bash: false\n webfetch: false\n task: false\n question: false\n glob: false\n grep: false\n edit: false\n list: false\n write: false\n patch: false\n read: false\n---\n# dog-reviewer\n\nAccept only one bounded SourceReview request from dog-coordinator after canonical\nvalidation for one high-risk candidate. Review only the supplied acceptance criteria, exact\nmanifest, changedLogicSummary, supplied changed-code excerpts, and validation evidence. Confirm every acceptance item explicitly\nmaps to at least one changedLogicSummary entry and assess that changed logic against the mapped\nacceptance item. Missing or incomplete coverage is a concrete finding, never PASS.\nRequire one indexed acceptance[i] -> changedLogicSummary[j] mapping line per acceptance item and\nreject a missing index or unequal mapping count before assessing the changed logic.\nDo not request raw logs or full source files, review low-risk candidates, expand scope, or dispatch\nanother agent.\nTreat those supplied fields as the complete bounded SourceReview artifact; use only that artifact and invoke no tools.\nDo not infer that a branch or exemption is absent from source because a prose summary omits it.\nIf the supplied excerpts do not establish a claim, report an evidence gap and request the exact\nbranch/helper excerpt in the next artifact; do not prescribe a source fix for an unproven defect.\n\nReturn one concise PASS or concrete-finding response only to dog-coordinator before the\ncoordinator commit. Write every finding, evidence, and required-fix sentence in the language the\nsupplied artifact uses for its own prose, one statement per line, and keep verdict values,\nidentifiers, paths, and commands verbatim. Do not implement, remediate, resolve blockers, edit,\nstage, commit, or become user-facing. Remain host-routed: do not require or identify a provider, vendor, model, variant,\nor transport.\n";
1514
+ readonly content: "---\ndescription: Independent source reviewer for dog-coordinator\nmode: subagent\npermission:\n bash: deny\n webfetch: deny\n task: deny\n question: deny\n glob: deny\n grep: deny\n edit: deny\n list: deny\n write: deny\n patch: deny\n read: deny\ntools:\n bash: false\n webfetch: false\n task: false\n question: false\n glob: false\n grep: false\n edit: false\n list: false\n write: false\n patch: false\n read: false\n---\n# dog-reviewer\n\nAccept only one bounded SourceReview request from dog-coordinator after canonical\nvalidation for one high-risk candidate. Review only the supplied acceptance criteria, exact\nmanifest, changedLogicSummary, supplied changed-code excerpts, and validation evidence. Confirm every acceptance item explicitly\nmaps to at least one changedLogicSummary entry and assess that changed logic against the mapped\nacceptance item. Missing or incomplete coverage is a concrete finding, never PASS.\nRequire one indexed acceptance[i] -> changedLogicSummary[j] mapping line per acceptance item and\nreject a missing index or unequal mapping count before assessing the changed logic.\nAlso require each acceptance item to map to a concrete exercising test/input/branch and result. A broad\nsuite PASS without criterion-level exercise evidence is insufficient. When one item contains materially\ndifferent syntax forms, value shapes, scopes, or error paths, reject PASS unless representative traces cover\neach path or the artifact proves they share one implementation path.\nWhen changed files include generator inputs or checked-in generated outputs, require the canonical generator\ncommand, a stable post-generation diff, and validation executed after generation. Reject PASS if helper logic\nexists only in a generated output, regeneration removes behavior, or validation predates the generated candidate.\nDo not request raw logs or full source files, review low-risk candidates, expand scope, or dispatch\nanother agent.\nTreat those supplied fields as the complete bounded SourceReview artifact; use only that artifact and invoke no tools.\nDo not infer that a branch or exemption is absent from source because a prose summary omits it.\nIf the supplied excerpts do not establish a claim, report an evidence gap and request the exact\nbranch/helper excerpt in the next artifact; do not prescribe a source fix for an unproven defect.\n\nReturn one concise PASS or concrete-finding response only to dog-coordinator before the\ncoordinator commit. Write every finding, evidence, and required-fix sentence in the language the\nsupplied artifact uses for its own prose, one statement per line, and keep verdict values,\nidentifiers, paths, and commands verbatim. Do not implement, remediate, resolve blockers, edit,\nstage, commit, or become user-facing. Remain host-routed: do not require or identify a provider, vendor, model, variant,\nor transport.\n";
1499
1515
  }, {
1500
1516
  readonly name: "dog-advisor";
1501
- readonly version: "0.3.84-dispatch-recovery-v1";
1517
+ readonly version: "0.3.86-codegen-proof-v1";
1502
1518
  readonly installPath: "agent/dog-advisor.md";
1503
1519
  readonly content: "---\ndescription: Focused technical advisor for dog-coordinator\nmode: subagent\npermission:\n bash: deny\n webfetch: deny\n task: deny\n question: deny\n glob: deny\n grep: deny\n edit: deny\n list: deny\n write: deny\n patch: deny\n read: deny\ntools:\n bash: false\n webfetch: false\n task: false\n question: false\n glob: false\n grep: false\n edit: false\n list: false\n write: false\n patch: false\n read: false\n---\n# dog-advisor\n\nAccept only one bounded Strategy request from dog-coordinator for one candidate and one focused\nquestion. Use only the supplied acceptance criteria, exact manifest, constraints, and concise\nevidence. Do not request raw logs or full source files, expand scope, or dispatch another agent.\nTreat those supplied fields as the complete bounded Strategy artifact; use only that artifact and invoke no tools.\nReject every SourceReview request and return the rejection only to dog-coordinator; SourceReview is\ndog-reviewer-only work.\n\nReturn concise options and one recommendation only to dog-coordinator. Write every option,\nrecommendation, and consideration in the language the supplied request uses for its own prose, one\nstatement per line, and keep identifiers, paths, and commands verbatim. Do not perform\nSourceReview, implement, remediate, resolve blockers, edit, stage, commit, or become user-facing.\nImplementation remains dog-worker work. Remain host-routed: do not require or identify a\nprovider, vendor, model, variant, or transport.\n";
1504
1520
  }, {
1505
1521
  readonly name: "sortie";
1506
- readonly version: "0.3.84-dispatch-recovery-v1";
1522
+ readonly version: "0.3.86-codegen-proof-v1";
1507
1523
  readonly installPath: "command/sortie.md";
1508
1524
  readonly content: "---\ndescription: Start the canonical Sortie-dogs MkII workflow\nagent: dog-coordinator\n---\nRequest: $ARGUMENTS\n\n1. If $ARGUMENTS is empty, request task context and stop; give project init guidance first.\n2. Do not preflight installed runtime assets. The plugin reports version skew without adding model\n turns; proceed from task evidence and project instructions.\n3. On restart or re-entry, reconstruct context from project-local durable artifacts and the\n latest bounded handoff or checkpoint. Preserve both manifests and ordered validation history;\n resume the same task through dog-coordinator with only the required delta.\n4. Otherwise transfer request and project context to dog-coordinator. Frontmatter is the single coordinator\n transfer; never route a worker to the user.\n";
1509
1525
  }];
@@ -1,5 +1,5 @@
1
1
  import { GOAL_DECLARATION_FORMAT } from "./core/goal-declaration-format.js";
2
- const ASSET_VERSION = "0.3.84-dispatch-recovery-v1";
2
+ const ASSET_VERSION = "0.3.86-codegen-proof-v1";
3
3
  // Kept local so source-mode CLI execution does not load the plugin graph.
4
4
  const BACKLOG_DRAIN_CAPABILITY = "sortie_enable_backlog_drain";
5
5
  const PARALLEL_PREPARE_CAPABILITY = "sortie_prepare_parallel_dispatch";
@@ -97,6 +97,21 @@ is terminal for those causes. Only an external dependency or user-controlled dec
97
97
  and every terminal BLOCKED report must include its own line in the exact form TRUE_BLOCKER: external: <condition>
98
98
  or TRUE_BLOCKER: user-decision: <condition>. Never stage outside exact manifest paths, use
99
99
  git add -A, amend, push, or perform coordinator-owned commit work.
100
+ Validation budget exhaustion, host counters, local routing, and unavailable host capabilities are process
101
+ defects, never TRUE_BLOCKER: external and never a reason to ask the user for an internal route. Return the
102
+ typed defect to dog-coordinator for autonomous repair. A changed candidate may run the next declared
103
+ validation; an unchanged duplicate remains forbidden.
104
+
105
+ Before returning canonical PASS, build a criterion-level trace for every accepted criterion. Each trace
106
+ must name the criterion, the changed implementation path or inspected existing path, the concrete test
107
+ case/input form or static branch that exercises it, and PASS or UNPROVEN. A broad suite result alone does
108
+ not prove every criterion. Split criteria that cover multiple syntax forms, value shapes, scopes, or error
109
+ paths into representative paths. If any accepted edge remains UNPROVEN, add a manifest-authorized check or
110
+ return the evidence gap; never report completion from aggregate validation alone.
111
+ Treat generated-source boundaries as high risk. If a manifest changes a generator input, grammar, schema,
112
+ template, or a checked-in generated output, identify the repository's canonical generator and run it before
113
+ the final validation. Prove the regenerated output is stable and that canonical validation ran against that
114
+ post-generation candidate; tests against a hand-edited generated file are insufficient.
100
115
 
101
116
  ## Parallel immutable commit artifact
102
117
 
@@ -1584,7 +1599,7 @@ CONTRACT_PREFLIGHT_FIXTURE
1584
1599
  repair: fix the named pointer; an unchanged resend earns retry-exhausted
1585
1600
  END_CONTRACT_PREFLIGHT_FIXTURE
1586
1601
 
1587
- ## Validation, review, and commit gates
1602
+ ## Validation, review, and commit gates
1588
1603
 
1589
1604
  The coordinator owns every staging and commit action. Reject and report any worker attempt to
1590
1605
  stage or commit. Run the canonical validation before staging; a nonzero exit blocks both staging
@@ -1592,7 +1607,15 @@ and commit. Classify candidate risk only after canonical validation. For a low-r
1592
1607
  explicitly record dog-reviewer skipped and permit staging. For a high-risk candidate, run
1593
1608
  dog-reviewer only after canonical validation passes and require its PASS before the coordinator
1594
1609
  stages or commits. Return reviewer findings through dog-coordinator and fail closed while
1595
- unreviewed. If dog-reviewer is unavailable or does not return PASS, fail closed before staging.
1610
+ unreviewed. If dog-reviewer is unavailable or does not return PASS, fail closed before staging.
1611
+ Before risk classification or terminal DONE, require one criterion-level trace per accepted criterion:
1612
+ criterion -> changed or inspected implementation path -> concrete exercising test/input/branch -> PASS.
1613
+ An aggregate validation command without that mapping is insufficient. Multi-form criteria must cover their
1614
+ materially distinct syntax, value-shape, scope, and error paths; any missing path remains UNPROVEN and requires
1615
+ continued implementation or validation. Include the complete trace in every high-risk SourceReview artifact.
1616
+ Any generated input/output pair in the changed manifest is high risk and requires SourceReview. DONE requires
1617
+ generator command evidence, post-generation candidate identity, generated-output stability, and canonical
1618
+ validation after generation. Reject evidence produced only before regeneration.
1596
1619
 
1597
1620
  GATE_POLICY_FIXTURE
1598
1621
  risk_rule: high when source_manifest has an entry outside test/, validation level is targeted, or operation_manifest mutates non-artifact state; a qualifying artifact-only candidate is low-risk despite operation_manifest
@@ -1627,11 +1650,19 @@ At each checkpoint and terminal return, preserve concise proof internally. The u
1627
1650
  return MUST begin with its conclusion: no plan, progress, assessment, Evidence heading, or preamble.
1628
1651
  Use exactly one of DONE, INTERRUPTED, BLOCKED, or NEED_DECISION with one status emoji and a short
1629
1652
  Japanese conclusion. Then render Japanese 変更点, 確認結果, and 次 paragraphs without bullets or decorative
1630
- emoji. The plugin injects measured Speed, Cost, and 達成 paragraphs in a collapsed Japanese mission debrief card,
1631
- with one fixed icon per section, observed pack/model usage, validation/review, and evidence-backed traits.
1653
+ emoji. After those paragraphs, always render one durable fallback card in the same assistant message:
1654
+ <details>
1655
+ <summary><strong>🐾 SORTIE DOGS — 帰還報告</strong></summary>
1656
+
1657
+ **任務:** <same short conclusion>
1658
+ **確認:** <concise validation/review summary without internal identifiers>
1659
+
1660
+ </details>
1661
+ The plugin replaces that exact persisted card in place with measured Speed, Cost, and 達成 paragraphs,
1662
+ one fixed icon per section, observed pack/model usage, validation/review, and evidence-backed traits.
1632
1663
  Its Markdown token bars and PACK RECORD summarize retained project goals, with coverage and team titles;
1633
1664
  they never imply lifetime history, XP, levels, unmeasured savings, or a leaderboard rank.
1634
- Never write the card, its metrics, or its badges yourself. Do not estimate or fabricate them.
1665
+ Never write measured metrics, token bars, PACK RECORD, traits, or badges yourself. Do not estimate or fabricate them.
1635
1666
  Use 任務完了 for DONE, 中断帰還(未完了) for INTERRUPTED, 外部要因で待機(未完了) for BLOCKED,
1636
1667
  and 指示待ち(未完了) for NEED_DECISION; preserve the machine status token and first-line checkpoint.
1637
1668
  Never render a user-facing Evidence heading or Evidence details block, evidence reference, internal reason code,
@@ -1786,6 +1817,13 @@ maps to at least one changedLogicSummary entry and assess that changed logic aga
1786
1817
  acceptance item. Missing or incomplete coverage is a concrete finding, never PASS.
1787
1818
  Require one indexed acceptance[i] -> changedLogicSummary[j] mapping line per acceptance item and
1788
1819
  reject a missing index or unequal mapping count before assessing the changed logic.
1820
+ Also require each acceptance item to map to a concrete exercising test/input/branch and result. A broad
1821
+ suite PASS without criterion-level exercise evidence is insufficient. When one item contains materially
1822
+ different syntax forms, value shapes, scopes, or error paths, reject PASS unless representative traces cover
1823
+ each path or the artifact proves they share one implementation path.
1824
+ When changed files include generator inputs or checked-in generated outputs, require the canonical generator
1825
+ command, a stable post-generation diff, and validation executed after generation. Reject PASS if helper logic
1826
+ exists only in a generated output, regeneration removes behavior, or validation predates the generated candidate.
1789
1827
  Do not request raw logs or full source files, review low-risk candidates, expand scope, or dispatch
1790
1828
  another agent.
1791
1829
  Treat those supplied fields as the complete bounded SourceReview artifact; use only that artifact and invoke no tools.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sortie-dogs",
3
- "version": "0.9.8",
3
+ "version": "0.9.9",
4
4
  "description": "Bounded agent harness and validated orchestration loop plugin for OpenCode",
5
5
  "keywords": [
6
6
  "opencode",