dsh-completion-guard 0.5.2 → 0.6.0

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.
@@ -358,6 +358,13 @@ function validateManifest(manifest = COMMAND_SURFACE_MANIFEST) {
358
358
  //#region src/domain/protocol-manifest.ts
359
359
  const STOP_PROTOCOL_VERSION = "2.0.0";
360
360
  const CERTIFICATE_VERSION = "1";
361
+ /**
362
+ * 0.6.0 v5-session identity (P0 §1): v2 certificates bind a work unit's
363
+ * closure instead of the whole session. Version-1 identity keeps its
364
+ * historical meaning for legacy sessions and is never silently re-read.
365
+ */
366
+ const STOP_PROTOCOL_VERSION_V2 = "3.0.0";
367
+ const CERTIFICATE_VERSION_V2 = "2";
361
368
  const ACTION_MANIFEST_VERSION = 1;
362
369
  const SUPPORTED_EVIDENCE_ADAPTERS = {
363
370
  "context-guard.git.v1": "1.0.0",
@@ -656,17 +663,78 @@ const REQUESTED_IDENTITY_KEY = {
656
663
  restart: "service_id",
657
664
  publish: "artifact_id"
658
665
  };
666
+ /** The single identity field a root instruction must name for this action. */
667
+ function requestedIdentityKey(action) {
668
+ return REQUESTED_IDENTITY_KEY[action];
669
+ }
659
670
  function stableTargetValue(value) {
660
671
  if (Array.isArray(value)) return `[${value.map(stableTargetValue).join(",")}]`;
661
672
  if (value && typeof value === "object") return `{${Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, entry]) => `${JSON.stringify(key)}:${stableTargetValue(entry)}`).join(",")}}`;
662
673
  return JSON.stringify(value);
663
674
  }
664
675
  /**
676
+ * The 0.6.0 bounded file-choice vocabulary (C07/S03): artifact-type nouns a
677
+ * root instruction may use instead of an exact path. The assistant may pick
678
+ * the exact file INSIDE the captured scope and inside the type, and the
679
+ * choice is frozen by the resolution producer before any effect. An absent
680
+ * extension set (`file`) admits any file the producer accepts.
681
+ */
682
+ const BOUNDED_ARTIFACT_TYPES = {
683
+ document: new Set([
684
+ "md",
685
+ "markdown",
686
+ "txt"
687
+ ]),
688
+ readme: new Set([
689
+ "md",
690
+ "rst",
691
+ "txt"
692
+ ]),
693
+ report: new Set(["md", "txt"]),
694
+ file: null
695
+ };
696
+ function extensionOf(path$1) {
697
+ const base = path$1.split(/[\\/]/).pop() ?? "";
698
+ const dot = base.lastIndexOf(".");
699
+ return dot > 0 ? base.slice(dot + 1).toLowerCase() : "";
700
+ }
701
+ function normalizeSlashes(value) {
702
+ return value.replace(/\\/g, "/");
703
+ }
704
+ /** Whether `artifact` lives inside `scope` (or exactly at it), slash-normalized. */
705
+ function insideScope(artifact, scope) {
706
+ if (scope === "scope" || scope === "") return false;
707
+ const normalizedArtifact = normalizeSlashes(artifact);
708
+ const normalizedScope = normalizeSlashes(scope).replace(/\/+$/, "");
709
+ if (normalizedArtifact === normalizedScope) return false;
710
+ return normalizedArtifact.startsWith(`${normalizedScope}/`);
711
+ }
712
+ /**
713
+ * Whether a bounded-choice requested target authorizes this resolved target:
714
+ * the resolved artifact must live inside the captured scope and match the
715
+ * captured type. The exact file name is the assistant's bounded decision,
716
+ * frozen by resolution — never a root-named identity substitution.
717
+ */
718
+ function boundedArtifactChoiceMatches(action, requested, resolved) {
719
+ if (action !== "create" && action !== "modify") return false;
720
+ const type = requested?.artifact_type;
721
+ const scope = requested?.scope;
722
+ if (typeof type !== "string" || typeof scope !== "string" || !(type in BOUNDED_ARTIFACT_TYPES)) return false;
723
+ const artifact = resolved?.artifact_id;
724
+ if (typeof artifact !== "string" || !artifact) return false;
725
+ if (!insideScope(artifact, scope)) return false;
726
+ const allowed = BOUNDED_ARTIFACT_TYPES[type];
727
+ return allowed === null || allowed.has(extensionOf(artifact));
728
+ }
729
+ /**
665
730
  * Compare identities captured from the root instruction with a complete
666
731
  * adapter-resolved target. Requested targets are partial by design: only
667
732
  * explicitly named identities (plus the active repository scope) are frozen.
733
+ * A bounded artifact choice (scope + type, C07) matches when the resolved
734
+ * exact file is inside the scope and of the captured type.
668
735
  */
669
736
  function requestedTargetMatchesResolved(action, requested, resolved) {
737
+ if (boundedArtifactChoiceMatches(action, requested, resolved)) return true;
670
738
  const identityKey = REQUESTED_IDENTITY_KEY[action];
671
739
  if (!identityKey || !requested || !resolved || !Object.hasOwn(requested, identityKey)) return false;
672
740
  const allowed = new Set(ACTION_MANIFEST.actions[action].resolvedTargetKeys);
@@ -710,6 +778,7 @@ const MUTATION_AUTHORITY_KEYS = {
710
778
  };
711
779
  /** A mutation requires every user-selectable identity field, not a partial match. */
712
780
  function requestedTargetAuthorizesMutation(action, requested, resolved) {
781
+ if (boundedArtifactChoiceMatches(action, requested, resolved)) return true;
713
782
  const required = MUTATION_AUTHORITY_KEYS[action];
714
783
  return !!requested && required.every((key) => Object.hasOwn(requested, key)) && requestedTargetMatchesResolved(action, requested, resolved);
715
784
  }
@@ -1808,6 +1877,36 @@ function namedActions(text) {
1808
1877
  return interpretMessage(text).map((scope) => semanticActionOfScope(scope.body)).filter((action) => action !== "generic_run");
1809
1878
  }
1810
1879
 
1880
+ //#endregion
1881
+ //#region src/domain/spans.ts
1882
+ /**
1883
+ * 0.6.0 source-span helpers (C01).
1884
+ *
1885
+ * Spans are UTF-8 BYTE half-open intervals `[start, end)` inside the original
1886
+ * root message text. All conversions go through TextEncoder byte counting —
1887
+ * JavaScript string indices are never allowed to masquerade as cross-language
1888
+ * positions, so a Python reader agrees with TypeScript on every boundary.
1889
+ */
1890
+ const encoder = new TextEncoder();
1891
+ function utf8ByteLength(text) {
1892
+ return encoder.encode(text).length;
1893
+ }
1894
+ /** Byte offset of `index` inside `text`: the UTF-8 length of the prefix. */
1895
+ function utf8ByteOffset(text, index) {
1896
+ return utf8ByteLength(text.slice(0, index));
1897
+ }
1898
+ /**
1899
+ * The coverage class of one captured clause: a prohibition is a constraint,
1900
+ * an informational reading is a question, an adopted block stays adoption,
1901
+ * and everything else is an instruction. Nothing captured is left unclassed.
1902
+ */
1903
+ function spanClassOf(kind, directive, authority) {
1904
+ if (authority === "root_adoption") return "adoption";
1905
+ if (kind === "prohibition") return "constraint";
1906
+ if (directive === "informational" || directive === "narrative") return "question";
1907
+ return "instruction";
1908
+ }
1909
+
1811
1910
  //#endregion
1812
1911
  //#region src/domain/capture.ts
1813
1912
  /**
@@ -1953,16 +2052,112 @@ function parentScope(subject) {
1953
2052
  if (separator === 2 && /^[A-Za-z]:[\\/]$/.test(subject.slice(0, 3))) return subject.slice(0, 3);
1954
2053
  return subject.slice(0, separator);
1955
2054
  }
2055
+ /**
2056
+ * The artifact-type nouns the bounded file-choice vocabulary admits (C07).
2057
+ * A closed list pinned by the v2 fixture: the noun must be the OBJECT of the
2058
+ * action, so "更新文档" captures a bounded choice while "更新皮肤中心" stays
2059
+ * a genuine clarification. The generic "文件/file" admits any file type the
2060
+ * producer accepts.
2061
+ */
2062
+ const BOUNDED_TYPE_NOUNS = [
2063
+ [/文档/, "document"],
2064
+ [/(?<!\w)readme(?!\w)/iu, "readme"],
2065
+ [/报告/, "report"],
2066
+ [/文件/, "file"],
2067
+ [/(?<!\w)files?(?!\w)/iu, "file"]
2068
+ ];
2069
+ function boundedArtifactTypeOf(text) {
2070
+ const masked = text;
2071
+ for (const [pattern, type] of BOUNDED_TYPE_NOUNS) if (pattern.test(masked)) return type;
2072
+ }
2073
+ /** A bounded choice needs a real path scope; the 'scope' sentinel is not one. */
2074
+ function scopeIsPath(subject) {
2075
+ return subject !== "scope" && subject !== "" && /[\\/]/.test(subject);
2076
+ }
2077
+ /**
2078
+ * Whether the clause's FIRST action word is the change verb 更新/调整 (the
2079
+ * object-driven modify mapping). A later 更新 inside a referenced task name
2080
+ * ("把更新插件明确为 apply…") never qualifies — the head verb is what the
2081
+ * clause orders.
2082
+ */
2083
+ function headVerbIsChangeWord(text) {
2084
+ const candidates = [];
2085
+ for (const word of ["更新", "调整"]) {
2086
+ let at = text.indexOf(word);
2087
+ while (at >= 0) {
2088
+ candidates.push({
2089
+ at,
2090
+ word
2091
+ });
2092
+ at = text.indexOf(word, at + word.length);
2093
+ }
2094
+ }
2095
+ for (const match of text.matchAll(/\b(?:update|adjust)\b/gi)) candidates.push({
2096
+ at: match.index,
2097
+ word: match[0]
2098
+ });
2099
+ if (candidates.length === 0) return false;
2100
+ candidates.sort((a, b) => a.at - b.at);
2101
+ const head = candidates[0];
2102
+ return CJK_ACTION_WORD_AT(text, head.at) === void 0;
2103
+ }
2104
+ /** Any other known action word strictly before `before`, if one exists. */
2105
+ function CJK_ACTION_WORD_AT(text, before) {
2106
+ const words = [
2107
+ "创建",
2108
+ "生成",
2109
+ "新建",
2110
+ "写入",
2111
+ "修改",
2112
+ "编辑",
2113
+ "更改",
2114
+ "读取",
2115
+ "运行",
2116
+ "执行",
2117
+ "安装",
2118
+ "部署",
2119
+ "上传",
2120
+ "提交",
2121
+ "推送",
2122
+ "发布",
2123
+ "升级",
2124
+ "重启",
2125
+ "重新启动",
2126
+ "合并",
2127
+ "删除",
2128
+ "下载",
2129
+ "拉取",
2130
+ "同步"
2131
+ ];
2132
+ let best;
2133
+ for (const word of words) {
2134
+ const at = text.indexOf(word);
2135
+ if (at >= 0 && at < before && (best === void 0 || at < best.at)) best = {
2136
+ at,
2137
+ word
2138
+ };
2139
+ }
2140
+ for (const match of text.matchAll(/\b(?:build|create|write|modify|change|edit|run|fix|install|push|publish|commit|deploy|migrate|delete|restart|fetch|pull|update)\b/gi)) if (match.index < before && (best === void 0 || match.index < best.at)) best = {
2141
+ at: match.index,
2142
+ word: match[0]
2143
+ };
2144
+ return best?.word;
2145
+ }
1956
2146
  function captureRequestedTarget(action, text, subject, surface) {
1957
2147
  if (action === "create" || action === "modify") {
1958
- if (surface !== "artifact") return {
1959
- target: {},
1960
- reasonCode: "requested_target_artifact_id_missing"
1961
- };
1962
- return { target: {
2148
+ if (surface === "artifact") return { target: {
1963
2149
  artifact_id: subject,
1964
2150
  scope: parentScope(subject)
1965
2151
  } };
2152
+ const artifactType = boundedArtifactTypeOf(text);
2153
+ if (artifactType && scopeIsPath(subject)) return { target: {
2154
+ scope: subject,
2155
+ artifact_type: artifactType
2156
+ } };
2157
+ return {
2158
+ target: {},
2159
+ reasonCode: "requested_target_artifact_id_missing"
2160
+ };
1966
2161
  }
1967
2162
  if (action === "install" || action === "apply") {
1968
2163
  const parsed = splitPackageSpec(actionObjectToken(text, action === "install" ? "install|add|安装" : "apply|应用", "package|plugin|包|插件"));
@@ -2065,7 +2260,8 @@ function segmentClauses(text, options = {}) {
2065
2260
  function captureItem(kind, body, sourceMessageId, id, revision, subject, surface, method, operation, interpretation) {
2066
2261
  const sanitized = sanitizeClauseText(body);
2067
2262
  const unsupportedVisual = /\bGUI\b|界面|视觉|截图|颜色|布局|视觉效果/i.test(sanitized);
2068
- const semanticAction = unsupportedVisual ? "generic_run" : semanticActionOfScope(sanitized, interpretation?.text ?? sanitized, kind === "prohibition");
2263
+ let semanticAction = unsupportedVisual ? "generic_run" : semanticActionOfScope(sanitized, interpretation?.text ?? sanitized, kind === "prohibition");
2264
+ if (semanticAction === "generic_run" && !unsupportedVisual && interpretation?.directive === "directive" && headVerbIsChangeWord(sanitized) && boundedArtifactTypeOf(sanitized) !== void 0) semanticAction = "modify";
2069
2265
  const capturedTarget = captureRequestedTarget(semanticAction, sanitized, subject, surface);
2070
2266
  const effectiveOperation = semanticAction === "verify" ? "verify" : operation;
2071
2267
  const item = {
@@ -2147,7 +2343,202 @@ function captureClause(text, sourceMessageId, id, revision, scope = {}, options
2147
2343
  const body = interpretation?.body ?? text;
2148
2344
  const path$1 = extractArtifactPaths(sanitizeClauseText(body))[0] ?? "";
2149
2345
  const surface = path$1 ? "artifact" : "scope";
2150
- return captureItem(kind, body, sourceMessageId, id, revision, path$1 || scope.cwd || "scope", surface, interpretation?.method ?? extractMethod(body), extractOperation(body), interpretation);
2346
+ const item = captureItem(kind, body, sourceMessageId, id, revision, path$1 || scope.cwd || "scope", surface, interpretation?.method ?? extractMethod(body), extractOperation(body), interpretation);
2347
+ if (interpretation) {
2348
+ const at = text.indexOf(interpretation.text);
2349
+ if (at >= 0) {
2350
+ item.rawTextSha256 = sha256(text);
2351
+ item.spans = [{
2352
+ partIndex: 0,
2353
+ start: utf8ByteOffset(text, at),
2354
+ end: utf8ByteOffset(text, at) + utf8ByteLength(interpretation.text),
2355
+ class: spanClassOf(kind, interpretation.directive, "root_instruction")
2356
+ }];
2357
+ }
2358
+ }
2359
+ return item;
2360
+ }
2361
+
2362
+ //#endregion
2363
+ //#region src/domain/reason-class.ts
2364
+ const REASON_CLASS_TABLE = {
2365
+ requested_target_package_id_missing: "parameter_missing",
2366
+ requested_target_artifact_id_missing: "parameter_missing",
2367
+ requested_target_repository_missing: "parameter_missing",
2368
+ requested_target_service_id_missing: "parameter_missing",
2369
+ requested_target_registry_missing_or_invalid: "parameter_missing",
2370
+ target_clarification_required: "parameter_missing",
2371
+ item_not_found: "parameter_missing",
2372
+ item_revision_mismatch: "parameter_missing",
2373
+ unsupported_action: "parameter_missing",
2374
+ missing_evidence: "parameter_missing",
2375
+ action_plan_target_missing: "parameter_missing",
2376
+ action_plan_evidence_missing: "parameter_missing",
2377
+ binding_missing_required_facet: "parameter_missing",
2378
+ evidence_missing: "parameter_missing",
2379
+ proof_subject_invalid: "parameter_missing",
2380
+ proof_source_invalid: "parameter_missing",
2381
+ proof_evidence_invalid: "parameter_missing",
2382
+ generic_run_non_certifiable: "source_insufficient",
2383
+ legacy_generic_run_non_certifiable: "source_insufficient",
2384
+ legacy_authority_unclassified: "source_insufficient",
2385
+ inquiry_non_certifiable: "source_insufficient",
2386
+ inquiry_awaiting_delivery: "source_insufficient",
2387
+ answer_delivered: "source_insufficient",
2388
+ certified: "source_insufficient",
2389
+ semantic_action_mismatch: "source_insufficient",
2390
+ evidence_matches_no_facet: "source_insufficient",
2391
+ requested_target_mismatch: "source_insufficient",
2392
+ requested_resolved_target_mismatch: "source_insufficient",
2393
+ binding_state_cross_pairing: "source_insufficient",
2394
+ binding_resolution_cross_pairing: "source_insufficient",
2395
+ binding_observed_state_mismatch: "source_insufficient",
2396
+ binding_state_observation_overlap: "source_insufficient",
2397
+ binding_expected_transition_mismatch: "source_insufficient",
2398
+ binding_state_closure_rejected: "source_insufficient",
2399
+ expected_transition_mismatch: "source_insufficient",
2400
+ non_stateful_role_manifest_invalid: "source_insufficient",
2401
+ resolved_target_incomplete: "source_insufficient",
2402
+ state_closure_incomplete: "source_insufficient",
2403
+ delegated_result_bounded: "source_insufficient",
2404
+ proof_subject_unbound: "source_insufficient",
2405
+ proof_source_unbound: "source_insufficient",
2406
+ proof_surface_unbound: "source_insufficient",
2407
+ proof_role_unbound: "source_insufficient",
2408
+ proof_operation_unbound: "source_insufficient",
2409
+ proof_scope_subject_unbound: "source_insufficient",
2410
+ proof_scope_digest_mismatch: "source_insufficient",
2411
+ proof_scope_digest_invalid: "source_insufficient",
2412
+ proof_evidence_outcome_invalid: "source_insufficient",
2413
+ proof_evidence_constraint_failed: "source_insufficient",
2414
+ root_condition_pending: "condition_unmet",
2415
+ prohibition_active: "condition_unmet",
2416
+ ancestor_condition_unsatisfied: "condition_unmet",
2417
+ ancestor_prohibition_active: "condition_unmet",
2418
+ mutation_awaiting_root_condition: "condition_unmet",
2419
+ mutation_awaiting_root_wait: "condition_unmet",
2420
+ action_plan_order_mismatch: "condition_unmet",
2421
+ adapter_unavailable: "producer_capability_unavailable",
2422
+ host_unavailable: "producer_capability_unavailable",
2423
+ stateful_adapter_unavailable: "producer_capability_unavailable",
2424
+ proof_producer_capability_unavailable: "producer_capability_unavailable",
2425
+ proof_readback_unavailable: "producer_capability_unavailable",
2426
+ proof_external_fact_unavailable: "producer_capability_unavailable",
2427
+ proof_external_fact_incomplete: "producer_capability_unavailable",
2428
+ proof_source_bounded_delegation: "producer_capability_unavailable",
2429
+ historical_evidence_gap: "historical_gap",
2430
+ effect_only_insufficient_state_readback: "historical_gap",
2431
+ rebind_evidence_predates_source: "historical_gap",
2432
+ resolution_expected_transition_missing: "historical_gap",
2433
+ resolution_expected_transition_digest_missing: "historical_gap",
2434
+ resolution_expected_transition_digest_mismatch: "historical_gap",
2435
+ resolution_expected_transition_invalid: "historical_gap",
2436
+ integrity_invalid: "integrity_failure",
2437
+ host_lock_unsupported: "integrity_failure",
2438
+ certificate_missing: "integrity_failure",
2439
+ certificate_replay_mismatch: "integrity_failure",
2440
+ boundary_replay_mismatch: "integrity_failure",
2441
+ proof_invalid: "integrity_failure",
2442
+ proof_unbound: "integrity_failure",
2443
+ proof_protocol_version_mismatch: "integrity_failure",
2444
+ proof_digest_invalid: "integrity_failure",
2445
+ proof_digest_mismatch: "integrity_failure",
2446
+ proof_kind_unsupported: "integrity_failure",
2447
+ proof_surface_unsupported: "integrity_failure",
2448
+ proof_operation_unsupported: "integrity_failure",
2449
+ proof_obligation_unbound: "integrity_failure",
2450
+ proof_obligation_not_pending: "integrity_failure",
2451
+ proof_evidence_unknown: "integrity_failure",
2452
+ proof_evidence_wrong_epoch: "integrity_failure",
2453
+ proof_manifest_invalid: "integrity_failure",
2454
+ proof_obligations_missing: "integrity_failure",
2455
+ proof_obligation_invalid: "integrity_failure",
2456
+ proof_obligation_id_duplicate_or_invalid: "integrity_failure",
2457
+ proof_asset_set_digest_invalid: "integrity_failure",
2458
+ evidence_wrong_epoch: "integrity_failure",
2459
+ evidence_outcome_not_success: "integrity_failure",
2460
+ stale_host_lock: "integrity_failure",
2461
+ stale_epoch: "integrity_failure",
2462
+ stale_contract_revision: "integrity_failure",
2463
+ stale_unit_ref: "integrity_failure",
2464
+ stale_goal_ref: "integrity_failure",
2465
+ legacy_certificate_in_v5_session: "integrity_failure",
2466
+ certificate_version_unavailable: "integrity_failure",
2467
+ mutation_integrity_unavailable: "integrity_failure",
2468
+ item_missing_or_superseded: "integrity_failure",
2469
+ unit_unavailable: "integrity_failure",
2470
+ certificate_manifest_rejected: "integrity_failure",
2471
+ session_ref_unavailable: "integrity_failure",
2472
+ projection_durability_unavailable: "integrity_failure",
2473
+ guard_unavailable: "integrity_failure",
2474
+ binding_role_mismatch: "integrity_failure",
2475
+ binding_role_order_invalid: "integrity_failure",
2476
+ action_plan_evidence_reused: "integrity_failure",
2477
+ action_plan_evidence_not_successful: "integrity_failure",
2478
+ action_plan_evidence_predates_item: "integrity_failure",
2479
+ action_plan_action_mismatch: "integrity_failure",
2480
+ action_plan_incomplete: "integrity_failure",
2481
+ action_plan_target_mismatch: "integrity_failure",
2482
+ release_contract_required: "policy_boundary",
2483
+ release_operation_not_adopted: "policy_boundary",
2484
+ release_operation_unprotectable: "policy_boundary",
2485
+ release_operation_unrouted: "policy_boundary",
2486
+ release_runner_opaque: "policy_boundary",
2487
+ release_contract_expired: "policy_boundary",
2488
+ release_expiry_unevaluable: "policy_boundary",
2489
+ release_expiry_invalid: "policy_boundary",
2490
+ release_operations_missing: "policy_boundary",
2491
+ release_operation_unknown: "policy_boundary",
2492
+ release_candidate_missing: "policy_boundary",
2493
+ release_candidate_ref_missing: "policy_boundary",
2494
+ release_candidate_sha_invalid: "policy_boundary",
2495
+ release_candidate_artifact_digest_invalid: "policy_boundary",
2496
+ release_contract_malformed: "policy_boundary",
2497
+ release_reservation_malformed: "policy_boundary",
2498
+ release_settlement_malformed: "policy_boundary",
2499
+ release_subcommand_unknown: "policy_boundary",
2500
+ release_operation_consumed: "policy_boundary",
2501
+ release_operation_in_flight: "policy_boundary",
2502
+ release_candidate_sha_mismatch: "policy_boundary",
2503
+ release_candidate_ref_mismatch: "policy_boundary",
2504
+ release_candidate_repository_mismatch: "policy_boundary",
2505
+ release_candidate_package_mismatch: "policy_boundary",
2506
+ release_candidate_version_mismatch: "policy_boundary",
2507
+ release_candidate_registry_mismatch: "policy_boundary",
2508
+ release_candidate_artifact_mismatch: "policy_boundary",
2509
+ release_candidate_artifact_sri_mismatch: "policy_boundary",
2510
+ release_candidate_sha_unresolved: "policy_boundary",
2511
+ release_candidate_ref_unresolved: "policy_boundary",
2512
+ release_candidate_repository_unresolved: "policy_boundary",
2513
+ release_candidate_package_unresolved: "policy_boundary",
2514
+ release_candidate_version_unresolved: "policy_boundary",
2515
+ release_candidate_registry_unresolved: "policy_boundary",
2516
+ release_artifact_sha256_unresolved: "policy_boundary",
2517
+ release_artifact_sri_unresolved: "policy_boundary",
2518
+ release_artifact_identity_required: "policy_boundary",
2519
+ release_candidate_sha256_invalid: "policy_boundary",
2520
+ release_candidate_sri_invalid: "policy_boundary",
2521
+ release_candidate_field_unknown: "policy_boundary",
2522
+ release_candidate_unobservable: "policy_boundary",
2523
+ release_contract_revoked: "policy_boundary",
2524
+ release_contract_revocation_unknown: "policy_boundary",
2525
+ release_state_damaged: "policy_boundary",
2526
+ release_readiness_unresolved: "policy_boundary",
2527
+ release_closure_unresolved: "policy_boundary",
2528
+ release_target_package_mismatch: "policy_boundary",
2529
+ release_target_version_mismatch: "policy_boundary",
2530
+ release_target_registry_mismatch: "policy_boundary",
2531
+ release_artifact_digest_unresolved: "policy_boundary",
2532
+ release_target_unresolved: "policy_boundary",
2533
+ release_contract_granted: "policy_boundary",
2534
+ release_profile_not_adopted: "policy_boundary",
2535
+ release_reservation_not_durable: "policy_boundary",
2536
+ release_gate_unavailable: "policy_boundary",
2537
+ strict_proof_required: "policy_boundary"
2538
+ };
2539
+ /** The seven-class label for one fine-grained reason code. */
2540
+ function reasonClassOf(reasonCode) {
2541
+ return REASON_CLASS_TABLE[reasonCode] ?? "source_insufficient";
2151
2542
  }
2152
2543
 
2153
2544
  //#endregion
@@ -2183,7 +2574,20 @@ function evidenceFacets(p, item) {
2183
2574
  * adapter, an executed-without-evidence historical gap, or nothing to do —
2184
2575
  * and it NEVER recommends a rebind that cannot change certification.
2185
2576
  */
2577
+ /**
2578
+ * The unified diagnosis, with the frozen seven-class label attached (C12).
2579
+ *
2580
+ * The class is derived from whatever `reason_code` the judge decides, so a new
2581
+ * branch cannot drift from the classification table.
2582
+ */
2186
2583
  function deriveItemDiagnosis(p, item) {
2584
+ const diagnosis = judgeItemDiagnosis(p, item);
2585
+ return {
2586
+ ...diagnosis,
2587
+ reason_class: reasonClassOf(diagnosis.reason_code)
2588
+ };
2589
+ }
2590
+ function judgeItemDiagnosis(p, item) {
2187
2591
  const kind = taskKindOf(item);
2188
2592
  const missing_facets = item.status === "pending" && kind !== "constraint" ? evidenceFacets(p, item) : [];
2189
2593
  const base = {
@@ -2218,6 +2622,19 @@ function deriveItemDiagnosis(p, item) {
2218
2622
  },
2219
2623
  attempt_fingerprint: fingerprint(p, item, "certified")
2220
2624
  };
2625
+ if (item.status === "answered") return {
2626
+ ...base,
2627
+ certification: "supported",
2628
+ reason_code: "answer_delivered",
2629
+ repairability: "none",
2630
+ missing_fields: [],
2631
+ missing_facets: [],
2632
+ next_action: {
2633
+ kind: "none",
2634
+ resume_condition: "The host-confirmed final answer was delivered; no further binding needed."
2635
+ },
2636
+ attempt_fingerprint: fingerprint(p, item, "answer_delivered")
2637
+ };
2221
2638
  if (item.status === "pending" && item.waitAuthorization?.kind === "root_explicit_wait") return {
2222
2639
  ...base,
2223
2640
  certification: "unavailable",
@@ -2231,6 +2648,22 @@ function deriveItemDiagnosis(p, item) {
2231
2648
  },
2232
2649
  attempt_fingerprint: fingerprint(p, item, "root_condition_pending")
2233
2650
  };
2651
+ if (kind === "inquiry") {
2652
+ const closable = p.boundaryProtocol === 5;
2653
+ return {
2654
+ ...base,
2655
+ certification: "unsupported",
2656
+ reason_code: closable ? "inquiry_awaiting_delivery" : "inquiry_non_certifiable",
2657
+ repairability: "unsupported",
2658
+ missing_fields: [],
2659
+ missing_facets: [],
2660
+ next_action: {
2661
+ kind: "report_only",
2662
+ resume_condition: closable ? "Deliver the actual answer; the host-confirmed final response of a completed turn closes this item." : "Complete the investigation and report the actual answer; the item stays recorded as uncertified. No confirmation or rebind changes this."
2663
+ },
2664
+ attempt_fingerprint: fingerprint(p, item, closable ? "inquiry_awaiting_delivery" : "inquiry_non_certifiable")
2665
+ };
2666
+ }
2234
2667
  if (action !== "generic_run" && !item.legacyFlags?.length && item.targetCaptureStatus === "clarification_required") {
2235
2668
  const missingFields = item.targetCaptureReasonCode ? [TARGET_FIELD_REASONS[item.targetCaptureReasonCode] ?? item.targetCaptureReasonCode] : [];
2236
2669
  return {
@@ -2248,33 +2681,19 @@ function deriveItemDiagnosis(p, item) {
2248
2681
  attempt_fingerprint: fingerprint(p, item, "target_clarification_required")
2249
2682
  };
2250
2683
  }
2251
- if (action === "generic_run" || item.legacyFlags?.length) {
2252
- if (kind === "inquiry") return {
2253
- ...base,
2254
- certification: "unsupported",
2255
- reason_code: "inquiry_non_certifiable",
2256
- repairability: "unsupported",
2257
- missing_fields: [],
2258
- next_action: {
2259
- kind: "report_only",
2260
- resume_condition: "Complete the investigation and report the actual answer; the item stays recorded as uncertified. No confirmation or rebind changes this."
2261
- },
2262
- attempt_fingerprint: fingerprint(p, item, "inquiry_non_certifiable")
2263
- };
2264
- return {
2265
- ...base,
2266
- certification: "unsupported",
2267
- reason_code: "generic_run_non_certifiable",
2268
- repairability: "user_input_required",
2269
- missing_fields: [],
2270
- next_action: {
2271
- kind: "report_only",
2272
- required_input: "a concrete supported action and target for this obligation",
2273
- resume_condition: "A fresh root-user instruction naming a supported action and exact target replaces the generic obligation; identical re-phrasing changes nothing."
2274
- },
2275
- attempt_fingerprint: fingerprint(p, item, "generic_run_non_certifiable")
2276
- };
2277
- }
2684
+ if (action === "generic_run" || item.legacyFlags?.length) return {
2685
+ ...base,
2686
+ certification: "unsupported",
2687
+ reason_code: "generic_run_non_certifiable",
2688
+ repairability: "user_input_required",
2689
+ missing_fields: [],
2690
+ next_action: {
2691
+ kind: "report_only",
2692
+ required_input: "a concrete supported action and target for this obligation",
2693
+ resume_condition: "A rebind proposal mapping this obligation to a concrete supported action and target is the only thing that replaces it; after the durable confirmation the original is superseded atomically. Similar re-phrasing changes nothing."
2694
+ },
2695
+ attempt_fingerprint: fingerprint(p, item, "generic_run_non_certifiable")
2696
+ };
2278
2697
  if (p.hostStatus !== "supported") return {
2279
2698
  ...base,
2280
2699
  certification: "unavailable",
@@ -2354,6 +2773,7 @@ const NATIVE_ADAPTERS = new Set([
2354
2773
  "dsh.web.v1"
2355
2774
  ]);
2356
2775
  function evidenceAvailabilityReason(evidence) {
2776
+ if (evidence.delegatedSubtask) return "delegated_result_bounded";
2357
2777
  if (evidence.parseStatus !== "supported") return evidence.reasonCode ?? evidence.parseStatus ?? "adapter_unavailable";
2358
2778
  if (!evidence.adapterId || !evidence.adapterVersion || (SUPPORTED_EVIDENCE_ADAPTERS[evidence.adapterId] ?? (NATIVE_ADAPTERS.has(evidence.adapterId) ? "1.0.0" : void 0)) !== evidence.adapterVersion) return "adapter_unavailable";
2359
2779
  if (evidence.outcome !== "success") return "evidence_outcome_not_success";
@@ -2448,7 +2868,18 @@ function isFrozenV042RebindResponse(recorded) {
2448
2868
  /** Whether the partition changes certification at all: a same-generic split
2449
2869
  * is organizational at best and must not cost a user confirmation. */
2450
2870
  function certificationGain(item, candidates) {
2451
- if ((item.semanticAction ?? "generic_run") !== "generic_run") return true;
2871
+ const current = item.semanticAction ?? "generic_run";
2872
+ if (current !== "generic_run" && item.targetCaptureStatus === "clarification_required") return candidates.some((candidate) => {
2873
+ if (candidate.action === void 0) return false;
2874
+ if (candidate.action !== current && candidate.action !== "generic_run") return true;
2875
+ const target = candidate.requestedTarget;
2876
+ if (!target) return false;
2877
+ const key = requestedIdentityKey(candidate.action);
2878
+ if (key && Object.hasOwn(target, key)) return true;
2879
+ if ((candidate.action === "create" || candidate.action === "modify") && target.artifact_type !== void 0 && target.scope !== void 0) return true;
2880
+ return false;
2881
+ });
2882
+ if (current !== "generic_run") return true;
2452
2883
  return candidates.some((candidate) => candidate.action !== void 0 && candidate.action !== "generic_run");
2453
2884
  }
2454
2885
  function preservesIdentity(old, clarified) {
@@ -2918,6 +3349,16 @@ function createProjection() {
2918
3349
  checkpoints: [],
2919
3350
  boundaries: [],
2920
3351
  externalOperations: /* @__PURE__ */ new Map(),
3352
+ units: /* @__PURE__ */ new Map(),
3353
+ coverage: [],
3354
+ releaseContracts: [],
3355
+ releaseReservations: [],
3356
+ releaseSettlements: [],
3357
+ releaseDiagnostics: [],
3358
+ releaseStateDamaged: false,
3359
+ policy: "standard",
3360
+ trustedSelections: [],
3361
+ approvals: [],
2921
3362
  sessionRefDigest: "11".repeat(32),
2922
3363
  hostLockDigest: "22".repeat(32),
2923
3364
  hostStatus: "supported",
@@ -2929,6 +3370,7 @@ function createProjection() {
2929
3370
  noProgressClaims: /* @__PURE__ */ new Map(),
2930
3371
  handledControlSeqs: /* @__PURE__ */ new Set(),
2931
3372
  rebindRejections: /* @__PURE__ */ new Map(),
3373
+ durabilityWatermark: "unknown",
2932
3374
  integrity: "valid"
2933
3375
  };
2934
3376
  }
@@ -3187,6 +3629,10 @@ function hasCurrentCertificate(projection) {
3187
3629
  else if (checkpoint.sessionRefDigest !== projection.sessionRefDigest) reason = "foreign_session";
3188
3630
  else if (checkpoint.hostLockDigest !== projection.hostLockDigest) reason = "stale_host_lock";
3189
3631
  else if (checkpoint.contractRevision !== projection.contractRevision) reason = "stale_contract_revision";
3632
+ else if (projection.boundaryProtocol === 5) {
3633
+ if (checkpoint.certificateVersion !== "2") reason = "legacy_certificate_in_v5_session";
3634
+ else if (checkpoint.unitId !== projection.currentUnitId) reason = "stale_unit_ref";
3635
+ } else if (checkpoint.certificateVersion !== "1") reason = "certificate_version_unavailable";
3190
3636
  else if (projection.currentGoalRef ? checkpoint.goalRef?.id !== projection.currentGoalRef.id || checkpoint.goalRef.revision !== projection.currentGoalRef.revision : checkpoint.goalRef !== void 0) reason = "stale_goal_ref";
3191
3637
  projection.certificateStatusReason = reason;
3192
3638
  return reason === void 0;
@@ -4073,6 +4519,74 @@ function certificationDigest(certificate) {
4073
4519
  checkFieldCount(count);
4074
4520
  return sha256Hex(Buffer.concat(parts));
4075
4521
  }
4522
+ const CERTIFICATE_V2_KEYS = [
4523
+ "stopProtocolVersion",
4524
+ "certificateVersion",
4525
+ "epoch",
4526
+ "sessionRefDigest",
4527
+ "hostLockDigest",
4528
+ "contractRevision",
4529
+ "contractSha256",
4530
+ "unitId",
4531
+ "unitClosureDigest",
4532
+ "evidenceSha256",
4533
+ "bindingDigest",
4534
+ "goalRef"
4535
+ ];
4536
+ /**
4537
+ * 0.6.0 v2 certificate field table over the new `ccg.certificationDigest.v4`
4538
+ * domain (P0 §1): the certified scope is a work unit's closure instead of the
4539
+ * whole session. digest_v3 domains and their golden vectors stay frozen; this
4540
+ * function never re-reads a v1 record.
4541
+ */
4542
+ function certificationDigestV2(certificate) {
4543
+ requireExactKeys(certificate, CERTIFICATE_V2_KEYS, "certificateV2");
4544
+ const parts = [Buffer.from("ccg.certificationDigest.v4\n", "utf8")];
4545
+ let count = 0;
4546
+ parts.push(field("stopProtocolVersion", typedToken(expectString(certificate.stopProtocolVersion, "stopProtocolVersion"))));
4547
+ parts.push(field("certificateVersion", typedToken(expectString(certificate.certificateVersion, "certificateVersion"))));
4548
+ parts.push(field("epoch", typedToken(expectInt(certificate.epoch, "epoch"))));
4549
+ parts.push(field("sessionRefDigest", typedToken({
4550
+ k: "x",
4551
+ v: expectHex(certificate.sessionRefDigest)
4552
+ })));
4553
+ parts.push(field("hostLockDigest", typedToken({
4554
+ k: "x",
4555
+ v: expectHex(certificate.hostLockDigest)
4556
+ })));
4557
+ parts.push(field("contractRevision", typedToken(expectInt(certificate.contractRevision, "contractRevision"))));
4558
+ parts.push(field("contractSha256", typedToken({
4559
+ k: "x",
4560
+ v: expectHex(certificate.contractSha256)
4561
+ })));
4562
+ count += 7;
4563
+ parts.push(field("unitId", typedToken(expectString(certificate.unitId, "unitId"))));
4564
+ parts.push(field("unitClosureDigest", typedToken({
4565
+ k: "x",
4566
+ v: expectHex(certificate.unitClosureDigest)
4567
+ })));
4568
+ parts.push(field("evidenceSha256", typedToken({
4569
+ k: "x",
4570
+ v: expectHex(certificate.evidenceSha256)
4571
+ })));
4572
+ parts.push(field("bindingDigest", typedToken({
4573
+ k: "x",
4574
+ v: expectHex(certificate.bindingDigest)
4575
+ })));
4576
+ count += 4;
4577
+ const goalRef = certificate.goalRef;
4578
+ if (goalRef !== void 0 && goalRef !== null) {
4579
+ requireExactKeys(goalRef, GOAL_REF_KEYS, "goalRef");
4580
+ parts.push(optField("goalRefId", expectString(goalRef.id, "goalRef.id"), (v) => typedToken(v)));
4581
+ parts.push(optField("goalRefRevision", expectInt(goalRef.revision, "goalRef.revision"), (v) => typedToken(v)));
4582
+ } else {
4583
+ parts.push(optField("goalRefId", null, () => Buffer.alloc(0)));
4584
+ parts.push(optField("goalRefRevision", null, () => Buffer.alloc(0)));
4585
+ }
4586
+ count += 2;
4587
+ checkFieldCount(count);
4588
+ return sha256Hex(Buffer.concat(parts));
4589
+ }
4076
4590
  /**
4077
4591
  * Verifier-side role matrix and binding closure. Digest derivation stays
4078
4592
  * pure; this mirrors the checks a proof verifier must run before accepting a
@@ -4389,7 +4903,7 @@ function closingHint(projection, item, evidenceIds) {
4389
4903
  else parts.push("needs a state-verification evidence (read tool, or a deterministic check run in scope) matching the subject");
4390
4904
  return parts.join("; ");
4391
4905
  }
4392
- function openItems$1(projection) {
4906
+ function openItems(projection) {
4393
4907
  return [...projection.items.values()].filter((item) => item.status === "pending").sort((a, b) => a.revision - b.revision || (a.id < b.id ? -1 : 1));
4394
4908
  }
4395
4909
  /**
@@ -4399,7 +4913,7 @@ function openItems$1(projection) {
4399
4913
  * injected once instead of looping (v0.2.1).
4400
4914
  */
4401
4915
  function recoveryDigest(packet, projection) {
4402
- const items = openItems$1(projection);
4916
+ const items = openItems(projection);
4403
4917
  const evidence = [...projection.evidence.values()].filter((row) => items.some((item) => relevantEvidence(projection, item, row)));
4404
4918
  return sha256(JSON.stringify({
4405
4919
  packet,
@@ -4413,7 +4927,7 @@ function renderRecoveryPacket(projection, options = {}) {
4413
4927
  const budget = options.charBudget ?? DEFAULT_RECOVERY_CHAR_BUDGET;
4414
4928
  if (!Number.isSafeInteger(budget) || budget < MIN_RECOVERY_CHAR_BUDGET) throw new RangeError("recovery charBudget must be an integer >= 512");
4415
4929
  const clip = (text, size) => text.length <= size ? text : text.slice(0, size - 1) + "…";
4416
- const items = openItems$1(projection).sort((a, b) => Number(b.kind === "prohibition") - Number(a.kind === "prohibition") || b.revision - a.revision || a.id.localeCompare(b.id));
4930
+ const items = openItems(projection).sort((a, b) => Number(b.kind === "prohibition") - Number(a.kind === "prohibition") || b.revision - a.revision || a.id.localeCompare(b.id));
4417
4931
  const rejected$1 = options.rejectedBindings ?? (projection.lastCheckpointRejectionRevision === projection.contractRevision ? projection.lastCheckpointRejections : []) ?? [];
4418
4932
  const compact = budget < 1e3;
4419
4933
  const lines = [`Context Guard: ${items.length} pending; revision ${projection.contractRevision}.`, compact ? "Checkpoint required before completion. Qualified safe end preserves pending work; it is not completion." : COMPLETION_RULE];
@@ -4440,7 +4954,7 @@ function renderRecoveryPacket(projection, options = {}) {
4440
4954
  if (add(`[${clip(item.id, 20)}] root_condition_pending; wait for trusted root: ${item.resumeEvent ?? item.condition ?? item.normalizedText}; do not execute before release`, compact ? 160 : 310)) count++;
4441
4955
  return;
4442
4956
  }
4443
- const remedy = diagnosis.repairability === "agent_repairable" ? "Collect matching evidence; checkpoint" : diagnosis.repairability === "historical_gap" ? "Read back observed state; do not re-execute" : diagnosis.certification === "unsupported" ? "Deliver honestly; stays uncertified unless a fresh instruction names a supported action" : "Restore audited host/adapter capability";
4957
+ const remedy = diagnosis.repairability === "agent_repairable" ? "Collect matching evidence; checkpoint" : diagnosis.repairability === "historical_gap" ? "Read back observed state; do not re-execute" : diagnosis.next_action.kind === "clarify_target" ? "Supply the exact target; then collect evidence and checkpoint" : diagnosis.certification === "unsupported" ? "Deliver honestly; stays uncertified unless a fresh instruction names a supported action" : "Restore audited host/adapter capability";
4444
4958
  if (add(`[${clip(item.id, 20)}] ${diagnosis.reason_code}; ${compact ? remedy : diagnosis.next_action.resume_condition ?? remedy}; ${clip(item.normalizedText, 70)}`, compact ? 110 : 310)) count++;
4445
4959
  };
4446
4960
  if (constraints[0]) constraint(constraints[0]);
@@ -4457,78 +4971,1044 @@ function renderRecoveryPacket(projection, options = {}) {
4457
4971
  }
4458
4972
 
4459
4973
  //#endregion
4460
- //#region src/domain/checkpoint.ts
4461
- function stable$2(value) {
4462
- if (Array.isArray(value)) return `[${value.map(stable$2).join(",")}]`;
4463
- if (value && typeof value === "object") return `{${Object.entries(value).sort(([a], [b]) => a.localeCompare(b)).map(([key, entry]) => `${JSON.stringify(key)}:${stable$2(entry)}`).join(",")}}`;
4464
- return JSON.stringify(value);
4974
+ //#region src/domain/work-unit.ts
4975
+ /**
4976
+ * Work-unit derivation rules (0.6.0, C04). Units are derived from the durable
4977
+ * message stream nothing is ever written to the log so the classification
4978
+ * below must stay deterministic and conservative: an ambiguous relation keeps
4979
+ * the current unit rather than inventing a new one, and a mis-assigned
4980
+ * obligation is recoverable through clarification, never through a silent
4981
+ * unit rewrite.
4982
+ *
4983
+ * The rules are the frozen P0 §3 C04 decision, in evaluation order:
4984
+ *
4985
+ * 1. The session's first root task message opens U001.
4986
+ * 2. A message explicitly linked to the current unit (item-ID reference,
4987
+ * rebind control, a direct answer while an inquiry is open) stays in it.
4988
+ * 3. When the current unit has no open executable obligations left, a
4989
+ * directive-bearing message opens a new unit; the old one is switched away,
4990
+ * never retroactively closed.
4991
+ * 4. While the current unit still has open work, only an EXPLICIT switch
4992
+ * marker (closed vocabulary, fixture-pinned) opens a new unit; anything
4993
+ * else stays in the current unit.
4994
+ * 5. A DELEGATION-marked message opens a CHILD unit of the current unit. The
4995
+ * child's open obligations are required descendants of the parent's
4996
+ * closure (C04), so the parent cannot be certified while the delegated
4997
+ * work is open, and the delegated result itself never closes the parent.
4998
+ */
4999
+ /** Explicit task-switch markers; a closed vocabulary pinned by the v2 fixture. */
5000
+ const SWITCH_MARKER = new RegExp([
5001
+ "^(?:另外|此外|另一(?:件事|个任务|个话题)|换个?话题|下一个任务|新任务|下一个问题|先做(?:另一|别的))[::,,。。\\s]",
5002
+ "^(?:now\\s+a\\s+)?(?:different|new|separate)\\s+task\\b",
5003
+ "^next\\s+task\\b",
5004
+ "^(?:on\\s+a\\s+related\\s+note|by\\s+the\\s+way)\\b"
5005
+ ].join("|"), "i");
5006
+ /**
5007
+ * Explicit delegation markers; the same closed-vocabulary discipline as the
5008
+ * switch markers, pinned by the v2 fixture. Only a root message that actually
5009
+ * hands work to a subagent/subtask opens a child unit — "let the subagent …",
5010
+ * "delegate … to a subagent", "spawn a subagent …".
5011
+ */
5012
+ const DELEGATION_MARKER = new RegExp([
5013
+ "(?:让|由|交给|委派给?|派给|安排)(?:一个)?(?:子代理|子任务|子会话|小助手)",
5014
+ "(?:子代理|子任务|子会话)(?:去|来|负责|执行|完成)",
5015
+ "\\bdelegate\\s+(?:this|it|the\\s+\\w+|\\w+)\\s+to\\s+(?:a\\s+|the\\s+)?(?:subagent|sub-agent|child\\s+agent)\\b",
5016
+ "\\b(?:spawn|dispatch|hand\\s+(?:this|it)\\s+off\\s+to)\\s+(?:a\\s+|the\\s+)?(?:subagent|sub-agent|child\\s+agent)\\b",
5017
+ "\\bsub-?agent\\s+(?:should|must|to)\\s+\\w+"
5018
+ ].join("|"), "i");
5019
+ /**
5020
+ * Whether a root message opens a new work unit rather than joining the
5021
+ * current one. `directiveBearing` says the message produced (or would
5022
+ * produce) requirement/acceptance work; `openWorkInCurrentUnit` is evaluated
5023
+ * against the state BEFORE the message is captured.
5024
+ */
5025
+ function opensNewUnit(projection, text, directiveBearing, openWorkInCurrentUnit) {
5026
+ if (!directiveBearing) return false;
5027
+ if (DELEGATION_MARKER.test(text)) return true;
5028
+ if (SWITCH_MARKER.test(text)) return true;
5029
+ return !openWorkInCurrentUnit;
4465
5030
  }
4466
- function tuplesEqual(left, right) {
4467
- return stable$2(left ?? {}) === stable$2(right ?? {});
5031
+ /**
5032
+ * Whether this message opens a child (delegated) unit of the current unit
5033
+ * rather than a sibling. Only meaningful together with {@link opensNewUnit}.
5034
+ */
5035
+ function opensChildUnit(projection, text) {
5036
+ return projection.currentUnitId !== void 0 && DELEGATION_MARKER.test(text);
4468
5037
  }
4469
- function transitionsEqual(left, right) {
4470
- return stable$2(left) === stable$2(right);
5038
+ /** An explicit reference to a contract item identity (R001/A001/P001/U001). */
5039
+ const ITEM_REFERENCE = /\b([RAPU]\d{3})\b/g;
5040
+ /**
5041
+ * Whether the message explicitly links itself to the current unit's items.
5042
+ *
5043
+ * A reference counts only when it names an item that still exists as live work:
5044
+ * an ID that never existed, or one already `passed`/`superseded`, is history and
5045
+ * cannot pull a new instruction back into an old unit. A live item binds when it
5046
+ * belongs to the current unit's lineage — the current unit, an ancestor, or a
5047
+ * required descendant — while a unit-less (pre-v5) obligation is always a
5048
+ * legitimate continuation target.
5049
+ */
5050
+ function explicitlyLinkedToCurrentUnit(projection, text) {
5051
+ const current = projection.currentUnitId;
5052
+ const lineage = current === void 0 ? void 0 : new Set([
5053
+ current,
5054
+ ...unitAncestorIds(projection, current),
5055
+ ...unitDescendantIds(projection, current)
5056
+ ]);
5057
+ for (const match of text.matchAll(ITEM_REFERENCE)) {
5058
+ const item = projection.items.get(match[1]);
5059
+ if (!item) continue;
5060
+ if (item.status === "passed" || item.status === "superseded") continue;
5061
+ if (lineage === void 0 || item.unitId === void 0) return true;
5062
+ if (lineage.has(item.unitId)) return true;
5063
+ }
5064
+ return false;
4471
5065
  }
4472
- function transitionIsSelfConsistent(action, transition) {
4473
- if (!transition?.parameters || transition.predicateId !== ACTION_MANIFEST.actions[action].predicateId || transition.version !== 1 || transition.predParamsKind !== "inline") return false;
4474
- const recomputed = predParamsDigest(transition.parameters, resolveAllowlist("product"));
4475
- return transition.parametersDigest === void 0 || transition.parametersDigest === recomputed;
5066
+ /** The next unit identity in the session's sequence. */
5067
+ function nextUnitId(projection) {
5068
+ let max = 0;
5069
+ for (const unitId of projection.units.keys()) {
5070
+ const num = Number(unitId.slice(1));
5071
+ if (Number.isInteger(num) && num > max) max = num;
5072
+ }
5073
+ return `U${String(max + 1).padStart(3, "0")}`;
4476
5074
  }
4477
- function evidenceFact(evidence) {
4478
- return {
4479
- id: evidence.id,
4480
- outcome: evidence.outcome,
4481
- method: evidence.toolName,
4482
- operations: (evidence.operations ?? []).map((entry) => entry.op),
4483
- executables: evidence.executables ?? [],
4484
- subjects: evidence.subjects,
4485
- surfaces: evidence.surfaces,
4486
- semanticAction: evidence.semanticAction ?? "generic_run",
4487
- evidenceRole: evidence.evidenceRole ?? "effect",
4488
- resolvedTarget: evidence.resolvedTarget ?? {},
4489
- observedState: evidence.observedState,
4490
- parseStatus: evidence.parseStatus ?? "adapter_unavailable",
4491
- reasonCode: evidence.reasonCode ?? (evidence.parseStatus ? void 0 : "adapter_unavailable"),
4492
- adapterId: evidence.adapterId,
4493
- adapterVersion: evidence.adapterVersion
4494
- };
5075
+ /**
5076
+ * Open a work unit.
5077
+ *
5078
+ * A SIBLING unit (no parent) becomes current and switches the previous current
5079
+ * unit away: that is a task switch, and the old unit's residual work stays
5080
+ * visible but no longer blocks the new task.
5081
+ *
5082
+ * A CHILD unit (delegated sub-unit) does NOT become current. The parent keeps
5083
+ * owning the session's certified scope, so the parent's own obligations are
5084
+ * never dropped when it delegates part of the work — the child's obligations
5085
+ * join the parent's closure as required descendants instead (C04). The child
5086
+ * is only ever created under an existing parent; a stray parent id would
5087
+ * create an orphan lineage, so it is dropped.
5088
+ */
5089
+ function openUnit(projection, seq, headline, parentUnitId) {
5090
+ const unitId = nextUnitId(projection);
5091
+ const parent = parentUnitId !== void 0 && projection.units.has(parentUnitId) ? parentUnitId : void 0;
5092
+ if (parent === void 0) {
5093
+ const previous = projection.currentUnitId !== void 0 ? projection.units.get(projection.currentUnitId) : void 0;
5094
+ if (previous && previous.switchedAwayAtSeq === void 0) previous.switchedAwayAtSeq = seq;
5095
+ projection.currentUnitId = unitId;
5096
+ }
5097
+ const unit = {
5098
+ unitId,
5099
+ openedAtSeq: seq,
5100
+ rootInputRefs: [{ seq }],
5101
+ headline,
5102
+ ...parent !== void 0 ? { parentUnitId: parent } : {}
5103
+ };
5104
+ projection.units.set(unitId, unit);
5105
+ return unit;
5106
+ }
5107
+ /** Fold one later root message into the current unit's input references. */
5108
+ function foldIntoCurrentUnit(projection, seq) {
5109
+ const unit = projection.currentUnitId !== void 0 ? projection.units.get(projection.currentUnitId) : void 0;
5110
+ if (unit) unit.rootInputRefs.push({ seq });
4495
5111
  }
4496
- function citedEvidence(projection, binding) {
4497
- return binding.evidenceIds.map((id) => projection.evidence.get(id)).filter((value) => value !== void 0);
5112
+ /**
5113
+ * The ancestors of `unitId`, nearest first. Lineage is derived from the
5114
+ * derived `parentUnitId` chain; a cycle (impossible from the derivation, but
5115
+ * possible in a hand-built projection) terminates instead of hanging.
5116
+ */
5117
+ function unitAncestorIds(projection, unitId) {
5118
+ const ancestors = [];
5119
+ const seen = new Set([unitId]);
5120
+ let cursor = projection.units.get(unitId)?.parentUnitId;
5121
+ while (cursor !== void 0 && !seen.has(cursor)) {
5122
+ ancestors.push(cursor);
5123
+ seen.add(cursor);
5124
+ cursor = projection.units.get(cursor)?.parentUnitId;
5125
+ }
5126
+ return ancestors;
4498
5127
  }
4499
- function evidenceProblem(projection, item, binding) {
4500
- const missing = binding.evidenceIds.filter((id) => !projection.evidence.has(id));
4501
- if (missing.length) return {
4502
- itemId: item.id,
4503
- reason: "cited evidence is missing",
4504
- reasonCode: "evidence_missing",
4505
- offendingEvidenceIds: missing
4506
- };
4507
- if (item.reboundFrom) {
4508
- const sourceSeq = /^m(\d+)(?::|$)/.exec(item.sourceMessageId);
4509
- const tooEarly = binding.evidenceIds.filter((id) => !sourceSeq || projection.evidence.get(id).toolResultSeq < Number(sourceSeq[1]));
4510
- if (tooEarly.length) return {
4511
- itemId: item.id,
4512
- reason: "evidence predates the authoritative root clause used by this replacement",
4513
- reasonCode: "rebind_evidence_predates_source",
4514
- offendingEvidenceIds: tooEarly
4515
- };
5128
+ /**
5129
+ * Every required descendant of `unitId`, in stable unit order: the units whose
5130
+ * `parentUnitId` chain reaches `unitId`. The closure of a unit includes the
5131
+ * open obligations of this set (C04).
5132
+ */
5133
+ function unitDescendantIds(projection, unitId) {
5134
+ const descendants = [];
5135
+ const seen = new Set([unitId]);
5136
+ const queue = [unitId];
5137
+ while (queue.length > 0) {
5138
+ const current = queue.shift();
5139
+ for (const unit of projection.units.values()) {
5140
+ if (unit.parentUnitId !== current || seen.has(unit.unitId)) continue;
5141
+ seen.add(unit.unitId);
5142
+ descendants.push(unit.unitId);
5143
+ queue.push(unit.unitId);
5144
+ }
4516
5145
  }
4517
- const wrongEpoch = binding.evidenceIds.filter((id) => projection.evidence.get(id)?.epoch !== projection.epoch);
4518
- if (wrongEpoch.length) return {
4519
- itemId: item.id,
4520
- reason: "cited evidence belongs to a different epoch",
4521
- reasonCode: "evidence_wrong_epoch",
4522
- offendingEvidenceIds: wrongEpoch
4523
- };
4524
- const notSuccess = binding.evidenceIds.filter((id) => projection.evidence.get(id)?.outcome !== "success");
4525
- if (notSuccess.length) return {
4526
- itemId: item.id,
4527
- reason: "cited evidence outcome is not success",
4528
- reasonCode: "evidence_outcome_not_success",
4529
- offendingEvidenceIds: notSuccess
4530
- };
4531
- const requiredAction = item.semanticAction ?? "generic_run";
5146
+ return descendants.sort();
5147
+ }
5148
+ /** Record one delegated round-trip inside a unit as bounded audit evidence. */
5149
+ function recordDelegation(projection, unitId, ref) {
5150
+ const unit = projection.units.get(unitId);
5151
+ if (!unit) return;
5152
+ const refs = unit.delegationRefs ?? [];
5153
+ if (refs.some((entry) => entry.callId === ref.callId)) return;
5154
+ refs.push({ ...ref });
5155
+ unit.delegationRefs = refs;
5156
+ }
5157
+ /**
5158
+ * Whether the current unit still holds open executable work — the rule-3
5159
+ * handover test, evaluated BEFORE the new message's items are inserted.
5160
+ *
5161
+ * This is the SAME closure the certificate uses: the current unit's own open
5162
+ * work plus the open work of every required descendant unit. A parent whose own
5163
+ * items are all passed but whose delegated child is still open has not finished,
5164
+ * so an ordinary follow-up must not be treated as a handover to a new sibling
5165
+ * task — that would silently exclude the child from the certified scope.
5166
+ */
5167
+ function currentUnitHasOpenWork(projection) {
5168
+ const current = projection.currentUnitId;
5169
+ if (current === void 0) return false;
5170
+ const closure = new Set([current, ...unitDescendantIds(projection, current)]);
5171
+ return [...projection.items.values()].some((item) => item.status === "pending" && item.unitId !== void 0 && closure.has(item.unitId) && item.kind !== "prohibition");
5172
+ }
5173
+
5174
+ //#endregion
5175
+ //#region src/domain/closure.ts
5176
+ /**
5177
+ * The single open-closure implementation (0.6.0, C02/C04/D06-03/D06-07).
5178
+ *
5179
+ * Before 0.6.0, checkpoint, recovery, diagnostics, and the Goal gate each
5180
+ * filtered pending obligations with their own slightly different rule, and the
5181
+ * answers could disagree. Every question about "what is open" now goes through
5182
+ * this module:
5183
+ *
5184
+ * - {@link visiblePendingItems} — everything still pending, constraints first
5185
+ * in spirit: display surfaces (recovery, status, checkpoint pages) show
5186
+ * prohibitions too, because a constraint is never finished work.
5187
+ * - {@link certifiableOpenItems} — the obligations a completion certificate
5188
+ * answers for: pending, not a prohibition. Prohibitions are standing
5189
+ * constraints, never counted work; `answered` items closed by a trusted
5190
+ * delivery are no longer open; `passed` and `superseded` never were.
5191
+ * - {@link unitClosureItemIds} — the v5 unit closure: the certified scope of
5192
+ * one work unit, which is the unit's OWN open obligations PLUS the open
5193
+ * obligations of every required descendant unit.
5194
+ * - {@link ancestorConstraints} / {@link ancestorConstraintForBinding} — the
5195
+ * ancestor units' standing constraints (prohibitions and unsatisfied
5196
+ * conditions) that stay in force for a descendant's matching obligations.
5197
+ *
5198
+ * Legacy sessions (no v5 boundary) have no units: they certify the whole
5199
+ * session, exactly what {@link certifiableOpenItems} returns.
5200
+ */
5201
+ /** Every pending item, in stable display order. Constraints stay visible. */
5202
+ function visiblePendingItems(projection) {
5203
+ return [...projection.items.values()].filter((item) => item.status === "pending").sort((a, b) => a.revision - b.revision || (a.id < b.id ? -1 : 1));
5204
+ }
5205
+ /** The obligations a completion certificate answers for: open work, no constraints. */
5206
+ function certifiableOpenItems(projection) {
5207
+ return visiblePendingItems(projection).filter((item) => item.kind !== "prohibition");
5208
+ }
5209
+ /**
5210
+ * The certifiable open obligations inside one work unit's closure: the unit's
5211
+ * own open work plus the open work of every required descendant unit.
5212
+ *
5213
+ * A delegated child unit is REQUIRED work of its parent (C04): the parent has
5214
+ * not finished while the sub-unit it handed work to still has open
5215
+ * obligations, so the parent's certificate must answer for them too. The
5216
+ * reverse is deliberately not true — a child may be certified while unrelated
5217
+ * residual work exists in an ancestor or a sibling, which is what keeps a task
5218
+ * switch from being blocked by history.
5219
+ */
5220
+ function unitClosureItemIds(projection, unitId) {
5221
+ if (projection.boundaryProtocol !== 5) return [];
5222
+ const inClosure = new Set([unitId, ...unitDescendantIds(projection, unitId)]);
5223
+ return certifiableOpenItems(projection).filter((item) => item.unitId !== void 0 && inClosure.has(item.unitId)).map((item) => item.id);
5224
+ }
5225
+ /** Whether a prohibition declares no identity at all — a blanket ban on the action. */
5226
+ function isBlanketProhibition(action, requested) {
5227
+ const key = requestedIdentityKey(action);
5228
+ if (!key) return false;
5229
+ return !requested || !Object.hasOwn(requested, key);
5230
+ }
5231
+ /** The ancestor obligations that act as standing constraints on this unit. */
5232
+ function standingAncestorConstraints(projection, unitId) {
5233
+ if (projection.boundaryProtocol !== 5) return [];
5234
+ const ancestors = unitAncestorIds(projection, unitId);
5235
+ if (ancestors.length === 0) return [];
5236
+ return visiblePendingItems(projection).filter((item) => {
5237
+ if (item.unitId === void 0 || !ancestors.includes(item.unitId)) return false;
5238
+ if (item.kind === "prohibition") return true;
5239
+ return item.authorityDisposition === "conditional_wait" || item.waitAuthorization !== void 0;
5240
+ });
5241
+ }
5242
+ /**
5243
+ * The ancestor constraint that blocks certifying `item` against a resolved
5244
+ * target, if any. This is the authoritative judge used by the certifier: the
5245
+ * ancestor constraint is compared with the SAME conservative identity rule the
5246
+ * mutation authorization uses, so a ban or an unsatisfied condition cannot be
5247
+ * discharged by certifying a descendant obligation that resolves the target
5248
+ * the ancestor constrained.
5249
+ */
5250
+ function ancestorConstraintForBinding(projection, item, resolvedTarget) {
5251
+ if (item.unitId === void 0) return void 0;
5252
+ const action = item.semanticAction;
5253
+ if (!action || action === "generic_run" || !isStatefulAction(action)) return void 0;
5254
+ for (const constraint of standingAncestorConstraints(projection, item.unitId)) {
5255
+ if (constraint.id === item.id || constraint.semanticAction !== action) continue;
5256
+ if (!(constraint.kind === "prohibition" && isBlanketProhibition(action, constraint.requestedTarget) || requestedTargetMatchesResolved(action, constraint.requestedTarget, resolvedTarget))) continue;
5257
+ return {
5258
+ constraintId: constraint.id,
5259
+ constraintUnitId: constraint.unitId,
5260
+ itemId: item.id,
5261
+ kind: constraint.kind === "prohibition" ? "prohibition" : "condition",
5262
+ reasonCode: constraint.kind === "prohibition" ? "ancestor_prohibition_active" : "ancestor_condition_unsatisfied"
5263
+ };
5264
+ }
5265
+ }
5266
+ /**
5267
+ * The closure a completion certificate must answer for right now.
5268
+ *
5269
+ * Legacy sessions certify the whole session. v5 sessions certify the current
5270
+ * work unit's closure PLUS every pre-v5 obligation: items captured before the
5271
+ * boundary carry no unit and keep their birth rules, so a unit certificate
5272
+ * must never silently shrink their scope (migration table, P0 §6).
5273
+ */
5274
+ function certificateClosure(projection) {
5275
+ if (projection.boundaryProtocol === 5) {
5276
+ const legacyIds = certifiableOpenItems(projection).filter((item) => item.unitId === void 0).map((item) => item.id);
5277
+ const unitIds = projection.currentUnitId !== void 0 ? unitClosureItemIds(projection, projection.currentUnitId) : [];
5278
+ return {
5279
+ unitId: projection.currentUnitId,
5280
+ itemIds: [...legacyIds, ...unitIds]
5281
+ };
5282
+ }
5283
+ return { itemIds: certifiableOpenItems(projection).map((item) => item.id) };
5284
+ }
5285
+
5286
+ //#endregion
5287
+ //#region src/domain/proof.ts
5288
+ const PROOF_PROTOCOL_VERSION = "0.4.0";
5289
+ const PROOF_KINDS = [
5290
+ "subject_readback",
5291
+ "scope_coverage",
5292
+ "state_verification"
5293
+ ];
5294
+ function stable$2(value) {
5295
+ if (Array.isArray(value)) return `[${value.map(stable$2).join(",")}]`;
5296
+ if (value && typeof value === "object") return `{${Object.entries(value).sort(([a], [b]) => a.localeCompare(b)).map(([k, v]) => `${JSON.stringify(k)}:${stable$2(v)}`).join(",")}}`;
5297
+ return JSON.stringify(value);
5298
+ }
5299
+ function digest(value) {
5300
+ return createHash("sha256").update("ccg.proofManifest.v1\n", "utf8").update(stable$2(value), "utf8").digest("hex");
5301
+ }
5302
+ function validDigest(value) {
5303
+ return /^[0-9a-f]{64}$/.test(value);
5304
+ }
5305
+ /**
5306
+ * The manifest digest root includes every integrity-bearing field, so a
5307
+ * tampered asset-set digest is exactly as detectable as a tampered obligation.
5308
+ */
5309
+ function proofDigest(obligations, assetSetSha256) {
5310
+ return digest({
5311
+ proofProtocolVersion: PROOF_PROTOCOL_VERSION,
5312
+ obligations: [...obligations],
5313
+ ...assetSetSha256 !== void 0 ? { assetSetSha256 } : {}
5314
+ });
5315
+ }
5316
+ function validateProofManifest(manifest) {
5317
+ const errors = [];
5318
+ if (!manifest || typeof manifest !== "object") return ["proof_manifest_invalid"];
5319
+ const value = manifest;
5320
+ if (value.proofProtocolVersion !== PROOF_PROTOCOL_VERSION) errors.push("proof_protocol_version_mismatch");
5321
+ if (!Array.isArray(value.obligations)) errors.push("proof_obligations_missing");
5322
+ if (value.assetSetSha256 !== void 0 && (typeof value.assetSetSha256 !== "string" || !validDigest(value.assetSetSha256))) errors.push("proof_asset_set_digest_invalid");
5323
+ if (typeof value.proofSha256 !== "string" || !validDigest(value.proofSha256)) errors.push("proof_digest_invalid");
5324
+ const obligations = Array.isArray(value.obligations) ? value.obligations : [];
5325
+ const ids = /* @__PURE__ */ new Set();
5326
+ for (const raw of obligations) {
5327
+ if (!raw || typeof raw !== "object") {
5328
+ errors.push("proof_obligation_invalid");
5329
+ continue;
5330
+ }
5331
+ const obligation = raw;
5332
+ if (typeof obligation.obligationId !== "string" || ids.has(obligation.obligationId)) errors.push("proof_obligation_id_duplicate_or_invalid");
5333
+ if (typeof obligation.obligationId === "string") ids.add(obligation.obligationId);
5334
+ if (!PROOF_KINDS.includes(obligation.kind)) errors.push("proof_kind_unsupported");
5335
+ if (![
5336
+ "artifact",
5337
+ "ui",
5338
+ "visual",
5339
+ "scope"
5340
+ ].includes(String(obligation.surface))) errors.push("proof_surface_unsupported");
5341
+ if (!Array.isArray(obligation.subjectIds) || obligation.subjectIds.length === 0 || obligation.subjectIds.some((id) => typeof id !== "string" || id.startsWith("codex:unsupported/"))) errors.push("proof_subject_invalid");
5342
+ if (!Array.isArray(obligation.evidenceIds) || obligation.evidenceIds.length === 0 || new Set(obligation.evidenceIds).size !== obligation.evidenceIds.length) errors.push("proof_evidence_invalid");
5343
+ const expected = obligation.expectedScopeDigest;
5344
+ const observed = obligation.observedScopeDigest;
5345
+ for (const digestValue of [expected, observed]) if (digestValue !== void 0 && (typeof digestValue !== "string" || !validDigest(digestValue))) errors.push("proof_scope_digest_invalid");
5346
+ if (expected !== void 0 && observed !== expected) errors.push("proof_scope_digest_mismatch");
5347
+ if (expected === void 0 && observed !== void 0) errors.push("proof_scope_digest_mismatch");
5348
+ }
5349
+ if (errors.length === 0) {
5350
+ const assetSet = typeof value.assetSetSha256 === "string" ? value.assetSetSha256 : void 0;
5351
+ if (value.proofSha256 !== proofDigest(obligations, assetSet)) errors.push("proof_digest_mismatch");
5352
+ }
5353
+ return [...new Set(errors)];
5354
+ }
5355
+ function createProofManifest(obligations, assetSetSha256) {
5356
+ const normalized = obligations.map((obligation) => ({
5357
+ obligationId: obligation.obligationId,
5358
+ kind: obligation.kind,
5359
+ surface: obligation.surface,
5360
+ subjectIds: [...obligation.subjectIds].sort(),
5361
+ evidenceIds: [...obligation.evidenceIds].sort(),
5362
+ ...obligation.expectedScopeDigest ? { expectedScopeDigest: obligation.expectedScopeDigest } : {},
5363
+ ...obligation.observedScopeDigest ? { observedScopeDigest: obligation.observedScopeDigest } : {}
5364
+ })).sort((a, b) => a.obligationId.localeCompare(b.obligationId));
5365
+ const manifest = {
5366
+ proofProtocolVersion: PROOF_PROTOCOL_VERSION,
5367
+ obligations: normalized,
5368
+ ...assetSetSha256 !== void 0 ? { assetSetSha256 } : {},
5369
+ proofSha256: proofDigest(normalized, assetSetSha256)
5370
+ };
5371
+ const errors = validateProofManifest(manifest);
5372
+ if (errors.length) throw new Error(`proof manifest rejected: ${errors.join(",")}`);
5373
+ return manifest;
5374
+ }
5375
+ /**
5376
+ * Bind a structurally valid proof to the actual replayed projection: every
5377
+ * obligation must name a pending item, every evidence id must exist in the
5378
+ * projection, and every bound evidence must satisfy the obligation's kind,
5379
+ * surface, subject, and outcome constraints. An empty projection therefore
5380
+ * rejects any proof, and cross-item or foreign evidence can never bind.
5381
+ */
5382
+ function bindProofToProjection(projection, proof) {
5383
+ const errors = [];
5384
+ const items = projection.items;
5385
+ const evidence = projection.evidence;
5386
+ for (const obligation of proof.obligations) {
5387
+ const item = items.get(obligation.obligationId);
5388
+ if (!item) {
5389
+ errors.push("proof_obligation_unbound");
5390
+ continue;
5391
+ }
5392
+ if (item.status !== "pending") {
5393
+ errors.push("proof_obligation_not_pending");
5394
+ continue;
5395
+ }
5396
+ if (item.verification.surface !== void 0 && item.verification.surface !== obligation.surface) errors.push("proof_surface_unbound");
5397
+ const seen = /* @__PURE__ */ new Set();
5398
+ for (const evidenceId of obligation.evidenceIds) {
5399
+ const record = evidence.get(evidenceId);
5400
+ if (!record) {
5401
+ errors.push("proof_evidence_unknown");
5402
+ continue;
5403
+ }
5404
+ if (!seen.has(evidenceId)) seen.add(evidenceId);
5405
+ if (record.outcome !== "success") {
5406
+ errors.push("proof_evidence_outcome_invalid");
5407
+ continue;
5408
+ }
5409
+ if (!proofEvidenceConstraints(record, obligation)) errors.push("proof_evidence_constraint_failed");
5410
+ }
5411
+ if (obligation.kind === "scope_coverage") {
5412
+ const itemScope = item.requestedTarget?.scope;
5413
+ const itemSubject = item.verification.subject;
5414
+ if (!obligation.subjectIds.every((subject) => subject === itemScope || subject === itemSubject)) errors.push("proof_scope_subject_unbound");
5415
+ }
5416
+ }
5417
+ return [...new Set(errors)];
5418
+ }
5419
+ function canonicalProjection(projection) {
5420
+ return {
5421
+ epoch: projection.epoch,
5422
+ contractRevision: projection.contractRevision,
5423
+ sessionRefDigest: projection.sessionRefDigest,
5424
+ hostLockDigest: projection.hostLockDigest,
5425
+ hostStatus: projection.hostStatus,
5426
+ hostCohortId: projection.hostCohortId,
5427
+ integrity: projection.integrity,
5428
+ items: [...projection.items.values()].map(({ id, revision, kind, status, semanticAction, requestedTarget, verification }) => ({
5429
+ id,
5430
+ revision,
5431
+ kind,
5432
+ status,
5433
+ semanticAction,
5434
+ requestedTarget,
5435
+ verification
5436
+ })).sort((a, b) => a.id.localeCompare(b.id)),
5437
+ evidence: [...projection.evidence.values()].map(({ id, epoch, toolName, outcome, capabilities, subjects, surfaces: surfaces$1, operations, semanticAction, evidenceRole, resolvedTarget, observedState }) => ({
5438
+ id,
5439
+ epoch,
5440
+ toolName,
5441
+ outcome,
5442
+ capabilities,
5443
+ subjects,
5444
+ surfaces: surfaces$1,
5445
+ operations,
5446
+ semanticAction,
5447
+ evidenceRole,
5448
+ resolvedTarget,
5449
+ observedState
5450
+ })).sort((a, b) => a.id.localeCompare(b.id)),
5451
+ checkpoints: projection.checkpoints.map(({ id, certificationDigest: certificationDigest$1, result }) => ({
5452
+ id,
5453
+ certificationDigest: certificationDigest$1,
5454
+ result
5455
+ }))
5456
+ };
5457
+ }
5458
+ function sessionQuery(projection, proof) {
5459
+ if (proof) {
5460
+ if (validateProofManifest(proof).length) return {
5461
+ sessionRefDigest: projection.sessionRefDigest,
5462
+ epoch: projection.epoch,
5463
+ contractRevision: projection.contractRevision,
5464
+ state: "corrupt",
5465
+ reasonCode: "proof_invalid",
5466
+ cohortId: projection.hostCohortId
5467
+ };
5468
+ if (bindProofToProjection(projection, proof).length) return {
5469
+ sessionRefDigest: projection.sessionRefDigest,
5470
+ epoch: projection.epoch,
5471
+ contractRevision: projection.contractRevision,
5472
+ state: "corrupt",
5473
+ reasonCode: "proof_unbound",
5474
+ cohortId: projection.hostCohortId
5475
+ };
5476
+ }
5477
+ const state = projection.integrity === "valid" ? projection.hostStatus === "supported" ? "valid" : "unknown" : projection.integrity;
5478
+ return {
5479
+ sessionRefDigest: projection.sessionRefDigest,
5480
+ epoch: projection.epoch,
5481
+ contractRevision: projection.contractRevision,
5482
+ state,
5483
+ ...proof ? { proof } : {},
5484
+ cohortId: projection.hostCohortId
5485
+ };
5486
+ }
5487
+ function proofEvidenceConstraints(evidence, obligation) {
5488
+ if (evidence.outcome !== "success" || evidence.surfaces.length !== 1 || evidence.surfaces[0] !== obligation.surface) return false;
5489
+ if (!obligation.subjectIds.every((subject) => evidence.subjects.includes(subject))) return false;
5490
+ if (obligation.kind === "subject_readback" && !(evidence.operations ?? []).some(({ op }) => op === "read" || op === "verify")) return false;
5491
+ if (obligation.kind === "scope_coverage" && !(evidence.operations ?? []).some(({ op }) => op === "run" || op === "verify")) return false;
5492
+ if (obligation.kind === "state_verification" && evidence.evidenceRole !== "state") return false;
5493
+ return true;
5494
+ }
5495
+ const PROOF_PROTOCOL_VERSION_V2 = "0.6.0";
5496
+ /** The v2 digest domain; the v1 domain string is untouched. */
5497
+ const PROOF_MANIFEST_DOMAIN_V2 = "ccg.proofManifest.v2";
5498
+ const PROOF_KINDS_V2 = [
5499
+ "subject_readback",
5500
+ "scope_coverage",
5501
+ "state_verification",
5502
+ "input_asset_check",
5503
+ "output_visual_readback",
5504
+ "object_url_readback",
5505
+ "execution_fact",
5506
+ "external_fact"
5507
+ ];
5508
+ const ALL_SURFACES = [
5509
+ "native_read",
5510
+ "native_write_edit",
5511
+ "shell",
5512
+ "web",
5513
+ "jobs",
5514
+ "subagent",
5515
+ "visual_capture"
5516
+ ];
5517
+ function surfaces(supported) {
5518
+ return {
5519
+ supportedSurfaces: [...supported],
5520
+ unavailableSurfaces: ALL_SURFACES.filter((surface) => !supported.includes(surface))
5521
+ };
5522
+ }
5523
+ const PROOF_CAPABILITY_MATRIX = {
5524
+ subject_readback: {
5525
+ kind: "subject_readback",
5526
+ capabilities: [
5527
+ "filesystem-read",
5528
+ "verify",
5529
+ "deterministic-check"
5530
+ ],
5531
+ readbackRequired: true,
5532
+ operationOnSubject: true,
5533
+ ...surfaces(["native_read", "shell"])
5534
+ },
5535
+ scope_coverage: {
5536
+ kind: "scope_coverage",
5537
+ capabilities: [
5538
+ "filesystem-read",
5539
+ "verify",
5540
+ "deterministic-check",
5541
+ "web-fetch"
5542
+ ],
5543
+ readbackRequired: true,
5544
+ operationOnSubject: false,
5545
+ ...surfaces([
5546
+ "native_read",
5547
+ "shell",
5548
+ "web"
5549
+ ])
5550
+ },
5551
+ state_verification: {
5552
+ kind: "state_verification",
5553
+ capabilities: [
5554
+ "filesystem-read",
5555
+ "web-fetch",
5556
+ "deterministic-check"
5557
+ ],
5558
+ readbackRequired: true,
5559
+ requiredRole: "state",
5560
+ operationOnSubject: false,
5561
+ ...surfaces(["native_read", "web"])
5562
+ },
5563
+ input_asset_check: {
5564
+ kind: "input_asset_check",
5565
+ capabilities: ["filesystem-read", "web-fetch"],
5566
+ readbackRequired: true,
5567
+ requiredRole: "resolution",
5568
+ operationOnSubject: true,
5569
+ ...surfaces(["native_read", "web"])
5570
+ },
5571
+ output_visual_readback: {
5572
+ kind: "output_visual_readback",
5573
+ capabilities: ["visual-readback"],
5574
+ readbackRequired: true,
5575
+ operationOnSubject: true,
5576
+ ...surfaces(["visual_capture"])
5577
+ },
5578
+ object_url_readback: {
5579
+ kind: "object_url_readback",
5580
+ capabilities: ["web-fetch"],
5581
+ readbackRequired: true,
5582
+ operationOnSubject: true,
5583
+ ...surfaces(["web"])
5584
+ },
5585
+ execution_fact: {
5586
+ kind: "execution_fact",
5587
+ capabilities: [],
5588
+ readbackRequired: false,
5589
+ requiredRole: "effect",
5590
+ operationOnSubject: false,
5591
+ ...surfaces([
5592
+ "shell",
5593
+ "native_write_edit",
5594
+ "native_read",
5595
+ "web",
5596
+ "jobs",
5597
+ "subagent"
5598
+ ])
5599
+ },
5600
+ external_fact: {
5601
+ kind: "external_fact",
5602
+ capabilities: [],
5603
+ readbackRequired: false,
5604
+ operationOnSubject: false,
5605
+ ...surfaces(["jobs", "subagent"])
5606
+ }
5607
+ };
5608
+ /** The host surface names a fact's tool/adapter identity maps to. */
5609
+ function proofHostSurfacesOf(evidence) {
5610
+ const surface = /* @__PURE__ */ new Set();
5611
+ if (evidence.externalOperationRef) surface.add("jobs");
5612
+ if (evidence.delegatedSubtask) surface.add("subagent");
5613
+ if (evidence.capabilities.includes("visual-readback")) surface.add("visual_capture");
5614
+ if (evidence.capabilities.includes("web-fetch")) surface.add("web");
5615
+ if (new Set([
5616
+ "write",
5617
+ "edit",
5618
+ "write_file",
5619
+ "edit_file"
5620
+ ]).has(evidence.toolName)) surface.add("native_write_edit");
5621
+ if (new Set([
5622
+ "read",
5623
+ "read_file",
5624
+ "web_fetch",
5625
+ "web_fetch_url",
5626
+ "web_search"
5627
+ ]).has(evidence.toolName)) surface.add("native_read");
5628
+ if ([
5629
+ "bash",
5630
+ "shell",
5631
+ "pwsh"
5632
+ ].includes(evidence.toolName)) surface.add("shell");
5633
+ if (evidence.capabilities.includes("filesystem-read")) surface.add("native_read");
5634
+ return [...surface].sort();
5635
+ }
5636
+ function digestV2(value) {
5637
+ return createHash("sha256").update(`${PROOF_MANIFEST_DOMAIN_V2}\n`, "utf8").update(stable$2(value), "utf8").digest("hex");
5638
+ }
5639
+ function proofDigestV2(obligations) {
5640
+ return digestV2({
5641
+ proofProtocolVersion: PROOF_PROTOCOL_VERSION_V2,
5642
+ obligations: [...obligations]
5643
+ });
5644
+ }
5645
+ function createProofManifestV2(obligations) {
5646
+ const normalized = obligations.map((obligation) => ({
5647
+ obligationId: obligation.obligationId,
5648
+ kind: obligation.kind,
5649
+ surface: obligation.surface,
5650
+ subjectIds: [...obligation.subjectIds].sort(),
5651
+ sourceIds: [...obligation.sourceIds].sort(),
5652
+ operation: obligation.operation,
5653
+ evidenceIds: [...obligation.evidenceIds].sort(),
5654
+ ...obligation.expectedScopeDigest ? { expectedScopeDigest: obligation.expectedScopeDigest } : {},
5655
+ ...obligation.observedScopeDigest ? { observedScopeDigest: obligation.observedScopeDigest } : {}
5656
+ })).sort((a, b) => a.obligationId.localeCompare(b.obligationId));
5657
+ const manifest = {
5658
+ proofProtocolVersion: PROOF_PROTOCOL_VERSION_V2,
5659
+ obligations: normalized,
5660
+ proofSha256: proofDigestV2(normalized)
5661
+ };
5662
+ const errors = validateProofManifestV2(manifest);
5663
+ if (errors.length) throw new Error(`proof v2 manifest rejected: ${errors.join(",")}`);
5664
+ return manifest;
5665
+ }
5666
+ function validateProofManifestV2(manifest) {
5667
+ const errors = [];
5668
+ if (!manifest || typeof manifest !== "object") return ["proof_manifest_invalid"];
5669
+ const value = manifest;
5670
+ if (value.proofProtocolVersion !== PROOF_PROTOCOL_VERSION_V2) errors.push("proof_protocol_version_mismatch");
5671
+ if (!Array.isArray(value.obligations)) errors.push("proof_obligations_missing");
5672
+ if (typeof value.proofSha256 !== "string" || !validDigest(value.proofSha256)) errors.push("proof_digest_invalid");
5673
+ const obligations = Array.isArray(value.obligations) ? value.obligations : [];
5674
+ const ids = /* @__PURE__ */ new Set();
5675
+ for (const raw of obligations) {
5676
+ if (!raw || typeof raw !== "object") {
5677
+ errors.push("proof_obligation_invalid");
5678
+ continue;
5679
+ }
5680
+ const obligation = raw;
5681
+ if (typeof obligation.obligationId !== "string" || !obligation.obligationId || ids.has(obligation.obligationId)) errors.push("proof_obligation_id_duplicate_or_invalid");
5682
+ if (typeof obligation.obligationId === "string") ids.add(obligation.obligationId);
5683
+ if (!PROOF_KINDS_V2.includes(obligation.kind)) errors.push("proof_kind_unsupported");
5684
+ if (![
5685
+ "artifact",
5686
+ "ui",
5687
+ "visual",
5688
+ "scope"
5689
+ ].includes(String(obligation.surface))) errors.push("proof_surface_unsupported");
5690
+ if (![
5691
+ "create",
5692
+ "write",
5693
+ "modify",
5694
+ "read",
5695
+ "run",
5696
+ "verify"
5697
+ ].includes(String(obligation.operation))) errors.push("proof_operation_unsupported");
5698
+ if (!Array.isArray(obligation.subjectIds) || obligation.subjectIds.length === 0 || obligation.subjectIds.some((id) => typeof id !== "string" || !id)) errors.push("proof_subject_invalid");
5699
+ if (!Array.isArray(obligation.sourceIds) || obligation.sourceIds.length === 0 || obligation.sourceIds.some((id) => typeof id !== "string" || !id)) errors.push("proof_source_invalid");
5700
+ if (!Array.isArray(obligation.evidenceIds) || obligation.evidenceIds.length === 0 || obligation.evidenceIds.some((id) => typeof id !== "string" || !id) || new Set(obligation.evidenceIds).size !== obligation.evidenceIds.length) errors.push("proof_evidence_invalid");
5701
+ const expected = obligation.expectedScopeDigest;
5702
+ const observed = obligation.observedScopeDigest;
5703
+ for (const digestValue of [expected, observed]) if (digestValue !== void 0 && (typeof digestValue !== "string" || !validDigest(digestValue))) errors.push("proof_scope_digest_invalid");
5704
+ if (expected !== observed) errors.push("proof_scope_digest_mismatch");
5705
+ }
5706
+ if (errors.length === 0 && value.proofSha256 !== proofDigestV2(obligations)) errors.push("proof_digest_mismatch");
5707
+ return [...new Set(errors)];
5708
+ }
5709
+ /**
5710
+ * Why one fact cannot discharge one v2 obligation, or `undefined` when it can.
5711
+ * The checks are ordered so the reported reason names the first unmet
5712
+ * requirement: missing producer capability, wrong role, absent readback, wrong
5713
+ * source, wrong subject, wrong operation.
5714
+ */
5715
+ function proofV2Rejection(evidence, obligation) {
5716
+ const spec = PROOF_CAPABILITY_MATRIX[obligation.kind];
5717
+ if (evidence.delegatedSubtask) return "proof_source_bounded_delegation";
5718
+ if (obligation.sourceIds.length > 0 && !obligation.sourceIds.includes(evidence.toolName) && !obligation.sourceIds.includes(evidence.adapterId ?? "")) return "proof_source_unbound";
5719
+ if (evidence.outcome !== "success") return "proof_evidence_outcome_invalid";
5720
+ if (obligation.kind === "external_fact") {
5721
+ if (!evidence.externalOperationRef) return "proof_external_fact_unavailable";
5722
+ if (evidence.externalOperationRef.status !== "completed") return "proof_external_fact_incomplete";
5723
+ }
5724
+ const available = proofHostSurfacesOf(evidence);
5725
+ if (available.length === 0 || !available.some((surface) => spec.supportedSurfaces.includes(surface))) return "proof_producer_capability_unavailable";
5726
+ if (spec.capabilities.length > 0 && !spec.capabilities.some((capability) => evidence.capabilities.includes(capability))) return "proof_producer_capability_unavailable";
5727
+ if (spec.requiredRole !== void 0 && evidence.evidenceRole !== spec.requiredRole) return "proof_role_unbound";
5728
+ if (spec.readbackRequired) {
5729
+ if (!(evidence.operations ?? []).some((entry) => entry.op === "read" || entry.op === "verify")) return "proof_readback_unavailable";
5730
+ if (spec.operationOnSubject && obligation.subjectIds.length > 0) {
5731
+ const subjects = evidence.subjects;
5732
+ if (!obligation.subjectIds.every((subject) => subjects.some((value) => value === subject))) return "proof_subject_unbound";
5733
+ }
5734
+ }
5735
+ if (obligation.kind === "state_verification" && evidence.surfaces.length > 0 && !evidence.surfaces.includes(obligation.surface)) return "proof_surface_unbound";
5736
+ if (obligation.kind === "execution_fact" && !(evidence.operations ?? []).some((entry) => entry.op === obligation.operation)) return "proof_operation_unbound";
5737
+ }
5738
+ /**
5739
+ * The subjects an item's own obligation requires. They come from the item's
5740
+ * frozen verification contract and captured target — never from the proof
5741
+ * manifest, which is exactly what a proof must be checked against.
5742
+ */
5743
+ function requiredSubjectsOf(item) {
5744
+ const values = /* @__PURE__ */ new Set();
5745
+ const subject = item.verification.subject;
5746
+ if (typeof subject === "string" && subject.length > 0 && subject !== "scope") values.add(subject);
5747
+ const target = item.requestedTarget ?? {};
5748
+ if (item.verification.surface === "scope") {
5749
+ const scope = target.scope;
5750
+ if (typeof scope === "string" && scope.length > 0 && scope !== "scope") values.add(scope);
5751
+ }
5752
+ for (const key of [
5753
+ "artifact_id",
5754
+ "package_id",
5755
+ "service_id",
5756
+ "repository"
5757
+ ]) {
5758
+ const value = target[key];
5759
+ if (typeof value === "string" && value.length > 0 && value !== "scope") values.add(value);
5760
+ }
5761
+ return [...values].sort();
5762
+ }
5763
+ /** The frozen coverage digest of a subject set: sorted, then hashed. */
5764
+ function scopeCoverageDigest(subjects) {
5765
+ return createHash("sha256").update("ccg.proofScopeCoverage.v2\n", "utf8").update(JSON.stringify([...subjects].sort()), "utf8").digest("hex");
5766
+ }
5767
+ /**
5768
+ * Operations a fact may perform to discharge one proof kind. `execution_fact`
5769
+ * is bound to the obligation's own declared operation; the readback kinds
5770
+ * accept only an actual read or verify, so a bare successful call never
5771
+ * satisfies them.
5772
+ */
5773
+ const KIND_OPERATIONS = {
5774
+ subject_readback: ["read", "verify"],
5775
+ scope_coverage: ["run", "verify"],
5776
+ state_verification: ["read", "verify"],
5777
+ input_asset_check: ["read", "verify"],
5778
+ output_visual_readback: ["read", "verify"],
5779
+ object_url_readback: ["read", "verify"],
5780
+ execution_fact: "declared",
5781
+ external_fact: []
5782
+ };
5783
+ /** Whether the fact performed an operation the kind accepts. */
5784
+ function proofOperationMatches(evidence, obligation) {
5785
+ const allowed = KIND_OPERATIONS[obligation.kind];
5786
+ const operations = evidence.operations ?? [];
5787
+ if (allowed === "declared") return operations.some((entry) => entry.op === obligation.operation);
5788
+ if (allowed.length === 0) return true;
5789
+ return operations.some((entry) => allowed.includes(entry.op));
5790
+ }
5791
+ /**
5792
+ * Bind a v2 manifest to the live projection; [] means every obligation binds.
5793
+ *
5794
+ * The binding is the whole chain the review demanded, in one place:
5795
+ * the user's obligation (frozen subject and scope on the ITEM) → the trusted
5796
+ * producer fact (qualified by the same availability rules ordinary evidence
5797
+ * uses) → the declared source → the declared operation and its order relative
5798
+ * to the effect → the real coverage set. Only then is the obligation
5799
+ * discharged. A manifest that describes a different subject than the item
5800
+ * asked about fails even when the manifest and the facts agree with each
5801
+ * other.
5802
+ */
5803
+ function bindProofV2ToProjection(projection, manifest) {
5804
+ const errors = [];
5805
+ for (const obligation of manifest.obligations) {
5806
+ const item = projection.items.get(obligation.obligationId);
5807
+ if (!item) {
5808
+ errors.push("proof_obligation_unbound");
5809
+ continue;
5810
+ }
5811
+ if (item.status !== "pending") {
5812
+ errors.push("proof_obligation_not_pending");
5813
+ continue;
5814
+ }
5815
+ if (item.verification.surface !== void 0 && item.verification.surface !== obligation.surface) errors.push("proof_surface_unbound");
5816
+ const required = requiredSubjectsOf(item);
5817
+ if (required.length > 0) {
5818
+ if (!obligation.subjectIds.every((subject) => required.includes(subject))) {
5819
+ errors.push("proof_subject_unbound");
5820
+ continue;
5821
+ }
5822
+ if (!required.every((subject) => obligation.subjectIds.includes(subject))) {
5823
+ errors.push("proof_scope_incomplete");
5824
+ continue;
5825
+ }
5826
+ }
5827
+ const cited = [];
5828
+ for (const evidenceId of obligation.evidenceIds) {
5829
+ const evidence = projection.evidence.get(evidenceId);
5830
+ if (!evidence) {
5831
+ errors.push("proof_evidence_unknown");
5832
+ continue;
5833
+ }
5834
+ const availability = evidenceAvailabilityReason(evidence);
5835
+ if (availability !== void 0) {
5836
+ errors.push(availability);
5837
+ continue;
5838
+ }
5839
+ if (evidence.epoch !== projection.epoch) {
5840
+ errors.push("proof_evidence_wrong_epoch");
5841
+ continue;
5842
+ }
5843
+ if (obligation.subjectIds.length > 0 && !evidence.subjects.some((subject) => obligation.subjectIds.includes(subject))) {
5844
+ errors.push("proof_subject_unbound");
5845
+ continue;
5846
+ }
5847
+ if (!proofOperationMatches(evidence, obligation)) {
5848
+ errors.push("proof_operation_unbound");
5849
+ continue;
5850
+ }
5851
+ const rejection = proofV2Rejection(evidence, obligation);
5852
+ if (rejection) {
5853
+ errors.push(rejection);
5854
+ continue;
5855
+ }
5856
+ cited.push(evidence);
5857
+ }
5858
+ if (cited.length === 0 && obligation.evidenceIds.length > 0) continue;
5859
+ if (required.length > 0 && !required.every((subject) => cited.some((fact) => fact.subjects.includes(subject)))) {
5860
+ errors.push("proof_scope_incomplete");
5861
+ continue;
5862
+ }
5863
+ if (obligation.kind === "input_asset_check") {
5864
+ const firstCheck = Math.min(...cited.map((fact) => fact.toolResultSeq));
5865
+ if ([...projection.evidence.values()].some((fact) => fact.evidenceRole === "effect" && required.some((subject) => fact.subjects.includes(subject)) && fact.toolResultSeq < firstCheck)) {
5866
+ errors.push("proof_input_check_after_effect");
5867
+ continue;
5868
+ }
5869
+ }
5870
+ if (obligation.kind === "scope_coverage") {
5871
+ const covered = [...new Set(cited.flatMap((fact) => fact.subjects))].sort();
5872
+ if (obligation.expectedScopeDigest !== void 0 && obligation.expectedScopeDigest !== scopeCoverageDigest(required)) {
5873
+ errors.push("proof_scope_digest_unbound");
5874
+ continue;
5875
+ }
5876
+ if (obligation.observedScopeDigest !== void 0 && obligation.observedScopeDigest !== scopeCoverageDigest(covered)) {
5877
+ errors.push("proof_scope_digest_unbound");
5878
+ continue;
5879
+ }
5880
+ }
5881
+ }
5882
+ return [...new Set(errors)];
5883
+ }
5884
+ function sessionQueryV2(projection, proof) {
5885
+ if (proof) {
5886
+ if (validateProofManifestV2(proof).length) return {
5887
+ sessionRefDigest: projection.sessionRefDigest,
5888
+ epoch: projection.epoch,
5889
+ contractRevision: projection.contractRevision,
5890
+ state: "corrupt",
5891
+ reasonCode: "proof_invalid",
5892
+ cohortId: projection.hostCohortId
5893
+ };
5894
+ if (bindProofV2ToProjection(projection, proof).length) return {
5895
+ sessionRefDigest: projection.sessionRefDigest,
5896
+ epoch: projection.epoch,
5897
+ contractRevision: projection.contractRevision,
5898
+ state: "corrupt",
5899
+ reasonCode: "proof_unbound",
5900
+ cohortId: projection.hostCohortId
5901
+ };
5902
+ }
5903
+ const state = projection.integrity === "valid" ? projection.hostStatus === "supported" ? "valid" : "unknown" : projection.integrity;
5904
+ return {
5905
+ sessionRefDigest: projection.sessionRefDigest,
5906
+ epoch: projection.epoch,
5907
+ contractRevision: projection.contractRevision,
5908
+ state,
5909
+ ...proof ? { proof } : {},
5910
+ cohortId: projection.hostCohortId
5911
+ };
5912
+ }
5913
+ /**
5914
+ * The capability report for one proof kind against the facts a cohort actually
5915
+ * produced: `unavailable` with a stable reason when no producer is observable,
5916
+ * never a silent pass.
5917
+ */
5918
+ function proofCapabilityReport(kind, facts) {
5919
+ for (const fact of facts) {
5920
+ if (fact.outcome !== "success") continue;
5921
+ const probe = {
5922
+ obligationId: "probe",
5923
+ kind,
5924
+ surface: "artifact",
5925
+ subjectIds: fact.subjects.slice(0, 1),
5926
+ sourceIds: [],
5927
+ operation: "verify",
5928
+ evidenceIds: []
5929
+ };
5930
+ if (probe.subjectIds.length === 0) probe.subjectIds = ["probe"];
5931
+ if (proofV2Rejection(fact, probe) === void 0) return { status: "supported" };
5932
+ }
5933
+ return {
5934
+ status: "unavailable",
5935
+ reasonCode: "proof_producer_capability_unavailable"
5936
+ };
5937
+ }
5938
+
5939
+ //#endregion
5940
+ //#region src/domain/checkpoint.ts
5941
+ function stable$1(value) {
5942
+ if (Array.isArray(value)) return `[${value.map(stable$1).join(",")}]`;
5943
+ if (value && typeof value === "object") return `{${Object.entries(value).sort(([a], [b]) => a.localeCompare(b)).map(([key, entry]) => `${JSON.stringify(key)}:${stable$1(entry)}`).join(",")}}`;
5944
+ return JSON.stringify(value);
5945
+ }
5946
+ function tuplesEqual(left, right) {
5947
+ return stable$1(left ?? {}) === stable$1(right ?? {});
5948
+ }
5949
+ function transitionsEqual(left, right) {
5950
+ return stable$1(left) === stable$1(right);
5951
+ }
5952
+ function transitionIsSelfConsistent(action, transition) {
5953
+ if (!transition?.parameters || transition.predicateId !== ACTION_MANIFEST.actions[action].predicateId || transition.version !== 1 || transition.predParamsKind !== "inline") return false;
5954
+ const recomputed = predParamsDigest(transition.parameters, resolveAllowlist("product"));
5955
+ return transition.parametersDigest === void 0 || transition.parametersDigest === recomputed;
5956
+ }
5957
+ function evidenceFact(evidence) {
5958
+ return {
5959
+ id: evidence.id,
5960
+ outcome: evidence.outcome,
5961
+ method: evidence.toolName,
5962
+ operations: (evidence.operations ?? []).map((entry) => entry.op),
5963
+ executables: evidence.executables ?? [],
5964
+ subjects: evidence.subjects,
5965
+ surfaces: evidence.surfaces,
5966
+ semanticAction: evidence.semanticAction ?? "generic_run",
5967
+ evidenceRole: evidence.evidenceRole ?? "effect",
5968
+ resolvedTarget: evidence.resolvedTarget ?? {},
5969
+ observedState: evidence.observedState,
5970
+ parseStatus: evidence.parseStatus ?? "adapter_unavailable",
5971
+ reasonCode: evidence.reasonCode ?? (evidence.parseStatus ? void 0 : "adapter_unavailable"),
5972
+ adapterId: evidence.adapterId,
5973
+ adapterVersion: evidence.adapterVersion
5974
+ };
5975
+ }
5976
+ function citedEvidence(projection, binding) {
5977
+ return binding.evidenceIds.map((id) => projection.evidence.get(id)).filter((value) => value !== void 0);
5978
+ }
5979
+ function evidenceProblem(projection, item, binding) {
5980
+ const missing = binding.evidenceIds.filter((id) => !projection.evidence.has(id));
5981
+ if (missing.length) return {
5982
+ itemId: item.id,
5983
+ reason: "cited evidence is missing",
5984
+ reasonCode: "evidence_missing",
5985
+ offendingEvidenceIds: missing
5986
+ };
5987
+ if (item.reboundFrom) {
5988
+ const sourceSeq = /^m(\d+)(?::|$)/.exec(item.sourceMessageId);
5989
+ const tooEarly = binding.evidenceIds.filter((id) => !sourceSeq || projection.evidence.get(id).toolResultSeq < Number(sourceSeq[1]));
5990
+ if (tooEarly.length) return {
5991
+ itemId: item.id,
5992
+ reason: "evidence predates the authoritative root clause used by this replacement",
5993
+ reasonCode: "rebind_evidence_predates_source",
5994
+ offendingEvidenceIds: tooEarly
5995
+ };
5996
+ }
5997
+ const wrongEpoch = binding.evidenceIds.filter((id) => projection.evidence.get(id)?.epoch !== projection.epoch);
5998
+ if (wrongEpoch.length) return {
5999
+ itemId: item.id,
6000
+ reason: "cited evidence belongs to a different epoch",
6001
+ reasonCode: "evidence_wrong_epoch",
6002
+ offendingEvidenceIds: wrongEpoch
6003
+ };
6004
+ const notSuccess = binding.evidenceIds.filter((id) => projection.evidence.get(id)?.outcome !== "success");
6005
+ if (notSuccess.length) return {
6006
+ itemId: item.id,
6007
+ reason: "cited evidence outcome is not success",
6008
+ reasonCode: "evidence_outcome_not_success",
6009
+ offendingEvidenceIds: notSuccess
6010
+ };
6011
+ const requiredAction = item.semanticAction ?? "generic_run";
4532
6012
  const compatibleWith = [requiredAction, ...(item.actionPlan ?? []).map((entry) => entry.action)];
4533
6013
  const facts = citedEvidence(projection, binding);
4534
6014
  const incompatible = facts.filter((fact) => !compatibleWith.some((action) => actionCompatible(action, fact.semanticAction ?? "generic_run")));
@@ -4556,6 +6036,37 @@ function evidenceProblem(projection, item, binding) {
4556
6036
  };
4557
6037
  }
4558
6038
  }
6039
+ /**
6040
+ * The strict-policy proof obligation (C06). Only the surfaces the USER asked
6041
+ * for add a requirement, and the requirement is a real readback from the v2
6042
+ * capability matrix: a visual verification needs a fact that actually observed
6043
+ * the output, a complete-scope verification needs a fact that actually covered
6044
+ * the scope. Standard policy does not run this, and no ordinary action gains an
6045
+ * approval step.
6046
+ */
6047
+ function strictProofProblem(projection, item, binding) {
6048
+ const surface = item.verification.surface;
6049
+ if (surface !== "visual" && surface !== "scope") return void 0;
6050
+ const kind = surface === "visual" ? "output_visual_readback" : "scope_coverage";
6051
+ const obligation = {
6052
+ obligationId: item.id,
6053
+ kind,
6054
+ surface,
6055
+ subjectIds: [item.verification.subject ?? item.requestedTarget?.scope ?? "scope"].filter((value) => typeof value === "string" && value.length > 0),
6056
+ sourceIds: [],
6057
+ operation: item.verification.operation ?? "verify",
6058
+ evidenceIds: binding.evidenceIds
6059
+ };
6060
+ const cited = citedEvidence(projection, binding);
6061
+ if (cited.some((fact) => fact.outcome === "success" && fact.subjects.some((subject) => obligation.subjectIds.includes(subject)) && proofV2Rejection(fact, obligation) === void 0)) return void 0;
6062
+ return {
6063
+ itemId: item.id,
6064
+ reason: `strict policy: the requested ${surface} verification needs a real readback fact from the current projection`,
6065
+ reasonCode: "strict_proof_required",
6066
+ offendingEvidenceIds: cited.map((fact) => fact.id),
6067
+ hint: closingHint(projection, item, binding.evidenceIds)
6068
+ };
6069
+ }
4559
6070
  function expectedTransitionMatches(action, transition, resolved, observed) {
4560
6071
  const expectedPredicate = ACTION_MANIFEST.actions[action].predicateId;
4561
6072
  if (transition.predicateId !== expectedPredicate || transition.version !== 1 || transition.predParamsKind !== "inline" || !transition.parameters) return false;
@@ -4570,14 +6081,14 @@ function expectedTransitionMatches(action, transition, resolved, observed) {
4570
6081
  "version",
4571
6082
  "integrity_digest",
4572
6083
  ...action === "publish" ? ["registry"] : ["profile"]
4573
- ].every((key) => stable$2(observed[key]) === stable$2(resolved[key]) && stable$2(params[key]) === stable$2(resolved[key]));
6084
+ ].every((key) => stable$1(observed[key]) === stable$1(resolved[key]) && stable$1(params[key]) === stable$1(resolved[key]));
4574
6085
  case "create":
4575
- case "modify": return stable$2(observed.post_digest) === stable$2(params.post_digest);
4576
- case "restart": return stable$2(params.pre_generation) === stable$2(resolved.pre_generation) && stable$2(observed.new_generation) !== stable$2(resolved.pre_generation) && stable$2(observed.health) === stable$2(params.health);
4577
- case "commit": return stable$2(params.pre_head_oid) === stable$2(resolved.pre_head_oid) && stable$2(params.change_set_digest) === stable$2(resolved.change_set_digest) && stable$2(observed.pre_head_oid) === stable$2(resolved.pre_head_oid) && stable$2(observed.post_head_oid) !== stable$2(resolved.pre_head_oid);
4578
- case "push": return stable$2(observed.remote_oid) === stable$2(resolved.local_oid) && stable$2(params.local_oid) === stable$2(resolved.local_oid);
4579
- case "pull": return stable$2(resolved.pull_mode) === stable$2("ff-only") && stable$2(params.pull_mode) === stable$2("ff-only") && stable$2(params.upstream_oid) === stable$2(resolved.upstream_oid) && stable$2(params.pre_head_oid) === stable$2(resolved.pre_head_oid) && stable$2(observed.post_head_oid) === stable$2(resolved.upstream_oid) && stable$2(observed.tracking_ref_oid) === stable$2(resolved.upstream_oid);
4580
- case "fetch": return stable$2(params.upstream_oid) === stable$2(resolved.upstream_oid) && stable$2(params.pre_head_oid) === stable$2(resolved.pre_head_oid) && stable$2(observed.tracking_ref_oid) === stable$2(resolved.upstream_oid) && stable$2(observed.post_head_oid) === stable$2(resolved.pre_head_oid);
6086
+ case "modify": return stable$1(observed.post_digest) === stable$1(params.post_digest);
6087
+ case "restart": return stable$1(params.pre_generation) === stable$1(resolved.pre_generation) && stable$1(observed.new_generation) !== stable$1(resolved.pre_generation) && stable$1(observed.health) === stable$1(params.health);
6088
+ case "commit": return stable$1(params.pre_head_oid) === stable$1(resolved.pre_head_oid) && stable$1(params.change_set_digest) === stable$1(resolved.change_set_digest) && stable$1(observed.pre_head_oid) === stable$1(resolved.pre_head_oid) && stable$1(observed.post_head_oid) !== stable$1(resolved.pre_head_oid);
6089
+ case "push": return stable$1(observed.remote_oid) === stable$1(resolved.local_oid) && stable$1(params.local_oid) === stable$1(resolved.local_oid);
6090
+ case "pull": return stable$1(resolved.pull_mode) === stable$1("ff-only") && stable$1(params.pull_mode) === stable$1("ff-only") && stable$1(params.upstream_oid) === stable$1(resolved.upstream_oid) && stable$1(params.pre_head_oid) === stable$1(resolved.pre_head_oid) && stable$1(observed.post_head_oid) === stable$1(resolved.upstream_oid) && stable$1(observed.tracking_ref_oid) === stable$1(resolved.upstream_oid);
6091
+ case "fetch": return stable$1(params.upstream_oid) === stable$1(resolved.upstream_oid) && stable$1(params.pre_head_oid) === stable$1(resolved.pre_head_oid) && stable$1(observed.tracking_ref_oid) === stable$1(resolved.upstream_oid) && stable$1(observed.post_head_oid) === stable$1(resolved.pre_head_oid);
4581
6092
  default: return true;
4582
6093
  }
4583
6094
  }
@@ -4586,8 +6097,8 @@ function nonStatefulTransitionMatches(action, transition, resolved, observed) {
4586
6097
  const params = transition.parameters;
4587
6098
  const recomputed = predParamsDigest(params, resolveAllowlist("product"));
4588
6099
  if (transition.parametersDigest && transition.parametersDigest !== recomputed) return false;
4589
- if (action === "inspect_remote_updates") return ["remote", "version"].every((key) => stable$2(params[key]) === stable$2(resolved[key])) && stable$2(params.upstream_oid) === stable$2(observed.upstream_oid);
4590
- return stable$2(params) === stable$2({
6100
+ if (action === "inspect_remote_updates") return ["remote", "version"].every((key) => stable$1(params[key]) === stable$1(resolved[key])) && stable$1(params.upstream_oid) === stable$1(observed.upstream_oid);
6101
+ return stable$1(params) === stable$1({
4591
6102
  expected_outcome: {
4592
6103
  k: "e",
4593
6104
  v: "success"
@@ -4694,7 +6205,7 @@ function richStatefulRecord(projection, item, binding) {
4694
6205
  reason: "resolution fact does not bind an expected transition digest",
4695
6206
  reasonCode: "resolution_expected_transition_digest_missing"
4696
6207
  } };
4697
- if (resolution.expectedTransitionDigest !== sha256(stable$2(resolution.expectedTransition))) return { rejected: {
6208
+ if (resolution.expectedTransitionDigest !== sha256(stable$1(resolution.expectedTransition))) return { rejected: {
4698
6209
  itemId: item.id,
4699
6210
  reason: "resolution expected transition digest does not match its stable payload",
4700
6211
  reasonCode: "resolution_expected_transition_digest_mismatch"
@@ -4789,7 +6300,7 @@ function simpleRecord(projection, item, binding) {
4789
6300
  const effectAction = effect.semanticAction ?? "generic_run";
4790
6301
  const effectTarget = effect.resolvedTarget ?? {};
4791
6302
  const effectObserved = effect.observedState ?? {};
4792
- if (!(Object.entries(binding.resolvedTarget ?? {}).every(([key, value]) => Object.hasOwn(effectTarget, key) && stable$2(value) === stable$2(effectTarget[key])) && Object.entries(binding.observedState ?? {}).every(([key, value]) => Object.hasOwn(effectObserved, key) && stable$2(value) === stable$2(effectObserved[key]))) || effectAction === action && (!tuplesEqual(binding.resolvedTarget, effectTarget) || !tuplesEqual(binding.observedState, effectObserved))) return { rejected: {
6303
+ if (!(Object.entries(binding.resolvedTarget ?? {}).every(([key, value]) => Object.hasOwn(effectTarget, key) && stable$1(value) === stable$1(effectTarget[key])) && Object.entries(binding.observedState ?? {}).every(([key, value]) => Object.hasOwn(effectObserved, key) && stable$1(value) === stable$1(effectObserved[key]))) || effectAction === action && (!tuplesEqual(binding.resolvedTarget, effectTarget) || !tuplesEqual(binding.observedState, effectObserved))) return { rejected: {
4793
6304
  itemId: item.id,
4794
6305
  reason: "binding target does not match the cited effect evidence",
4795
6306
  reasonCode: "binding_state_cross_pairing"
@@ -4828,7 +6339,7 @@ function certifyCheckpoint(projection, bindings, id, commit = true) {
4828
6339
  if (projection.integrity !== "valid" || projection.hostStatus !== "supported") return {
4829
6340
  status: "unknown",
4830
6341
  contractRevision: projection.contractRevision,
4831
- openItems: openItems(projection),
6342
+ openItems: certifiableOpenItems(projection).map((item) => item.id),
4832
6343
  rejectedBindings: []
4833
6344
  };
4834
6345
  const rejectedBindings = [];
@@ -4860,6 +6371,16 @@ function certifyCheckpoint(projection, bindings, id, commit = true) {
4860
6371
  });
4861
6372
  continue;
4862
6373
  }
6374
+ const ancestorBlock = ancestorConstraintForBinding(projection, item, binding.resolvedTarget);
6375
+ if (ancestorBlock) {
6376
+ rejectedBindings.push({
6377
+ itemId: item.id,
6378
+ reason: ancestorBlock.kind === "prohibition" ? `an ancestor unit (${ancestorBlock.constraintUnitId}) holds prohibition ${ancestorBlock.constraintId} on this action and target` : `an ancestor unit (${ancestorBlock.constraintUnitId}) holds the unsatisfied condition ${ancestorBlock.constraintId} that reserves this action`,
6379
+ reasonCode: ancestorBlock.reasonCode,
6380
+ hint: closingHint(projection, item)
6381
+ });
6382
+ continue;
6383
+ }
4863
6384
  if (item.targetCaptureStatus === "clarification_required") {
4864
6385
  rejectedBindings.push({
4865
6386
  itemId: item.id,
@@ -4890,6 +6411,13 @@ function certifyCheckpoint(projection, bindings, id, commit = true) {
4890
6411
  rejectedBindings.push(problem);
4891
6412
  continue;
4892
6413
  }
6414
+ if (projection.policy === "strict") {
6415
+ const strictProblem = strictProofProblem(projection, item, binding);
6416
+ if (strictProblem) {
6417
+ rejectedBindings.push(strictProblem);
6418
+ continue;
6419
+ }
6420
+ }
4893
6421
  if ((item.semanticAction ?? "generic_run") === "generic_run") {
4894
6422
  rejectedBindings.push({
4895
6423
  itemId: item.id,
@@ -4906,48 +6434,95 @@ function certifyCheckpoint(projection, bindings, id, commit = true) {
4906
6434
  records.push(built.record);
4907
6435
  referencedFacts.push(...citedEvidence(projection, binding).map(evidenceFact));
4908
6436
  }
4909
- const open = openItems(projection).filter((itemId) => !bindings.some((binding) => binding.itemId === itemId));
6437
+ const closure = certificateClosure(projection);
6438
+ const open = closure.itemIds.filter((itemId) => !bindings.some((binding) => binding.itemId === itemId));
4910
6439
  if (rejectedBindings.length || open.length) return {
4911
6440
  status: "incomplete",
4912
6441
  contractRevision: projection.contractRevision,
4913
- openItems: openItems(projection),
6442
+ openItems: closure.itemIds,
4914
6443
  rejectedBindings
4915
6444
  };
6445
+ if (projection.boundaryProtocol === 5 && closure.unitId === void 0) return {
6446
+ status: "incomplete",
6447
+ contractRevision: projection.contractRevision,
6448
+ openItems: closure.itemIds,
6449
+ rejectedBindings: [{
6450
+ itemId: "*",
6451
+ reason: "no current work unit is available for a v2 certificate",
6452
+ reasonCode: "unit_unavailable"
6453
+ }]
6454
+ };
4916
6455
  try {
4917
6456
  const contractSha256 = currentContractDigest(projection);
4918
- const openDigest = digestStrings(openItems(projection));
6457
+ const openDigest = digestStrings(closure.itemIds);
4919
6458
  const evidenceSha256 = evidenceSha256Digest(referencedFacts);
4920
6459
  const bindingDigest$1 = bindingDigest(records, resolveAllowlist("product"));
4921
- const certification = certificationDigest({
4922
- stopProtocolVersion: STOP_PROTOCOL_VERSION,
4923
- certificateVersion: CERTIFICATE_VERSION,
4924
- epoch: projection.epoch,
4925
- sessionRefDigest: projection.sessionRefDigest,
4926
- hostLockDigest: projection.hostLockDigest,
4927
- contractRevision: projection.contractRevision,
4928
- contractSha256,
4929
- ...projection.currentGoalRef ? { goalRef: projection.currentGoalRef } : {},
4930
- openDigest,
4931
- evidenceSha256,
4932
- bindingDigest: bindingDigest$1
4933
- });
4934
- const checkpoint = {
4935
- id,
4936
- stopProtocolVersion: STOP_PROTOCOL_VERSION,
4937
- certificateVersion: CERTIFICATE_VERSION,
4938
- epoch: projection.epoch,
4939
- sessionRefDigest: projection.sessionRefDigest,
4940
- hostLockDigest: projection.hostLockDigest,
4941
- contractRevision: projection.contractRevision,
4942
- contractSha256,
4943
- openDigest,
4944
- evidenceSha256,
4945
- bindingDigest: bindingDigest$1,
4946
- bindings,
4947
- ...projection.currentGoalRef ? { goalRef: { ...projection.currentGoalRef } } : {},
4948
- certificationDigest: certification,
4949
- result: "certified"
4950
- };
6460
+ const checkpoint = projection.boundaryProtocol === 5 ? (() => {
6461
+ const certification = certificationDigestV2({
6462
+ stopProtocolVersion: STOP_PROTOCOL_VERSION_V2,
6463
+ certificateVersion: CERTIFICATE_VERSION_V2,
6464
+ epoch: projection.epoch,
6465
+ sessionRefDigest: projection.sessionRefDigest,
6466
+ hostLockDigest: projection.hostLockDigest,
6467
+ contractRevision: projection.contractRevision,
6468
+ contractSha256,
6469
+ unitId: closure.unitId,
6470
+ unitClosureDigest: openDigest,
6471
+ evidenceSha256,
6472
+ bindingDigest: bindingDigest$1,
6473
+ goalRef: projection.currentGoalRef ?? null
6474
+ });
6475
+ return {
6476
+ id,
6477
+ stopProtocolVersion: STOP_PROTOCOL_VERSION_V2,
6478
+ certificateVersion: CERTIFICATE_VERSION_V2,
6479
+ epoch: projection.epoch,
6480
+ sessionRefDigest: projection.sessionRefDigest,
6481
+ hostLockDigest: projection.hostLockDigest,
6482
+ contractRevision: projection.contractRevision,
6483
+ contractSha256,
6484
+ openDigest,
6485
+ evidenceSha256,
6486
+ bindingDigest: bindingDigest$1,
6487
+ bindings,
6488
+ ...projection.currentGoalRef ? { goalRef: { ...projection.currentGoalRef } } : {},
6489
+ unitId: closure.unitId,
6490
+ unitClosureDigest: openDigest,
6491
+ certificationDigest: certification,
6492
+ result: "certified"
6493
+ };
6494
+ })() : (() => {
6495
+ const certification = certificationDigest({
6496
+ stopProtocolVersion: STOP_PROTOCOL_VERSION,
6497
+ certificateVersion: CERTIFICATE_VERSION,
6498
+ epoch: projection.epoch,
6499
+ sessionRefDigest: projection.sessionRefDigest,
6500
+ hostLockDigest: projection.hostLockDigest,
6501
+ contractRevision: projection.contractRevision,
6502
+ contractSha256,
6503
+ ...projection.currentGoalRef ? { goalRef: projection.currentGoalRef } : {},
6504
+ openDigest,
6505
+ evidenceSha256,
6506
+ bindingDigest: bindingDigest$1
6507
+ });
6508
+ return {
6509
+ id,
6510
+ stopProtocolVersion: STOP_PROTOCOL_VERSION,
6511
+ certificateVersion: CERTIFICATE_VERSION,
6512
+ epoch: projection.epoch,
6513
+ sessionRefDigest: projection.sessionRefDigest,
6514
+ hostLockDigest: projection.hostLockDigest,
6515
+ contractRevision: projection.contractRevision,
6516
+ contractSha256,
6517
+ openDigest,
6518
+ evidenceSha256,
6519
+ bindingDigest: bindingDigest$1,
6520
+ bindings,
6521
+ ...projection.currentGoalRef ? { goalRef: { ...projection.currentGoalRef } } : {},
6522
+ certificationDigest: certification,
6523
+ result: "certified"
6524
+ };
6525
+ })();
4951
6526
  if (commit) {
4952
6527
  projection.checkpoints.push(checkpoint);
4953
6528
  for (const binding of bindings) projection.items.get(binding.itemId).status = "passed";
@@ -4964,7 +6539,7 @@ function certifyCheckpoint(projection, bindings, id, commit = true) {
4964
6539
  return {
4965
6540
  status: "incomplete",
4966
6541
  contractRevision: projection.contractRevision,
4967
- openItems: openItems(projection),
6542
+ openItems: closure.itemIds,
4968
6543
  rejectedBindings: [{
4969
6544
  itemId: "*",
4970
6545
  reason: error instanceof Error ? error.message : "certificate manifest rejected",
@@ -5056,9 +6631,6 @@ function bindingActionPlanProblem(projection, item, binding) {
5056
6631
  }
5057
6632
  }
5058
6633
  }
5059
- function openItems(projection) {
5060
- return [...projection.items.values()].filter((item) => item.status === "pending" && item.kind !== "prohibition").map((item) => item.id);
5061
- }
5062
6634
 
5063
6635
  //#endregion
5064
6636
  //#region src/domain/contract-segment.ts
@@ -7662,13 +9234,13 @@ function parseArguments$1(raw) {
7662
9234
  return {};
7663
9235
  }
7664
9236
  }
7665
- function asRecord$1(value) {
9237
+ function asRecord$2(value) {
7666
9238
  return typeof value === "object" && value !== null ? value : void 0;
7667
9239
  }
7668
9240
  function extractTextContent(content) {
7669
9241
  const parts = [];
7670
9242
  for (const block$1 of content) {
7671
- const record = asRecord$1(block$1);
9243
+ const record = asRecord$2(block$1);
7672
9244
  if (!record) continue;
7673
9245
  if (record.type === "text" && typeof record.text === "string") parts.push(record.text);
7674
9246
  if (record.type === "tool-result" && Array.isArray(record.content)) parts.push(extractTextContent(record.content));
@@ -7676,10 +9248,10 @@ function extractTextContent(content) {
7676
9248
  return parts.join("\n");
7677
9249
  }
7678
9250
  function metaPaths(meta) {
7679
- const record = asRecord$1(meta);
9251
+ const record = asRecord$2(meta);
7680
9252
  if (!record) return [];
7681
9253
  if (typeof record.path === "string") return [record.path];
7682
- if (Array.isArray(record.diffs)) return record.diffs.map((diff) => asRecord$1(diff)?.path).filter((path$1) => typeof path$1 === "string");
9254
+ if (Array.isArray(record.diffs)) return record.diffs.map((diff) => asRecord$2(diff)?.path).filter((path$1) => typeof path$1 === "string");
7683
9255
  return [];
7684
9256
  }
7685
9257
  function argsPaths(args) {
@@ -7693,24 +9265,24 @@ function resolveCommandPath(reference, cwd) {
7693
9265
  if (/^[A-Za-z]:[\\/]/.test(reference) || reference.startsWith("//") || reference.startsWith("\\\\") || reference.startsWith("/") || reference.startsWith("\\")) return reference;
7694
9266
  return `${cwd.replace(/[\\/]+$/, "")}/${reference}`;
7695
9267
  }
7696
- function stable$1(value) {
7697
- if (Array.isArray(value)) return `[${value.map(stable$1).join(",")}]`;
7698
- if (value && typeof value === "object") return `{${Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, entry]) => `${JSON.stringify(key)}:${stable$1(entry)}`).join(",")}}`;
9268
+ function stable(value) {
9269
+ if (Array.isArray(value)) return `[${value.map(stable).join(",")}]`;
9270
+ if (value && typeof value === "object") return `{${Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, entry]) => `${JSON.stringify(key)}:${stable(entry)}`).join(",")}}`;
7699
9271
  return JSON.stringify(value);
7700
9272
  }
7701
9273
  function structuredGuardMeta(meta, toolName) {
7702
9274
  if (toolName !== "context_guard_evidence") return void 0;
7703
- const outer = asRecord$1(meta);
7704
- const value = asRecord$1(outer?.contextGuard ?? outer?.context_guard);
9275
+ const outer = asRecord$2(meta);
9276
+ const value = asRecord$2(outer?.contextGuard ?? outer?.context_guard);
7705
9277
  if (!value) return void 0;
7706
9278
  const action = value.semanticAction ?? value.semantic_action;
7707
9279
  const role = value.evidenceRole ?? value.evidence_role;
7708
- const resolved = asRecord$1(value.resolvedTarget ?? value.resolved_target);
7709
- const observed = asRecord$1(value.observedState ?? value.observed_state);
7710
- const rawExpected = asRecord$1(value.expectedTransition);
7711
- const expectedParameters = asRecord$1(rawExpected?.parameters);
9280
+ const resolved = asRecord$2(value.resolvedTarget ?? value.resolved_target);
9281
+ const observed = asRecord$2(value.observedState ?? value.observed_state);
9282
+ const rawExpected = asRecord$2(value.expectedTransition);
9283
+ const expectedParameters = asRecord$2(rawExpected?.parameters);
7712
9284
  const expectedDigest = value.expectedTransitionDigest;
7713
- const expectedTransition = rawExpected && typeof rawExpected.predicateId === "string" && rawExpected.version === 1 && rawExpected.predParamsKind === "inline" && expectedParameters && typeof expectedDigest === "string" && expectedDigest === sha256(stable$1(rawExpected)) ? rawExpected : void 0;
9285
+ const expectedTransition = rawExpected && typeof rawExpected.predicateId === "string" && rawExpected.version === 1 && rawExpected.predParamsKind === "inline" && expectedParameters && typeof expectedDigest === "string" && expectedDigest === sha256(stable(rawExpected)) ? rawExpected : void 0;
7714
9286
  if (typeof value.adapterId !== "string" || typeof value.adapterVersion !== "string") return void 0;
7715
9287
  if (SUPPORTED_EVIDENCE_ADAPTERS[value.adapterId] !== value.adapterVersion) return void 0;
7716
9288
  if (typeof action !== "string" || typeof role !== "string" || !resolved) return void 0;
@@ -7807,7 +9379,7 @@ const PERSISTENT_TIMEOUT_INTRO = /^Your command timed out after \d+ seconds or e
7807
9379
  * the fallback.
7808
9380
  */
7809
9381
  function structuredTerminalFacts(meta) {
7810
- const record = asRecord$1(meta);
9382
+ const record = asRecord$2(meta);
7811
9383
  if (!record) return void 0;
7812
9384
  const rawExit = record.exitCode ?? record.exit_code;
7813
9385
  const rawSignal = record.signal;
@@ -7858,10 +9430,10 @@ function extractTerminalFacts(textContent) {
7858
9430
  };
7859
9431
  }
7860
9432
  function metaUrls(meta) {
7861
- const record = asRecord$1(meta);
9433
+ const record = asRecord$2(meta);
7862
9434
  if (!record) return [];
7863
9435
  if (typeof record.url === "string") return [sanitizeUrl(record.url)];
7864
- if (Array.isArray(record.sources)) return record.sources.map((source) => asRecord$1(source)?.url).filter((url) => typeof url === "string").map((url) => sanitizeUrl(url));
9436
+ if (Array.isArray(record.sources)) return record.sources.map((source) => asRecord$2(source)?.url).filter((url) => typeof url === "string").map((url) => sanitizeUrl(url));
7865
9437
  return [];
7866
9438
  }
7867
9439
  const DETERMINISTIC_CHECK_PATTERNS = [
@@ -7918,7 +9490,7 @@ function resolveSubjectPaths(values, cwd) {
7918
9490
  function extractToolSubject(call, result, defaultCwd, hostLock) {
7919
9491
  const args = parseArguments$1(call.arguments);
7920
9492
  if (call.name === "context_guard_external_operation") {
7921
- const external = asRecord$1(asRecord$1(result.meta)?.contextGuardExternalOperation);
9493
+ const external = asRecord$2(asRecord$2(result.meta)?.contextGuardExternalOperation);
7922
9494
  const status = external?.status;
7923
9495
  if (typeof external?.id === "string" && typeof external.adapterId === "string" && (status === "running" || status === "pending" || status === "completed" || status === "failed" || status === "unknown")) return {
7924
9496
  capabilities: ["external-operation-readback"],
@@ -7949,7 +9521,7 @@ function extractToolSubject(call, result, defaultCwd, hostLock) {
7949
9521
  }
7950
9522
  const structured = structuredGuardMeta(result.meta, call.name);
7951
9523
  if (call.name === "context_guard_evidence" && !structured) {
7952
- const disposition = asRecord$1(asRecord$1(result.meta)?.contextGuardDisposition);
9524
+ const disposition = asRecord$2(asRecord$2(result.meta)?.contextGuardDisposition);
7953
9525
  return {
7954
9526
  capabilities: ["guard-state-readback"],
7955
9527
  subjects: [],
@@ -8165,8 +9737,760 @@ function supersedeItem(items, oldId, replacement) {
8165
9737
  return true;
8166
9738
  }
8167
9739
 
9740
+ //#endregion
9741
+ //#region src/domain/delivery.ts
9742
+ function assistantTextOf(data) {
9743
+ return (data?.message?.content ?? []).filter((part) => part?.type === "text").map((part) => part?.text ?? "").join("\n");
9744
+ }
9745
+ function integerField(data, field$1) {
9746
+ const value = data?.[field$1];
9747
+ return typeof value === "number" && Number.isSafeInteger(value) ? value : void 0;
9748
+ }
9749
+ /**
9750
+ * Derive the trusted deliveries from the event log. Deterministic: a replay of
9751
+ * identical events yields identical facts.
9752
+ */
9753
+ function deriveTrustedDeliveries(events) {
9754
+ const turns = /* @__PURE__ */ new Map();
9755
+ const factsFor = (turn) => {
9756
+ let facts = turns.get(turn);
9757
+ if (!facts) {
9758
+ facts = {
9759
+ started: false,
9760
+ steps: /* @__PURE__ */ new Set(),
9761
+ assistants: [],
9762
+ ends: []
9763
+ };
9764
+ turns.set(turn, facts);
9765
+ }
9766
+ return facts;
9767
+ };
9768
+ for (const event of events) switch (event.type) {
9769
+ case "turn/start": {
9770
+ const turn = integerField(event.data, "turn");
9771
+ if (turn !== void 0) factsFor(turn).started = true;
9772
+ break;
9773
+ }
9774
+ case "step/start":
9775
+ case "step/end": {
9776
+ const turn = integerField(event.data, "turn");
9777
+ const step = integerField(event.data, "step");
9778
+ if (turn !== void 0 && step !== void 0) factsFor(turn).steps.add(step);
9779
+ break;
9780
+ }
9781
+ case "assistant/message": {
9782
+ const turn = integerField(event.data, "turn");
9783
+ const step = integerField(event.data, "step");
9784
+ if (turn === void 0 || step === void 0) break;
9785
+ const facts = factsFor(turn);
9786
+ facts.steps.add(step);
9787
+ facts.assistants.push({
9788
+ seq: event.seq,
9789
+ step,
9790
+ text: assistantTextOf(event.data),
9791
+ interrupted: event.data?.interrupted === true
9792
+ });
9793
+ break;
9794
+ }
9795
+ case "turn/end": {
9796
+ const turn = integerField(event.data, "turn");
9797
+ if (turn === void 0) break;
9798
+ const reason = event.data?.reason;
9799
+ factsFor(turn).ends.push({
9800
+ seq: event.seq,
9801
+ kind: typeof reason?.kind === "string" ? reason.kind : ""
9802
+ });
9803
+ break;
9804
+ }
9805
+ default: break;
9806
+ }
9807
+ const deliveries = [];
9808
+ for (const [turn, facts] of turns) {
9809
+ if (!facts.started) continue;
9810
+ if (facts.ends.length !== 1) continue;
9811
+ const end = facts.ends[0];
9812
+ if (end.kind !== "completed") continue;
9813
+ if (facts.steps.size === 0) continue;
9814
+ const finalStep = Math.max(...facts.steps);
9815
+ const inFinalStep = facts.assistants.filter((row) => row.step === finalStep && row.seq < end.seq && !row.interrupted && row.text.trim().length > 0);
9816
+ if (inFinalStep.length === 0) continue;
9817
+ const final = inFinalStep.reduce((left, right) => right.seq > left.seq ? right : left);
9818
+ if (facts.assistants.some((row) => row.seq > final.seq && row.seq < end.seq)) continue;
9819
+ deliveries.push({
9820
+ turn,
9821
+ turnEndSeq: end.seq,
9822
+ responseSeq: final.seq,
9823
+ responseSha256: sha256(final.text)
9824
+ });
9825
+ }
9826
+ return deliveries.sort((left, right) => left.turnEndSeq - right.turnEndSeq);
9827
+ }
9828
+ /**
9829
+ * The information-slot items a delivery closes: obligations captured from a
9830
+ * root message inside the delivered turn, in the unit that turn's input
9831
+ * belonged to (or in one of that unit's delegated sub-units), whose semantic
9832
+ * slot is information (an inquiry or an explanation request). Execution,
9833
+ * constraints, and unknowns are never closed by delivery, and neither are
9834
+ * questions from earlier messages.
9835
+ */
9836
+ function informationItemIdsForDelivery(items, delivery, turnRootInputSeqs, eligibleUnitIds) {
9837
+ const closed = [];
9838
+ for (const [itemId, item] of items) {
9839
+ if (item.status !== "pending") continue;
9840
+ if (item.kind === "prohibition") continue;
9841
+ if (eligibleUnitIds !== void 0 && (item.unitId === void 0 || !eligibleUnitIds.has(item.unitId))) continue;
9842
+ const sourceSeq = /^m(\d+)(?::|$)/.exec(item.sourceMessageId);
9843
+ if (!sourceSeq || !turnRootInputSeqs.has(Number(sourceSeq[1]))) continue;
9844
+ if (item.taskKind === "inquiry" || item.authorityDisposition === "informational" && item.kind === "requirement") closed.push(itemId);
9845
+ }
9846
+ return closed;
9847
+ }
9848
+
9849
+ //#endregion
9850
+ //#region src/domain/host-selection.ts
9851
+ /**
9852
+ * The default question-tool allowlist. The real names are a host tool-bundle
9853
+ * surface: native acceptance pins the audited names for the running cohort,
9854
+ * and the runtime may override this list per cohort.
9855
+ */
9856
+ const DEFAULT_QUESTION_TOOL_NAMES = ["question", "ask_user"];
9857
+ function parseQuestionCall(rawArguments) {
9858
+ if (typeof rawArguments !== "string") return void 0;
9859
+ let args;
9860
+ try {
9861
+ args = JSON.parse(rawArguments);
9862
+ } catch {
9863
+ return;
9864
+ }
9865
+ if (!args || typeof args !== "object" || Array.isArray(args)) return void 0;
9866
+ const record = args;
9867
+ const rawOptions = record.options ?? record.choices;
9868
+ if (!Array.isArray(rawOptions) || rawOptions.length === 0) return void 0;
9869
+ const options = [];
9870
+ for (const entry of rawOptions) {
9871
+ if (typeof entry !== "string" || !entry.trim()) return void 0;
9872
+ options.push(entry.trim());
9873
+ }
9874
+ return {
9875
+ questionId: typeof record.question_id === "string" ? record.question_id : typeof record.questionId === "string" ? record.questionId : void 0,
9876
+ question: typeof record.question === "string" ? record.question : void 0,
9877
+ options
9878
+ };
9879
+ }
9880
+ function parseSelectionAnswer(content, options) {
9881
+ if (!Array.isArray(content)) return void 0;
9882
+ const text = content.filter((part) => !!part && typeof part === "object").filter((part) => part.type === "text").map((part) => part.text ?? "").join("\n");
9883
+ if (!text.trim()) return void 0;
9884
+ const trimmed = text.trim();
9885
+ if (options.includes(trimmed)) return trimmed;
9886
+ try {
9887
+ const parsed = JSON.parse(trimmed);
9888
+ const answer = parsed.answer ?? parsed.selected ?? parsed.value;
9889
+ if (typeof answer === "string" && options.includes(answer.trim())) return answer.trim();
9890
+ } catch {}
9891
+ }
9892
+ function looksLikeDirectory(value) {
9893
+ return /^[~.]?(?:[\\/][^\n]*)+$/.test(value);
9894
+ }
9895
+ /**
9896
+ * Derive the trusted selections from the durable log. Deterministic: a replay
9897
+ * of identical events yields identical selections.
9898
+ */
9899
+ function deriveTrustedSelections(events, options) {
9900
+ const names = new Set(options.questionToolNames);
9901
+ const pending = /* @__PURE__ */ new Map();
9902
+ const selections = [];
9903
+ for (const event of events) {
9904
+ if (event.type === "tool/call") {
9905
+ const data$1 = event.data ?? {};
9906
+ const name = String(data$1.name ?? "");
9907
+ if (!names.has(name)) continue;
9908
+ const shape = parseQuestionCall(data$1.arguments);
9909
+ if (!shape) continue;
9910
+ pending.set(String(data$1.callId ?? ""), {
9911
+ callId: String(data$1.callId ?? ""),
9912
+ seq: event.seq,
9913
+ turn: typeof data$1.turn === "number" ? data$1.turn : void 0,
9914
+ toolName: name,
9915
+ shape
9916
+ });
9917
+ continue;
9918
+ }
9919
+ if (event.type !== "tool/result") continue;
9920
+ const data = event.data ?? {};
9921
+ const callId = String(data.message?.source?.callId ?? "");
9922
+ const call = pending.get(callId);
9923
+ if (!call) continue;
9924
+ pending.delete(callId);
9925
+ if (data.error !== void 0) continue;
9926
+ const selected = parseSelectionAnswer(data.message?.content, call.shape.options);
9927
+ if (!selected) continue;
9928
+ selections.push({
9929
+ callId,
9930
+ resultSeq: event.seq,
9931
+ turn: call.turn,
9932
+ toolName: call.toolName,
9933
+ questionId: call.shape.questionId,
9934
+ question: call.shape.question,
9935
+ options: call.shape.options,
9936
+ selected,
9937
+ kind: looksLikeDirectory(selected) ? "directory" : "value"
9938
+ });
9939
+ }
9940
+ return selections;
9941
+ }
9942
+
9943
+ //#endregion
9944
+ //#region src/domain/release.ts
9945
+ /**
9946
+ * Explicit release adoption and single-use tickets (0.6.0 C10 / DS06-F).
9947
+ *
9948
+ * A release is never implicit. "release", a loaded Skill, or an installation
9949
+ * never activates this profile: a root user must explicitly ADOPT a release
9950
+ * contract that names the exact candidate, and every effect must then match
9951
+ * that contract and spend a one-shot reservation.
9952
+ *
9953
+ * Three durable records carry the state machine (P0 §5), all written through
9954
+ * the plugin-notice channel the host already persists:
9955
+ *
9956
+ * - `contract` — the adopted scope: operations, the exact candidate, the
9957
+ * readiness/closure references and an optional expiry.
9958
+ * - `reservation` — written BEFORE any effect; the operation is `in_flight`
9959
+ * from that moment, so a crash cannot be mistaken for "never
9960
+ * started" and the operation is never blindly re-sent.
9961
+ * - `settlement` — written after the effect. Its outcome distinguishes a
9962
+ * PROVEN no-effect (`not_effected`, which releases the lock)
9963
+ * from an UNKNOWN effect (`unknown`/`failed`/`unconfirmed`,
9964
+ * which keeps the lock until a trusted readback reconciles
9965
+ * it) and from a `settled` release.
9966
+ *
9967
+ * CANDIDATE IDENTITY IS TYPED, NOT CONFLATED. A release artifact has several
9968
+ * genuinely different identities — the commit it was built from, the SHA-256 of
9969
+ * the exact bytes, npm's SHA-512 SRI, the package name, the version, the
9970
+ * repository, the ref and the target registry. Each is a separate field and is
9971
+ * compared with its own observed value read from a trusted producer. Comparing,
9972
+ * say, a 64-hex SHA-256 against an SRI can never succeed, so a legitimate
9973
+ * release would have been permanently refused; and accepting a model-supplied
9974
+ * SHA instead of the artifact's embedded one would bind nothing. Every field
9975
+ * the contract declares must be OBSERVED, so omitting evidence is a refusal,
9976
+ * never a bypass.
9977
+ *
9978
+ * COVERAGE SURFACE (frozen wording): only the surfaces Guard itself routes can
9979
+ * be protected. Operations with no Guard execution surface are refused before
9980
+ * any effect, and the plugin never suggests falling back to a plain shell
9981
+ * command. A trusted in-process caller that bypasses Guard entirely is a host
9982
+ * trust boundary and is disclosed as such in the documentation, not pretended
9983
+ * away.
9984
+ */
9985
+ const RELEASE_CONTRACT_PREFIX = "Context Guard release contract v1: ";
9986
+ const RELEASE_RESERVATION_PREFIX = "Context Guard release reservation v1: ";
9987
+ const RELEASE_SETTLEMENT_PREFIX = "Context Guard release settlement v1: ";
9988
+ const RELEASE_REVOCATION_PREFIX = "Context Guard release revocation v1: ";
9989
+ const RELEASE_OPERATIONS = [
9990
+ "npm_publish",
9991
+ "git_tag",
9992
+ "github_release_create",
9993
+ "github_release_update",
9994
+ "github_release_delete",
9995
+ "composite_runner"
9996
+ ];
9997
+ /**
9998
+ * The routing table for this release. `git_tag` and the GitHub Release
9999
+ * operations have no Guard-owned execution route yet; the coordinator
10000
+ * explicitly approved that staged scope reduction on 2026-09-14, and the new
10001
+ * route is the way each of them becomes protectable. A composite runner stays
10002
+ * opaque by construction.
10003
+ */
10004
+ const RELEASE_OPERATION_SURFACES = {
10005
+ npm_publish: {
10006
+ surface: "context_guard_action",
10007
+ protectable: true,
10008
+ reasonCode: "release_operation_protectable",
10009
+ attribution: "implemented"
10010
+ },
10011
+ git_tag: {
10012
+ surface: "none",
10013
+ protectable: false,
10014
+ reasonCode: "release_operation_unrouted",
10015
+ attribution: "scope_reduction"
10016
+ },
10017
+ github_release_create: {
10018
+ surface: "none",
10019
+ protectable: false,
10020
+ reasonCode: "release_operation_unrouted",
10021
+ attribution: "scope_reduction"
10022
+ },
10023
+ github_release_update: {
10024
+ surface: "none",
10025
+ protectable: false,
10026
+ reasonCode: "release_operation_unrouted",
10027
+ attribution: "scope_reduction"
10028
+ },
10029
+ github_release_delete: {
10030
+ surface: "none",
10031
+ protectable: false,
10032
+ reasonCode: "release_operation_unrouted",
10033
+ attribution: "scope_reduction"
10034
+ },
10035
+ composite_runner: {
10036
+ surface: "none",
10037
+ protectable: false,
10038
+ reasonCode: "release_runner_opaque",
10039
+ attribution: "host_boundary"
10040
+ }
10041
+ };
10042
+ const FULL_SHA40 = /^[0-9a-f]{40}$/;
10043
+ const SHA256 = /^[0-9a-f]{64}$/;
10044
+ const SRI = /^sha(?:256|384|512)-[A-Za-z0-9+/]+={0,2}$/;
10045
+ /**
10046
+ * How strongly an outcome resolves the reservation. A `settled` release is
10047
+ * never downgraded by a later record, while a stronger record reconciles a
10048
+ * weaker one — that is how a trusted readback recovers an earlier unconfirmed
10049
+ * attempt instead of being discarded.
10050
+ */
10051
+ const OUTCOME_STRENGTH = {
10052
+ not_effected: 0,
10053
+ unknown: 1,
10054
+ failed: 1,
10055
+ unconfirmed: 2,
10056
+ settled: 3
10057
+ };
10058
+ function asRecord$1(value) {
10059
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
10060
+ }
10061
+ function optionalString(value) {
10062
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
10063
+ }
10064
+ /**
10065
+ * Normalize a candidate release contract from a root adoption payload. Every
10066
+ * field is validated: an unparsable or partial adoption is refused rather than
10067
+ * approximated, because a half-specified contract would authorize an
10068
+ * unspecified candidate.
10069
+ */
10070
+ function normalizeReleaseContract(raw, adoptedBy, adoptedAtRevision = 0) {
10071
+ const errors = [];
10072
+ const value = asRecord$1(raw);
10073
+ if (!value) return { errors: ["release_contract_malformed"] };
10074
+ const operations = [];
10075
+ if (!Array.isArray(value.operations) || value.operations.length === 0) errors.push("release_operations_missing");
10076
+ else for (const entry of value.operations) {
10077
+ if (typeof entry !== "string" || !RELEASE_OPERATIONS.includes(entry)) {
10078
+ errors.push("release_operation_unknown");
10079
+ continue;
10080
+ }
10081
+ const operation = entry;
10082
+ if (!RELEASE_OPERATION_SURFACES[operation].protectable) {
10083
+ errors.push(RELEASE_OPERATION_SURFACES[operation].reasonCode);
10084
+ continue;
10085
+ }
10086
+ if (!operations.includes(operation)) operations.push(operation);
10087
+ }
10088
+ const candidate = asRecord$1(value.candidate);
10089
+ if (!candidate) errors.push("release_candidate_missing");
10090
+ const CANDIDATE_FIELDS = [
10091
+ "fullSha40",
10092
+ "ref",
10093
+ "repository",
10094
+ "packageId",
10095
+ "version",
10096
+ "artifactSha256",
10097
+ "artifactSri",
10098
+ "registry",
10099
+ "artifactDigest"
10100
+ ];
10101
+ for (const key of Object.keys(candidate ?? {})) if (!CANDIDATE_FIELDS.includes(key)) errors.push("release_candidate_field_unknown");
10102
+ const fullSha40 = optionalString(candidate?.fullSha40) ?? "";
10103
+ if (!FULL_SHA40.test(fullSha40)) errors.push("release_candidate_sha_invalid");
10104
+ const ref = optionalString(candidate?.ref);
10105
+ const repository = optionalString(candidate?.repository);
10106
+ const packageId = optionalString(candidate?.packageId);
10107
+ const version = optionalString(candidate?.version);
10108
+ let artifactSha256 = optionalString(candidate?.artifactSha256);
10109
+ let artifactSri = optionalString(candidate?.artifactSri);
10110
+ const legacyDigest = optionalString(candidate?.artifactDigest);
10111
+ if (legacyDigest !== void 0) if (SHA256.test(legacyDigest)) artifactSha256 ??= legacyDigest;
10112
+ else if (SRI.test(legacyDigest)) artifactSri ??= legacyDigest;
10113
+ else errors.push("release_candidate_artifact_digest_invalid");
10114
+ const registry = optionalString(candidate?.registry);
10115
+ if (artifactSha256 !== void 0 && !SHA256.test(artifactSha256)) errors.push("release_candidate_sha256_invalid");
10116
+ if (artifactSri !== void 0 && !SRI.test(artifactSri)) errors.push("release_candidate_sri_invalid");
10117
+ const readinessRefs = Array.isArray(value.readinessRefs) ? value.readinessRefs.filter((entry) => typeof entry === "string" && entry.length > 0) : [];
10118
+ const closureCertRef = optionalString(value.closureCertRef);
10119
+ let expiresAtEpochMs;
10120
+ if (value.expiresAtEpochMs !== void 0) if (typeof value.expiresAtEpochMs !== "number" || !Number.isSafeInteger(value.expiresAtEpochMs) || value.expiresAtEpochMs <= 0) errors.push("release_expiry_invalid");
10121
+ else expiresAtEpochMs = value.expiresAtEpochMs;
10122
+ if (errors.length) return { errors: [...new Set(errors)] };
10123
+ const suppliedId = optionalString(value.contractId);
10124
+ const body = {
10125
+ operations: [...operations].sort(),
10126
+ candidate: {
10127
+ fullSha40,
10128
+ ...ref ? { ref } : {},
10129
+ ...repository ? { repository } : {},
10130
+ ...packageId ? { packageId } : {},
10131
+ ...version ? { version } : {},
10132
+ ...artifactSha256 ? { artifactSha256 } : {},
10133
+ ...artifactSri ? { artifactSri } : {},
10134
+ ...registry ? { registry } : {}
10135
+ },
10136
+ readinessRefs: [...readinessRefs].sort(),
10137
+ ...closureCertRef ? { closureCertRef } : {},
10138
+ ...expiresAtEpochMs !== void 0 ? { expiresAtEpochMs } : {}
10139
+ };
10140
+ return {
10141
+ contract: {
10142
+ contractId: suppliedId ?? `rel-${sha256(JSON.stringify(body)).slice(0, 16)}`,
10143
+ adoptedBy,
10144
+ adoptedAtRevision,
10145
+ operations: operations.sort(),
10146
+ candidate: body.candidate,
10147
+ readinessRefs: body.readinessRefs,
10148
+ ...closureCertRef ? { closureCertRef } : {},
10149
+ ...expiresAtEpochMs !== void 0 ? { expiresAtEpochMs } : {}
10150
+ },
10151
+ errors: []
10152
+ };
10153
+ }
10154
+ function normalizeReservation(raw) {
10155
+ const value = asRecord$1(raw);
10156
+ if (!value) return void 0;
10157
+ const contractId = optionalString(value.contractId);
10158
+ const operation = optionalString(value.operation);
10159
+ const callId = optionalString(value.callId);
10160
+ const startedAtSeq = value.startedAtSeq;
10161
+ if (!contractId || !callId) return void 0;
10162
+ if (!operation || !RELEASE_OPERATIONS.includes(operation)) return void 0;
10163
+ if (typeof startedAtSeq !== "number" || !Number.isSafeInteger(startedAtSeq)) return void 0;
10164
+ const observedArtifactSri = optionalString(value.observedArtifactSri);
10165
+ return {
10166
+ contractId,
10167
+ operation,
10168
+ callId,
10169
+ startedAtSeq,
10170
+ status: "in_flight",
10171
+ ...observedArtifactSri ? { observedArtifactSri } : {}
10172
+ };
10173
+ }
10174
+ const RELEASE_OUTCOMES = [
10175
+ "settled",
10176
+ "unconfirmed",
10177
+ "unknown",
10178
+ "failed",
10179
+ "not_effected"
10180
+ ];
10181
+ function normalizeSettlement(raw) {
10182
+ const value = asRecord$1(raw);
10183
+ if (!value) return void 0;
10184
+ const contractId = optionalString(value.contractId);
10185
+ const operation = optionalString(value.operation);
10186
+ const callId = optionalString(value.callId);
10187
+ const settledAtSeq = value.settledAtSeq;
10188
+ const outcome = optionalString(value.outcome);
10189
+ if (!contractId || !callId) return void 0;
10190
+ if (!operation || !RELEASE_OPERATIONS.includes(operation)) return void 0;
10191
+ if (typeof settledAtSeq !== "number" || !Number.isSafeInteger(settledAtSeq)) return void 0;
10192
+ if (!outcome || !RELEASE_OUTCOMES.includes(outcome)) return void 0;
10193
+ const readbackRaw = asRecord$1(value.readback);
10194
+ const kind = readbackRaw?.kind;
10195
+ return {
10196
+ contractId,
10197
+ operation,
10198
+ callId,
10199
+ settledAtSeq,
10200
+ readback: readbackRaw && (kind === "npm_integrity" || kind === "git_ref" || kind === "github_release") && optionalString(readbackRaw.identity) ? {
10201
+ kind,
10202
+ identity: optionalString(readbackRaw.identity)
10203
+ } : "unavailable",
10204
+ outcome
10205
+ };
10206
+ }
10207
+ /** The adopted, not-revoked contract that covers an operation, newest first. */
10208
+ function releaseContractFor(projection, operation, contractId) {
10209
+ const contracts = projection.releaseContracts.filter((contract) => contract.revokedAtSeq === void 0 && (contractId === void 0 || contract.contractId === contractId) && contract.operations.includes(operation));
10210
+ return contracts.length ? contracts[contracts.length - 1] : void 0;
10211
+ }
10212
+ /** Whether a contract was explicitly revoked by a durable root command. */
10213
+ function isContractRevoked(projection, contractId) {
10214
+ return projection.releaseContracts.some((contract) => contract.contractId === contractId && contract.revokedAtSeq !== void 0);
10215
+ }
10216
+ /**
10217
+ * The reconciled settlement per (contract, operation, callId): the strongest
10218
+ * outcome wins, ties resolve to the later record. A `settled` release is never
10219
+ * revoked by a later weaker record.
10220
+ */
10221
+ function reconciledSettlements(projection, contractId, operation) {
10222
+ const byCall = /* @__PURE__ */ new Map();
10223
+ for (const settlement of projection.releaseSettlements) {
10224
+ if (settlement.contractId !== contractId || settlement.operation !== operation) continue;
10225
+ const existing = byCall.get(settlement.callId);
10226
+ if (!existing) {
10227
+ byCall.set(settlement.callId, settlement);
10228
+ continue;
10229
+ }
10230
+ const stronger = OUTCOME_STRENGTH[settlement.outcome] > OUTCOME_STRENGTH[existing.outcome];
10231
+ const newer = OUTCOME_STRENGTH[settlement.outcome] === OUTCOME_STRENGTH[existing.outcome] && settlement.settledAtSeq >= existing.settledAtSeq;
10232
+ if (stronger || newer) byCall.set(settlement.callId, settlement);
10233
+ }
10234
+ return [...byCall.values()];
10235
+ }
10236
+ /** Whether a settlement releases the one-shot lock: settled, or proven no-effect. */
10237
+ function releasesLock(settlement) {
10238
+ return settlement.outcome === "settled" || settlement.outcome === "not_effected";
10239
+ }
10240
+ /** The in-flight (unresolved) reservation for one contract operation, if any. */
10241
+ function inFlightReservation(projection, contractId, operation) {
10242
+ const settled = reconciledSettlements(projection, contractId, operation);
10243
+ for (const reservation of projection.releaseReservations) {
10244
+ if (reservation.contractId !== contractId || reservation.operation !== operation) continue;
10245
+ const resolution = settled.find((settlement) => settlement.callId === reservation.callId);
10246
+ if (!resolution || !releasesLock(resolution)) return reservation;
10247
+ }
10248
+ }
10249
+ /** Whether a contract operation has already been consumed by a settled effect. */
10250
+ function settledOperations(projection, contractId) {
10251
+ const consumed = [];
10252
+ for (const settlement of projection.releaseSettlements) {
10253
+ if (settlement.contractId !== contractId) continue;
10254
+ if (settlement.outcome !== "settled") continue;
10255
+ if (!consumed.includes(settlement.operation)) consumed.push(settlement.operation);
10256
+ }
10257
+ return consumed.sort();
10258
+ }
10259
+ const CANDIDATE_FIELD_CODES = [
10260
+ {
10261
+ field: "fullSha40",
10262
+ label: "commit",
10263
+ unresolvedCode: "release_candidate_sha_unresolved",
10264
+ mismatchCode: "release_candidate_sha_mismatch"
10265
+ },
10266
+ {
10267
+ field: "ref",
10268
+ label: "ref",
10269
+ unresolvedCode: "release_candidate_ref_unresolved",
10270
+ mismatchCode: "release_candidate_ref_mismatch"
10271
+ },
10272
+ {
10273
+ field: "repository",
10274
+ label: "repository",
10275
+ unresolvedCode: "release_candidate_repository_unresolved",
10276
+ mismatchCode: "release_candidate_repository_mismatch"
10277
+ },
10278
+ {
10279
+ field: "packageId",
10280
+ label: "package",
10281
+ unresolvedCode: "release_candidate_package_unresolved",
10282
+ mismatchCode: "release_candidate_package_mismatch"
10283
+ },
10284
+ {
10285
+ field: "version",
10286
+ label: "version",
10287
+ unresolvedCode: "release_candidate_version_unresolved",
10288
+ mismatchCode: "release_candidate_version_mismatch"
10289
+ },
10290
+ {
10291
+ field: "artifactSha256",
10292
+ label: "artifact SHA-256",
10293
+ unresolvedCode: "release_artifact_sha256_unresolved",
10294
+ mismatchCode: "release_candidate_artifact_mismatch"
10295
+ },
10296
+ {
10297
+ field: "artifactSri",
10298
+ label: "artifact SRI",
10299
+ unresolvedCode: "release_artifact_sri_unresolved",
10300
+ mismatchCode: "release_candidate_artifact_sri_mismatch"
10301
+ },
10302
+ {
10303
+ field: "registry",
10304
+ label: "registry",
10305
+ unresolvedCode: "release_candidate_registry_unresolved",
10306
+ mismatchCode: "release_candidate_registry_mismatch"
10307
+ }
10308
+ ];
10309
+ /** A readiness reference resolves to a real, already-established fact. */
10310
+ function readinessResolves(projection, ref) {
10311
+ if (projection.checkpoints.some((checkpoint) => checkpoint.id === ref && checkpoint.result === "certified")) return true;
10312
+ if (projection.boundaries.some((boundary) => boundary.id === ref)) return true;
10313
+ return projection.items.get(ref)?.status === "passed";
10314
+ }
10315
+ /**
10316
+ * The pre-effect release decision. Order matters: an unprotectable surface and
10317
+ * a damaged release state are refused before expiry or candidate checks,
10318
+ * because running an unprotected operation is never made acceptable by a valid
10319
+ * ticket, and because unreadable release state must not authorize anything.
10320
+ */
10321
+ function releasePreEffectDecision(projection, request) {
10322
+ const surface = RELEASE_OPERATION_SURFACES[request.operation];
10323
+ if (!surface.protectable) return {
10324
+ status: "denied",
10325
+ reasonCode: surface.reasonCode
10326
+ };
10327
+ if (projection.releaseStateDamaged) return {
10328
+ status: "denied",
10329
+ reasonCode: "release_state_damaged"
10330
+ };
10331
+ const contract = releaseContractFor(projection, request.operation, request.contractId);
10332
+ if (!contract) {
10333
+ if (projection.releaseContracts.some((entry) => entry.revokedAtSeq !== void 0 && entry.operations.includes(request.operation) && (request.contractId === void 0 || entry.contractId === request.contractId))) return {
10334
+ status: "denied",
10335
+ reasonCode: "release_contract_revoked"
10336
+ };
10337
+ if (request.contractId !== void 0 && isContractRevoked(projection, request.contractId)) return {
10338
+ status: "denied",
10339
+ reasonCode: "release_contract_revoked"
10340
+ };
10341
+ return {
10342
+ status: "denied",
10343
+ reasonCode: projection.releaseContracts.length === 0 ? "release_contract_required" : "release_operation_not_adopted"
10344
+ };
10345
+ }
10346
+ if (contract.expiresAtEpochMs !== void 0) {
10347
+ if (request.nowEpochMs === void 0) return {
10348
+ status: "denied",
10349
+ reasonCode: "release_expiry_unevaluable",
10350
+ contractId: contract.contractId
10351
+ };
10352
+ if (request.nowEpochMs >= contract.expiresAtEpochMs) return {
10353
+ status: "denied",
10354
+ reasonCode: "release_contract_expired",
10355
+ contractId: contract.contractId
10356
+ };
10357
+ }
10358
+ if (settledOperations(projection, contract.contractId).includes(request.operation)) return {
10359
+ status: "denied",
10360
+ reasonCode: "release_operation_consumed",
10361
+ contractId: contract.contractId
10362
+ };
10363
+ if (inFlightReservation(projection, contract.contractId, request.operation)) return {
10364
+ status: "denied",
10365
+ reasonCode: "release_operation_in_flight",
10366
+ contractId: contract.contractId
10367
+ };
10368
+ for (const ref of contract.readinessRefs) if (!readinessResolves(projection, ref)) return {
10369
+ status: "denied",
10370
+ reasonCode: "release_readiness_unresolved",
10371
+ contractId: contract.contractId
10372
+ };
10373
+ const frozen = contract.frozenClosure;
10374
+ const closure = frozen !== void 0 ? projection.checkpoints.find((checkpoint) => checkpoint.id === frozen.id) : void 0;
10375
+ if (!frozen || frozen.contractRevision !== contract.adoptedAtRevision || frozen.id !== contract.closureCertRef || !closure || closure.result !== "certified" || closure.certificationDigest !== frozen.certificationDigest || closure.epoch !== projection.epoch) return {
10376
+ status: "denied",
10377
+ reasonCode: "release_closure_unresolved",
10378
+ contractId: contract.contractId
10379
+ };
10380
+ if (contract.candidate.artifactSha256 === void 0 && contract.candidate.artifactSri === void 0) return {
10381
+ status: "denied",
10382
+ reasonCode: "release_artifact_identity_required",
10383
+ contractId: contract.contractId
10384
+ };
10385
+ const candidate = contract.candidate;
10386
+ const observedIdentity = request.observed ?? {};
10387
+ const compared = [];
10388
+ for (const entry of CANDIDATE_FIELD_CODES) {
10389
+ const declared = candidate[entry.field];
10390
+ if (typeof declared !== "string") continue;
10391
+ compared.push([entry, declared]);
10392
+ }
10393
+ for (const [entry, declared] of compared) {
10394
+ const observed = observedIdentity[entry.field];
10395
+ if (observed === void 0) return {
10396
+ status: "denied",
10397
+ reasonCode: entry.unresolvedCode,
10398
+ contractId: contract.contractId
10399
+ };
10400
+ if (observed !== declared) return {
10401
+ status: "denied",
10402
+ reasonCode: entry.mismatchCode,
10403
+ contractId: contract.contractId
10404
+ };
10405
+ }
10406
+ if (candidate.ref !== void 0 && observedIdentity.refSha !== void 0 && observedIdentity.fullSha40 !== void 0 && observedIdentity.refSha !== observedIdentity.fullSha40) return {
10407
+ status: "denied",
10408
+ reasonCode: "release_ref_commit_mismatch",
10409
+ contractId: contract.contractId
10410
+ };
10411
+ const resolved = request.resolvedTarget;
10412
+ if (resolved === void 0) return {
10413
+ status: "denied",
10414
+ reasonCode: "release_target_unresolved",
10415
+ contractId: contract.contractId
10416
+ };
10417
+ if (resolved.version === void 0) return {
10418
+ status: "denied",
10419
+ reasonCode: "release_target_unresolved",
10420
+ contractId: contract.contractId
10421
+ };
10422
+ if (candidate.packageId !== void 0 && resolved.artifact_id !== candidate.packageId) return {
10423
+ status: "denied",
10424
+ reasonCode: "release_target_package_mismatch",
10425
+ contractId: contract.contractId
10426
+ };
10427
+ if (candidate.version !== void 0 && resolved.version !== candidate.version) return {
10428
+ status: "denied",
10429
+ reasonCode: "release_target_version_mismatch",
10430
+ contractId: contract.contractId
10431
+ };
10432
+ if (candidate.registry !== void 0 && resolved.registry !== candidate.registry) return {
10433
+ status: "denied",
10434
+ reasonCode: "release_target_registry_mismatch",
10435
+ contractId: contract.contractId
10436
+ };
10437
+ return {
10438
+ status: "granted",
10439
+ reasonCode: "release_contract_granted",
10440
+ contractId: contract.contractId
10441
+ };
10442
+ }
10443
+ /**
10444
+ * Whether a trusted readback settles the attempt: the readback must name the
10445
+ * SAME artifact identity the contract froze. A registry that answers with a
10446
+ * different integrity proves the wrong bytes are published, which is an
10447
+ * unknown outcome for this contract, never a settlement.
10448
+ */
10449
+ function readbackSettlesContract(contract, readback, reservationSri) {
10450
+ if (readback === "unavailable") return "unconfirmed";
10451
+ if (readback.kind !== "npm_integrity") return "unconfirmed";
10452
+ const expected = contract.candidate.artifactSri ?? reservationSri;
10453
+ if (expected === void 0) return "unconfirmed";
10454
+ return readback.identity === expected ? "settled" : "mismatch";
10455
+ }
10456
+ /**
10457
+ * The reservation for one call, including revoked contracts: reconciling an
10458
+ * operation that was already in flight when its contract was revoked is the
10459
+ * recovery case, not an authority question.
10460
+ */
10461
+ function reservationFor(projection, contractId, callId) {
10462
+ return projection.releaseReservations.find((entry) => entry.contractId === contractId && entry.callId === callId);
10463
+ }
10464
+ /** A contract by id, INCLUDING revoked ones, for recovery lookups. */
10465
+ function contractById(projection, contractId) {
10466
+ return projection.releaseContracts.find((entry) => entry.contractId === contractId);
10467
+ }
10468
+ /** The coverage report for one contract: which adopted operations Guard can protect. */
10469
+ function releaseCoverage(contract) {
10470
+ return [...contract.operations].sort().map((operation) => ({
10471
+ operation,
10472
+ ...RELEASE_OPERATION_SURFACES[operation]
10473
+ }));
10474
+ }
10475
+
8168
10476
  //#endregion
8169
10477
  //#region src/domain/derive.ts
10478
+ /**
10479
+ * Audited delegation tool names (C04/DS06-B). A tool result from one of these
10480
+ * is a subagent's answer: bounded evidence for the unit that asked for it, and
10481
+ * never a parent completion. The real names are a host tool-bundle surface —
10482
+ * native acceptance pins the audited cohort, exactly like the question-tool
10483
+ * allowlist — so this list is the production default and can be overridden by
10484
+ * an audited cohort.
10485
+ */
10486
+ const DEFAULT_DELEGATION_TOOL_NAMES = [
10487
+ "task",
10488
+ "delegate",
10489
+ "delegate_task",
10490
+ "subagent",
10491
+ "subagent_fork",
10492
+ "spawn_agent"
10493
+ ];
8170
10494
  const CAPTURE_V042_NOTICE = "Context Guard capture boundary: v0.4.2";
8171
10495
  const PROTOCOL_V3_NOTICE = "Context Guard protocol boundary: v3.0.0";
8172
10496
  /**
@@ -8177,6 +10501,15 @@ const PROTOCOL_V3_NOTICE = "Context Guard protocol boundary: v3.0.0";
8177
10501
  * their historical meaning for replay.
8178
10502
  */
8179
10503
  const PROTOCOL_V4_NOTICE = "Context Guard protocol boundary: v4.0.0";
10504
+ /**
10505
+ * 0.6.0 first-step boundary: same placement discipline as v4. It cuts the
10506
+ * work-unit, delivery, and certificate-v2 semantics (P0 §1): messages before
10507
+ * it keep their historical rules, messages after it are captured into work
10508
+ * units and close through unit-closure certificates and trusted deliveries.
10509
+ * An old binary ignores this notice (plugin source, unmatched pattern), so the
10510
+ * fail direction on rollback is closed, never a misread.
10511
+ */
10512
+ const PROTOCOL_V5_NOTICE = "Context Guard protocol boundary: v5.0.0";
8180
10513
  function isProtocolBoundaryNotice(event, notice = PROTOCOL_V3_NOTICE) {
8181
10514
  if (event.type !== "user/message") return false;
8182
10515
  const data = asRecord(event.data);
@@ -8196,6 +10529,39 @@ function parseArguments(raw) {
8196
10529
  function asRecord(value) {
8197
10530
  return typeof value === "object" && value !== null ? value : void 0;
8198
10531
  }
10532
+ /** Stable JSON, used for the release adoption digest. */
10533
+ function stableJson(value) {
10534
+ if (Array.isArray(value)) return `[${value.map(stableJson).join(",")}]`;
10535
+ if (value && typeof value === "object") return `{${Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, entry]) => `${JSON.stringify(key)}:${stableJson(entry)}`).join(",")}}`;
10536
+ return JSON.stringify(value);
10537
+ }
10538
+ /**
10539
+ * Bounded release diagnostic ledger (last 16 entries). A rejected record also
10540
+ * marks the release state damaged: an unreadable reservation, settlement or
10541
+ * contract must block release operations rather than being silently forgotten,
10542
+ * and it must not touch the projection's own integrity, which governs ordinary
10543
+ * work.
10544
+ */
10545
+ function pushReleaseDiagnostic(projection, seq, reasonCode, damaging = false) {
10546
+ if (damaging) projection.releaseStateDamaged = true;
10547
+ if (projection.releaseDiagnostics.some((entry) => entry.seq === seq && entry.reasonCode === reasonCode)) return;
10548
+ projection.releaseDiagnostics.push({
10549
+ seq,
10550
+ reasonCode
10551
+ });
10552
+ if (projection.releaseDiagnostics.length > 16) projection.releaseDiagnostics.shift();
10553
+ }
10554
+ /**
10555
+ * Whether a recorded certificate is exactly the certificate this log re-derives.
10556
+ *
10557
+ * The comparison is by FIELD SEMANTICS, not by JSON text: a tool output is a
10558
+ * JSON object whose property order is an artifact of serialization, so
10559
+ * `JSON.stringify` equality made an identical certificate replay as corrupt
10560
+ * whenever the writer emitted `unit_id` before `goal_ref` (or vice versa). The
10561
+ * field set is still exact — an extra, missing, or renamed field stays a
10562
+ * mismatch — and values are compared by canonical encoding, so tampering is as
10563
+ * detectable as before.
10564
+ */
8199
10565
  function recordedCertificateMatches(recorded, checkpoint) {
8200
10566
  const value = asRecord(recorded);
8201
10567
  if (!value) return false;
@@ -8214,6 +10580,10 @@ function recordedCertificateMatches(recorded, checkpoint) {
8214
10580
  certification_digest: checkpoint.certificationDigest,
8215
10581
  goal_ref: checkpoint.goalRef ?? null
8216
10582
  };
10583
+ if (checkpoint.unitId !== void 0) {
10584
+ exact.unit_id = checkpoint.unitId;
10585
+ exact.unit_closure_digest = checkpoint.unitClosureDigest;
10586
+ }
8217
10587
  const normalized = {
8218
10588
  ...value,
8219
10589
  goal_ref: goal ? {
@@ -8221,7 +10591,61 @@ function recordedCertificateMatches(recorded, checkpoint) {
8221
10591
  revision: goal.revision
8222
10592
  } : value.goal_ref
8223
10593
  };
8224
- return JSON.stringify(normalized) === JSON.stringify(exact);
10594
+ const expectedKeys = Object.keys(exact).sort();
10595
+ const actualKeys = Object.keys(normalized).sort();
10596
+ if (expectedKeys.length !== actualKeys.length) return false;
10597
+ return expectedKeys.every((key, index) => key === actualKeys[index] && stableJson(normalized[key]) === stableJson(exact[key]));
10598
+ }
10599
+ /**
10600
+ * The proof binding state the log itself implies for one checkpoint call. This
10601
+ * is the same computation the signing tool performs, replayed against the
10602
+ * projection derived up to that call.
10603
+ */
10604
+ function replayProofState(projection, proof) {
10605
+ if (proof === void 0) return {
10606
+ status: "absent",
10607
+ reason_codes: []
10608
+ };
10609
+ const structural = validateProofManifestV2(proof);
10610
+ if (structural.length) return {
10611
+ status: "invalid",
10612
+ reason_codes: [...structural].sort()
10613
+ };
10614
+ const binding = bindProofV2ToProjection(projection, proof);
10615
+ return binding.length ? {
10616
+ status: "rejected",
10617
+ reason_codes: [...binding].sort()
10618
+ } : {
10619
+ status: "bound",
10620
+ reason_codes: []
10621
+ };
10622
+ }
10623
+ /** Bounded set equality for reason-code lists, order-insensitive. */
10624
+ function sameStringSet(recorded, expected) {
10625
+ if (!Array.isArray(recorded)) return false;
10626
+ const left = [...new Set(recorded.filter((entry) => typeof entry === "string"))].sort();
10627
+ const right = [...new Set(expected)].sort();
10628
+ return left.length === right.length && left.every((value, index) => value === right[index]);
10629
+ }
10630
+ /**
10631
+ * Freeze the closure certificate the adopter relied on, resolved AT the
10632
+ * adoption watermark. Only the checkpoints restored so far existed then, so a
10633
+ * certificate that appears LATER in the log can never ratify an earlier
10634
+ * adoption; an unresolvable reference is recorded as unresolved rather than
10635
+ * left open for a future entry to satisfy.
10636
+ */
10637
+ function freezeAdoptionClosure(projection, contract) {
10638
+ const ref = contract.closureCertRef;
10639
+ const closure = ref !== void 0 ? projection.checkpoints.find((checkpoint) => checkpoint.id === ref && checkpoint.result === "certified") : void 0;
10640
+ return closure === void 0 ? contract : {
10641
+ ...contract,
10642
+ frozenClosure: {
10643
+ id: closure.id,
10644
+ certificationDigest: closure.certificationDigest,
10645
+ epoch: closure.epoch,
10646
+ contractRevision: closure.contractRevision
10647
+ }
10648
+ };
8225
10649
  }
8226
10650
  function restoreHistoricalCheckpoint(recorded, bindings, id) {
8227
10651
  const stringField = (name) => typeof recorded[name] === "string" ? recorded[name] : void 0;
@@ -8241,6 +10665,7 @@ function restoreHistoricalCheckpoint(recorded, bindings, id) {
8241
10665
  "certification_digest"
8242
10666
  ].some((name) => !stringField(name))) return void 0;
8243
10667
  if (goal && (typeof goal.id !== "string" || !Number.isSafeInteger(goal.revision))) return void 0;
10668
+ if (recorded.unit_id !== void 0 && (typeof recorded.unit_id !== "string" || !stringField("unit_closure_digest"))) return void 0;
8244
10669
  return {
8245
10670
  id,
8246
10671
  stopProtocolVersion: stringField("stop_protocol_version"),
@@ -8258,6 +10683,10 @@ function restoreHistoricalCheckpoint(recorded, bindings, id) {
8258
10683
  id: goal.id,
8259
10684
  revision: goal.revision
8260
10685
  } } : {},
10686
+ ...typeof recorded.unit_id === "string" ? {
10687
+ unitId: recorded.unit_id,
10688
+ unitClosureDigest: stringField("unit_closure_digest")
10689
+ } : {},
8261
10690
  certificationDigest: stringField("certification_digest"),
8262
10691
  result: "certified"
8263
10692
  };
@@ -8295,11 +10724,39 @@ function resolveArtifact(path$1, scope) {
8295
10724
  * an item whose action/target could not be derived deterministically stays
8296
10725
  * `legacy_authority_unclassified` instead of being retroactively authorized.
8297
10726
  */
8298
- function captureRootText(projection, text, seq, scope, legacy, priorRootMessages, prefix = `m${seq}`, coordinationSplit = true) {
10727
+ function captureRootText(projection, text, seq, scope, legacy, priorRootMessages, prefix = `m${seq}`, coordinationSplit = true, unitId, clarification = false) {
8299
10728
  const blocks = segmentAuthorityBlocks(text, priorRootMessages);
10729
+ const provenance = legacy ? void 0 : {
10730
+ rawTextSha256: sha256(text),
10731
+ rawText: text
10732
+ };
10733
+ let coveredSpans = 0;
10734
+ let blockCursor = 0;
8300
10735
  for (const block$1 of blocks) {
8301
10736
  if (!block$1.capture) continue;
8302
- insertItems(projection, block$1.text, `${prefix}:${block$1.blockId}`, scope, block$1.authority === "root_adoption" ? "root_adoption" : "root_instruction", legacy, block$1.kind === "instruction" || block$1.authority === "root_adoption", coordinationSplit);
10737
+ let blockOffset;
10738
+ if (provenance) {
10739
+ const at = provenance.rawText.indexOf(block$1.text, blockCursor);
10740
+ if (at >= 0) {
10741
+ blockCursor = at + 1;
10742
+ blockOffset = utf8ByteOffset(provenance.rawText, at);
10743
+ }
10744
+ }
10745
+ coveredSpans += insertItems(projection, block$1.text, `${prefix}:${block$1.blockId}`, scope, block$1.authority === "root_adoption" ? "root_adoption" : "root_instruction", legacy, block$1.kind === "instruction" || block$1.authority === "root_adoption", coordinationSplit, unitId, provenance ? {
10746
+ ...provenance,
10747
+ blockOffset,
10748
+ blockText: block$1.text,
10749
+ blockAuthority: block$1.authority
10750
+ } : void 0, clarification ? text : void 0);
10751
+ }
10752
+ if (provenance) {
10753
+ projection.coverage.push({
10754
+ seq,
10755
+ rawTextSha256: provenance.rawTextSha256,
10756
+ byteLength: utf8ByteLength(provenance.rawText),
10757
+ coveredSpans
10758
+ });
10759
+ if (projection.coverage.length > 16) projection.coverage.shift();
8303
10760
  }
8304
10761
  priorRootMessages.push(text);
8305
10762
  if (priorRootMessages.length > 16) priorRootMessages.shift();
@@ -8310,16 +10767,40 @@ function captureRootText(projection, text, seq, scope, legacy, priorRootMessages
8310
10767
  * item, so evidence for one file cannot close a message that also covers other
8311
10768
  * files or embeds prohibitions.
8312
10769
  */
8313
- function insertItems(projection, text, sourceMessageId, scope, authority = "root_instruction", legacy = false, legacyAuthorityProven = false, coordinationSplit = true) {
10770
+ function insertItems(projection, text, sourceMessageId, scope, authority = "root_instruction", legacy = false, legacyAuthorityProven = false, coordinationSplit = true, unitId, provenance, clarificationText) {
8314
10771
  const before = new Set(projection.items.keys());
10772
+ let coveredSpans = 0;
10773
+ const usedOccurrences = /* @__PURE__ */ new Set();
8315
10774
  for (const segment of segmentClauses(text, { coordinationSplit })) {
8316
10775
  if (classifyUserInteraction(segment.body) === "conversational") continue;
8317
10776
  if (segment.kind === "requirement" && segment.paths.length === 0 && isInstructionFraming(segment.body)) continue;
10777
+ let span;
10778
+ if (provenance) {
10779
+ let at = provenance.blockText.indexOf(segment.text);
10780
+ while (at >= 0 && usedOccurrences.has(at)) at = provenance.blockText.indexOf(segment.text, at + 1);
10781
+ if (at >= 0) {
10782
+ usedOccurrences.add(at);
10783
+ const start = (provenance.blockOffset ?? 0) + utf8ByteOffset(provenance.blockText, at);
10784
+ span = {
10785
+ partIndex: 0,
10786
+ start,
10787
+ end: start + utf8ByteLength(segment.text),
10788
+ class: spanClassOf(segment.kind, segment.interpretation.directive, provenance.blockAuthority)
10789
+ };
10790
+ }
10791
+ }
10792
+ if (span) coveredSpans += 1;
8318
10793
  if (segment.paths.length === 0) {
8319
- insert(projection, segment, sourceMessageId, scope.cwd || "scope", "scope");
10794
+ insert(projection, segment, sourceMessageId, scope.cwd || "scope", "scope", unitId, provenance ? {
10795
+ rawTextSha256: provenance.rawTextSha256,
10796
+ span
10797
+ } : void 0);
8320
10798
  continue;
8321
10799
  }
8322
- for (const path$1 of segment.paths) insert(projection, segment, sourceMessageId, resolveArtifact(path$1, scope), "artifact");
10800
+ for (const path$1 of segment.paths) insert(projection, segment, sourceMessageId, resolveArtifact(path$1, scope), "artifact", unitId, provenance ? {
10801
+ rawTextSha256: provenance.rawTextSha256,
10802
+ span
10803
+ } : void 0);
8323
10804
  }
8324
10805
  for (const [id, item] of projection.items) {
8325
10806
  if (before.has(id)) continue;
@@ -8335,6 +10816,24 @@ function insertItems(projection, text, sourceMessageId, scope, authority = "root
8335
10816
  break;
8336
10817
  }
8337
10818
  }
10819
+ if (clarificationText) for (const [id, item] of projection.items) {
10820
+ if (before.has(id)) continue;
10821
+ if (item.kind === "prohibition" || item.status !== "pending") continue;
10822
+ if (item.authorityDisposition !== "executable_now") continue;
10823
+ if (!item.semanticAction || item.semanticAction === "generic_run") continue;
10824
+ for (const [otherId, other] of projection.items) {
10825
+ if (otherId === id || !before.has(otherId)) continue;
10826
+ if (other.status !== "pending" || other.kind === "prohibition") continue;
10827
+ if (other.waitAuthorization || other.legacyFlags?.length) continue;
10828
+ if (other.semanticAction !== "generic_run" || other.authorityDisposition !== "executable_now") continue;
10829
+ if (other.normalizedText.length < 4) continue;
10830
+ if (!clarificationText.includes(other.normalizedText)) continue;
10831
+ if (other.verification.subject !== item.verification.subject) continue;
10832
+ supersedeItem(projection.items, otherId, item);
10833
+ item.clarifiesItemId = otherId;
10834
+ break;
10835
+ }
10836
+ }
8338
10837
  for (const [id, item] of projection.items) {
8339
10838
  if (before.has(id)) continue;
8340
10839
  if (legacy) if (legacyAuthorityProven && item.semanticAction !== void 0 && item.semanticAction !== "generic_run" && item.targetCaptureStatus === "resolved") {
@@ -8347,13 +10846,19 @@ function insertItems(projection, text, sourceMessageId, scope, authority = "root
8347
10846
  }
8348
10847
  else item.authority = authority;
8349
10848
  }
10849
+ return coveredSpans;
8350
10850
  }
8351
- function insert(projection, segment, sourceMessageId, subject, surface) {
10851
+ function insert(projection, segment, sourceMessageId, subject, surface, unitId, provenance) {
8352
10852
  const revision = projection.contractRevision + 1;
8353
10853
  const id = nextId(projection.items, segment.kind);
8354
10854
  const method = extractMethod(segment.body);
8355
10855
  const operation = extractOperation(segment.body);
8356
10856
  const item = captureItem(segment.kind, segment.body, sourceMessageId, id, revision, subject, surface, method, operation, segment.interpretation);
10857
+ if (unitId !== void 0) item.unitId = unitId;
10858
+ if (provenance) {
10859
+ item.rawTextSha256 = provenance.rawTextSha256;
10860
+ if (provenance.span) item.spans = [provenance.span];
10861
+ }
8357
10862
  const duplicate = [...projection.items.values()].find((existing) => existing.kind === segment.kind && existing.status === "pending" && existing.textSha256 === item.textSha256 && existing.verification.subject === subject);
8358
10863
  if (duplicate) supersedeItem(projection.items, duplicate.id, item);
8359
10864
  else projection.items.set(id, item);
@@ -8368,6 +10873,7 @@ function insert(projection, segment, sourceMessageId, subject, surface) {
8368
10873
  */
8369
10874
  function deriveProjection(sourceEvents, config, scope, durableConfirmed, hostLock = DEFAULT_HOST_LOCK) {
8370
10875
  const projection = createProjection();
10876
+ projection.policy = config.policy ?? "standard";
8371
10877
  if (scope.sessionHeader) projection.sessionRefDigest = sessionRefDigest(scope.sessionHeader);
8372
10878
  projection.hostLockDigest = hostLock.digest;
8373
10879
  projection.hostStatus = hostLock.status;
@@ -8380,14 +10886,44 @@ function deriveProjection(sourceEvents, config, scope, durableConfirmed, hostLoc
8380
10886
  let enablementTransitioned = false;
8381
10887
  let lastCompactionSeq = -1;
8382
10888
  const pendingCalls = /* @__PURE__ */ new Map();
10889
+ const v5BoundarySeq = sourceEvents.find((event) => isProtocolBoundaryNotice(event, PROTOCOL_V5_NOTICE))?.seq;
8383
10890
  const v4BoundarySeq = sourceEvents.find((event) => isProtocolBoundaryNotice(event, PROTOCOL_V4_NOTICE))?.seq;
8384
- const protocolBoundarySeq = sourceEvents.find((event) => isProtocolBoundaryNotice(event) || isProtocolBoundaryNotice(event, PROTOCOL_V4_NOTICE))?.seq;
8385
- const captureBoundarySeq = sourceEvents.find((event) => isProtocolBoundaryNotice(event, CAPTURE_V042_NOTICE) || isProtocolBoundaryNotice(event, PROTOCOL_V4_NOTICE))?.seq;
10891
+ const protocolBoundarySeq = sourceEvents.find((event) => isProtocolBoundaryNotice(event) || isProtocolBoundaryNotice(event, PROTOCOL_V4_NOTICE) || isProtocolBoundaryNotice(event, PROTOCOL_V5_NOTICE))?.seq;
10892
+ const captureBoundarySeq = sourceEvents.find((event) => isProtocolBoundaryNotice(event, CAPTURE_V042_NOTICE) || isProtocolBoundaryNotice(event, PROTOCOL_V4_NOTICE) || isProtocolBoundaryNotice(event, PROTOCOL_V5_NOTICE))?.seq;
8386
10893
  const priorRootMessages = [];
8387
10894
  let realRootInputSeen = false;
10895
+ const trustedDeliveries = (v5BoundarySeq !== void 0 ? deriveTrustedDeliveries(sourceEvents) : []).filter((delivery) => delivery.turnEndSeq > v5BoundarySeq);
10896
+ let deliveryCursor = 0;
10897
+ const applyDeliveriesUpTo = (seq) => {
10898
+ while (deliveryCursor < trustedDeliveries.length && trustedDeliveries[deliveryCursor].turnEndSeq <= seq) {
10899
+ const delivery = trustedDeliveries[deliveryCursor];
10900
+ deliveryCursor += 1;
10901
+ const inputSeqs = turnRootInputSeqs.get(delivery.turn);
10902
+ if (!inputSeqs) continue;
10903
+ const owningUnitId = turnUnitIds.get(delivery.turn);
10904
+ const eligibleUnitIds = owningUnitId === void 0 ? void 0 : new Set([owningUnitId, ...unitDescendantIds(projection, owningUnitId)]);
10905
+ for (const itemId of informationItemIdsForDelivery(projection.items, delivery, inputSeqs, eligibleUnitIds)) {
10906
+ const item = projection.items.get(itemId);
10907
+ if (!item || item.status !== "pending") continue;
10908
+ const sourceSeq = /^m(\d+)(?::|$)/.exec(item.sourceMessageId);
10909
+ if (!sourceSeq || Number(sourceSeq[1]) <= v5BoundarySeq) continue;
10910
+ item.status = "answered";
10911
+ item.answeredBy = {
10912
+ turn: delivery.turn,
10913
+ responseSeq: delivery.responseSeq,
10914
+ responseSha256: delivery.responseSha256
10915
+ };
10916
+ }
10917
+ }
10918
+ };
10919
+ const turnRootInputSeqs = /* @__PURE__ */ new Map();
10920
+ const turnUnitIds = /* @__PURE__ */ new Map();
10921
+ let activeTurn;
10922
+ const unitSemanticsActive = () => v5BoundarySeq !== void 0 && !scope.sessionHeader?.parentSession && !scope.sessionHeader?.delegationDepth && scope.sessionHeader?.origin !== "subagent";
8388
10923
  for (const event of sourceEvents) {
8389
10924
  projection.enabled = enabled;
8390
10925
  projection.lastObservedSourceSeq = Math.max(projection.lastObservedSourceSeq, event.seq);
10926
+ applyDeliveriesUpTo(event.seq);
8391
10927
  switch (event.type) {
8392
10928
  case "command/run": {
8393
10929
  const data = asRecord(event.data);
@@ -8408,6 +10944,26 @@ function deriveProjection(sourceEvents, config, scope, durableConfirmed, hostLoc
8408
10944
  item.supersededBy = `CLEAR:${revision}`;
8409
10945
  }
8410
10946
  projection.contractRevision = revision;
10947
+ } else if (subcommand === "release") {
10948
+ const rest = typeof data.args === "string" ? data.args.trim().slice(7).trim() : "";
10949
+ const revoke = /^revoke(?:\s+(\S+))?$/.exec(rest);
10950
+ if (revoke) {
10951
+ const contractId = revoke[1] ?? "";
10952
+ const contract = projection.releaseContracts.find((entry) => entry.contractId === contractId);
10953
+ if (!contract) pushReleaseDiagnostic(projection, event.seq, "release_contract_revocation_unknown");
10954
+ else if (contract.revokedAtSeq === void 0) contract.revokedAtSeq = event.seq;
10955
+ break;
10956
+ }
10957
+ const match = /^adopt(?:\s+([\s\S]+))?$/.exec(rest);
10958
+ if (match) {
10959
+ const payload = parseArguments((match[1] ?? "").trim());
10960
+ const normalized = normalizeReleaseContract(payload, {
10961
+ seq: event.seq,
10962
+ digest: sha256(stableJson(payload))
10963
+ }, projection.contractRevision);
10964
+ if (!normalized.contract) for (const code of normalized.errors) pushReleaseDiagnostic(projection, event.seq, code);
10965
+ else if (!projection.releaseContracts.some((contract) => contract.contractId === normalized.contract.contractId)) projection.releaseContracts.push(freezeAdoptionClosure(projection, normalized.contract));
10966
+ } else if (rest.length > 0 && !/^status$/.test(rest)) pushReleaseDiagnostic(projection, event.seq, "release_subcommand_unknown");
8411
10967
  }
8412
10968
  break;
8413
10969
  }
@@ -8417,10 +10973,22 @@ function deriveProjection(sourceEvents, config, scope, durableConfirmed, hostLoc
8417
10973
  break;
8418
10974
  case "turn/start": {
8419
10975
  const started = asRecord(event.data);
8420
- if (typeof started?.turn === "number" && Number.isSafeInteger(started.turn)) projection.hostTurn = started.turn;
10976
+ if (typeof started?.turn === "number" && Number.isSafeInteger(started.turn)) {
10977
+ projection.hostTurn = started.turn;
10978
+ activeTurn = started.turn;
10979
+ }
10980
+ break;
10981
+ }
10982
+ case "turn/end": {
10983
+ const ended = asRecord(event.data);
10984
+ if (typeof ended?.turn === "number" && Number.isSafeInteger(ended.turn)) activeTurn = void 0;
8421
10985
  break;
8422
10986
  }
8423
10987
  case "user/message": {
10988
+ if (isProtocolBoundaryNotice(event, PROTOCOL_V5_NOTICE)) {
10989
+ projection.boundaryProtocol = 5;
10990
+ break;
10991
+ }
8424
10992
  if (isProtocolBoundaryNotice(event) || isProtocolBoundaryNotice(event, CAPTURE_V042_NOTICE) || isProtocolBoundaryNotice(event, PROTOCOL_V4_NOTICE)) break;
8425
10993
  {
8426
10994
  const record = asRecord(event.data);
@@ -8444,17 +11012,76 @@ function deriveProjection(sourceEvents, config, scope, durableConfirmed, hostLoc
8444
11012
  if (rootSeq !== void 0) projection.handledControlSeqs.add(rootSeq);
8445
11013
  break;
8446
11014
  }
11015
+ if (recordSource?.kind === "plugin" && recordSource.plugin === "context-guard") {
11016
+ if (recordText.startsWith(RELEASE_CONTRACT_PREFIX)) {
11017
+ const payload = parseArguments(recordText.slice(RELEASE_CONTRACT_PREFIX.length));
11018
+ const adoptionSeq = typeof payload.adoptedBySeq === "number" && Number.isSafeInteger(payload.adoptedBySeq) ? payload.adoptedBySeq : event.seq;
11019
+ const normalized = normalizeReleaseContract(asRecord(payload.contract) ?? payload, {
11020
+ seq: adoptionSeq,
11021
+ digest: sha256(stableJson(payload.contract ?? null))
11022
+ }, projection.contractRevision);
11023
+ if (!normalized.contract) for (const code of normalized.errors) pushReleaseDiagnostic(projection, event.seq, code, true);
11024
+ else if (!projection.releaseContracts.some((contract) => contract.contractId === normalized.contract.contractId)) projection.releaseContracts.push(freezeAdoptionClosure(projection, normalized.contract));
11025
+ break;
11026
+ }
11027
+ if (recordText.startsWith(RELEASE_RESERVATION_PREFIX)) {
11028
+ const reservation = normalizeReservation(parseArguments(recordText.slice(RELEASE_RESERVATION_PREFIX.length)));
11029
+ if (!reservation) pushReleaseDiagnostic(projection, event.seq, "release_reservation_malformed", true);
11030
+ else if (!projection.releaseReservations.some((entry) => entry.callId === reservation.callId)) projection.releaseReservations.push({
11031
+ ...reservation,
11032
+ startedAtSeq: event.seq
11033
+ });
11034
+ break;
11035
+ }
11036
+ if (recordText.startsWith(RELEASE_REVOCATION_PREFIX)) {
11037
+ const payload = asRecord(parseArguments(recordText.slice(RELEASE_REVOCATION_PREFIX.length)));
11038
+ const contractId = typeof payload?.contractId === "string" ? payload.contractId : "";
11039
+ const contract = projection.releaseContracts.find((entry) => entry.contractId === contractId);
11040
+ if (!contract) pushReleaseDiagnostic(projection, event.seq, "release_contract_revocation_unknown");
11041
+ else if (contract.revokedAtSeq === void 0) contract.revokedAtSeq = event.seq;
11042
+ break;
11043
+ }
11044
+ if (recordText.startsWith(RELEASE_SETTLEMENT_PREFIX)) {
11045
+ const settlement = normalizeSettlement(parseArguments(recordText.slice(RELEASE_SETTLEMENT_PREFIX.length)));
11046
+ if (!settlement) pushReleaseDiagnostic(projection, event.seq, "release_settlement_malformed", true);
11047
+ else {
11048
+ const pinned = {
11049
+ ...settlement,
11050
+ settledAtSeq: event.seq
11051
+ };
11052
+ const key = (row) => `${row.contractId}\u0000${row.operation}\u0000${row.callId}`;
11053
+ const index = projection.releaseSettlements.findIndex((entry) => key(entry) === key(pinned));
11054
+ if (index < 0) projection.releaseSettlements.push(pinned);
11055
+ else if (OUTCOME_STRENGTH[pinned.outcome] >= OUTCOME_STRENGTH[projection.releaseSettlements[index].outcome]) projection.releaseSettlements[index] = pinned;
11056
+ }
11057
+ break;
11058
+ }
11059
+ }
8447
11060
  }
8448
11061
  if (!enabled) break;
8449
11062
  const data = asRecord(event.data);
8450
11063
  if (asRecord(data?.source)?.kind !== "user") break;
8451
11064
  const content = data?.content ?? [];
8452
11065
  const text = extractTextContent(content);
8453
- if (text.trim() || content.some((part) => part && typeof part === "object" && part.type !== "text")) realRootInputSeen = true;
11066
+ if (text.trim() || content.some((part) => part && typeof part === "object" && part.type !== "text")) {
11067
+ realRootInputSeen = true;
11068
+ if (activeTurn !== void 0) {
11069
+ const seqs = turnRootInputSeqs.get(activeTurn) ?? /* @__PURE__ */ new Set();
11070
+ seqs.add(event.seq);
11071
+ turnRootInputSeqs.set(activeTurn, seqs);
11072
+ }
11073
+ }
11074
+ const unitSemantics = unitSemanticsActive() && v5BoundarySeq !== void 0 && event.seq > v5BoundarySeq;
11075
+ const foldUnitId = () => {
11076
+ if (!unitSemantics) return void 0;
11077
+ foldIntoCurrentUnit(projection, event.seq);
11078
+ if (activeTurn !== void 0) turnUnitIds.set(activeTurn, projection.currentUnitId);
11079
+ return projection.currentUnitId;
11080
+ };
8454
11081
  const legacyMessage = protocolBoundarySeq !== void 0 && event.seq < protocolBoundarySeq;
8455
11082
  const coordinationSplit = !(protocolBoundarySeq !== void 0 && (captureBoundarySeq === void 0 || event.seq < captureBoundarySeq));
8456
- const captureAssets = () => {
8457
- if (v4BoundarySeq !== void 0 && event.seq > v4BoundarySeq) content.forEach((part, index) => {
11083
+ const captureAssets = (unitId) => {
11084
+ if ((v4BoundarySeq ?? v5BoundarySeq) !== void 0 && event.seq > (v4BoundarySeq ?? v5BoundarySeq)) content.forEach((part, index) => {
8458
11085
  if (!part || typeof part !== "object" || part.type === "text") return;
8459
11086
  const identity = sha256(JSON.stringify(part));
8460
11087
  insert(projection, {
@@ -8471,15 +11098,16 @@ function deriveProjection(sourceEvents, config, scope, durableConfirmed, hostLoc
8471
11098
  authorityDisposition: "executable_now",
8472
11099
  fingerprint: `asset:${identity.slice(0, 16)}`
8473
11100
  }
8474
- }, `m${event.seq}:asset:${index}`, scope.cwd || "scope", "scope");
11101
+ }, `m${event.seq}:asset:${index}`, scope.cwd || "scope", "scope", unitId);
8475
11102
  });
8476
11103
  };
8477
11104
  if (!text.trim()) {
8478
- captureAssets();
11105
+ captureAssets(unitSemantics ? foldUnitId() : void 0);
8479
11106
  break;
8480
11107
  }
8481
11108
  if (!scope.sessionHeader?.parentSession && !scope.sessionHeader?.delegationDepth && scope.sessionHeader?.origin !== "subagent") {
8482
- const parsed = v4BoundarySeq !== void 0 && event.seq > v4BoundarySeq ? parseConfirmationMessage(text) : (() => {
11109
+ const confirmGrammarSeq = v4BoundarySeq ?? v5BoundarySeq;
11110
+ const parsed = confirmGrammarSeq !== void 0 && event.seq > confirmGrammarSeq ? parseConfirmationMessage(text) : (() => {
8483
11111
  const match = CONFIRM_LINE_PATTERN.exec(text.trim());
8484
11112
  return match ? {
8485
11113
  kind: "confirm",
@@ -8489,12 +11117,14 @@ function deriveProjection(sourceEvents, config, scope, durableConfirmed, hostLoc
8489
11117
  })();
8490
11118
  if (parsed.kind === "confirm") {
8491
11119
  if (confirmRebind(projection, parsed.proposalId, `m${event.seq}`, durableConfirmed)) {
8492
- captureAssets();
8493
- if (parsed.remainder) captureRootText(projection, parsed.remainder, event.seq, scope, legacyMessage, priorRootMessages, `m${event.seq}:r`, coordinationSplit);
11120
+ const unitId = unitSemantics ? foldUnitId() : void 0;
11121
+ captureAssets(unitId);
11122
+ if (parsed.remainder) captureRootText(projection, parsed.remainder, event.seq, scope, legacyMessage, priorRootMessages, `m${event.seq}:r`, coordinationSplit, unitId, unitSemantics);
8494
11123
  break;
8495
11124
  }
8496
11125
  } else if (parsed.kind !== "none") {
8497
- captureAssets();
11126
+ const unitId = unitSemantics ? foldUnitId() : void 0;
11127
+ captureAssets(unitId);
8498
11128
  projection.lastConfirmationRejection = {
8499
11129
  eventSeq: event.seq,
8500
11130
  kind: parsed.kind,
@@ -8502,14 +11132,23 @@ function deriveProjection(sourceEvents, config, scope, durableConfirmed, hostLoc
8502
11132
  };
8503
11133
  const stripped = text.split(/\r?\n/).filter((line) => !CONFIRM_LINE_PATTERN.test(line.trim())).join("\n");
8504
11134
  if (!stripped.trim()) break;
8505
- captureRootText(projection, stripped, event.seq, scope, legacyMessage, priorRootMessages, `m${event.seq}`, coordinationSplit);
11135
+ captureRootText(projection, stripped, event.seq, scope, legacyMessage, priorRootMessages, `m${event.seq}`, coordinationSplit, unitId, unitSemantics);
8506
11136
  break;
8507
11137
  }
8508
11138
  }
8509
- captureAssets();
11139
+ const directiveBearing = text.trim().length > 0 && !isInformationalMessage(text) && classifyUserInteraction(text) !== "conversational";
11140
+ let captureUnitId;
11141
+ if (unitSemantics && directiveBearing) {
11142
+ if (!explicitlyLinkedToCurrentUnit(projection, text) && opensNewUnit(projection, text, true, currentUnitHasOpenWork(projection))) {
11143
+ const parentUnitId = opensChildUnit(projection, text) ? projection.currentUnitId : void 0;
11144
+ captureUnitId = openUnit(projection, event.seq, text.slice(0, 200), parentUnitId).unitId;
11145
+ } else captureUnitId = foldUnitId();
11146
+ if (activeTurn !== void 0) turnUnitIds.set(activeTurn, projection.currentUnitId);
11147
+ }
11148
+ captureAssets(captureUnitId ?? (unitSemantics ? foldUnitId() : void 0));
8510
11149
  if (isInformationalMessage(text)) break;
8511
11150
  if (classifyUserInteraction(text) === "conversational") break;
8512
- captureRootText(projection, text, event.seq, scope, legacyMessage, priorRootMessages, `m${event.seq}`, coordinationSplit);
11151
+ captureRootText(projection, text, event.seq, scope, legacyMessage, priorRootMessages, `m${event.seq}`, coordinationSplit, captureUnitId, unitSemantics);
8513
11152
  break;
8514
11153
  }
8515
11154
  case "goal/change": {
@@ -8546,10 +11185,12 @@ function deriveProjection(sourceEvents, config, scope, durableConfirmed, hostLoc
8546
11185
  const call = {
8547
11186
  name: String(data?.name ?? ""),
8548
11187
  arguments: String(data?.arguments ?? ""),
8549
- rootCallId: typeof data?.rootCallId === "string" ? data.rootCallId : void 0
11188
+ rootCallId: typeof data?.rootCallId === "string" ? data.rootCallId : void 0,
11189
+ ...projection.currentUnitId !== void 0 ? { unitIdAtCall: projection.currentUnitId } : {}
8550
11190
  };
8551
11191
  if (call.name === "context_guard_checkpoint") {
8552
11192
  const args = parseArguments(call.arguments);
11193
+ if (asRecord(args.proof)) call.proof = args.proof;
8553
11194
  call.bindings = Array.isArray(args.bindings) ? args.bindings.map((binding) => {
8554
11195
  const record = asRecord(binding);
8555
11196
  const transition = asRecord(record?.expected_transition);
@@ -8639,6 +11280,18 @@ function deriveProjection(sourceEvents, config, scope, durableConfirmed, hostLoc
8639
11280
  }
8640
11281
  break;
8641
11282
  }
11283
+ const recordedProof = asRecord(recorded.proof_state);
11284
+ const recomputedProof = replayProofState(projection, call.proof);
11285
+ if ((recordedProof !== void 0 || call.proof !== void 0) && (recordedProof === void 0 || String(recordedProof.status ?? "") !== recomputedProof.status || !sameStringSet(recordedProof.reason_codes, recomputedProof.reason_codes))) {
11286
+ projection.integrity = "corrupt";
11287
+ projection.integrityViolations.push("proof_replay_mismatch");
11288
+ break;
11289
+ }
11290
+ if (recomputedProof.status === "invalid" || recomputedProof.status === "rejected") {
11291
+ projection.integrity = "corrupt";
11292
+ projection.integrityViolations.push("proof_replay_mismatch");
11293
+ break;
11294
+ }
8642
11295
  if (!asRecord(recorded.certificate)) {
8643
11296
  for (const binding of call.bindings ?? []) {
8644
11297
  const item = projection.items.get(binding.itemId);
@@ -8689,7 +11342,8 @@ function deriveProjection(sourceEvents, config, scope, durableConfirmed, hostLoc
8689
11342
  break;
8690
11343
  }
8691
11344
  evidenceCounter += 1;
8692
- const evidence = withDurability(evidenceFromPersistedToolResult({
11345
+ const delegated = DEFAULT_DELEGATION_TOOL_NAMES.includes(call.name);
11346
+ const baseEvidence = withDurability(evidenceFromPersistedToolResult({
8693
11347
  callId,
8694
11348
  name: call.name,
8695
11349
  arguments: call.arguments,
@@ -8703,7 +11357,17 @@ function deriveProjection(sourceEvents, config, scope, durableConfirmed, hostLoc
8703
11357
  meta: data?.meta,
8704
11358
  textContent
8705
11359
  }, epoch, `E${String(evidenceCounter).padStart(4, "0")}`, scope.cwd || void 0, hostLock), durableConfirmed);
11360
+ const evidence = delegated ? {
11361
+ ...baseEvidence,
11362
+ delegatedSubtask: true
11363
+ } : baseEvidence;
8706
11364
  projection.evidence.set(evidence.id, evidence);
11365
+ if (delegated && call.unitIdAtCall !== void 0) recordDelegation(projection, call.unitIdAtCall, {
11366
+ callId,
11367
+ resultSeq: event.seq,
11368
+ toolName: call.name,
11369
+ status: data?.error !== void 0 ? "failed" : "completed"
11370
+ });
8707
11371
  if (evidence.externalOperationRef) projection.externalOperations.set(evidence.externalOperationRef.id, evidence.externalOperationRef);
8708
11372
  break;
8709
11373
  }
@@ -8712,13 +11376,58 @@ function deriveProjection(sourceEvents, config, scope, durableConfirmed, hostLoc
8712
11376
  }
8713
11377
  projection.enabled = enabled;
8714
11378
  projection.epoch = epoch;
11379
+ projection.trustedSelections = deriveTrustedSelections(sourceEvents, { questionToolNames: DEFAULT_QUESTION_TOOL_NAMES });
11380
+ if (projection.trustedSelections.length > 16) projection.trustedSelections = projection.trustedSelections.slice(-16);
11381
+ const approvalAsked = /* @__PURE__ */ new Map();
11382
+ for (const event of sourceEvents) {
11383
+ const data = asRecord(event.data);
11384
+ if (event.type === "approval/asked") {
11385
+ const id = typeof data?.id === "string" ? data.id : "";
11386
+ const toolName = typeof data?.toolName === "string" ? data.toolName : void 0;
11387
+ if (id) approvalAsked.set(id, {
11388
+ id,
11389
+ seq: event.seq,
11390
+ toolName
11391
+ });
11392
+ continue;
11393
+ }
11394
+ if (event.type === "approval/decided") {
11395
+ const id = typeof data?.id === "string" ? data.id : "";
11396
+ const asked = approvalAsked.get(id);
11397
+ if (!asked) continue;
11398
+ const outcome = String(data?.outcome ?? "");
11399
+ if (![
11400
+ "allowed-once",
11401
+ "rejected",
11402
+ "cancelled",
11403
+ "unavailable"
11404
+ ].includes(outcome)) continue;
11405
+ projection.approvals.push({
11406
+ id: asked.id,
11407
+ seq: asked.seq,
11408
+ toolName: asked.toolName,
11409
+ outcome
11410
+ });
11411
+ approvalAsked.delete(id);
11412
+ }
11413
+ }
11414
+ if (projection.approvals.length > 16) projection.approvals = projection.approvals.slice(-16);
11415
+ if (!projection.releaseStateDamaged) projection.releaseStateDamaged = projection.releaseSettlements.some((settlement) => {
11416
+ if (settlement.readback === "unavailable") return false;
11417
+ const contract = projection.releaseContracts.find((entry) => entry.contractId === settlement.contractId);
11418
+ if (!contract || settlement.readback.kind !== "npm_integrity") return false;
11419
+ const reservation = projection.releaseReservations.find((entry) => entry.contractId === settlement.contractId && entry.operation === settlement.operation && entry.callId === settlement.callId);
11420
+ const expected = contract.candidate.artifactSri ?? reservation?.observedArtifactSri;
11421
+ return expected !== void 0 && expected !== settlement.readback.identity;
11422
+ });
8715
11423
  return {
8716
11424
  projection,
8717
11425
  compacted,
8718
11426
  enablementTransitioned,
8719
11427
  lastCompactionSeq,
8720
11428
  realRootInputSeen,
8721
- protocolV4Present: v4BoundarySeq !== void 0
11429
+ protocolV4Present: v4BoundarySeq !== void 0,
11430
+ boundaryV5: v5BoundarySeq !== void 0
8722
11431
  };
8723
11432
  }
8724
11433
 
@@ -8770,12 +11479,17 @@ function claimedBatchHasRealRootInput(messages) {
8770
11479
  * persisted step batch; guidance is compact and never claims a recovery that
8771
11480
  * did not happen. `opt-in` reaches this path only after its explicit `on` command. Delegated sessions receive neither: their
8772
11481
  * scope arrives through the parent's delegation prompt (A04).
11482
+ *
11483
+ * A session without a v5 boundary receives the 0.6 boundary: it cuts the
11484
+ * work-unit/delivery/certificate-v2 semantics at exactly this message. A
11485
+ * session that already has v5 injects nothing.
8773
11486
  */
8774
11487
  function previewFirstStepInjection(input, claimedRealInput) {
8775
- if (!input.enabled || input.boundaryPresent || input.delegated) return void 0;
11488
+ if (!input.enabled || input.delegated) return void 0;
8776
11489
  if (!claimedRealInput) return void 0;
11490
+ if (input.boundaryV5Present) return void 0;
8777
11491
  return {
8778
- boundary: PROTOCOL_V4_NOTICE,
11492
+ boundary: PROTOCOL_V5_NOTICE,
8779
11493
  guidance: FIRST_STEP_GUIDANCE
8780
11494
  };
8781
11495
  }
@@ -9693,214 +12407,4 @@ function verifyComposedHostLockDump(text, expected, roots) {
9693
12407
  }
9694
12408
 
9695
12409
  //#endregion
9696
- //#region src/domain/proof.ts
9697
- const PROOF_PROTOCOL_VERSION = "0.4.0";
9698
- const PROOF_KINDS = [
9699
- "subject_readback",
9700
- "scope_coverage",
9701
- "state_verification"
9702
- ];
9703
- function stable(value) {
9704
- if (Array.isArray(value)) return `[${value.map(stable).join(",")}]`;
9705
- if (value && typeof value === "object") return `{${Object.entries(value).sort(([a], [b]) => a.localeCompare(b)).map(([k, v]) => `${JSON.stringify(k)}:${stable(v)}`).join(",")}}`;
9706
- return JSON.stringify(value);
9707
- }
9708
- function digest(value) {
9709
- return createHash("sha256").update("ccg.proofManifest.v1\n", "utf8").update(stable(value), "utf8").digest("hex");
9710
- }
9711
- function validDigest(value) {
9712
- return /^[0-9a-f]{64}$/.test(value);
9713
- }
9714
- /**
9715
- * The manifest digest root includes every integrity-bearing field, so a
9716
- * tampered asset-set digest is exactly as detectable as a tampered obligation.
9717
- */
9718
- function proofDigest(obligations, assetSetSha256) {
9719
- return digest({
9720
- proofProtocolVersion: PROOF_PROTOCOL_VERSION,
9721
- obligations: [...obligations],
9722
- ...assetSetSha256 !== void 0 ? { assetSetSha256 } : {}
9723
- });
9724
- }
9725
- function validateProofManifest(manifest) {
9726
- const errors = [];
9727
- if (!manifest || typeof manifest !== "object") return ["proof_manifest_invalid"];
9728
- const value = manifest;
9729
- if (value.proofProtocolVersion !== PROOF_PROTOCOL_VERSION) errors.push("proof_protocol_version_mismatch");
9730
- if (!Array.isArray(value.obligations)) errors.push("proof_obligations_missing");
9731
- if (value.assetSetSha256 !== void 0 && (typeof value.assetSetSha256 !== "string" || !validDigest(value.assetSetSha256))) errors.push("proof_asset_set_digest_invalid");
9732
- if (typeof value.proofSha256 !== "string" || !validDigest(value.proofSha256)) errors.push("proof_digest_invalid");
9733
- const obligations = Array.isArray(value.obligations) ? value.obligations : [];
9734
- const ids = /* @__PURE__ */ new Set();
9735
- for (const raw of obligations) {
9736
- if (!raw || typeof raw !== "object") {
9737
- errors.push("proof_obligation_invalid");
9738
- continue;
9739
- }
9740
- const obligation = raw;
9741
- if (typeof obligation.obligationId !== "string" || ids.has(obligation.obligationId)) errors.push("proof_obligation_id_duplicate_or_invalid");
9742
- if (typeof obligation.obligationId === "string") ids.add(obligation.obligationId);
9743
- if (!PROOF_KINDS.includes(obligation.kind)) errors.push("proof_kind_unsupported");
9744
- if (![
9745
- "artifact",
9746
- "ui",
9747
- "visual",
9748
- "scope"
9749
- ].includes(String(obligation.surface))) errors.push("proof_surface_unsupported");
9750
- if (!Array.isArray(obligation.subjectIds) || obligation.subjectIds.length === 0 || obligation.subjectIds.some((id) => typeof id !== "string" || id.startsWith("codex:unsupported/"))) errors.push("proof_subject_invalid");
9751
- if (!Array.isArray(obligation.evidenceIds) || obligation.evidenceIds.length === 0 || new Set(obligation.evidenceIds).size !== obligation.evidenceIds.length) errors.push("proof_evidence_invalid");
9752
- const expected = obligation.expectedScopeDigest;
9753
- const observed = obligation.observedScopeDigest;
9754
- for (const digestValue of [expected, observed]) if (digestValue !== void 0 && (typeof digestValue !== "string" || !validDigest(digestValue))) errors.push("proof_scope_digest_invalid");
9755
- if (expected !== void 0 && observed !== expected) errors.push("proof_scope_digest_mismatch");
9756
- if (expected === void 0 && observed !== void 0) errors.push("proof_scope_digest_mismatch");
9757
- }
9758
- if (errors.length === 0) {
9759
- const assetSet = typeof value.assetSetSha256 === "string" ? value.assetSetSha256 : void 0;
9760
- if (value.proofSha256 !== proofDigest(obligations, assetSet)) errors.push("proof_digest_mismatch");
9761
- }
9762
- return [...new Set(errors)];
9763
- }
9764
- function createProofManifest(obligations, assetSetSha256) {
9765
- const normalized = obligations.map((obligation) => ({
9766
- obligationId: obligation.obligationId,
9767
- kind: obligation.kind,
9768
- surface: obligation.surface,
9769
- subjectIds: [...obligation.subjectIds].sort(),
9770
- evidenceIds: [...obligation.evidenceIds].sort(),
9771
- ...obligation.expectedScopeDigest ? { expectedScopeDigest: obligation.expectedScopeDigest } : {},
9772
- ...obligation.observedScopeDigest ? { observedScopeDigest: obligation.observedScopeDigest } : {}
9773
- })).sort((a, b) => a.obligationId.localeCompare(b.obligationId));
9774
- const manifest = {
9775
- proofProtocolVersion: PROOF_PROTOCOL_VERSION,
9776
- obligations: normalized,
9777
- ...assetSetSha256 !== void 0 ? { assetSetSha256 } : {},
9778
- proofSha256: proofDigest(normalized, assetSetSha256)
9779
- };
9780
- const errors = validateProofManifest(manifest);
9781
- if (errors.length) throw new Error(`proof manifest rejected: ${errors.join(",")}`);
9782
- return manifest;
9783
- }
9784
- /**
9785
- * Bind a structurally valid proof to the actual replayed projection: every
9786
- * obligation must name a pending item, every evidence id must exist in the
9787
- * projection, and every bound evidence must satisfy the obligation's kind,
9788
- * surface, subject, and outcome constraints. An empty projection therefore
9789
- * rejects any proof, and cross-item or foreign evidence can never bind.
9790
- */
9791
- function bindProofToProjection(projection, proof) {
9792
- const errors = [];
9793
- const items = projection.items;
9794
- const evidence = projection.evidence;
9795
- for (const obligation of proof.obligations) {
9796
- const item = items.get(obligation.obligationId);
9797
- if (!item) {
9798
- errors.push("proof_obligation_unbound");
9799
- continue;
9800
- }
9801
- if (item.status !== "pending") {
9802
- errors.push("proof_obligation_not_pending");
9803
- continue;
9804
- }
9805
- if (item.verification.surface !== void 0 && item.verification.surface !== obligation.surface) errors.push("proof_surface_unbound");
9806
- const seen = /* @__PURE__ */ new Set();
9807
- for (const evidenceId of obligation.evidenceIds) {
9808
- const record = evidence.get(evidenceId);
9809
- if (!record) {
9810
- errors.push("proof_evidence_unknown");
9811
- continue;
9812
- }
9813
- if (!seen.has(evidenceId)) seen.add(evidenceId);
9814
- if (record.outcome !== "success") {
9815
- errors.push("proof_evidence_outcome_invalid");
9816
- continue;
9817
- }
9818
- if (!proofEvidenceConstraints(record, obligation)) errors.push("proof_evidence_constraint_failed");
9819
- }
9820
- if (obligation.kind === "scope_coverage") {
9821
- const itemScope = item.requestedTarget?.scope;
9822
- const itemSubject = item.verification.subject;
9823
- if (!obligation.subjectIds.every((subject) => subject === itemScope || subject === itemSubject)) errors.push("proof_scope_subject_unbound");
9824
- }
9825
- }
9826
- return [...new Set(errors)];
9827
- }
9828
- function canonicalProjection(projection) {
9829
- return {
9830
- epoch: projection.epoch,
9831
- contractRevision: projection.contractRevision,
9832
- sessionRefDigest: projection.sessionRefDigest,
9833
- hostLockDigest: projection.hostLockDigest,
9834
- hostStatus: projection.hostStatus,
9835
- hostCohortId: projection.hostCohortId,
9836
- integrity: projection.integrity,
9837
- items: [...projection.items.values()].map(({ id, revision, kind, status, semanticAction, requestedTarget, verification }) => ({
9838
- id,
9839
- revision,
9840
- kind,
9841
- status,
9842
- semanticAction,
9843
- requestedTarget,
9844
- verification
9845
- })).sort((a, b) => a.id.localeCompare(b.id)),
9846
- evidence: [...projection.evidence.values()].map(({ id, epoch, toolName, outcome, capabilities, subjects, surfaces, operations, semanticAction, evidenceRole, resolvedTarget, observedState }) => ({
9847
- id,
9848
- epoch,
9849
- toolName,
9850
- outcome,
9851
- capabilities,
9852
- subjects,
9853
- surfaces,
9854
- operations,
9855
- semanticAction,
9856
- evidenceRole,
9857
- resolvedTarget,
9858
- observedState
9859
- })).sort((a, b) => a.id.localeCompare(b.id)),
9860
- checkpoints: projection.checkpoints.map(({ id, certificationDigest: certificationDigest$1, result }) => ({
9861
- id,
9862
- certificationDigest: certificationDigest$1,
9863
- result
9864
- }))
9865
- };
9866
- }
9867
- function sessionQuery(projection, proof) {
9868
- if (proof) {
9869
- if (validateProofManifest(proof).length) return {
9870
- sessionRefDigest: projection.sessionRefDigest,
9871
- epoch: projection.epoch,
9872
- contractRevision: projection.contractRevision,
9873
- state: "corrupt",
9874
- reasonCode: "proof_invalid",
9875
- cohortId: projection.hostCohortId
9876
- };
9877
- if (bindProofToProjection(projection, proof).length) return {
9878
- sessionRefDigest: projection.sessionRefDigest,
9879
- epoch: projection.epoch,
9880
- contractRevision: projection.contractRevision,
9881
- state: "corrupt",
9882
- reasonCode: "proof_unbound",
9883
- cohortId: projection.hostCohortId
9884
- };
9885
- }
9886
- const state = projection.integrity === "valid" ? projection.hostStatus === "supported" ? "valid" : "unknown" : projection.integrity;
9887
- return {
9888
- sessionRefDigest: projection.sessionRefDigest,
9889
- epoch: projection.epoch,
9890
- contractRevision: projection.contractRevision,
9891
- state,
9892
- ...proof ? { proof } : {},
9893
- cohortId: projection.hostCohortId
9894
- };
9895
- }
9896
- function proofEvidenceConstraints(evidence, obligation) {
9897
- if (evidence.outcome !== "success" || evidence.surfaces.length !== 1 || evidence.surfaces[0] !== obligation.surface) return false;
9898
- if (!obligation.subjectIds.every((subject) => evidence.subjects.includes(subject))) return false;
9899
- if (obligation.kind === "subject_readback" && !(evidence.operations ?? []).some(({ op }) => op === "read" || op === "verify")) return false;
9900
- if (obligation.kind === "scope_coverage" && !(evidence.operations ?? []).some(({ op }) => op === "run" || op === "verify")) return false;
9901
- if (obligation.kind === "state_verification" && evidence.evidenceRole !== "state") return false;
9902
- return true;
9903
- }
9904
-
9905
- //#endregion
9906
- export { parseShellCommand as $, isStatefulAction as $n, isWholeTaskCompletionClaim as $t, createGitPrestateEnvelope as A, extractMethod as An, RC1_HOST_PACKAGES as At, CAPTURE_V042_NOTICE as B, namedActions as Bn, renderRecoveryPacket as Bt, SESSION_EVENT_ENVELOPE_INVALID as C, evidenceAvailabilityReason as Cn, SUPPORTED_HOST_VERSIONS as Ct, GIT_COMMAND_TEMPLATES as D, captureItem as Dn, satisfiesSupportedHostRange as Dt, GIT_COMMAND_MANIFEST_IDS as E, captureClause as En, parseHostVersion as Et, verifiedLinearCommitReadback as F, interpretMessage as Fn, DEFAULT_RECOVERY_CHAR_BUDGET as Ft, evidenceFromPersistedToolResult as G, ACTION_MANIFEST as Gn, CONTROL_RECORD_PREFIX as Gt, PROTOCOL_V4_NOTICE as H, statefulActionsOfScope as Hn, evidenceCoverage as Ht, FIRST_STEP_GUIDANCE as I, isExecutableItem as In, MIN_RECOVERY_CHAR_BUDGET as It, isDeterministicCheck as J, SEMANTIC_ACTIONS as Jn, classifyCompletionClaim as Jt, extractTextContent as K, ACTION_MANIFEST_VERSION as Kn, NO_PROGRESS_RECORD_PREFIX as Kt, claimedBatchHasRealRootInput as L, isOpenObligation as Ln, closingHint as Lt, gitCommandMatchesTarget as M, isInformationalMessage as Mn, authorityCaptureCounts as Mt, parseGitCommandManifest as N, segmentClauses as Nn, segmentAuthorityBlocks as Nt, commitIndexSnapshotDigest as O, classifyClause as On, RC015_RC2_HOST_PACKAGES as Ot, revalidateGitPrestate as P, interpretClause as Pn, certifyCheckpoint as Pt, parsePwshCommand as Q, actionCompatible as Qn, isRootPauseRequest as Qt, lifecyclePhase as R, kindOfScope as Rn, openItems$1 as Rt, SESSION_API_UNSUPPORTED as S, deriveItemDiagnosis as Sn, SUPPORTED_HOST_RANGE as St, snapshotSessionEvents as T, relevantEvidence as Tn, evaluateMinimumHostVersion as Tt, deriveProjection as U, canonicalRegistryBase as Un, evidenceMatchesItem as Ut, PROTOCOL_V3_NOTICE as V, semanticActionOfScope as Vn, bindingSatisfies as Vt, supersedeItem as W, npmEscapedPackageName as Wn, isVerifyingCapability as Wt, canonicalArgvFromCommand as X, STOP_PROTOCOL_VERSION as Xn, decideTurnStopping as Xt, withDurability as Y, STATEFUL_ACTIONS as Yn, decideTurnBoundary as Yt, isRunExecutable as Z, SUPPORTED_EVIDENCE_ADAPTERS as Zn, decisionBoundaryKey as Zt, packageRowsFromPnpmLock as _, rebindResponse as _n, evaluateToolSurfaceCapability as _t, createProofManifest as a, hasCurrentCertificate as an, validateActionTarget as ar, BASE_HOST_PACKAGES as at, resolveInstalledHostLock as b, isFrozenV042RebindResponse as bn, LATEST_SUPPORTED_HOST_VERSION as bt, sessionQuery as c, isCurrentAcceptedBoundary as cn, classifyTaskIntent as cr, GOAL_HOST_PACKAGES as ct, combineHostPolicy as d, createProjection as dn, digestStrings as dr, LEGACY_HOST_COHORTS as dt, latestAssistantText as en, requestedTargetAuthorizesMutation as er, ACTIVE_HOST_COHORT_ID as et, hostLockContextFromComposedDump as f, confirmRebind as fn, normalizeClause as fr, bindExecutableIdentity as ft, packageRowsFromActiveGraph as g, rebindAttemptKey as gn, evaluateHostLock as gt, inspectTargetHostGraph as h, proposeRebindV042 as hn, sha256 as hr, evaluateHostCapability as ht, canonicalProjection as i, goalCompletionDenial as in, validateActionManifest as ir, ALPHA2_HOST_PACKAGES as it, executeRevalidatedGitEffect as j, extractOperation as jn, ALPHA3_HOST_PACKAGES as jt, commitTreeSnapshotDigest as k, extractArtifactPaths as kn, RC015_HOST_PACKAGES as kt, validateProofManifest as l, qualifyBoundary as ln, classifyUserInteraction as lr, HOST_CAPABILITY_PACKAGE_GROUPS as lt, injectActiveProfileHostLock as m, proposeRebindOutcome as mn, sanitizeUrl as mr, evaluateExternalWaitCapability as mt, PROOF_PROTOCOL_VERSION as n, observeAssistantOutcome as nn, semanticActionFromCommand as nr, ACTIVE_HOST_LAUNCHER_VERSION as nt, proofDigest as o, availableBoundaryQualifications as on, COMMAND_SURFACE_MANIFEST as or, DEFAULT_HOST_LOCK as ot, hostLockRowsFromComposedDump as p, proposeRebind as pn, sanitizeClauseText as pr, bindLiveGoalCapability as pt, extractToolSubject as q, CERTIFICATE_VERSION as qn, NO_PROGRESS_TURNS_BEFORE_STOP as qt, bindProofToProjection as r, progressFingerprint as rn, semanticActionFromText as rr, ALPHA2_DSHMARKET_139_HOST_PACKAGES as rt, proofEvidenceConstraints as s, effectuateBoundary as sn, validateManifest as sr, EXPECTED_HOST_PACKAGES as st, PROOF_KINDS as t, latestRootInstruction as tn, requestedTargetMatchesResolved as tr, ACTIVE_HOST_COHORT_IDS as tt, HostProfileError as u, currentContractDigest as un, canonicalizePath as ur, HOST_COHORTS as ut, readActiveHostGraph as v, replayRebindResult as vn, hostVersionFromPackages as vt, SessionApiError as w, itemDiagnosis as wn, compareHostVersions as wt, verifyComposedHostLockDump as x, parseConfirmationMessage as xn, MIN_SUPPORTED_HOST_VERSION as xt, resolveActiveProfileHostLock as y, CONFIRM_LINE_PATTERN as yn, selectHostCohort as yt, previewFirstStepInjection as z, maskCodeSpans as zn, recoveryDigest as zt };
12410
+ export { isDeterministicCheck as $, deriveItemDiagnosis as $n, proofDigestV2 as $t, previewFirstStepInjection as A, isWholeTaskCompletionClaim as An, STOP_PROTOCOL_VERSION_V2 as Ar, evaluateMinimumHostVersion as At, RELEASE_SETTLEMENT_PREFIX as B, qualifyBoundary as Bn, validateActionManifest as Br, PROOF_CAPABILITY_MATRIX as Bt, gitCommandMatchesTarget as C, NO_PROGRESS_RECORD_PREFIX as Cn, ACTION_MANIFEST_VERSION as Cr, hostVersionFromPackages as Ct, FIRST_STEP_GUIDANCE as D, decideTurnStopping as Dn, SEMANTIC_ACTIONS as Dr, SUPPORTED_HOST_RANGE as Dt, verifiedLinearCommitReadback as E, decideTurnBoundary as En, CERTIFICATE_VERSION_V2 as Er, MIN_SUPPORTED_HOST_VERSION as Et, PROTOCOL_V5_NOTICE as F, goalCompletionDenial as Fn, requestedIdentityKey as Fr, RC1_HOST_PACKAGES as Ft, releaseContractFor as G, proposeRebindOutcome as Gn, classifyUserInteraction as Gr, PROOF_PROTOCOL_VERSION_V2 as Gt, inFlightReservation as H, createProjection as Hn, COMMAND_SURFACE_MANIFEST as Hr, PROOF_KINDS_V2 as Ht, deriveProjection as I, hasCurrentCertificate as In, requestedTargetAuthorizesMutation as Ir, ALPHA3_HOST_PACKAGES as It, reservationFor as J, rebindResponse as Jn, normalizeClause as Jr, canonicalProjection as Jt, releaseCoverage as K, proposeRebindV042 as Kn, canonicalizePath as Kr, bindProofToProjection as Kt, RELEASE_OPERATIONS as L, availableBoundaryQualifications as Ln, requestedTargetMatchesResolved as Lr, authorityCaptureCounts as Lt, DEFAULT_DELEGATION_TOOL_NAMES as M, latestRootInstruction as Mn, actionCompatible as Mr, satisfiesSupportedHostRange as Mt, PROTOCOL_V3_NOTICE as N, observeAssistantOutcome as Nn, boundedArtifactChoiceMatches as Nr, RC015_RC2_HOST_PACKAGES as Nt, claimedBatchHasRealRootInput as O, decisionBoundaryKey as On, STATEFUL_ACTIONS as Or, SUPPORTED_HOST_VERSIONS as Ot, PROTOCOL_V4_NOTICE as P, progressFingerprint as Pn, isStatefulAction as Pr, RC015_HOST_PACKAGES as Pt, extractToolSubject as Q, parseConfirmationMessage as Qn, proofDigest as Qt, RELEASE_OPERATION_SURFACES as R, effectuateBoundary as Rn, semanticActionFromCommand as Rr, segmentAuthorityBlocks as Rt, executeRevalidatedGitEffect as S, CONTROL_RECORD_PREFIX as Sn, ACTION_MANIFEST as Sr, evaluateToolSurfaceCapability as St, revalidateGitPrestate as T, classifyCompletionClaim as Tn, CERTIFICATE_VERSION as Tr, LATEST_SUPPORTED_HOST_VERSION as Tt, normalizeReleaseContract as U, confirmRebind as Un, validateManifest as Ur, PROOF_MANIFEST_DOMAIN_V2 as Ut, contractById as V, currentContractDigest as Vn, validateActionTarget as Vr, PROOF_KINDS as Vt, readbackSettlesContract as W, proposeRebind as Wn, classifyTaskIntent as Wr, PROOF_PROTOCOL_VERSION as Wt, evidenceFromPersistedToolResult as X, CONFIRM_LINE_PATTERN as Xn, sanitizeUrl as Xr, createProofManifestV2 as Xt, supersedeItem as Y, replayRebindResult as Yn, sanitizeClauseText as Yr, createProofManifest as Yt, extractTextContent as Z, isFrozenV042RebindResponse as Zn, sha256 as Zr, proofCapabilityReport as Zt, GIT_COMMAND_MANIFEST_IDS as _, renderRecoveryPacket as _n, namedActions as _r, bindExecutableIdentity as _t, injectActiveProfileHostLock as a, scopeCoverageDigest as an, classifyClause as ar, ACTIVE_HOST_COHORT_ID as at, commitTreeSnapshotDigest as b, evidenceMatchesItem as bn, canonicalRegistryBase as br, evaluateHostCapability as bt, packageRowsFromPnpmLock as c, validateProofManifest as cn, extractOperation as cr, ALPHA2_DSHMARKET_139_HOST_PACKAGES as ct, resolveInstalledHostLock as d, certificateClosure as dn, interpretClause as dr, DEFAULT_HOST_LOCK as dt, proofEvidenceConstraints as en, evidenceAvailabilityReason as er, withDurability as et, verifyComposedHostLockDump as f, DEFAULT_RECOVERY_CHAR_BUDGET as fn, interpretMessage as fr, EXPECTED_HOST_PACKAGES as ft, snapshotSessionEvents as g, recoveryDigest as gn, maskCodeSpans as gr, LEGACY_HOST_COHORTS as gt, SessionApiError as h, openItems as hn, kindOfScope as hr, HOST_COHORTS as ht, hostLockRowsFromComposedDump as i, requiredSubjectsOf as in, captureItem as ir, parseShellCommand as it, CAPTURE_V042_NOTICE as j, latestAssistantText as jn, SUPPORTED_EVIDENCE_ADAPTERS as jr, parseHostVersion as jt, lifecyclePhase as k, isRootPauseRequest as kn, STOP_PROTOCOL_VERSION as kr, compareHostVersions as kt, readActiveHostGraph as l, validateProofManifestV2 as ln, isInformationalMessage as lr, ALPHA2_HOST_PACKAGES as lt, SESSION_EVENT_ENVELOPE_INVALID as m, closingHint as mn, isOpenObligation as mr, HOST_CAPABILITY_PACKAGE_GROUPS as mt, combineHostPolicy as n, proofOperationMatches as nn, relevantEvidence as nr, isRunExecutable as nt, inspectTargetHostGraph as o, sessionQuery as on, extractArtifactPaths as or, ACTIVE_HOST_COHORT_IDS as ot, SESSION_API_UNSUPPORTED as p, MIN_RECOVERY_CHAR_BUDGET as pn, isExecutableItem as pr, GOAL_HOST_PACKAGES as pt, releasePreEffectDecision as q, rebindAttemptKey as qn, digestStrings as qr, bindProofV2ToProjection as qt, hostLockContextFromComposedDump as r, proofV2Rejection as rn, captureClause as rr, parsePwshCommand as rt, packageRowsFromActiveGraph as s, sessionQueryV2 as sn, extractMethod as sr, ACTIVE_HOST_LAUNCHER_VERSION as st, HostProfileError as t, proofHostSurfacesOf as tn, itemDiagnosis as tr, canonicalArgvFromCommand as tt, resolveActiveProfileHostLock as u, certifiableOpenItems as un, segmentClauses as ur, BASE_HOST_PACKAGES as ut, GIT_COMMAND_TEMPLATES as v, bindingSatisfies as vn, semanticActionOfScope as vr, bindLiveGoalCapability as vt, parseGitCommandManifest as w, NO_PROGRESS_TURNS_BEFORE_STOP as wn, BOUNDED_ARTIFACT_TYPES as wr, selectHostCohort as wt, createGitPrestateEnvelope as x, isVerifyingCapability as xn, npmEscapedPackageName as xr, evaluateHostLock as xt, commitIndexSnapshotDigest as y, evidenceCoverage as yn, statefulActionsOfScope as yr, evaluateExternalWaitCapability as yt, RELEASE_RESERVATION_PREFIX as z, isCurrentAcceptedBoundary as zn, semanticActionFromText as zr, certifyCheckpoint as zt };