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.
- package/CHANGELOG.md +329 -0
- package/CHANGELOG.zh-CN.md +113 -0
- package/README.md +97 -6
- package/README.zh-CN.md +56 -6
- package/dist/domain/index.d.ts +2 -2
- package/dist/domain/index.js +2 -2
- package/dist/{domain-RuX9l07o.js → domain-COehX7MB.js} +2914 -410
- package/dist/{index-BTaiOcE-.d.ts → index-BJPVGdg9.d.ts} +503 -2
- package/dist/index.d.ts +127 -3
- package/dist/index.js +925 -30
- package/docs/ARCHITECTURE.md +88 -0
- package/docs/LOCAL_ACCEPTANCE.md +112 -0
- package/docs/NEXT_VERSION_REPAIR_NOTES.md +81 -0
- package/docs/PORTING_NOTES.md +36 -2
- package/docs/SEMANTIC_COMPATIBILITY.md +123 -0
- package/docs/distribution.md +5 -3
- package/docs/upstream-deltas.json +90 -9
- package/package.json +2 -2
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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/
|
|
4461
|
-
|
|
4462
|
-
|
|
4463
|
-
|
|
4464
|
-
|
|
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
|
-
|
|
4467
|
-
|
|
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
|
-
|
|
4470
|
-
|
|
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
|
-
|
|
4473
|
-
|
|
4474
|
-
|
|
4475
|
-
|
|
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
|
-
|
|
4478
|
-
|
|
4479
|
-
|
|
4480
|
-
|
|
4481
|
-
|
|
4482
|
-
|
|
4483
|
-
|
|
4484
|
-
|
|
4485
|
-
|
|
4486
|
-
|
|
4487
|
-
|
|
4488
|
-
|
|
4489
|
-
|
|
4490
|
-
|
|
4491
|
-
|
|
4492
|
-
|
|
4493
|
-
|
|
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
|
-
|
|
4497
|
-
|
|
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
|
-
|
|
4500
|
-
|
|
4501
|
-
|
|
4502
|
-
|
|
4503
|
-
|
|
4504
|
-
|
|
4505
|
-
|
|
4506
|
-
|
|
4507
|
-
|
|
4508
|
-
|
|
4509
|
-
const
|
|
4510
|
-
|
|
4511
|
-
|
|
4512
|
-
|
|
4513
|
-
|
|
4514
|
-
|
|
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
|
-
|
|
4518
|
-
|
|
4519
|
-
|
|
4520
|
-
|
|
4521
|
-
|
|
4522
|
-
|
|
4523
|
-
|
|
4524
|
-
|
|
4525
|
-
|
|
4526
|
-
|
|
4527
|
-
|
|
4528
|
-
|
|
4529
|
-
|
|
4530
|
-
|
|
4531
|
-
|
|
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$
|
|
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$
|
|
4576
|
-
case "restart": return stable$
|
|
4577
|
-
case "commit": return stable$
|
|
4578
|
-
case "push": return stable$
|
|
4579
|
-
case "pull": return stable$
|
|
4580
|
-
case "fetch": return stable$
|
|
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$
|
|
4590
|
-
return stable$
|
|
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$
|
|
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$
|
|
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:
|
|
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
|
|
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:
|
|
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(
|
|
6457
|
+
const openDigest = digestStrings(closure.itemIds);
|
|
4919
6458
|
const evidenceSha256 = evidenceSha256Digest(referencedFacts);
|
|
4920
6459
|
const bindingDigest$1 = bindingDigest(records, resolveAllowlist("product"));
|
|
4921
|
-
const
|
|
4922
|
-
|
|
4923
|
-
|
|
4924
|
-
|
|
4925
|
-
|
|
4926
|
-
|
|
4927
|
-
|
|
4928
|
-
|
|
4929
|
-
|
|
4930
|
-
|
|
4931
|
-
|
|
4932
|
-
|
|
4933
|
-
|
|
4934
|
-
|
|
4935
|
-
|
|
4936
|
-
|
|
4937
|
-
|
|
4938
|
-
|
|
4939
|
-
|
|
4940
|
-
|
|
4941
|
-
|
|
4942
|
-
|
|
4943
|
-
|
|
4944
|
-
|
|
4945
|
-
|
|
4946
|
-
|
|
4947
|
-
|
|
4948
|
-
|
|
4949
|
-
|
|
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:
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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
|
|
7697
|
-
if (Array.isArray(value)) return `[${value.map(stable
|
|
7698
|
-
if (value && typeof value === "object") return `{${Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, entry]) => `${JSON.stringify(key)}:${stable
|
|
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$
|
|
7704
|
-
const value = asRecord$
|
|
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$
|
|
7709
|
-
const observed = asRecord$
|
|
7710
|
-
const rawExpected = asRecord$
|
|
7711
|
-
const expectedParameters = asRecord$
|
|
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
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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
|
-
|
|
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
|
-
|
|
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))
|
|
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"))
|
|
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
|
|
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
|
-
|
|
8493
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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.
|
|
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:
|
|
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
|
-
|
|
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 };
|