dsh-completion-guard 0.5.3 → 0.6.1
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 +332 -0
- package/CHANGELOG.zh-CN.md +116 -0
- package/README.md +107 -9
- package/README.zh-CN.md +60 -9
- package/dist/domain/index.d.ts +2 -2
- package/dist/domain/index.js +2 -2
- package/dist/{domain-RuX9l07o.js → domain-DKr8sLZZ.js} +3306 -393
- package/dist/{index-BTaiOcE-.d.ts → index-C_N6DaSF.d.ts} +570 -7
- package/dist/index.d.ts +127 -3
- package/dist/index.js +1139 -87
- package/docs/ARCHITECTURE.md +88 -0
- package/docs/LOCAL_ACCEPTANCE.md +199 -0
- package/docs/NEXT_VERSION_REPAIR_NOTES.md +45 -2
- package/docs/PORTING_NOTES.md +36 -2
- package/docs/SEMANTIC_COMPATIBILITY.md +123 -0
- package/docs/WINDOWS_0_6_0_REPAIR_PLAN.md +92 -0
- package/docs/upstream-deltas.json +90 -9
- package/package.json +2 -2
|
@@ -79,9 +79,11 @@ const PUNCT = String.raw`[\s。,、;:!?.,;:!?\-*"'“”‘’()(
|
|
|
79
79
|
/**
|
|
80
80
|
* Session-layer phrases that acknowledge or advance the conversation without
|
|
81
81
|
* stating a task. Longer forms come first so the alternation consumes them
|
|
82
|
-
* before their prefixes.
|
|
82
|
+
* before their prefixes. A bare whole-message acknowledgment ("当然。",
|
|
83
|
+
* "Of course.") is session talk: it is never captured as an obligation, so it
|
|
84
|
+
* can never block certification either.
|
|
83
85
|
*/
|
|
84
|
-
const PROGRESSION_SOURCE = String.raw`(
|
|
86
|
+
const PROGRESSION_SOURCE = String.raw`(?:继续执行|继续吧|请继续|继续|接着做|接着|下一步|没问题|知道了|明白了|了解|好的?|是的?|对的?|收到|可以|行|嗯+|当然|那当然|continue|go on|go ahead|keep going|proceed|okay|ok|yes|sure|right|next|of course)`;
|
|
85
87
|
const PROGRESSION_WHOLE = new RegExp(`^${PUNCT}*${PROGRESSION_SOURCE}${PUNCT}*$`, "i");
|
|
86
88
|
const PROGRESSION_LEAD = new RegExp(`^${PROGRESSION_SOURCE}${PUNCT}+`, "i");
|
|
87
89
|
const PROGRESSION_ANYWHERE = new RegExp(PROGRESSION_SOURCE, "gi");
|
|
@@ -358,6 +360,13 @@ function validateManifest(manifest = COMMAND_SURFACE_MANIFEST) {
|
|
|
358
360
|
//#region src/domain/protocol-manifest.ts
|
|
359
361
|
const STOP_PROTOCOL_VERSION = "2.0.0";
|
|
360
362
|
const CERTIFICATE_VERSION = "1";
|
|
363
|
+
/**
|
|
364
|
+
* 0.6.0 v5-session identity (P0 §1): v2 certificates bind a work unit's
|
|
365
|
+
* closure instead of the whole session. Version-1 identity keeps its
|
|
366
|
+
* historical meaning for legacy sessions and is never silently re-read.
|
|
367
|
+
*/
|
|
368
|
+
const STOP_PROTOCOL_VERSION_V2 = "3.0.0";
|
|
369
|
+
const CERTIFICATE_VERSION_V2 = "2";
|
|
361
370
|
const ACTION_MANIFEST_VERSION = 1;
|
|
362
371
|
const SUPPORTED_EVIDENCE_ADAPTERS = {
|
|
363
372
|
"context-guard.git.v1": "1.0.0",
|
|
@@ -656,17 +665,78 @@ const REQUESTED_IDENTITY_KEY = {
|
|
|
656
665
|
restart: "service_id",
|
|
657
666
|
publish: "artifact_id"
|
|
658
667
|
};
|
|
668
|
+
/** The single identity field a root instruction must name for this action. */
|
|
669
|
+
function requestedIdentityKey(action) {
|
|
670
|
+
return REQUESTED_IDENTITY_KEY[action];
|
|
671
|
+
}
|
|
659
672
|
function stableTargetValue(value) {
|
|
660
673
|
if (Array.isArray(value)) return `[${value.map(stableTargetValue).join(",")}]`;
|
|
661
674
|
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
675
|
return JSON.stringify(value);
|
|
663
676
|
}
|
|
664
677
|
/**
|
|
678
|
+
* The 0.6.0 bounded file-choice vocabulary (C07/S03): artifact-type nouns a
|
|
679
|
+
* root instruction may use instead of an exact path. The assistant may pick
|
|
680
|
+
* the exact file INSIDE the captured scope and inside the type, and the
|
|
681
|
+
* choice is frozen by the resolution producer before any effect. An absent
|
|
682
|
+
* extension set (`file`) admits any file the producer accepts.
|
|
683
|
+
*/
|
|
684
|
+
const BOUNDED_ARTIFACT_TYPES = {
|
|
685
|
+
document: new Set([
|
|
686
|
+
"md",
|
|
687
|
+
"markdown",
|
|
688
|
+
"txt"
|
|
689
|
+
]),
|
|
690
|
+
readme: new Set([
|
|
691
|
+
"md",
|
|
692
|
+
"rst",
|
|
693
|
+
"txt"
|
|
694
|
+
]),
|
|
695
|
+
report: new Set(["md", "txt"]),
|
|
696
|
+
file: null
|
|
697
|
+
};
|
|
698
|
+
function extensionOf(path$1) {
|
|
699
|
+
const base = path$1.split(/[\\/]/).pop() ?? "";
|
|
700
|
+
const dot = base.lastIndexOf(".");
|
|
701
|
+
return dot > 0 ? base.slice(dot + 1).toLowerCase() : "";
|
|
702
|
+
}
|
|
703
|
+
function normalizeSlashes(value) {
|
|
704
|
+
return value.replace(/\\/g, "/");
|
|
705
|
+
}
|
|
706
|
+
/** Whether `artifact` lives inside `scope` (or exactly at it), slash-normalized. */
|
|
707
|
+
function insideScope(artifact, scope) {
|
|
708
|
+
if (scope === "scope" || scope === "") return false;
|
|
709
|
+
const normalizedArtifact = normalizeSlashes(artifact);
|
|
710
|
+
const normalizedScope = normalizeSlashes(scope).replace(/\/+$/, "");
|
|
711
|
+
if (normalizedArtifact === normalizedScope) return false;
|
|
712
|
+
return normalizedArtifact.startsWith(`${normalizedScope}/`);
|
|
713
|
+
}
|
|
714
|
+
/**
|
|
715
|
+
* Whether a bounded-choice requested target authorizes this resolved target:
|
|
716
|
+
* the resolved artifact must live inside the captured scope and match the
|
|
717
|
+
* captured type. The exact file name is the assistant's bounded decision,
|
|
718
|
+
* frozen by resolution — never a root-named identity substitution.
|
|
719
|
+
*/
|
|
720
|
+
function boundedArtifactChoiceMatches(action, requested, resolved) {
|
|
721
|
+
if (action !== "create" && action !== "modify") return false;
|
|
722
|
+
const type = requested?.artifact_type;
|
|
723
|
+
const scope = requested?.scope;
|
|
724
|
+
if (typeof type !== "string" || typeof scope !== "string" || !(type in BOUNDED_ARTIFACT_TYPES)) return false;
|
|
725
|
+
const artifact = resolved?.artifact_id;
|
|
726
|
+
if (typeof artifact !== "string" || !artifact) return false;
|
|
727
|
+
if (!insideScope(artifact, scope)) return false;
|
|
728
|
+
const allowed = BOUNDED_ARTIFACT_TYPES[type];
|
|
729
|
+
return allowed === null || allowed.has(extensionOf(artifact));
|
|
730
|
+
}
|
|
731
|
+
/**
|
|
665
732
|
* Compare identities captured from the root instruction with a complete
|
|
666
733
|
* adapter-resolved target. Requested targets are partial by design: only
|
|
667
734
|
* explicitly named identities (plus the active repository scope) are frozen.
|
|
735
|
+
* A bounded artifact choice (scope + type, C07) matches when the resolved
|
|
736
|
+
* exact file is inside the scope and of the captured type.
|
|
668
737
|
*/
|
|
669
738
|
function requestedTargetMatchesResolved(action, requested, resolved) {
|
|
739
|
+
if (boundedArtifactChoiceMatches(action, requested, resolved)) return true;
|
|
670
740
|
const identityKey = REQUESTED_IDENTITY_KEY[action];
|
|
671
741
|
if (!identityKey || !requested || !resolved || !Object.hasOwn(requested, identityKey)) return false;
|
|
672
742
|
const allowed = new Set(ACTION_MANIFEST.actions[action].resolvedTargetKeys);
|
|
@@ -710,6 +780,7 @@ const MUTATION_AUTHORITY_KEYS = {
|
|
|
710
780
|
};
|
|
711
781
|
/** A mutation requires every user-selectable identity field, not a partial match. */
|
|
712
782
|
function requestedTargetAuthorizesMutation(action, requested, resolved) {
|
|
783
|
+
if (boundedArtifactChoiceMatches(action, requested, resolved)) return true;
|
|
713
784
|
const required = MUTATION_AUTHORITY_KEYS[action];
|
|
714
785
|
return !!requested && required.every((key) => Object.hasOwn(requested, key)) && requestedTargetMatchesResolved(action, requested, resolved);
|
|
715
786
|
}
|
|
@@ -875,9 +946,16 @@ const ACTION_VERB = new RegExp(ACTION_VERB_PATTERN, "i");
|
|
|
875
946
|
/** Operation verbs beyond the guard action surface (local work and diagnosis). */
|
|
876
947
|
const WORK_VERB = /创建|生成|新建|写入|修改|编辑|运行|执行|编写|撰写|部署|安装|升级|提交|下载|上传|拉取|同步|重启|测试|检查|验证|确认|修复|更新|清理|整理|记录|构建|编译|重构|迁移|删除|回滚|发布|推送|合并|继续|恢复|还原|回滚|实现|\b(?:build|create|write|modify|change|edit|run|fix|update|install|push|publish|test|verify|check|commit|deploy|migrate|remove|delete|restart|revert|refactor|inspect|fetch|pull|implement)\b/i;
|
|
877
948
|
/** Explanatory framings: an action named afterwards is an object, not an order. */
|
|
878
|
-
const EXPLAIN_VERB = /解释|说明|讲解|介绍|阐述|分析|讨论|描述|科普|什么意思|是什么意思|有什么(?:作用|影响|区别)|\bexplain\b|\bdescribe\b|\bclarify\b|\bwhat\s+does\b|\bwhat\s+is\b|\bhow\s+does\b|\bmeaning\s+of\b/i;
|
|
949
|
+
const EXPLAIN_VERB = /解释|说明|讲解|介绍|阐述|分析|讨论|描述|科普|什么意思|是什么意思|有什么(?:作用|影响|区别)|\bexplain\b|\bdescribe\b|\bclarify\b|\btell\b|\bhow\s+to\b|\bwhat\s+does\b|\bwhat\s+is\b|\bhow\s+does\b|\bmeaning\s+of\b/i;
|
|
879
950
|
/** Interrogative framings that make a scope a question rather than an order. */
|
|
880
|
-
|
|
951
|
+
/**
|
|
952
|
+
* Interrogative framings that make a scope a question rather than an order.
|
|
953
|
+
* The bare English wh-words are matched only at the START of a scope ("What
|
|
954
|
+
* changed in the build"), where they are genuine interrogatives; a mid-clause
|
|
955
|
+
* match would misread the relative clause of a real order ("Create a file
|
|
956
|
+
* where logs are stored") as a question — the 0.6.1 review regression.
|
|
957
|
+
*/
|
|
958
|
+
const QUESTION_SCOPE = /[??]|是否|是不是|为什么|为何|怎么|如何|什么|哪些|哪一种|能否|可否|要不要|该不该|由谁|是谁|^\s*(?:what|how|when|where|who|which|whether|why)\b|\b(?:whether|which|why|should|could|would)\b/i;
|
|
881
959
|
const NEGATORS = [
|
|
882
960
|
["不要", "zh"],
|
|
883
961
|
["不用", "zh"],
|
|
@@ -1028,7 +1106,17 @@ const NARRATIVE_PAST = /(?:已经|已|刚刚|刚才|此前|之前)(?:经)?(?:推
|
|
|
1028
1106
|
*/
|
|
1029
1107
|
const NARRATIVE_ASPECT = /(?:完了|好了|过了)|(?:已经|已|刚刚|刚才|此前|之前)[\p{Script=Han}]{0,4}(?:了|过)|\b(?:was|were|has been|have been)\b/iu;
|
|
1030
1108
|
const NARRATIVE_DIRECTIVE = /请|需要你|帮我|麻烦|务必|\b(?:please|must)\b/i;
|
|
1031
|
-
|
|
1109
|
+
/**
|
|
1110
|
+
* POSITIVE statement evidence (0.6.1 review): a clause with no resolvable
|
|
1111
|
+
* action reads as a statement only when one of these structural markers is
|
|
1112
|
+
* present — a passive (被/受到/遭到), a negator or progress marker
|
|
1113
|
+
* ("不要"/"没有"/"尚未"/"还没"/"从未"), or an English declarative shape
|
|
1114
|
+
* (finite aux/copula, or an article/possessive-led subject, which an
|
|
1115
|
+
* imperative can never start with). Everything else defaults to `unresolved`:
|
|
1116
|
+
* an unknown request ("Please sanitize these inputs", "处理这个问题") must
|
|
1117
|
+
* never degrade to information, where the turn's answer would auto-close it —
|
|
1118
|
+
* and no vocabulary can be complete, so the default never consults one.
|
|
1119
|
+
*/
|
|
1032
1120
|
/**
|
|
1033
1121
|
* A completed confirmation receipt: the root reports that the event it was
|
|
1034
1122
|
* waiting for already happened. It reserves nothing, so it must not mint a
|
|
@@ -1291,8 +1379,11 @@ function scopeOf(raw, options = {}) {
|
|
|
1291
1379
|
...conditionText ? { condition: conditionText } : {}
|
|
1292
1380
|
}
|
|
1293
1381
|
});
|
|
1294
|
-
|
|
1295
|
-
|
|
1382
|
+
if (head$1 && (clauseStart < 0 || firstActionVerb(maskCodeSpans(head$1)) >= 0)) pending.push({
|
|
1383
|
+
text: head$1,
|
|
1384
|
+
offset
|
|
1385
|
+
});
|
|
1386
|
+
} else if (head$1) pending.push({
|
|
1296
1387
|
text: head$1,
|
|
1297
1388
|
offset
|
|
1298
1389
|
});
|
|
@@ -1595,23 +1686,37 @@ function stripNegatorsPrefix(value) {
|
|
|
1595
1686
|
function stripConnectors(text) {
|
|
1596
1687
|
return text.replace(new RegExp(`^${CONNECTOR_PATTERN}\\s*`, "i"), "").trim();
|
|
1597
1688
|
}
|
|
1598
|
-
/**
|
|
1689
|
+
/**
|
|
1690
|
+
* Whether a past/aspect marker is the clause's ENTIRE predicate: the span
|
|
1691
|
+
* extends to the end of the clause (only particles and punctuation may
|
|
1692
|
+
* follow), so no modifier ("…的"), attributive chain, or coordinated demand
|
|
1693
|
+
* can hide behind the report (0.6.1 review round 8: distance thresholds and
|
|
1694
|
+
* coordinator lists cannot enumerate modifiers).
|
|
1695
|
+
*/
|
|
1696
|
+
function mainClauseTailReport(masked) {
|
|
1697
|
+
for (const pattern of [NARRATIVE_PAST, NARRATIVE_ASPECT]) {
|
|
1698
|
+
const match = pattern.exec(masked);
|
|
1699
|
+
if (!match) continue;
|
|
1700
|
+
if (/^[^,。;!?\s]*的/u.test(masked.slice(match.index + match[0].length))) continue;
|
|
1701
|
+
if (/^(?:了|过)?[。,;!?、\s.!?]*$/u.test(masked.slice(match.index + match[0].length))) return true;
|
|
1702
|
+
}
|
|
1703
|
+
return false;
|
|
1704
|
+
}
|
|
1599
1705
|
function classifyPositive(text) {
|
|
1600
1706
|
const masked = maskCodeSpans(text);
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
if (explain) {
|
|
1604
|
-
const verb = firstActionVerb(masked);
|
|
1605
|
-
if (verb < 0 || verb >= explain.index) return "informational";
|
|
1606
|
-
}
|
|
1707
|
+
const visibleVerb = firstActionVerb(masked);
|
|
1708
|
+
if (visibleVerb < 0 && firstActionVerb(text) >= 0) return "informational";
|
|
1607
1709
|
if (QUESTION_SCOPE.test(masked)) return "informational";
|
|
1608
|
-
if (
|
|
1609
|
-
if ((NARRATIVE_PAST.test(masked) || NARRATIVE_ASPECT.test(masked)) && !NARRATIVE_DIRECTIVE.test(masked)) return "narrative";
|
|
1710
|
+
if (mainClauseTailReport(masked) && !NARRATIVE_DIRECTIVE.test(masked)) return "narrative";
|
|
1610
1711
|
if (CONFIRMATION_RECEIPT.test(masked.trim())) return "narrative";
|
|
1712
|
+
if (visibleVerb < 0) return "unresolved";
|
|
1713
|
+
const explain = EXPLAIN_VERB.exec(masked);
|
|
1714
|
+
if (explain && (visibleVerb < 0 || visibleVerb >= explain.index)) return "unresolved";
|
|
1715
|
+
if (/了[。,;!?、\s.!?]*$/u.test(masked)) return "unresolved";
|
|
1611
1716
|
return "directive";
|
|
1612
1717
|
}
|
|
1613
1718
|
function executeeOf(text, directive) {
|
|
1614
|
-
if (directive
|
|
1719
|
+
if (directive !== "directive") return "unresolved";
|
|
1615
1720
|
const masked = maskCodeSpans(text);
|
|
1616
1721
|
if (USER_ACTOR_PATTERNS.some((pattern) => pattern.test(masked))) return "user";
|
|
1617
1722
|
if (AGENT_ACTOR_PATTERNS.some((pattern) => pattern.test(masked))) return "agent";
|
|
@@ -1624,6 +1729,7 @@ function isOutputRequest(text) {
|
|
|
1624
1729
|
}
|
|
1625
1730
|
function dispositionOf(scope, executee) {
|
|
1626
1731
|
if (scope.directive === "prohibition") return "prohibition";
|
|
1732
|
+
if (scope.directive === "unresolved") return "unresolved";
|
|
1627
1733
|
if (scope.directive === "informational" || scope.directive === "narrative") return "informational";
|
|
1628
1734
|
if (scope.directive === "conditional") return "conditional_wait";
|
|
1629
1735
|
if (scope.condition) return "conditional_wait";
|
|
@@ -1694,7 +1800,7 @@ function interpretClause(text, options = {}) {
|
|
|
1694
1800
|
directive: "informational"
|
|
1695
1801
|
});
|
|
1696
1802
|
if (scopes.length === 1) return interpret(scopes[0]);
|
|
1697
|
-
const directive = scopes.some((scope) => scope.directive === "directive") ? "directive" : scopes.some((scope) => scope.directive === "prohibition") ? "prohibition" : "informational";
|
|
1803
|
+
const directive = scopes.some((scope) => scope.directive === "directive") ? "directive" : scopes.some((scope) => scope.directive === "prohibition") ? "prohibition" : scopes.some((scope) => scope.directive === "unresolved") ? "unresolved" : "informational";
|
|
1698
1804
|
return interpret({
|
|
1699
1805
|
text: normalized,
|
|
1700
1806
|
body: scopes.map((scope) => scope.body).join(";"),
|
|
@@ -1808,6 +1914,36 @@ function namedActions(text) {
|
|
|
1808
1914
|
return interpretMessage(text).map((scope) => semanticActionOfScope(scope.body)).filter((action) => action !== "generic_run");
|
|
1809
1915
|
}
|
|
1810
1916
|
|
|
1917
|
+
//#endregion
|
|
1918
|
+
//#region src/domain/spans.ts
|
|
1919
|
+
/**
|
|
1920
|
+
* 0.6.0 source-span helpers (C01).
|
|
1921
|
+
*
|
|
1922
|
+
* Spans are UTF-8 BYTE half-open intervals `[start, end)` inside the original
|
|
1923
|
+
* root message text. All conversions go through TextEncoder byte counting —
|
|
1924
|
+
* JavaScript string indices are never allowed to masquerade as cross-language
|
|
1925
|
+
* positions, so a Python reader agrees with TypeScript on every boundary.
|
|
1926
|
+
*/
|
|
1927
|
+
const encoder = new TextEncoder();
|
|
1928
|
+
function utf8ByteLength(text) {
|
|
1929
|
+
return encoder.encode(text).length;
|
|
1930
|
+
}
|
|
1931
|
+
/** Byte offset of `index` inside `text`: the UTF-8 length of the prefix. */
|
|
1932
|
+
function utf8ByteOffset(text, index) {
|
|
1933
|
+
return utf8ByteLength(text.slice(0, index));
|
|
1934
|
+
}
|
|
1935
|
+
/**
|
|
1936
|
+
* The coverage class of one captured clause: a prohibition is a constraint,
|
|
1937
|
+
* an informational reading is a question, an adopted block stays adoption,
|
|
1938
|
+
* and everything else is an instruction. Nothing captured is left unclassed.
|
|
1939
|
+
*/
|
|
1940
|
+
function spanClassOf(kind, directive, authority) {
|
|
1941
|
+
if (authority === "root_adoption") return "adoption";
|
|
1942
|
+
if (kind === "prohibition") return "constraint";
|
|
1943
|
+
if (directive === "informational" || directive === "narrative") return "question";
|
|
1944
|
+
return "instruction";
|
|
1945
|
+
}
|
|
1946
|
+
|
|
1811
1947
|
//#endregion
|
|
1812
1948
|
//#region src/domain/capture.ts
|
|
1813
1949
|
/**
|
|
@@ -1953,16 +2089,112 @@ function parentScope(subject) {
|
|
|
1953
2089
|
if (separator === 2 && /^[A-Za-z]:[\\/]$/.test(subject.slice(0, 3))) return subject.slice(0, 3);
|
|
1954
2090
|
return subject.slice(0, separator);
|
|
1955
2091
|
}
|
|
2092
|
+
/**
|
|
2093
|
+
* The artifact-type nouns the bounded file-choice vocabulary admits (C07).
|
|
2094
|
+
* A closed list pinned by the v2 fixture: the noun must be the OBJECT of the
|
|
2095
|
+
* action, so "更新文档" captures a bounded choice while "更新皮肤中心" stays
|
|
2096
|
+
* a genuine clarification. The generic "文件/file" admits any file type the
|
|
2097
|
+
* producer accepts.
|
|
2098
|
+
*/
|
|
2099
|
+
const BOUNDED_TYPE_NOUNS = [
|
|
2100
|
+
[/文档/, "document"],
|
|
2101
|
+
[/(?<!\w)readme(?!\w)/iu, "readme"],
|
|
2102
|
+
[/报告/, "report"],
|
|
2103
|
+
[/文件/, "file"],
|
|
2104
|
+
[/(?<!\w)files?(?!\w)/iu, "file"]
|
|
2105
|
+
];
|
|
2106
|
+
function boundedArtifactTypeOf(text) {
|
|
2107
|
+
const masked = text;
|
|
2108
|
+
for (const [pattern, type] of BOUNDED_TYPE_NOUNS) if (pattern.test(masked)) return type;
|
|
2109
|
+
}
|
|
2110
|
+
/** A bounded choice needs a real path scope; the 'scope' sentinel is not one. */
|
|
2111
|
+
function scopeIsPath(subject) {
|
|
2112
|
+
return subject !== "scope" && subject !== "" && /[\\/]/.test(subject);
|
|
2113
|
+
}
|
|
2114
|
+
/**
|
|
2115
|
+
* Whether the clause's FIRST action word is the change verb 更新/调整 (the
|
|
2116
|
+
* object-driven modify mapping). A later 更新 inside a referenced task name
|
|
2117
|
+
* ("把更新插件明确为 apply…") never qualifies — the head verb is what the
|
|
2118
|
+
* clause orders.
|
|
2119
|
+
*/
|
|
2120
|
+
function headVerbIsChangeWord(text) {
|
|
2121
|
+
const candidates = [];
|
|
2122
|
+
for (const word of ["更新", "调整"]) {
|
|
2123
|
+
let at = text.indexOf(word);
|
|
2124
|
+
while (at >= 0) {
|
|
2125
|
+
candidates.push({
|
|
2126
|
+
at,
|
|
2127
|
+
word
|
|
2128
|
+
});
|
|
2129
|
+
at = text.indexOf(word, at + word.length);
|
|
2130
|
+
}
|
|
2131
|
+
}
|
|
2132
|
+
for (const match of text.matchAll(/\b(?:update|adjust)\b/gi)) candidates.push({
|
|
2133
|
+
at: match.index,
|
|
2134
|
+
word: match[0]
|
|
2135
|
+
});
|
|
2136
|
+
if (candidates.length === 0) return false;
|
|
2137
|
+
candidates.sort((a, b) => a.at - b.at);
|
|
2138
|
+
const head = candidates[0];
|
|
2139
|
+
return CJK_ACTION_WORD_AT(text, head.at) === void 0;
|
|
2140
|
+
}
|
|
2141
|
+
/** Any other known action word strictly before `before`, if one exists. */
|
|
2142
|
+
function CJK_ACTION_WORD_AT(text, before) {
|
|
2143
|
+
const words = [
|
|
2144
|
+
"创建",
|
|
2145
|
+
"生成",
|
|
2146
|
+
"新建",
|
|
2147
|
+
"写入",
|
|
2148
|
+
"修改",
|
|
2149
|
+
"编辑",
|
|
2150
|
+
"更改",
|
|
2151
|
+
"读取",
|
|
2152
|
+
"运行",
|
|
2153
|
+
"执行",
|
|
2154
|
+
"安装",
|
|
2155
|
+
"部署",
|
|
2156
|
+
"上传",
|
|
2157
|
+
"提交",
|
|
2158
|
+
"推送",
|
|
2159
|
+
"发布",
|
|
2160
|
+
"升级",
|
|
2161
|
+
"重启",
|
|
2162
|
+
"重新启动",
|
|
2163
|
+
"合并",
|
|
2164
|
+
"删除",
|
|
2165
|
+
"下载",
|
|
2166
|
+
"拉取",
|
|
2167
|
+
"同步"
|
|
2168
|
+
];
|
|
2169
|
+
let best;
|
|
2170
|
+
for (const word of words) {
|
|
2171
|
+
const at = text.indexOf(word);
|
|
2172
|
+
if (at >= 0 && at < before && (best === void 0 || at < best.at)) best = {
|
|
2173
|
+
at,
|
|
2174
|
+
word
|
|
2175
|
+
};
|
|
2176
|
+
}
|
|
2177
|
+
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 = {
|
|
2178
|
+
at: match.index,
|
|
2179
|
+
word: match[0]
|
|
2180
|
+
};
|
|
2181
|
+
return best?.word;
|
|
2182
|
+
}
|
|
1956
2183
|
function captureRequestedTarget(action, text, subject, surface) {
|
|
1957
2184
|
if (action === "create" || action === "modify") {
|
|
1958
|
-
if (surface
|
|
1959
|
-
target: {},
|
|
1960
|
-
reasonCode: "requested_target_artifact_id_missing"
|
|
1961
|
-
};
|
|
1962
|
-
return { target: {
|
|
2185
|
+
if (surface === "artifact") return { target: {
|
|
1963
2186
|
artifact_id: subject,
|
|
1964
2187
|
scope: parentScope(subject)
|
|
1965
2188
|
} };
|
|
2189
|
+
const artifactType = boundedArtifactTypeOf(text);
|
|
2190
|
+
if (artifactType && scopeIsPath(subject)) return { target: {
|
|
2191
|
+
scope: subject,
|
|
2192
|
+
artifact_type: artifactType
|
|
2193
|
+
} };
|
|
2194
|
+
return {
|
|
2195
|
+
target: {},
|
|
2196
|
+
reasonCode: "requested_target_artifact_id_missing"
|
|
2197
|
+
};
|
|
1966
2198
|
}
|
|
1967
2199
|
if (action === "install" || action === "apply") {
|
|
1968
2200
|
const parsed = splitPackageSpec(actionObjectToken(text, action === "install" ? "install|add|安装" : "apply|应用", "package|plugin|包|插件"));
|
|
@@ -2065,7 +2297,8 @@ function segmentClauses(text, options = {}) {
|
|
|
2065
2297
|
function captureItem(kind, body, sourceMessageId, id, revision, subject, surface, method, operation, interpretation) {
|
|
2066
2298
|
const sanitized = sanitizeClauseText(body);
|
|
2067
2299
|
const unsupportedVisual = /\bGUI\b|界面|视觉|截图|颜色|布局|视觉效果/i.test(sanitized);
|
|
2068
|
-
|
|
2300
|
+
let semanticAction = unsupportedVisual ? "generic_run" : semanticActionOfScope(sanitized, interpretation?.text ?? sanitized, kind === "prohibition");
|
|
2301
|
+
if (semanticAction === "generic_run" && !unsupportedVisual && interpretation?.directive === "directive" && headVerbIsChangeWord(sanitized) && boundedArtifactTypeOf(sanitized) !== void 0) semanticAction = "modify";
|
|
2069
2302
|
const capturedTarget = captureRequestedTarget(semanticAction, sanitized, subject, surface);
|
|
2070
2303
|
const effectiveOperation = semanticAction === "verify" ? "verify" : operation;
|
|
2071
2304
|
const item = {
|
|
@@ -2147,7 +2380,210 @@ function captureClause(text, sourceMessageId, id, revision, scope = {}, options
|
|
|
2147
2380
|
const body = interpretation?.body ?? text;
|
|
2148
2381
|
const path$1 = extractArtifactPaths(sanitizeClauseText(body))[0] ?? "";
|
|
2149
2382
|
const surface = path$1 ? "artifact" : "scope";
|
|
2150
|
-
|
|
2383
|
+
const item = captureItem(kind, body, sourceMessageId, id, revision, path$1 || scope.cwd || "scope", surface, interpretation?.method ?? extractMethod(body), extractOperation(body), interpretation);
|
|
2384
|
+
if (interpretation) {
|
|
2385
|
+
const at = text.indexOf(interpretation.text);
|
|
2386
|
+
if (at >= 0) {
|
|
2387
|
+
item.rawTextSha256 = sha256(text);
|
|
2388
|
+
item.spans = [{
|
|
2389
|
+
partIndex: 0,
|
|
2390
|
+
start: utf8ByteOffset(text, at),
|
|
2391
|
+
end: utf8ByteOffset(text, at) + utf8ByteLength(interpretation.text),
|
|
2392
|
+
class: spanClassOf(kind, interpretation.directive, "root_instruction")
|
|
2393
|
+
}];
|
|
2394
|
+
}
|
|
2395
|
+
}
|
|
2396
|
+
return item;
|
|
2397
|
+
}
|
|
2398
|
+
|
|
2399
|
+
//#endregion
|
|
2400
|
+
//#region src/domain/reason-class.ts
|
|
2401
|
+
const REASON_CLASS_TABLE = {
|
|
2402
|
+
requested_target_package_id_missing: "parameter_missing",
|
|
2403
|
+
requested_target_artifact_id_missing: "parameter_missing",
|
|
2404
|
+
requested_target_repository_missing: "parameter_missing",
|
|
2405
|
+
requested_target_service_id_missing: "parameter_missing",
|
|
2406
|
+
requested_target_registry_missing_or_invalid: "parameter_missing",
|
|
2407
|
+
target_clarification_required: "parameter_missing",
|
|
2408
|
+
item_not_found: "parameter_missing",
|
|
2409
|
+
item_revision_mismatch: "parameter_missing",
|
|
2410
|
+
unsupported_action: "parameter_missing",
|
|
2411
|
+
missing_evidence: "parameter_missing",
|
|
2412
|
+
action_plan_target_missing: "parameter_missing",
|
|
2413
|
+
action_plan_evidence_missing: "parameter_missing",
|
|
2414
|
+
binding_missing_required_facet: "parameter_missing",
|
|
2415
|
+
evidence_missing: "parameter_missing",
|
|
2416
|
+
proof_subject_invalid: "parameter_missing",
|
|
2417
|
+
proof_source_invalid: "parameter_missing",
|
|
2418
|
+
proof_evidence_invalid: "parameter_missing",
|
|
2419
|
+
generic_run_non_certifiable: "source_insufficient",
|
|
2420
|
+
legacy_generic_run_non_certifiable: "source_insufficient",
|
|
2421
|
+
legacy_authority_unclassified: "source_insufficient",
|
|
2422
|
+
inquiry_non_certifiable: "source_insufficient",
|
|
2423
|
+
inquiry_awaiting_delivery: "source_insufficient",
|
|
2424
|
+
asset_interpretation_required: "source_insufficient",
|
|
2425
|
+
information_non_certifiable: "source_insufficient",
|
|
2426
|
+
information_awaiting_delivery: "source_insufficient",
|
|
2427
|
+
interpretation_unresolved: "source_insufficient",
|
|
2428
|
+
answer_delivered: "source_insufficient",
|
|
2429
|
+
certified: "source_insufficient",
|
|
2430
|
+
semantic_action_mismatch: "source_insufficient",
|
|
2431
|
+
evidence_matches_no_facet: "source_insufficient",
|
|
2432
|
+
requested_target_mismatch: "source_insufficient",
|
|
2433
|
+
requested_resolved_target_mismatch: "source_insufficient",
|
|
2434
|
+
binding_state_cross_pairing: "source_insufficient",
|
|
2435
|
+
binding_resolution_cross_pairing: "source_insufficient",
|
|
2436
|
+
binding_observed_state_mismatch: "source_insufficient",
|
|
2437
|
+
binding_state_observation_overlap: "source_insufficient",
|
|
2438
|
+
binding_expected_transition_mismatch: "source_insufficient",
|
|
2439
|
+
binding_state_closure_rejected: "source_insufficient",
|
|
2440
|
+
expected_transition_mismatch: "source_insufficient",
|
|
2441
|
+
non_stateful_role_manifest_invalid: "source_insufficient",
|
|
2442
|
+
resolved_target_incomplete: "source_insufficient",
|
|
2443
|
+
state_closure_incomplete: "source_insufficient",
|
|
2444
|
+
delegated_result_bounded: "source_insufficient",
|
|
2445
|
+
proof_subject_unbound: "source_insufficient",
|
|
2446
|
+
proof_source_unbound: "source_insufficient",
|
|
2447
|
+
proof_surface_unbound: "source_insufficient",
|
|
2448
|
+
proof_role_unbound: "source_insufficient",
|
|
2449
|
+
proof_operation_unbound: "source_insufficient",
|
|
2450
|
+
proof_scope_subject_unbound: "source_insufficient",
|
|
2451
|
+
proof_scope_digest_mismatch: "source_insufficient",
|
|
2452
|
+
proof_scope_digest_invalid: "source_insufficient",
|
|
2453
|
+
proof_evidence_outcome_invalid: "source_insufficient",
|
|
2454
|
+
proof_evidence_constraint_failed: "source_insufficient",
|
|
2455
|
+
root_condition_pending: "condition_unmet",
|
|
2456
|
+
prohibition_active: "condition_unmet",
|
|
2457
|
+
ancestor_condition_unsatisfied: "condition_unmet",
|
|
2458
|
+
ancestor_prohibition_active: "condition_unmet",
|
|
2459
|
+
mutation_awaiting_root_condition: "condition_unmet",
|
|
2460
|
+
mutation_awaiting_root_wait: "condition_unmet",
|
|
2461
|
+
action_plan_order_mismatch: "condition_unmet",
|
|
2462
|
+
adapter_unavailable: "producer_capability_unavailable",
|
|
2463
|
+
host_unavailable: "producer_capability_unavailable",
|
|
2464
|
+
stateful_adapter_unavailable: "producer_capability_unavailable",
|
|
2465
|
+
proof_producer_capability_unavailable: "producer_capability_unavailable",
|
|
2466
|
+
proof_readback_unavailable: "producer_capability_unavailable",
|
|
2467
|
+
proof_external_fact_unavailable: "producer_capability_unavailable",
|
|
2468
|
+
proof_external_fact_incomplete: "producer_capability_unavailable",
|
|
2469
|
+
proof_source_bounded_delegation: "producer_capability_unavailable",
|
|
2470
|
+
historical_evidence_gap: "historical_gap",
|
|
2471
|
+
effect_already_applied: "historical_gap",
|
|
2472
|
+
action_already_applied: "historical_gap",
|
|
2473
|
+
effect_only_insufficient_state_readback: "historical_gap",
|
|
2474
|
+
execution_unattributable: "historical_gap",
|
|
2475
|
+
rebind_evidence_predates_source: "historical_gap",
|
|
2476
|
+
resolution_expected_transition_missing: "historical_gap",
|
|
2477
|
+
resolution_expected_transition_digest_missing: "historical_gap",
|
|
2478
|
+
resolution_expected_transition_digest_mismatch: "historical_gap",
|
|
2479
|
+
resolution_expected_transition_invalid: "historical_gap",
|
|
2480
|
+
integrity_invalid: "integrity_failure",
|
|
2481
|
+
host_lock_unsupported: "integrity_failure",
|
|
2482
|
+
certificate_missing: "integrity_failure",
|
|
2483
|
+
certificate_replay_mismatch: "integrity_failure",
|
|
2484
|
+
boundary_replay_mismatch: "integrity_failure",
|
|
2485
|
+
proof_invalid: "integrity_failure",
|
|
2486
|
+
proof_unbound: "integrity_failure",
|
|
2487
|
+
proof_protocol_version_mismatch: "integrity_failure",
|
|
2488
|
+
proof_digest_invalid: "integrity_failure",
|
|
2489
|
+
proof_digest_mismatch: "integrity_failure",
|
|
2490
|
+
proof_kind_unsupported: "integrity_failure",
|
|
2491
|
+
proof_surface_unsupported: "integrity_failure",
|
|
2492
|
+
proof_operation_unsupported: "integrity_failure",
|
|
2493
|
+
proof_obligation_unbound: "integrity_failure",
|
|
2494
|
+
proof_obligation_not_pending: "integrity_failure",
|
|
2495
|
+
proof_evidence_unknown: "integrity_failure",
|
|
2496
|
+
proof_evidence_wrong_epoch: "integrity_failure",
|
|
2497
|
+
proof_manifest_invalid: "integrity_failure",
|
|
2498
|
+
proof_obligations_missing: "integrity_failure",
|
|
2499
|
+
proof_obligation_invalid: "integrity_failure",
|
|
2500
|
+
proof_obligation_id_duplicate_or_invalid: "integrity_failure",
|
|
2501
|
+
proof_asset_set_digest_invalid: "integrity_failure",
|
|
2502
|
+
evidence_wrong_epoch: "integrity_failure",
|
|
2503
|
+
evidence_outcome_not_success: "integrity_failure",
|
|
2504
|
+
stale_host_lock: "integrity_failure",
|
|
2505
|
+
stale_epoch: "integrity_failure",
|
|
2506
|
+
stale_contract_revision: "integrity_failure",
|
|
2507
|
+
stale_unit_ref: "integrity_failure",
|
|
2508
|
+
stale_goal_ref: "integrity_failure",
|
|
2509
|
+
legacy_certificate_in_v5_session: "integrity_failure",
|
|
2510
|
+
certificate_version_unavailable: "integrity_failure",
|
|
2511
|
+
mutation_integrity_unavailable: "integrity_failure",
|
|
2512
|
+
item_missing_or_superseded: "integrity_failure",
|
|
2513
|
+
unit_unavailable: "integrity_failure",
|
|
2514
|
+
certificate_manifest_rejected: "integrity_failure",
|
|
2515
|
+
session_ref_unavailable: "integrity_failure",
|
|
2516
|
+
projection_durability_unavailable: "integrity_failure",
|
|
2517
|
+
interpretation_receipt_mismatch: "integrity_failure",
|
|
2518
|
+
guard_unavailable: "integrity_failure",
|
|
2519
|
+
binding_role_mismatch: "integrity_failure",
|
|
2520
|
+
binding_role_order_invalid: "integrity_failure",
|
|
2521
|
+
action_plan_evidence_reused: "integrity_failure",
|
|
2522
|
+
action_plan_evidence_not_successful: "integrity_failure",
|
|
2523
|
+
action_plan_evidence_predates_item: "integrity_failure",
|
|
2524
|
+
action_plan_action_mismatch: "integrity_failure",
|
|
2525
|
+
action_plan_incomplete: "integrity_failure",
|
|
2526
|
+
action_plan_target_mismatch: "integrity_failure",
|
|
2527
|
+
release_contract_required: "policy_boundary",
|
|
2528
|
+
release_operation_not_adopted: "policy_boundary",
|
|
2529
|
+
release_operation_unprotectable: "policy_boundary",
|
|
2530
|
+
release_operation_unrouted: "policy_boundary",
|
|
2531
|
+
release_runner_opaque: "policy_boundary",
|
|
2532
|
+
release_contract_expired: "policy_boundary",
|
|
2533
|
+
release_expiry_unevaluable: "policy_boundary",
|
|
2534
|
+
release_expiry_invalid: "policy_boundary",
|
|
2535
|
+
release_operations_missing: "policy_boundary",
|
|
2536
|
+
release_operation_unknown: "policy_boundary",
|
|
2537
|
+
release_candidate_missing: "policy_boundary",
|
|
2538
|
+
release_candidate_ref_missing: "policy_boundary",
|
|
2539
|
+
release_candidate_sha_invalid: "policy_boundary",
|
|
2540
|
+
release_candidate_artifact_digest_invalid: "policy_boundary",
|
|
2541
|
+
release_contract_malformed: "policy_boundary",
|
|
2542
|
+
release_reservation_malformed: "policy_boundary",
|
|
2543
|
+
release_settlement_malformed: "policy_boundary",
|
|
2544
|
+
release_subcommand_unknown: "policy_boundary",
|
|
2545
|
+
release_operation_consumed: "policy_boundary",
|
|
2546
|
+
release_operation_in_flight: "policy_boundary",
|
|
2547
|
+
release_candidate_sha_mismatch: "policy_boundary",
|
|
2548
|
+
release_candidate_ref_mismatch: "policy_boundary",
|
|
2549
|
+
release_candidate_repository_mismatch: "policy_boundary",
|
|
2550
|
+
release_candidate_package_mismatch: "policy_boundary",
|
|
2551
|
+
release_candidate_version_mismatch: "policy_boundary",
|
|
2552
|
+
release_candidate_registry_mismatch: "policy_boundary",
|
|
2553
|
+
release_candidate_artifact_mismatch: "policy_boundary",
|
|
2554
|
+
release_candidate_artifact_sri_mismatch: "policy_boundary",
|
|
2555
|
+
release_candidate_sha_unresolved: "policy_boundary",
|
|
2556
|
+
release_candidate_ref_unresolved: "policy_boundary",
|
|
2557
|
+
release_candidate_repository_unresolved: "policy_boundary",
|
|
2558
|
+
release_candidate_package_unresolved: "policy_boundary",
|
|
2559
|
+
release_candidate_version_unresolved: "policy_boundary",
|
|
2560
|
+
release_candidate_registry_unresolved: "policy_boundary",
|
|
2561
|
+
release_artifact_sha256_unresolved: "policy_boundary",
|
|
2562
|
+
release_artifact_sri_unresolved: "policy_boundary",
|
|
2563
|
+
release_artifact_identity_required: "policy_boundary",
|
|
2564
|
+
release_candidate_sha256_invalid: "policy_boundary",
|
|
2565
|
+
release_candidate_sri_invalid: "policy_boundary",
|
|
2566
|
+
release_candidate_field_unknown: "policy_boundary",
|
|
2567
|
+
release_candidate_unobservable: "policy_boundary",
|
|
2568
|
+
release_contract_revoked: "policy_boundary",
|
|
2569
|
+
release_contract_revocation_unknown: "policy_boundary",
|
|
2570
|
+
release_state_damaged: "policy_boundary",
|
|
2571
|
+
release_readiness_unresolved: "policy_boundary",
|
|
2572
|
+
release_closure_unresolved: "policy_boundary",
|
|
2573
|
+
release_target_package_mismatch: "policy_boundary",
|
|
2574
|
+
release_target_version_mismatch: "policy_boundary",
|
|
2575
|
+
release_target_registry_mismatch: "policy_boundary",
|
|
2576
|
+
release_artifact_digest_unresolved: "policy_boundary",
|
|
2577
|
+
release_target_unresolved: "policy_boundary",
|
|
2578
|
+
release_contract_granted: "policy_boundary",
|
|
2579
|
+
release_profile_not_adopted: "policy_boundary",
|
|
2580
|
+
release_reservation_not_durable: "policy_boundary",
|
|
2581
|
+
release_gate_unavailable: "policy_boundary",
|
|
2582
|
+
strict_proof_required: "policy_boundary"
|
|
2583
|
+
};
|
|
2584
|
+
/** The seven-class label for one fine-grained reason code. */
|
|
2585
|
+
function reasonClassOf(reasonCode) {
|
|
2586
|
+
return REASON_CLASS_TABLE[reasonCode] ?? "source_insufficient";
|
|
2151
2587
|
}
|
|
2152
2588
|
|
|
2153
2589
|
//#endregion
|
|
@@ -2165,17 +2601,51 @@ const TARGET_FIELD_REASONS = {
|
|
|
2165
2601
|
requested_target_service_id_missing: "service_id",
|
|
2166
2602
|
requested_target_registry_missing_or_invalid: "registry"
|
|
2167
2603
|
};
|
|
2604
|
+
/**
|
|
2605
|
+
* The evidence roles the item's OWN obligation contract requires (0.6.1,
|
|
2606
|
+
* W060-04). A stateful change needs the full resolution/effect/state chain; a
|
|
2607
|
+
* read-only verification (inspect, test, verify, generic readback) needs ONE
|
|
2608
|
+
* matching fact in the `effect` role — exactly the manifest `simpleRecord`
|
|
2609
|
+
* accepts. Asking every obligation for all three roles made prepare and
|
|
2610
|
+
* diagnosis demand a change chain a read-only verification can never produce.
|
|
2611
|
+
*/
|
|
2612
|
+
function requiredEvidenceRoles(item) {
|
|
2613
|
+
return isStatefulAction(item.semanticAction ?? "generic_run") ? [
|
|
2614
|
+
"resolution",
|
|
2615
|
+
"effect",
|
|
2616
|
+
"state"
|
|
2617
|
+
] : ["effect"];
|
|
2618
|
+
}
|
|
2168
2619
|
function evidenceFacets(p, item) {
|
|
2169
2620
|
const present = /* @__PURE__ */ new Set();
|
|
2170
2621
|
for (const evidence of p.evidence.values()) {
|
|
2171
2622
|
if (!relevantEvidence(p, item, evidence)) continue;
|
|
2172
2623
|
if (evidence.evidenceRole) present.add(evidence.evidenceRole);
|
|
2173
2624
|
}
|
|
2174
|
-
return
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2625
|
+
return requiredEvidenceRoles(item).filter((facet) => !present.has(facet));
|
|
2626
|
+
}
|
|
2627
|
+
/**
|
|
2628
|
+
* An ordinary shell command whose TEXT ANCHORED at command position to this
|
|
2629
|
+
* obligation's action completed successfully, but which failed closed
|
|
2630
|
+
* parsing, so per-command execution cannot be established (0.6.1, W060-05).
|
|
2631
|
+
* Only the pre-existing head-anchored action signal counts: the guard does
|
|
2632
|
+
* NOT scan compound text for actions, because quoted data and short-circuit
|
|
2633
|
+
* control flow would fabricate observations. A failed command is not a
|
|
2634
|
+
* signal either.
|
|
2635
|
+
*/
|
|
2636
|
+
function unattributedExecutionOf(p, item) {
|
|
2637
|
+
const action = item.semanticAction;
|
|
2638
|
+
if (!action || action === "generic_run") return void 0;
|
|
2639
|
+
for (const evidence of p.evidence.values()) {
|
|
2640
|
+
if (evidence.outcome !== "success") continue;
|
|
2641
|
+
if (evidence.parseStatus === void 0 || evidence.parseStatus === "supported") continue;
|
|
2642
|
+
if (![
|
|
2643
|
+
"bash",
|
|
2644
|
+
"pwsh",
|
|
2645
|
+
"shell"
|
|
2646
|
+
].includes(evidence.toolName)) continue;
|
|
2647
|
+
if (evidence.semanticAction !== void 0 && evidence.semanticAction !== "generic_run" && actionCompatible(action, evidence.semanticAction)) return evidence;
|
|
2648
|
+
}
|
|
2179
2649
|
}
|
|
2180
2650
|
/**
|
|
2181
2651
|
* The pure repair judge. It decides between: fixable from existing evidence,
|
|
@@ -2183,7 +2653,20 @@ function evidenceFacets(p, item) {
|
|
|
2183
2653
|
* adapter, an executed-without-evidence historical gap, or nothing to do —
|
|
2184
2654
|
* and it NEVER recommends a rebind that cannot change certification.
|
|
2185
2655
|
*/
|
|
2656
|
+
/**
|
|
2657
|
+
* The unified diagnosis, with the frozen seven-class label attached (C12).
|
|
2658
|
+
*
|
|
2659
|
+
* The class is derived from whatever `reason_code` the judge decides, so a new
|
|
2660
|
+
* branch cannot drift from the classification table.
|
|
2661
|
+
*/
|
|
2186
2662
|
function deriveItemDiagnosis(p, item) {
|
|
2663
|
+
const diagnosis = judgeItemDiagnosis(p, item);
|
|
2664
|
+
return {
|
|
2665
|
+
...diagnosis,
|
|
2666
|
+
reason_class: reasonClassOf(diagnosis.reason_code)
|
|
2667
|
+
};
|
|
2668
|
+
}
|
|
2669
|
+
function judgeItemDiagnosis(p, item) {
|
|
2187
2670
|
const kind = taskKindOf(item);
|
|
2188
2671
|
const missing_facets = item.status === "pending" && kind !== "constraint" ? evidenceFacets(p, item) : [];
|
|
2189
2672
|
const base = {
|
|
@@ -2218,6 +2701,19 @@ function deriveItemDiagnosis(p, item) {
|
|
|
2218
2701
|
},
|
|
2219
2702
|
attempt_fingerprint: fingerprint(p, item, "certified")
|
|
2220
2703
|
};
|
|
2704
|
+
if (item.status === "answered") return {
|
|
2705
|
+
...base,
|
|
2706
|
+
certification: "supported",
|
|
2707
|
+
reason_code: "answer_delivered",
|
|
2708
|
+
repairability: "none",
|
|
2709
|
+
missing_fields: [],
|
|
2710
|
+
missing_facets: [],
|
|
2711
|
+
next_action: {
|
|
2712
|
+
kind: "none",
|
|
2713
|
+
resume_condition: "The host-confirmed final answer was delivered; no further binding needed."
|
|
2714
|
+
},
|
|
2715
|
+
attempt_fingerprint: fingerprint(p, item, "answer_delivered")
|
|
2716
|
+
};
|
|
2221
2717
|
if (item.status === "pending" && item.waitAuthorization?.kind === "root_explicit_wait") return {
|
|
2222
2718
|
...base,
|
|
2223
2719
|
certification: "unavailable",
|
|
@@ -2231,50 +2727,96 @@ function deriveItemDiagnosis(p, item) {
|
|
|
2231
2727
|
},
|
|
2232
2728
|
attempt_fingerprint: fingerprint(p, item, "root_condition_pending")
|
|
2233
2729
|
};
|
|
2234
|
-
if (
|
|
2235
|
-
const
|
|
2236
|
-
return {
|
|
2730
|
+
if (kind === "inquiry") {
|
|
2731
|
+
const closable = p.boundaryProtocol === 5;
|
|
2732
|
+
if (item.asset !== void 0 && !p.interpretationFacts.some((fact) => fact.itemId === item.id)) return {
|
|
2237
2733
|
...base,
|
|
2238
|
-
certification: "
|
|
2239
|
-
reason_code: "
|
|
2240
|
-
repairability: "
|
|
2241
|
-
missing_fields:
|
|
2734
|
+
certification: "unsupported",
|
|
2735
|
+
reason_code: "asset_interpretation_required",
|
|
2736
|
+
repairability: "unsupported",
|
|
2737
|
+
missing_fields: [],
|
|
2738
|
+
missing_facets: [],
|
|
2242
2739
|
next_action: {
|
|
2243
|
-
kind: "
|
|
2244
|
-
tool: "
|
|
2245
|
-
required_input:
|
|
2246
|
-
resume_condition: "
|
|
2740
|
+
kind: "report_only",
|
|
2741
|
+
tool: "context_guard_interpret",
|
|
2742
|
+
required_input: `the item ID of the interpreted attachment (${item.id})`,
|
|
2743
|
+
resume_condition: "Read the attachment, record it with context_guard_interpret for this item, and deliver the actual answer; the host-confirmed final response of a completed turn then closes this item. The record proves the asset was read, never that the interpretation is correct."
|
|
2247
2744
|
},
|
|
2248
|
-
attempt_fingerprint: fingerprint(p, item, "
|
|
2745
|
+
attempt_fingerprint: fingerprint(p, item, "asset_interpretation_required")
|
|
2249
2746
|
};
|
|
2250
|
-
|
|
2251
|
-
if (action === "generic_run" || item.legacyFlags?.length) {
|
|
2252
|
-
if (kind === "inquiry") return {
|
|
2747
|
+
return {
|
|
2253
2748
|
...base,
|
|
2254
2749
|
certification: "unsupported",
|
|
2255
|
-
reason_code: "inquiry_non_certifiable",
|
|
2750
|
+
reason_code: closable ? "inquiry_awaiting_delivery" : "inquiry_non_certifiable",
|
|
2256
2751
|
repairability: "unsupported",
|
|
2257
2752
|
missing_fields: [],
|
|
2753
|
+
missing_facets: [],
|
|
2258
2754
|
next_action: {
|
|
2259
2755
|
kind: "report_only",
|
|
2260
|
-
resume_condition: "Complete the investigation and report the actual answer; the item stays recorded as uncertified. No confirmation or rebind changes this."
|
|
2756
|
+
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."
|
|
2261
2757
|
},
|
|
2262
|
-
attempt_fingerprint: fingerprint(p, item, "inquiry_non_certifiable")
|
|
2758
|
+
attempt_fingerprint: fingerprint(p, item, closable ? "inquiry_awaiting_delivery" : "inquiry_non_certifiable")
|
|
2263
2759
|
};
|
|
2760
|
+
}
|
|
2761
|
+
if (item.authorityDisposition === "informational") {
|
|
2762
|
+
const closable = p.boundaryProtocol === 5;
|
|
2264
2763
|
return {
|
|
2265
2764
|
...base,
|
|
2266
2765
|
certification: "unsupported",
|
|
2267
|
-
reason_code: "
|
|
2268
|
-
repairability: "
|
|
2766
|
+
reason_code: closable ? "information_awaiting_delivery" : "information_non_certifiable",
|
|
2767
|
+
repairability: "unsupported",
|
|
2269
2768
|
missing_fields: [],
|
|
2769
|
+
missing_facets: [],
|
|
2270
2770
|
next_action: {
|
|
2271
2771
|
kind: "report_only",
|
|
2272
|
-
|
|
2273
|
-
|
|
2772
|
+
resume_condition: closable ? "The trusted final response of this turn closes the recorded statement; it certifies the answer was delivered, never its accuracy." : "The recorded statement stays open as uncertified information; no confirmation, rebind, or execution changes this."
|
|
2773
|
+
},
|
|
2774
|
+
attempt_fingerprint: fingerprint(p, item, closable ? "information_awaiting_delivery" : "information_non_certifiable")
|
|
2775
|
+
};
|
|
2776
|
+
}
|
|
2777
|
+
if (item.authorityDisposition === "unresolved") return {
|
|
2778
|
+
...base,
|
|
2779
|
+
certification: "unsupported",
|
|
2780
|
+
reason_code: "interpretation_unresolved",
|
|
2781
|
+
repairability: "none",
|
|
2782
|
+
missing_fields: [],
|
|
2783
|
+
missing_facets: [],
|
|
2784
|
+
next_action: {
|
|
2785
|
+
kind: "report_only",
|
|
2786
|
+
resume_condition: "The clause could not be read as a concrete instruction; it stays recorded, non-executable, and never closes by delivery. A new explicit root instruction naming a supported action supersedes it."
|
|
2787
|
+
},
|
|
2788
|
+
attempt_fingerprint: fingerprint(p, item, "interpretation_unresolved")
|
|
2789
|
+
};
|
|
2790
|
+
if (action !== "generic_run" && !item.legacyFlags?.length && item.targetCaptureStatus === "clarification_required") {
|
|
2791
|
+
const missingFields = item.targetCaptureReasonCode ? [TARGET_FIELD_REASONS[item.targetCaptureReasonCode] ?? item.targetCaptureReasonCode] : [];
|
|
2792
|
+
return {
|
|
2793
|
+
...base,
|
|
2794
|
+
certification: "needs_target",
|
|
2795
|
+
reason_code: "target_clarification_required",
|
|
2796
|
+
repairability: "user_input_required",
|
|
2797
|
+
missing_fields: missingFields,
|
|
2798
|
+
next_action: {
|
|
2799
|
+
kind: "clarify_target",
|
|
2800
|
+
tool: "context_guard_prepare",
|
|
2801
|
+
required_input: missingFields.length > 0 ? `the exact ${missingFields.join(" and ")} for this action` : "the exact target fields for this action",
|
|
2802
|
+
resume_condition: "A root-user instruction supplying the exact target re-enables certification."
|
|
2274
2803
|
},
|
|
2275
|
-
attempt_fingerprint: fingerprint(p, item, "
|
|
2804
|
+
attempt_fingerprint: fingerprint(p, item, "target_clarification_required")
|
|
2276
2805
|
};
|
|
2277
2806
|
}
|
|
2807
|
+
if (action === "generic_run" || item.legacyFlags?.length) return {
|
|
2808
|
+
...base,
|
|
2809
|
+
certification: "unsupported",
|
|
2810
|
+
reason_code: "generic_run_non_certifiable",
|
|
2811
|
+
repairability: "user_input_required",
|
|
2812
|
+
missing_fields: [],
|
|
2813
|
+
next_action: {
|
|
2814
|
+
kind: "report_only",
|
|
2815
|
+
required_input: "a concrete supported action and target for this obligation",
|
|
2816
|
+
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."
|
|
2817
|
+
},
|
|
2818
|
+
attempt_fingerprint: fingerprint(p, item, "generic_run_non_certifiable")
|
|
2819
|
+
};
|
|
2278
2820
|
if (p.hostStatus !== "supported") return {
|
|
2279
2821
|
...base,
|
|
2280
2822
|
certification: "unavailable",
|
|
@@ -2299,7 +2841,20 @@ function deriveItemDiagnosis(p, item) {
|
|
|
2299
2841
|
},
|
|
2300
2842
|
attempt_fingerprint: fingerprint(p, item, "adapter_unavailable")
|
|
2301
2843
|
};
|
|
2302
|
-
|
|
2844
|
+
const statefulChain = isStatefulAction(action);
|
|
2845
|
+
if (requiredEvidenceRoles(item).some((role) => !missing_facets.includes(role)) ? void 0 : unattributedExecutionOf(p, item)) return {
|
|
2846
|
+
...base,
|
|
2847
|
+
certification: "unsupported",
|
|
2848
|
+
reason_code: "execution_unattributable",
|
|
2849
|
+
repairability: "historical_gap",
|
|
2850
|
+
missing_fields: [],
|
|
2851
|
+
next_action: {
|
|
2852
|
+
kind: "report_only",
|
|
2853
|
+
resume_condition: "An ordinary shell command beginning with this action succeeded earlier in the session, but the command could not be securely parsed, so whether it performed the action cannot be established. Check the actual current state with a read-only command first. The obligation stays uncertified; perform the action through the guarded producer path only if the state shows it has not happened and the instruction still calls for it; never repeat an action to mint evidence, and do not assert it never ran."
|
|
2854
|
+
},
|
|
2855
|
+
attempt_fingerprint: fingerprint(p, item, "execution_unattributable")
|
|
2856
|
+
};
|
|
2857
|
+
if (statefulChain && missing_facets.includes("resolution") && !missing_facets.includes("effect")) return {
|
|
2303
2858
|
...base,
|
|
2304
2859
|
certification: "unsupported",
|
|
2305
2860
|
reason_code: "historical_evidence_gap",
|
|
@@ -2320,7 +2875,7 @@ function deriveItemDiagnosis(p, item) {
|
|
|
2320
2875
|
next_action: {
|
|
2321
2876
|
kind: "collect_evidence",
|
|
2322
2877
|
tool: "context_guard_prepare",
|
|
2323
|
-
resume_condition: "Collect the matching durable evidence in resolution/effect/state order, then checkpoint."
|
|
2878
|
+
resume_condition: statefulChain ? "Collect the matching durable evidence in resolution/effect/state order, then checkpoint." : "Collect the single matching durable verification fact, then checkpoint."
|
|
2324
2879
|
},
|
|
2325
2880
|
attempt_fingerprint: fingerprint(p, item, "missing_evidence")
|
|
2326
2881
|
};
|
|
@@ -2354,6 +2909,7 @@ const NATIVE_ADAPTERS = new Set([
|
|
|
2354
2909
|
"dsh.web.v1"
|
|
2355
2910
|
]);
|
|
2356
2911
|
function evidenceAvailabilityReason(evidence) {
|
|
2912
|
+
if (evidence.delegatedSubtask) return "delegated_result_bounded";
|
|
2357
2913
|
if (evidence.parseStatus !== "supported") return evidence.reasonCode ?? evidence.parseStatus ?? "adapter_unavailable";
|
|
2358
2914
|
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
2915
|
if (evidence.outcome !== "success") return "evidence_outcome_not_success";
|
|
@@ -2448,7 +3004,18 @@ function isFrozenV042RebindResponse(recorded) {
|
|
|
2448
3004
|
/** Whether the partition changes certification at all: a same-generic split
|
|
2449
3005
|
* is organizational at best and must not cost a user confirmation. */
|
|
2450
3006
|
function certificationGain(item, candidates) {
|
|
2451
|
-
|
|
3007
|
+
const current = item.semanticAction ?? "generic_run";
|
|
3008
|
+
if (current !== "generic_run" && item.targetCaptureStatus === "clarification_required") return candidates.some((candidate) => {
|
|
3009
|
+
if (candidate.action === void 0) return false;
|
|
3010
|
+
if (candidate.action !== current && candidate.action !== "generic_run") return true;
|
|
3011
|
+
const target = candidate.requestedTarget;
|
|
3012
|
+
if (!target) return false;
|
|
3013
|
+
const key = requestedIdentityKey(candidate.action);
|
|
3014
|
+
if (key && Object.hasOwn(target, key)) return true;
|
|
3015
|
+
if ((candidate.action === "create" || candidate.action === "modify") && target.artifact_type !== void 0 && target.scope !== void 0) return true;
|
|
3016
|
+
return false;
|
|
3017
|
+
});
|
|
3018
|
+
if (current !== "generic_run") return true;
|
|
2452
3019
|
return candidates.some((candidate) => candidate.action !== void 0 && candidate.action !== "generic_run");
|
|
2453
3020
|
}
|
|
2454
3021
|
function preservesIdentity(old, clarified) {
|
|
@@ -2918,6 +3485,17 @@ function createProjection() {
|
|
|
2918
3485
|
checkpoints: [],
|
|
2919
3486
|
boundaries: [],
|
|
2920
3487
|
externalOperations: /* @__PURE__ */ new Map(),
|
|
3488
|
+
units: /* @__PURE__ */ new Map(),
|
|
3489
|
+
coverage: [],
|
|
3490
|
+
releaseContracts: [],
|
|
3491
|
+
releaseReservations: [],
|
|
3492
|
+
releaseSettlements: [],
|
|
3493
|
+
releaseDiagnostics: [],
|
|
3494
|
+
releaseStateDamaged: false,
|
|
3495
|
+
policy: "standard",
|
|
3496
|
+
trustedSelections: [],
|
|
3497
|
+
interpretationFacts: [],
|
|
3498
|
+
approvals: [],
|
|
2921
3499
|
sessionRefDigest: "11".repeat(32),
|
|
2922
3500
|
hostLockDigest: "22".repeat(32),
|
|
2923
3501
|
hostStatus: "supported",
|
|
@@ -2929,6 +3507,7 @@ function createProjection() {
|
|
|
2929
3507
|
noProgressClaims: /* @__PURE__ */ new Map(),
|
|
2930
3508
|
handledControlSeqs: /* @__PURE__ */ new Set(),
|
|
2931
3509
|
rebindRejections: /* @__PURE__ */ new Map(),
|
|
3510
|
+
durabilityWatermark: "unknown",
|
|
2932
3511
|
integrity: "valid"
|
|
2933
3512
|
};
|
|
2934
3513
|
}
|
|
@@ -3187,6 +3766,10 @@ function hasCurrentCertificate(projection) {
|
|
|
3187
3766
|
else if (checkpoint.sessionRefDigest !== projection.sessionRefDigest) reason = "foreign_session";
|
|
3188
3767
|
else if (checkpoint.hostLockDigest !== projection.hostLockDigest) reason = "stale_host_lock";
|
|
3189
3768
|
else if (checkpoint.contractRevision !== projection.contractRevision) reason = "stale_contract_revision";
|
|
3769
|
+
else if (projection.boundaryProtocol === 5) {
|
|
3770
|
+
if (checkpoint.certificateVersion !== "2") reason = "legacy_certificate_in_v5_session";
|
|
3771
|
+
else if (checkpoint.unitId !== projection.currentUnitId) reason = "stale_unit_ref";
|
|
3772
|
+
} else if (checkpoint.certificateVersion !== "1") reason = "certificate_version_unavailable";
|
|
3190
3773
|
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
3774
|
projection.certificateStatusReason = reason;
|
|
3192
3775
|
return reason === void 0;
|
|
@@ -4073,6 +4656,74 @@ function certificationDigest(certificate) {
|
|
|
4073
4656
|
checkFieldCount(count);
|
|
4074
4657
|
return sha256Hex(Buffer.concat(parts));
|
|
4075
4658
|
}
|
|
4659
|
+
const CERTIFICATE_V2_KEYS = [
|
|
4660
|
+
"stopProtocolVersion",
|
|
4661
|
+
"certificateVersion",
|
|
4662
|
+
"epoch",
|
|
4663
|
+
"sessionRefDigest",
|
|
4664
|
+
"hostLockDigest",
|
|
4665
|
+
"contractRevision",
|
|
4666
|
+
"contractSha256",
|
|
4667
|
+
"unitId",
|
|
4668
|
+
"unitClosureDigest",
|
|
4669
|
+
"evidenceSha256",
|
|
4670
|
+
"bindingDigest",
|
|
4671
|
+
"goalRef"
|
|
4672
|
+
];
|
|
4673
|
+
/**
|
|
4674
|
+
* 0.6.0 v2 certificate field table over the new `ccg.certificationDigest.v4`
|
|
4675
|
+
* domain (P0 §1): the certified scope is a work unit's closure instead of the
|
|
4676
|
+
* whole session. digest_v3 domains and their golden vectors stay frozen; this
|
|
4677
|
+
* function never re-reads a v1 record.
|
|
4678
|
+
*/
|
|
4679
|
+
function certificationDigestV2(certificate) {
|
|
4680
|
+
requireExactKeys(certificate, CERTIFICATE_V2_KEYS, "certificateV2");
|
|
4681
|
+
const parts = [Buffer.from("ccg.certificationDigest.v4\n", "utf8")];
|
|
4682
|
+
let count = 0;
|
|
4683
|
+
parts.push(field("stopProtocolVersion", typedToken(expectString(certificate.stopProtocolVersion, "stopProtocolVersion"))));
|
|
4684
|
+
parts.push(field("certificateVersion", typedToken(expectString(certificate.certificateVersion, "certificateVersion"))));
|
|
4685
|
+
parts.push(field("epoch", typedToken(expectInt(certificate.epoch, "epoch"))));
|
|
4686
|
+
parts.push(field("sessionRefDigest", typedToken({
|
|
4687
|
+
k: "x",
|
|
4688
|
+
v: expectHex(certificate.sessionRefDigest)
|
|
4689
|
+
})));
|
|
4690
|
+
parts.push(field("hostLockDigest", typedToken({
|
|
4691
|
+
k: "x",
|
|
4692
|
+
v: expectHex(certificate.hostLockDigest)
|
|
4693
|
+
})));
|
|
4694
|
+
parts.push(field("contractRevision", typedToken(expectInt(certificate.contractRevision, "contractRevision"))));
|
|
4695
|
+
parts.push(field("contractSha256", typedToken({
|
|
4696
|
+
k: "x",
|
|
4697
|
+
v: expectHex(certificate.contractSha256)
|
|
4698
|
+
})));
|
|
4699
|
+
count += 7;
|
|
4700
|
+
parts.push(field("unitId", typedToken(expectString(certificate.unitId, "unitId"))));
|
|
4701
|
+
parts.push(field("unitClosureDigest", typedToken({
|
|
4702
|
+
k: "x",
|
|
4703
|
+
v: expectHex(certificate.unitClosureDigest)
|
|
4704
|
+
})));
|
|
4705
|
+
parts.push(field("evidenceSha256", typedToken({
|
|
4706
|
+
k: "x",
|
|
4707
|
+
v: expectHex(certificate.evidenceSha256)
|
|
4708
|
+
})));
|
|
4709
|
+
parts.push(field("bindingDigest", typedToken({
|
|
4710
|
+
k: "x",
|
|
4711
|
+
v: expectHex(certificate.bindingDigest)
|
|
4712
|
+
})));
|
|
4713
|
+
count += 4;
|
|
4714
|
+
const goalRef = certificate.goalRef;
|
|
4715
|
+
if (goalRef !== void 0 && goalRef !== null) {
|
|
4716
|
+
requireExactKeys(goalRef, GOAL_REF_KEYS, "goalRef");
|
|
4717
|
+
parts.push(optField("goalRefId", expectString(goalRef.id, "goalRef.id"), (v) => typedToken(v)));
|
|
4718
|
+
parts.push(optField("goalRefRevision", expectInt(goalRef.revision, "goalRef.revision"), (v) => typedToken(v)));
|
|
4719
|
+
} else {
|
|
4720
|
+
parts.push(optField("goalRefId", null, () => Buffer.alloc(0)));
|
|
4721
|
+
parts.push(optField("goalRefRevision", null, () => Buffer.alloc(0)));
|
|
4722
|
+
}
|
|
4723
|
+
count += 2;
|
|
4724
|
+
checkFieldCount(count);
|
|
4725
|
+
return sha256Hex(Buffer.concat(parts));
|
|
4726
|
+
}
|
|
4076
4727
|
/**
|
|
4077
4728
|
* Verifier-side role matrix and binding closure. Digest derivation stays
|
|
4078
4729
|
* pure; this mirrors the checks a proof verifier must run before accepting a
|
|
@@ -4389,7 +5040,7 @@ function closingHint(projection, item, evidenceIds) {
|
|
|
4389
5040
|
else parts.push("needs a state-verification evidence (read tool, or a deterministic check run in scope) matching the subject");
|
|
4390
5041
|
return parts.join("; ");
|
|
4391
5042
|
}
|
|
4392
|
-
function openItems
|
|
5043
|
+
function openItems(projection) {
|
|
4393
5044
|
return [...projection.items.values()].filter((item) => item.status === "pending").sort((a, b) => a.revision - b.revision || (a.id < b.id ? -1 : 1));
|
|
4394
5045
|
}
|
|
4395
5046
|
/**
|
|
@@ -4399,7 +5050,7 @@ function openItems$1(projection) {
|
|
|
4399
5050
|
* injected once instead of looping (v0.2.1).
|
|
4400
5051
|
*/
|
|
4401
5052
|
function recoveryDigest(packet, projection) {
|
|
4402
|
-
const items = openItems
|
|
5053
|
+
const items = openItems(projection);
|
|
4403
5054
|
const evidence = [...projection.evidence.values()].filter((row) => items.some((item) => relevantEvidence(projection, item, row)));
|
|
4404
5055
|
return sha256(JSON.stringify({
|
|
4405
5056
|
packet,
|
|
@@ -4413,7 +5064,7 @@ function renderRecoveryPacket(projection, options = {}) {
|
|
|
4413
5064
|
const budget = options.charBudget ?? DEFAULT_RECOVERY_CHAR_BUDGET;
|
|
4414
5065
|
if (!Number.isSafeInteger(budget) || budget < MIN_RECOVERY_CHAR_BUDGET) throw new RangeError("recovery charBudget must be an integer >= 512");
|
|
4415
5066
|
const clip = (text, size) => text.length <= size ? text : text.slice(0, size - 1) + "…";
|
|
4416
|
-
const items = openItems
|
|
5067
|
+
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
5068
|
const rejected$1 = options.rejectedBindings ?? (projection.lastCheckpointRejectionRevision === projection.contractRevision ? projection.lastCheckpointRejections : []) ?? [];
|
|
4418
5069
|
const compact = budget < 1e3;
|
|
4419
5070
|
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 +5091,7 @@ function renderRecoveryPacket(projection, options = {}) {
|
|
|
4440
5091
|
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
5092
|
return;
|
|
4442
5093
|
}
|
|
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";
|
|
5094
|
+
const remedy = diagnosis.repairability === "agent_repairable" ? "Collect matching evidence; checkpoint" : diagnosis.repairability === "historical_gap" ? "Read back observed state; do not re-execute" : diagnosis.repairability === "none" ? "Recorded as unresolved; only a fresh explicit instruction resolves it" : diagnosis.next_action.tool === "context_guard_interpret" ? "Read the attachment; record context_guard_interpret; then answer" : 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
5095
|
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
5096
|
};
|
|
4446
5097
|
if (constraints[0]) constraint(constraints[0]);
|
|
@@ -4457,28 +5108,994 @@ function renderRecoveryPacket(projection, options = {}) {
|
|
|
4457
5108
|
}
|
|
4458
5109
|
|
|
4459
5110
|
//#endregion
|
|
4460
|
-
//#region src/domain/
|
|
4461
|
-
|
|
4462
|
-
|
|
4463
|
-
|
|
4464
|
-
|
|
5111
|
+
//#region src/domain/work-unit.ts
|
|
5112
|
+
/**
|
|
5113
|
+
* Work-unit derivation rules (0.6.0, C04). Units are derived from the durable
|
|
5114
|
+
* message stream — nothing is ever written to the log — so the classification
|
|
5115
|
+
* below must stay deterministic and conservative: an ambiguous relation keeps
|
|
5116
|
+
* the current unit rather than inventing a new one, and a mis-assigned
|
|
5117
|
+
* obligation is recoverable through clarification, never through a silent
|
|
5118
|
+
* unit rewrite.
|
|
5119
|
+
*
|
|
5120
|
+
* The rules are the frozen P0 §3 C04 decision, in evaluation order:
|
|
5121
|
+
*
|
|
5122
|
+
* 1. The session's first root task message opens U001.
|
|
5123
|
+
* 2. A message explicitly linked to the current unit (item-ID reference,
|
|
5124
|
+
* rebind control, a direct answer while an inquiry is open) stays in it.
|
|
5125
|
+
* 3. When the current unit has no open executable obligations left, a
|
|
5126
|
+
* directive-bearing message opens a new unit; the old one is switched away,
|
|
5127
|
+
* never retroactively closed.
|
|
5128
|
+
* 4. While the current unit still has open work, only an EXPLICIT switch
|
|
5129
|
+
* marker (closed vocabulary, fixture-pinned) opens a new unit; anything
|
|
5130
|
+
* else stays in the current unit.
|
|
5131
|
+
* 5. A DELEGATION-marked message opens a CHILD unit of the current unit. The
|
|
5132
|
+
* child's open obligations are required descendants of the parent's
|
|
5133
|
+
* closure (C04), so the parent cannot be certified while the delegated
|
|
5134
|
+
* work is open, and the delegated result itself never closes the parent.
|
|
5135
|
+
*/
|
|
5136
|
+
/** Explicit task-switch markers; a closed vocabulary pinned by the v2 fixture. */
|
|
5137
|
+
const SWITCH_MARKER = new RegExp([
|
|
5138
|
+
"^(?:另外|此外|另一(?:件事|个任务|个话题)|换个?话题|下一个任务|新任务|下一个问题|先做(?:另一|别的))[::,,。。\\s]",
|
|
5139
|
+
"^(?:now\\s+a\\s+)?(?:different|new|separate)\\s+task\\b",
|
|
5140
|
+
"^next\\s+task\\b",
|
|
5141
|
+
"^(?:on\\s+a\\s+related\\s+note|by\\s+the\\s+way)\\b"
|
|
5142
|
+
].join("|"), "i");
|
|
5143
|
+
/**
|
|
5144
|
+
* Explicit delegation markers; the same closed-vocabulary discipline as the
|
|
5145
|
+
* switch markers, pinned by the v2 fixture. Only a root message that actually
|
|
5146
|
+
* hands work to a subagent/subtask opens a child unit — "let the subagent …",
|
|
5147
|
+
* "delegate … to a subagent", "spawn a subagent …".
|
|
5148
|
+
*/
|
|
5149
|
+
const DELEGATION_MARKER = new RegExp([
|
|
5150
|
+
"(?:让|由|交给|委派给?|派给|安排)(?:一个)?(?:子代理|子任务|子会话|小助手)",
|
|
5151
|
+
"(?:子代理|子任务|子会话)(?:去|来|负责|执行|完成)",
|
|
5152
|
+
"\\bdelegate\\s+(?:this|it|the\\s+\\w+|\\w+)\\s+to\\s+(?:a\\s+|the\\s+)?(?:subagent|sub-agent|child\\s+agent)\\b",
|
|
5153
|
+
"\\b(?:spawn|dispatch|hand\\s+(?:this|it)\\s+off\\s+to)\\s+(?:a\\s+|the\\s+)?(?:subagent|sub-agent|child\\s+agent)\\b",
|
|
5154
|
+
"\\bsub-?agent\\s+(?:should|must|to)\\s+\\w+"
|
|
5155
|
+
].join("|"), "i");
|
|
5156
|
+
/**
|
|
5157
|
+
* Whether a root message opens a new work unit rather than joining the
|
|
5158
|
+
* current one. `directiveBearing` says the message produced (or would
|
|
5159
|
+
* produce) requirement/acceptance work; `openWorkInCurrentUnit` is evaluated
|
|
5160
|
+
* against the state BEFORE the message is captured.
|
|
5161
|
+
*/
|
|
5162
|
+
function opensNewUnit(projection, text, directiveBearing, openWorkInCurrentUnit) {
|
|
5163
|
+
if (!directiveBearing) return false;
|
|
5164
|
+
if (DELEGATION_MARKER.test(text)) return true;
|
|
5165
|
+
if (SWITCH_MARKER.test(text)) return true;
|
|
5166
|
+
return !openWorkInCurrentUnit;
|
|
4465
5167
|
}
|
|
4466
|
-
|
|
4467
|
-
|
|
5168
|
+
/**
|
|
5169
|
+
* Whether this message opens a child (delegated) unit of the current unit
|
|
5170
|
+
* rather than a sibling. Only meaningful together with {@link opensNewUnit}.
|
|
5171
|
+
*/
|
|
5172
|
+
function opensChildUnit(projection, text) {
|
|
5173
|
+
return projection.currentUnitId !== void 0 && DELEGATION_MARKER.test(text);
|
|
4468
5174
|
}
|
|
4469
|
-
|
|
4470
|
-
|
|
5175
|
+
/** An explicit reference to a contract item identity (R001/A001/P001/U001). */
|
|
5176
|
+
const ITEM_REFERENCE = /\b([RAPU]\d{3})\b/g;
|
|
5177
|
+
/**
|
|
5178
|
+
* Whether the message explicitly links itself to the current unit's items.
|
|
5179
|
+
*
|
|
5180
|
+
* A reference counts only when it names an item that still exists as live work:
|
|
5181
|
+
* an ID that never existed, or one already `passed`/`superseded`, is history and
|
|
5182
|
+
* cannot pull a new instruction back into an old unit. A live item binds when it
|
|
5183
|
+
* belongs to the current unit's lineage — the current unit, an ancestor, or a
|
|
5184
|
+
* required descendant — while a unit-less (pre-v5) obligation is always a
|
|
5185
|
+
* legitimate continuation target.
|
|
5186
|
+
*/
|
|
5187
|
+
function explicitlyLinkedToCurrentUnit(projection, text) {
|
|
5188
|
+
const current = projection.currentUnitId;
|
|
5189
|
+
const lineage = current === void 0 ? void 0 : new Set([
|
|
5190
|
+
current,
|
|
5191
|
+
...unitAncestorIds(projection, current),
|
|
5192
|
+
...unitDescendantIds(projection, current)
|
|
5193
|
+
]);
|
|
5194
|
+
for (const match of text.matchAll(ITEM_REFERENCE)) {
|
|
5195
|
+
const item = projection.items.get(match[1]);
|
|
5196
|
+
if (!item) continue;
|
|
5197
|
+
if (item.status === "passed" || item.status === "superseded") continue;
|
|
5198
|
+
if (lineage === void 0 || item.unitId === void 0) return true;
|
|
5199
|
+
if (lineage.has(item.unitId)) return true;
|
|
5200
|
+
}
|
|
5201
|
+
return false;
|
|
4471
5202
|
}
|
|
4472
|
-
|
|
4473
|
-
|
|
4474
|
-
|
|
4475
|
-
|
|
5203
|
+
/** The next unit identity in the session's sequence. */
|
|
5204
|
+
function nextUnitId(projection) {
|
|
5205
|
+
let max = 0;
|
|
5206
|
+
for (const unitId of projection.units.keys()) {
|
|
5207
|
+
const num = Number(unitId.slice(1));
|
|
5208
|
+
if (Number.isInteger(num) && num > max) max = num;
|
|
5209
|
+
}
|
|
5210
|
+
return `U${String(max + 1).padStart(3, "0")}`;
|
|
4476
5211
|
}
|
|
4477
|
-
|
|
4478
|
-
|
|
4479
|
-
|
|
4480
|
-
|
|
4481
|
-
|
|
5212
|
+
/**
|
|
5213
|
+
* Open a work unit.
|
|
5214
|
+
*
|
|
5215
|
+
* A SIBLING unit (no parent) becomes current and switches the previous current
|
|
5216
|
+
* unit away: that is a task switch, and the old unit's residual work stays
|
|
5217
|
+
* visible but no longer blocks the new task.
|
|
5218
|
+
*
|
|
5219
|
+
* A CHILD unit (delegated sub-unit) does NOT become current. The parent keeps
|
|
5220
|
+
* owning the session's certified scope, so the parent's own obligations are
|
|
5221
|
+
* never dropped when it delegates part of the work — the child's obligations
|
|
5222
|
+
* join the parent's closure as required descendants instead (C04). The child
|
|
5223
|
+
* is only ever created under an existing parent; a stray parent id would
|
|
5224
|
+
* create an orphan lineage, so it is dropped.
|
|
5225
|
+
*/
|
|
5226
|
+
function openUnit(projection, seq, headline, parentUnitId) {
|
|
5227
|
+
const unitId = nextUnitId(projection);
|
|
5228
|
+
const parent = parentUnitId !== void 0 && projection.units.has(parentUnitId) ? parentUnitId : void 0;
|
|
5229
|
+
if (parent === void 0) {
|
|
5230
|
+
const previous = projection.currentUnitId !== void 0 ? projection.units.get(projection.currentUnitId) : void 0;
|
|
5231
|
+
if (previous && previous.switchedAwayAtSeq === void 0) previous.switchedAwayAtSeq = seq;
|
|
5232
|
+
projection.currentUnitId = unitId;
|
|
5233
|
+
}
|
|
5234
|
+
const unit = {
|
|
5235
|
+
unitId,
|
|
5236
|
+
openedAtSeq: seq,
|
|
5237
|
+
rootInputRefs: [{ seq }],
|
|
5238
|
+
headline,
|
|
5239
|
+
...parent !== void 0 ? { parentUnitId: parent } : {}
|
|
5240
|
+
};
|
|
5241
|
+
projection.units.set(unitId, unit);
|
|
5242
|
+
return unit;
|
|
5243
|
+
}
|
|
5244
|
+
/** Fold one later root message into the current unit's input references. */
|
|
5245
|
+
function foldIntoCurrentUnit(projection, seq) {
|
|
5246
|
+
const unit = projection.currentUnitId !== void 0 ? projection.units.get(projection.currentUnitId) : void 0;
|
|
5247
|
+
if (unit) unit.rootInputRefs.push({ seq });
|
|
5248
|
+
}
|
|
5249
|
+
/**
|
|
5250
|
+
* The ancestors of `unitId`, nearest first. Lineage is derived from the
|
|
5251
|
+
* derived `parentUnitId` chain; a cycle (impossible from the derivation, but
|
|
5252
|
+
* possible in a hand-built projection) terminates instead of hanging.
|
|
5253
|
+
*/
|
|
5254
|
+
function unitAncestorIds(projection, unitId) {
|
|
5255
|
+
const ancestors = [];
|
|
5256
|
+
const seen = new Set([unitId]);
|
|
5257
|
+
let cursor = projection.units.get(unitId)?.parentUnitId;
|
|
5258
|
+
while (cursor !== void 0 && !seen.has(cursor)) {
|
|
5259
|
+
ancestors.push(cursor);
|
|
5260
|
+
seen.add(cursor);
|
|
5261
|
+
cursor = projection.units.get(cursor)?.parentUnitId;
|
|
5262
|
+
}
|
|
5263
|
+
return ancestors;
|
|
5264
|
+
}
|
|
5265
|
+
/**
|
|
5266
|
+
* Every required descendant of `unitId`, in stable unit order: the units whose
|
|
5267
|
+
* `parentUnitId` chain reaches `unitId`. The closure of a unit includes the
|
|
5268
|
+
* open obligations of this set (C04).
|
|
5269
|
+
*/
|
|
5270
|
+
function unitDescendantIds(projection, unitId) {
|
|
5271
|
+
const descendants = [];
|
|
5272
|
+
const seen = new Set([unitId]);
|
|
5273
|
+
const queue = [unitId];
|
|
5274
|
+
while (queue.length > 0) {
|
|
5275
|
+
const current = queue.shift();
|
|
5276
|
+
for (const unit of projection.units.values()) {
|
|
5277
|
+
if (unit.parentUnitId !== current || seen.has(unit.unitId)) continue;
|
|
5278
|
+
seen.add(unit.unitId);
|
|
5279
|
+
descendants.push(unit.unitId);
|
|
5280
|
+
queue.push(unit.unitId);
|
|
5281
|
+
}
|
|
5282
|
+
}
|
|
5283
|
+
return descendants.sort();
|
|
5284
|
+
}
|
|
5285
|
+
/** Record one delegated round-trip inside a unit as bounded audit evidence. */
|
|
5286
|
+
function recordDelegation(projection, unitId, ref) {
|
|
5287
|
+
const unit = projection.units.get(unitId);
|
|
5288
|
+
if (!unit) return;
|
|
5289
|
+
const refs = unit.delegationRefs ?? [];
|
|
5290
|
+
if (refs.some((entry) => entry.callId === ref.callId)) return;
|
|
5291
|
+
refs.push({ ...ref });
|
|
5292
|
+
unit.delegationRefs = refs;
|
|
5293
|
+
}
|
|
5294
|
+
/**
|
|
5295
|
+
* Whether the current unit still holds open executable work — the rule-3
|
|
5296
|
+
* handover test, evaluated BEFORE the new message's items are inserted.
|
|
5297
|
+
*
|
|
5298
|
+
* This is the SAME closure the certificate uses: the current unit's own open
|
|
5299
|
+
* work plus the open work of every required descendant unit. A parent whose own
|
|
5300
|
+
* items are all passed but whose delegated child is still open has not finished,
|
|
5301
|
+
* so an ordinary follow-up must not be treated as a handover to a new sibling
|
|
5302
|
+
* task — that would silently exclude the child from the certified scope.
|
|
5303
|
+
*/
|
|
5304
|
+
function currentUnitHasOpenWork(projection) {
|
|
5305
|
+
const current = projection.currentUnitId;
|
|
5306
|
+
if (current === void 0) return false;
|
|
5307
|
+
const closure = new Set([current, ...unitDescendantIds(projection, current)]);
|
|
5308
|
+
return [...projection.items.values()].some((item) => item.status === "pending" && item.unitId !== void 0 && closure.has(item.unitId) && item.kind !== "prohibition");
|
|
5309
|
+
}
|
|
5310
|
+
|
|
5311
|
+
//#endregion
|
|
5312
|
+
//#region src/domain/closure.ts
|
|
5313
|
+
/**
|
|
5314
|
+
* The single open-closure implementation (0.6.0, C02/C04/D06-03/D06-07).
|
|
5315
|
+
*
|
|
5316
|
+
* Before 0.6.0, checkpoint, recovery, diagnostics, and the Goal gate each
|
|
5317
|
+
* filtered pending obligations with their own slightly different rule, and the
|
|
5318
|
+
* answers could disagree. Every question about "what is open" now goes through
|
|
5319
|
+
* this module:
|
|
5320
|
+
*
|
|
5321
|
+
* - {@link visiblePendingItems} — everything still pending, constraints first
|
|
5322
|
+
* in spirit: display surfaces (recovery, status, checkpoint pages) show
|
|
5323
|
+
* prohibitions too, because a constraint is never finished work.
|
|
5324
|
+
* - {@link certifiableOpenItems} — the obligations a completion certificate
|
|
5325
|
+
* answers for: pending, not a prohibition. Prohibitions are standing
|
|
5326
|
+
* constraints, never counted work; `answered` items closed by a trusted
|
|
5327
|
+
* delivery are no longer open; `passed` and `superseded` never were.
|
|
5328
|
+
* - {@link unitClosureItemIds} — the v5 unit closure: the certified scope of
|
|
5329
|
+
* one work unit, which is the unit's OWN open obligations PLUS the open
|
|
5330
|
+
* obligations of every required descendant unit.
|
|
5331
|
+
* - {@link ancestorConstraints} / {@link ancestorConstraintForBinding} — the
|
|
5332
|
+
* ancestor units' standing constraints (prohibitions and unsatisfied
|
|
5333
|
+
* conditions) that stay in force for a descendant's matching obligations.
|
|
5334
|
+
*
|
|
5335
|
+
* Legacy sessions (no v5 boundary) have no units: they certify the whole
|
|
5336
|
+
* session, exactly what {@link certifiableOpenItems} returns.
|
|
5337
|
+
*/
|
|
5338
|
+
/** Every pending item, in stable display order. Constraints stay visible. */
|
|
5339
|
+
function visiblePendingItems(projection) {
|
|
5340
|
+
return [...projection.items.values()].filter((item) => item.status === "pending").sort((a, b) => a.revision - b.revision || (a.id < b.id ? -1 : 1));
|
|
5341
|
+
}
|
|
5342
|
+
/** The obligations a completion certificate answers for: open work, no constraints. */
|
|
5343
|
+
function certifiableOpenItems(projection) {
|
|
5344
|
+
return visiblePendingItems(projection).filter((item) => item.kind !== "prohibition");
|
|
5345
|
+
}
|
|
5346
|
+
/**
|
|
5347
|
+
* The certifiable open obligations inside one work unit's closure: the unit's
|
|
5348
|
+
* own open work plus the open work of every required descendant unit.
|
|
5349
|
+
*
|
|
5350
|
+
* A delegated child unit is REQUIRED work of its parent (C04): the parent has
|
|
5351
|
+
* not finished while the sub-unit it handed work to still has open
|
|
5352
|
+
* obligations, so the parent's certificate must answer for them too. The
|
|
5353
|
+
* reverse is deliberately not true — a child may be certified while unrelated
|
|
5354
|
+
* residual work exists in an ancestor or a sibling, which is what keeps a task
|
|
5355
|
+
* switch from being blocked by history.
|
|
5356
|
+
*/
|
|
5357
|
+
function unitClosureItemIds(projection, unitId) {
|
|
5358
|
+
if (projection.boundaryProtocol !== 5) return [];
|
|
5359
|
+
const inClosure = new Set([unitId, ...unitDescendantIds(projection, unitId)]);
|
|
5360
|
+
return certifiableOpenItems(projection).filter((item) => item.unitId !== void 0 && inClosure.has(item.unitId)).map((item) => item.id);
|
|
5361
|
+
}
|
|
5362
|
+
/** Whether a prohibition declares no identity at all — a blanket ban on the action. */
|
|
5363
|
+
function isBlanketProhibition(action, requested) {
|
|
5364
|
+
const key = requestedIdentityKey(action);
|
|
5365
|
+
if (!key) return false;
|
|
5366
|
+
return !requested || !Object.hasOwn(requested, key);
|
|
5367
|
+
}
|
|
5368
|
+
/** The ancestor obligations that act as standing constraints on this unit. */
|
|
5369
|
+
function standingAncestorConstraints(projection, unitId) {
|
|
5370
|
+
if (projection.boundaryProtocol !== 5) return [];
|
|
5371
|
+
const ancestors = unitAncestorIds(projection, unitId);
|
|
5372
|
+
if (ancestors.length === 0) return [];
|
|
5373
|
+
return visiblePendingItems(projection).filter((item) => {
|
|
5374
|
+
if (item.unitId === void 0 || !ancestors.includes(item.unitId)) return false;
|
|
5375
|
+
if (item.kind === "prohibition") return true;
|
|
5376
|
+
return item.authorityDisposition === "conditional_wait" || item.waitAuthorization !== void 0;
|
|
5377
|
+
});
|
|
5378
|
+
}
|
|
5379
|
+
/**
|
|
5380
|
+
* The ancestor constraint that blocks certifying `item` against a resolved
|
|
5381
|
+
* target, if any. This is the authoritative judge used by the certifier: the
|
|
5382
|
+
* ancestor constraint is compared with the SAME conservative identity rule the
|
|
5383
|
+
* mutation authorization uses, so a ban or an unsatisfied condition cannot be
|
|
5384
|
+
* discharged by certifying a descendant obligation that resolves the target
|
|
5385
|
+
* the ancestor constrained.
|
|
5386
|
+
*/
|
|
5387
|
+
function ancestorConstraintForBinding(projection, item, resolvedTarget) {
|
|
5388
|
+
if (item.unitId === void 0) return void 0;
|
|
5389
|
+
const action = item.semanticAction;
|
|
5390
|
+
if (!action || action === "generic_run" || !isStatefulAction(action)) return void 0;
|
|
5391
|
+
for (const constraint of standingAncestorConstraints(projection, item.unitId)) {
|
|
5392
|
+
if (constraint.id === item.id || constraint.semanticAction !== action) continue;
|
|
5393
|
+
if (!(constraint.kind === "prohibition" && isBlanketProhibition(action, constraint.requestedTarget) || requestedTargetMatchesResolved(action, constraint.requestedTarget, resolvedTarget))) continue;
|
|
5394
|
+
return {
|
|
5395
|
+
constraintId: constraint.id,
|
|
5396
|
+
constraintUnitId: constraint.unitId,
|
|
5397
|
+
itemId: item.id,
|
|
5398
|
+
kind: constraint.kind === "prohibition" ? "prohibition" : "condition",
|
|
5399
|
+
reasonCode: constraint.kind === "prohibition" ? "ancestor_prohibition_active" : "ancestor_condition_unsatisfied"
|
|
5400
|
+
};
|
|
5401
|
+
}
|
|
5402
|
+
}
|
|
5403
|
+
/**
|
|
5404
|
+
* The closure a completion certificate must answer for right now.
|
|
5405
|
+
*
|
|
5406
|
+
* Legacy sessions certify the whole session. v5 sessions certify the current
|
|
5407
|
+
* work unit's closure PLUS every pre-v5 obligation: items captured before the
|
|
5408
|
+
* boundary carry no unit and keep their birth rules, so a unit certificate
|
|
5409
|
+
* must never silently shrink their scope (migration table, P0 §6).
|
|
5410
|
+
*/
|
|
5411
|
+
function certificateClosure(projection) {
|
|
5412
|
+
if (projection.boundaryProtocol === 5) {
|
|
5413
|
+
const legacyIds = certifiableOpenItems(projection).filter((item) => item.unitId === void 0).map((item) => item.id);
|
|
5414
|
+
const unitIds = projection.currentUnitId !== void 0 ? unitClosureItemIds(projection, projection.currentUnitId) : [];
|
|
5415
|
+
return {
|
|
5416
|
+
unitId: projection.currentUnitId,
|
|
5417
|
+
itemIds: [...legacyIds, ...unitIds]
|
|
5418
|
+
};
|
|
5419
|
+
}
|
|
5420
|
+
return { itemIds: certifiableOpenItems(projection).map((item) => item.id) };
|
|
5421
|
+
}
|
|
5422
|
+
|
|
5423
|
+
//#endregion
|
|
5424
|
+
//#region src/domain/proof.ts
|
|
5425
|
+
const PROOF_PROTOCOL_VERSION = "0.4.0";
|
|
5426
|
+
const PROOF_KINDS = [
|
|
5427
|
+
"subject_readback",
|
|
5428
|
+
"scope_coverage",
|
|
5429
|
+
"state_verification"
|
|
5430
|
+
];
|
|
5431
|
+
function stable$2(value) {
|
|
5432
|
+
if (Array.isArray(value)) return `[${value.map(stable$2).join(",")}]`;
|
|
5433
|
+
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(",")}}`;
|
|
5434
|
+
return JSON.stringify(value);
|
|
5435
|
+
}
|
|
5436
|
+
function digest(value) {
|
|
5437
|
+
return createHash("sha256").update("ccg.proofManifest.v1\n", "utf8").update(stable$2(value), "utf8").digest("hex");
|
|
5438
|
+
}
|
|
5439
|
+
function validDigest(value) {
|
|
5440
|
+
return /^[0-9a-f]{64}$/.test(value);
|
|
5441
|
+
}
|
|
5442
|
+
/**
|
|
5443
|
+
* The manifest digest root includes every integrity-bearing field, so a
|
|
5444
|
+
* tampered asset-set digest is exactly as detectable as a tampered obligation.
|
|
5445
|
+
*/
|
|
5446
|
+
function proofDigest(obligations, assetSetSha256) {
|
|
5447
|
+
return digest({
|
|
5448
|
+
proofProtocolVersion: PROOF_PROTOCOL_VERSION,
|
|
5449
|
+
obligations: [...obligations],
|
|
5450
|
+
...assetSetSha256 !== void 0 ? { assetSetSha256 } : {}
|
|
5451
|
+
});
|
|
5452
|
+
}
|
|
5453
|
+
function validateProofManifest(manifest) {
|
|
5454
|
+
const errors = [];
|
|
5455
|
+
if (!manifest || typeof manifest !== "object") return ["proof_manifest_invalid"];
|
|
5456
|
+
const value = manifest;
|
|
5457
|
+
if (value.proofProtocolVersion !== PROOF_PROTOCOL_VERSION) errors.push("proof_protocol_version_mismatch");
|
|
5458
|
+
if (!Array.isArray(value.obligations)) errors.push("proof_obligations_missing");
|
|
5459
|
+
if (value.assetSetSha256 !== void 0 && (typeof value.assetSetSha256 !== "string" || !validDigest(value.assetSetSha256))) errors.push("proof_asset_set_digest_invalid");
|
|
5460
|
+
if (typeof value.proofSha256 !== "string" || !validDigest(value.proofSha256)) errors.push("proof_digest_invalid");
|
|
5461
|
+
const obligations = Array.isArray(value.obligations) ? value.obligations : [];
|
|
5462
|
+
const ids = /* @__PURE__ */ new Set();
|
|
5463
|
+
for (const raw of obligations) {
|
|
5464
|
+
if (!raw || typeof raw !== "object") {
|
|
5465
|
+
errors.push("proof_obligation_invalid");
|
|
5466
|
+
continue;
|
|
5467
|
+
}
|
|
5468
|
+
const obligation = raw;
|
|
5469
|
+
if (typeof obligation.obligationId !== "string" || ids.has(obligation.obligationId)) errors.push("proof_obligation_id_duplicate_or_invalid");
|
|
5470
|
+
if (typeof obligation.obligationId === "string") ids.add(obligation.obligationId);
|
|
5471
|
+
if (!PROOF_KINDS.includes(obligation.kind)) errors.push("proof_kind_unsupported");
|
|
5472
|
+
if (![
|
|
5473
|
+
"artifact",
|
|
5474
|
+
"ui",
|
|
5475
|
+
"visual",
|
|
5476
|
+
"scope"
|
|
5477
|
+
].includes(String(obligation.surface))) errors.push("proof_surface_unsupported");
|
|
5478
|
+
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");
|
|
5479
|
+
if (!Array.isArray(obligation.evidenceIds) || obligation.evidenceIds.length === 0 || new Set(obligation.evidenceIds).size !== obligation.evidenceIds.length) errors.push("proof_evidence_invalid");
|
|
5480
|
+
const expected = obligation.expectedScopeDigest;
|
|
5481
|
+
const observed = obligation.observedScopeDigest;
|
|
5482
|
+
for (const digestValue of [expected, observed]) if (digestValue !== void 0 && (typeof digestValue !== "string" || !validDigest(digestValue))) errors.push("proof_scope_digest_invalid");
|
|
5483
|
+
if (expected !== void 0 && observed !== expected) errors.push("proof_scope_digest_mismatch");
|
|
5484
|
+
if (expected === void 0 && observed !== void 0) errors.push("proof_scope_digest_mismatch");
|
|
5485
|
+
}
|
|
5486
|
+
if (errors.length === 0) {
|
|
5487
|
+
const assetSet = typeof value.assetSetSha256 === "string" ? value.assetSetSha256 : void 0;
|
|
5488
|
+
if (value.proofSha256 !== proofDigest(obligations, assetSet)) errors.push("proof_digest_mismatch");
|
|
5489
|
+
}
|
|
5490
|
+
return [...new Set(errors)];
|
|
5491
|
+
}
|
|
5492
|
+
function createProofManifest(obligations, assetSetSha256) {
|
|
5493
|
+
const normalized = obligations.map((obligation) => ({
|
|
5494
|
+
obligationId: obligation.obligationId,
|
|
5495
|
+
kind: obligation.kind,
|
|
5496
|
+
surface: obligation.surface,
|
|
5497
|
+
subjectIds: [...obligation.subjectIds].sort(),
|
|
5498
|
+
evidenceIds: [...obligation.evidenceIds].sort(),
|
|
5499
|
+
...obligation.expectedScopeDigest ? { expectedScopeDigest: obligation.expectedScopeDigest } : {},
|
|
5500
|
+
...obligation.observedScopeDigest ? { observedScopeDigest: obligation.observedScopeDigest } : {}
|
|
5501
|
+
})).sort((a, b) => a.obligationId.localeCompare(b.obligationId));
|
|
5502
|
+
const manifest = {
|
|
5503
|
+
proofProtocolVersion: PROOF_PROTOCOL_VERSION,
|
|
5504
|
+
obligations: normalized,
|
|
5505
|
+
...assetSetSha256 !== void 0 ? { assetSetSha256 } : {},
|
|
5506
|
+
proofSha256: proofDigest(normalized, assetSetSha256)
|
|
5507
|
+
};
|
|
5508
|
+
const errors = validateProofManifest(manifest);
|
|
5509
|
+
if (errors.length) throw new Error(`proof manifest rejected: ${errors.join(",")}`);
|
|
5510
|
+
return manifest;
|
|
5511
|
+
}
|
|
5512
|
+
/**
|
|
5513
|
+
* Bind a structurally valid proof to the actual replayed projection: every
|
|
5514
|
+
* obligation must name a pending item, every evidence id must exist in the
|
|
5515
|
+
* projection, and every bound evidence must satisfy the obligation's kind,
|
|
5516
|
+
* surface, subject, and outcome constraints. An empty projection therefore
|
|
5517
|
+
* rejects any proof, and cross-item or foreign evidence can never bind.
|
|
5518
|
+
*/
|
|
5519
|
+
function bindProofToProjection(projection, proof) {
|
|
5520
|
+
const errors = [];
|
|
5521
|
+
const items = projection.items;
|
|
5522
|
+
const evidence = projection.evidence;
|
|
5523
|
+
for (const obligation of proof.obligations) {
|
|
5524
|
+
const item = items.get(obligation.obligationId);
|
|
5525
|
+
if (!item) {
|
|
5526
|
+
errors.push("proof_obligation_unbound");
|
|
5527
|
+
continue;
|
|
5528
|
+
}
|
|
5529
|
+
if (item.status !== "pending") {
|
|
5530
|
+
errors.push("proof_obligation_not_pending");
|
|
5531
|
+
continue;
|
|
5532
|
+
}
|
|
5533
|
+
if (item.verification.surface !== void 0 && item.verification.surface !== obligation.surface) errors.push("proof_surface_unbound");
|
|
5534
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5535
|
+
for (const evidenceId of obligation.evidenceIds) {
|
|
5536
|
+
const record = evidence.get(evidenceId);
|
|
5537
|
+
if (!record) {
|
|
5538
|
+
errors.push("proof_evidence_unknown");
|
|
5539
|
+
continue;
|
|
5540
|
+
}
|
|
5541
|
+
if (!seen.has(evidenceId)) seen.add(evidenceId);
|
|
5542
|
+
if (record.outcome !== "success") {
|
|
5543
|
+
errors.push("proof_evidence_outcome_invalid");
|
|
5544
|
+
continue;
|
|
5545
|
+
}
|
|
5546
|
+
if (!proofEvidenceConstraints(record, obligation)) errors.push("proof_evidence_constraint_failed");
|
|
5547
|
+
}
|
|
5548
|
+
if (obligation.kind === "scope_coverage") {
|
|
5549
|
+
const itemScope = item.requestedTarget?.scope;
|
|
5550
|
+
const itemSubject = item.verification.subject;
|
|
5551
|
+
if (!obligation.subjectIds.every((subject) => subject === itemScope || subject === itemSubject)) errors.push("proof_scope_subject_unbound");
|
|
5552
|
+
}
|
|
5553
|
+
}
|
|
5554
|
+
return [...new Set(errors)];
|
|
5555
|
+
}
|
|
5556
|
+
function canonicalProjection(projection) {
|
|
5557
|
+
return {
|
|
5558
|
+
epoch: projection.epoch,
|
|
5559
|
+
contractRevision: projection.contractRevision,
|
|
5560
|
+
sessionRefDigest: projection.sessionRefDigest,
|
|
5561
|
+
hostLockDigest: projection.hostLockDigest,
|
|
5562
|
+
hostStatus: projection.hostStatus,
|
|
5563
|
+
hostCohortId: projection.hostCohortId,
|
|
5564
|
+
integrity: projection.integrity,
|
|
5565
|
+
items: [...projection.items.values()].map(({ id, revision, kind, status, semanticAction, requestedTarget, verification }) => ({
|
|
5566
|
+
id,
|
|
5567
|
+
revision,
|
|
5568
|
+
kind,
|
|
5569
|
+
status,
|
|
5570
|
+
semanticAction,
|
|
5571
|
+
requestedTarget,
|
|
5572
|
+
verification
|
|
5573
|
+
})).sort((a, b) => a.id.localeCompare(b.id)),
|
|
5574
|
+
evidence: [...projection.evidence.values()].map(({ id, epoch, toolName, outcome, capabilities, subjects, surfaces: surfaces$1, operations, semanticAction, evidenceRole, resolvedTarget, observedState }) => ({
|
|
5575
|
+
id,
|
|
5576
|
+
epoch,
|
|
5577
|
+
toolName,
|
|
5578
|
+
outcome,
|
|
5579
|
+
capabilities,
|
|
5580
|
+
subjects,
|
|
5581
|
+
surfaces: surfaces$1,
|
|
5582
|
+
operations,
|
|
5583
|
+
semanticAction,
|
|
5584
|
+
evidenceRole,
|
|
5585
|
+
resolvedTarget,
|
|
5586
|
+
observedState
|
|
5587
|
+
})).sort((a, b) => a.id.localeCompare(b.id)),
|
|
5588
|
+
checkpoints: projection.checkpoints.map(({ id, certificationDigest: certificationDigest$1, result }) => ({
|
|
5589
|
+
id,
|
|
5590
|
+
certificationDigest: certificationDigest$1,
|
|
5591
|
+
result
|
|
5592
|
+
}))
|
|
5593
|
+
};
|
|
5594
|
+
}
|
|
5595
|
+
function sessionQuery(projection, proof) {
|
|
5596
|
+
if (proof) {
|
|
5597
|
+
if (validateProofManifest(proof).length) return {
|
|
5598
|
+
sessionRefDigest: projection.sessionRefDigest,
|
|
5599
|
+
epoch: projection.epoch,
|
|
5600
|
+
contractRevision: projection.contractRevision,
|
|
5601
|
+
state: "corrupt",
|
|
5602
|
+
reasonCode: "proof_invalid",
|
|
5603
|
+
cohortId: projection.hostCohortId
|
|
5604
|
+
};
|
|
5605
|
+
if (bindProofToProjection(projection, proof).length) return {
|
|
5606
|
+
sessionRefDigest: projection.sessionRefDigest,
|
|
5607
|
+
epoch: projection.epoch,
|
|
5608
|
+
contractRevision: projection.contractRevision,
|
|
5609
|
+
state: "corrupt",
|
|
5610
|
+
reasonCode: "proof_unbound",
|
|
5611
|
+
cohortId: projection.hostCohortId
|
|
5612
|
+
};
|
|
5613
|
+
}
|
|
5614
|
+
const state = projection.integrity === "valid" ? projection.hostStatus === "supported" ? "valid" : "unknown" : projection.integrity;
|
|
5615
|
+
return {
|
|
5616
|
+
sessionRefDigest: projection.sessionRefDigest,
|
|
5617
|
+
epoch: projection.epoch,
|
|
5618
|
+
contractRevision: projection.contractRevision,
|
|
5619
|
+
state,
|
|
5620
|
+
...proof ? { proof } : {},
|
|
5621
|
+
cohortId: projection.hostCohortId
|
|
5622
|
+
};
|
|
5623
|
+
}
|
|
5624
|
+
function proofEvidenceConstraints(evidence, obligation) {
|
|
5625
|
+
if (evidence.outcome !== "success" || evidence.surfaces.length !== 1 || evidence.surfaces[0] !== obligation.surface) return false;
|
|
5626
|
+
if (!obligation.subjectIds.every((subject) => evidence.subjects.includes(subject))) return false;
|
|
5627
|
+
if (obligation.kind === "subject_readback" && !(evidence.operations ?? []).some(({ op }) => op === "read" || op === "verify")) return false;
|
|
5628
|
+
if (obligation.kind === "scope_coverage" && !(evidence.operations ?? []).some(({ op }) => op === "run" || op === "verify")) return false;
|
|
5629
|
+
if (obligation.kind === "state_verification" && evidence.evidenceRole !== "state") return false;
|
|
5630
|
+
return true;
|
|
5631
|
+
}
|
|
5632
|
+
const PROOF_PROTOCOL_VERSION_V2 = "0.6.0";
|
|
5633
|
+
/** The v2 digest domain; the v1 domain string is untouched. */
|
|
5634
|
+
const PROOF_MANIFEST_DOMAIN_V2 = "ccg.proofManifest.v2";
|
|
5635
|
+
const PROOF_KINDS_V2 = [
|
|
5636
|
+
"subject_readback",
|
|
5637
|
+
"scope_coverage",
|
|
5638
|
+
"state_verification",
|
|
5639
|
+
"input_asset_check",
|
|
5640
|
+
"output_visual_readback",
|
|
5641
|
+
"object_url_readback",
|
|
5642
|
+
"execution_fact",
|
|
5643
|
+
"external_fact"
|
|
5644
|
+
];
|
|
5645
|
+
const ALL_SURFACES = [
|
|
5646
|
+
"native_read",
|
|
5647
|
+
"native_write_edit",
|
|
5648
|
+
"shell",
|
|
5649
|
+
"web",
|
|
5650
|
+
"jobs",
|
|
5651
|
+
"subagent",
|
|
5652
|
+
"visual_capture"
|
|
5653
|
+
];
|
|
5654
|
+
function surfaces(supported) {
|
|
5655
|
+
return {
|
|
5656
|
+
supportedSurfaces: [...supported],
|
|
5657
|
+
unavailableSurfaces: ALL_SURFACES.filter((surface) => !supported.includes(surface))
|
|
5658
|
+
};
|
|
5659
|
+
}
|
|
5660
|
+
const PROOF_CAPABILITY_MATRIX = {
|
|
5661
|
+
subject_readback: {
|
|
5662
|
+
kind: "subject_readback",
|
|
5663
|
+
capabilities: [
|
|
5664
|
+
"filesystem-read",
|
|
5665
|
+
"verify",
|
|
5666
|
+
"deterministic-check"
|
|
5667
|
+
],
|
|
5668
|
+
readbackRequired: true,
|
|
5669
|
+
operationOnSubject: true,
|
|
5670
|
+
...surfaces(["native_read", "shell"])
|
|
5671
|
+
},
|
|
5672
|
+
scope_coverage: {
|
|
5673
|
+
kind: "scope_coverage",
|
|
5674
|
+
capabilities: [
|
|
5675
|
+
"filesystem-read",
|
|
5676
|
+
"verify",
|
|
5677
|
+
"deterministic-check",
|
|
5678
|
+
"web-fetch"
|
|
5679
|
+
],
|
|
5680
|
+
readbackRequired: true,
|
|
5681
|
+
operationOnSubject: false,
|
|
5682
|
+
...surfaces([
|
|
5683
|
+
"native_read",
|
|
5684
|
+
"shell",
|
|
5685
|
+
"web"
|
|
5686
|
+
])
|
|
5687
|
+
},
|
|
5688
|
+
state_verification: {
|
|
5689
|
+
kind: "state_verification",
|
|
5690
|
+
capabilities: [
|
|
5691
|
+
"filesystem-read",
|
|
5692
|
+
"web-fetch",
|
|
5693
|
+
"deterministic-check"
|
|
5694
|
+
],
|
|
5695
|
+
readbackRequired: true,
|
|
5696
|
+
requiredRole: "state",
|
|
5697
|
+
operationOnSubject: false,
|
|
5698
|
+
...surfaces(["native_read", "web"])
|
|
5699
|
+
},
|
|
5700
|
+
input_asset_check: {
|
|
5701
|
+
kind: "input_asset_check",
|
|
5702
|
+
capabilities: ["filesystem-read", "web-fetch"],
|
|
5703
|
+
readbackRequired: true,
|
|
5704
|
+
requiredRole: "resolution",
|
|
5705
|
+
operationOnSubject: true,
|
|
5706
|
+
...surfaces(["native_read", "web"])
|
|
5707
|
+
},
|
|
5708
|
+
output_visual_readback: {
|
|
5709
|
+
kind: "output_visual_readback",
|
|
5710
|
+
capabilities: ["visual-readback"],
|
|
5711
|
+
readbackRequired: true,
|
|
5712
|
+
operationOnSubject: true,
|
|
5713
|
+
...surfaces(["visual_capture"])
|
|
5714
|
+
},
|
|
5715
|
+
object_url_readback: {
|
|
5716
|
+
kind: "object_url_readback",
|
|
5717
|
+
capabilities: ["web-fetch"],
|
|
5718
|
+
readbackRequired: true,
|
|
5719
|
+
operationOnSubject: true,
|
|
5720
|
+
...surfaces(["web"])
|
|
5721
|
+
},
|
|
5722
|
+
execution_fact: {
|
|
5723
|
+
kind: "execution_fact",
|
|
5724
|
+
capabilities: [],
|
|
5725
|
+
readbackRequired: false,
|
|
5726
|
+
requiredRole: "effect",
|
|
5727
|
+
operationOnSubject: false,
|
|
5728
|
+
...surfaces([
|
|
5729
|
+
"shell",
|
|
5730
|
+
"native_write_edit",
|
|
5731
|
+
"native_read",
|
|
5732
|
+
"web",
|
|
5733
|
+
"jobs",
|
|
5734
|
+
"subagent"
|
|
5735
|
+
])
|
|
5736
|
+
},
|
|
5737
|
+
external_fact: {
|
|
5738
|
+
kind: "external_fact",
|
|
5739
|
+
capabilities: [],
|
|
5740
|
+
readbackRequired: false,
|
|
5741
|
+
operationOnSubject: false,
|
|
5742
|
+
...surfaces(["jobs", "subagent"])
|
|
5743
|
+
}
|
|
5744
|
+
};
|
|
5745
|
+
/** The host surface names a fact's tool/adapter identity maps to. */
|
|
5746
|
+
function proofHostSurfacesOf(evidence) {
|
|
5747
|
+
const surface = /* @__PURE__ */ new Set();
|
|
5748
|
+
if (evidence.externalOperationRef) surface.add("jobs");
|
|
5749
|
+
if (evidence.delegatedSubtask) surface.add("subagent");
|
|
5750
|
+
if (evidence.capabilities.includes("visual-readback")) surface.add("visual_capture");
|
|
5751
|
+
if (evidence.capabilities.includes("web-fetch")) surface.add("web");
|
|
5752
|
+
if (new Set([
|
|
5753
|
+
"write",
|
|
5754
|
+
"edit",
|
|
5755
|
+
"write_file",
|
|
5756
|
+
"edit_file"
|
|
5757
|
+
]).has(evidence.toolName)) surface.add("native_write_edit");
|
|
5758
|
+
if (new Set([
|
|
5759
|
+
"read",
|
|
5760
|
+
"read_file",
|
|
5761
|
+
"web_fetch",
|
|
5762
|
+
"web_fetch_url",
|
|
5763
|
+
"web_search"
|
|
5764
|
+
]).has(evidence.toolName)) surface.add("native_read");
|
|
5765
|
+
if ([
|
|
5766
|
+
"bash",
|
|
5767
|
+
"shell",
|
|
5768
|
+
"pwsh"
|
|
5769
|
+
].includes(evidence.toolName)) surface.add("shell");
|
|
5770
|
+
if (evidence.capabilities.includes("filesystem-read")) surface.add("native_read");
|
|
5771
|
+
return [...surface].sort();
|
|
5772
|
+
}
|
|
5773
|
+
function digestV2(value) {
|
|
5774
|
+
return createHash("sha256").update(`${PROOF_MANIFEST_DOMAIN_V2}\n`, "utf8").update(stable$2(value), "utf8").digest("hex");
|
|
5775
|
+
}
|
|
5776
|
+
function proofDigestV2(obligations) {
|
|
5777
|
+
return digestV2({
|
|
5778
|
+
proofProtocolVersion: PROOF_PROTOCOL_VERSION_V2,
|
|
5779
|
+
obligations: [...obligations]
|
|
5780
|
+
});
|
|
5781
|
+
}
|
|
5782
|
+
function createProofManifestV2(obligations) {
|
|
5783
|
+
const normalized = obligations.map((obligation) => ({
|
|
5784
|
+
obligationId: obligation.obligationId,
|
|
5785
|
+
kind: obligation.kind,
|
|
5786
|
+
surface: obligation.surface,
|
|
5787
|
+
subjectIds: [...obligation.subjectIds].sort(),
|
|
5788
|
+
sourceIds: [...obligation.sourceIds].sort(),
|
|
5789
|
+
operation: obligation.operation,
|
|
5790
|
+
evidenceIds: [...obligation.evidenceIds].sort(),
|
|
5791
|
+
...obligation.expectedScopeDigest ? { expectedScopeDigest: obligation.expectedScopeDigest } : {},
|
|
5792
|
+
...obligation.observedScopeDigest ? { observedScopeDigest: obligation.observedScopeDigest } : {}
|
|
5793
|
+
})).sort((a, b) => a.obligationId.localeCompare(b.obligationId));
|
|
5794
|
+
const manifest = {
|
|
5795
|
+
proofProtocolVersion: PROOF_PROTOCOL_VERSION_V2,
|
|
5796
|
+
obligations: normalized,
|
|
5797
|
+
proofSha256: proofDigestV2(normalized)
|
|
5798
|
+
};
|
|
5799
|
+
const errors = validateProofManifestV2(manifest);
|
|
5800
|
+
if (errors.length) throw new Error(`proof v2 manifest rejected: ${errors.join(",")}`);
|
|
5801
|
+
return manifest;
|
|
5802
|
+
}
|
|
5803
|
+
function validateProofManifestV2(manifest) {
|
|
5804
|
+
const errors = [];
|
|
5805
|
+
if (!manifest || typeof manifest !== "object") return ["proof_manifest_invalid"];
|
|
5806
|
+
const value = manifest;
|
|
5807
|
+
if (value.proofProtocolVersion !== PROOF_PROTOCOL_VERSION_V2) errors.push("proof_protocol_version_mismatch");
|
|
5808
|
+
if (!Array.isArray(value.obligations)) errors.push("proof_obligations_missing");
|
|
5809
|
+
if (typeof value.proofSha256 !== "string" || !validDigest(value.proofSha256)) errors.push("proof_digest_invalid");
|
|
5810
|
+
const obligations = Array.isArray(value.obligations) ? value.obligations : [];
|
|
5811
|
+
const ids = /* @__PURE__ */ new Set();
|
|
5812
|
+
for (const raw of obligations) {
|
|
5813
|
+
if (!raw || typeof raw !== "object") {
|
|
5814
|
+
errors.push("proof_obligation_invalid");
|
|
5815
|
+
continue;
|
|
5816
|
+
}
|
|
5817
|
+
const obligation = raw;
|
|
5818
|
+
if (typeof obligation.obligationId !== "string" || !obligation.obligationId || ids.has(obligation.obligationId)) errors.push("proof_obligation_id_duplicate_or_invalid");
|
|
5819
|
+
if (typeof obligation.obligationId === "string") ids.add(obligation.obligationId);
|
|
5820
|
+
if (!PROOF_KINDS_V2.includes(obligation.kind)) errors.push("proof_kind_unsupported");
|
|
5821
|
+
if (![
|
|
5822
|
+
"artifact",
|
|
5823
|
+
"ui",
|
|
5824
|
+
"visual",
|
|
5825
|
+
"scope"
|
|
5826
|
+
].includes(String(obligation.surface))) errors.push("proof_surface_unsupported");
|
|
5827
|
+
if (![
|
|
5828
|
+
"create",
|
|
5829
|
+
"write",
|
|
5830
|
+
"modify",
|
|
5831
|
+
"read",
|
|
5832
|
+
"run",
|
|
5833
|
+
"verify"
|
|
5834
|
+
].includes(String(obligation.operation))) errors.push("proof_operation_unsupported");
|
|
5835
|
+
if (!Array.isArray(obligation.subjectIds) || obligation.subjectIds.length === 0 || obligation.subjectIds.some((id) => typeof id !== "string" || !id)) errors.push("proof_subject_invalid");
|
|
5836
|
+
if (!Array.isArray(obligation.sourceIds) || obligation.sourceIds.length === 0 || obligation.sourceIds.some((id) => typeof id !== "string" || !id)) errors.push("proof_source_invalid");
|
|
5837
|
+
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");
|
|
5838
|
+
const expected = obligation.expectedScopeDigest;
|
|
5839
|
+
const observed = obligation.observedScopeDigest;
|
|
5840
|
+
for (const digestValue of [expected, observed]) if (digestValue !== void 0 && (typeof digestValue !== "string" || !validDigest(digestValue))) errors.push("proof_scope_digest_invalid");
|
|
5841
|
+
if (expected !== observed) errors.push("proof_scope_digest_mismatch");
|
|
5842
|
+
}
|
|
5843
|
+
if (errors.length === 0 && value.proofSha256 !== proofDigestV2(obligations)) errors.push("proof_digest_mismatch");
|
|
5844
|
+
return [...new Set(errors)];
|
|
5845
|
+
}
|
|
5846
|
+
/**
|
|
5847
|
+
* Why one fact cannot discharge one v2 obligation, or `undefined` when it can.
|
|
5848
|
+
* The checks are ordered so the reported reason names the first unmet
|
|
5849
|
+
* requirement: missing producer capability, wrong role, absent readback, wrong
|
|
5850
|
+
* source, wrong subject, wrong operation.
|
|
5851
|
+
*/
|
|
5852
|
+
function proofV2Rejection(evidence, obligation) {
|
|
5853
|
+
const spec = PROOF_CAPABILITY_MATRIX[obligation.kind];
|
|
5854
|
+
if (evidence.delegatedSubtask) return "proof_source_bounded_delegation";
|
|
5855
|
+
if (obligation.sourceIds.length > 0 && !obligation.sourceIds.includes(evidence.toolName) && !obligation.sourceIds.includes(evidence.adapterId ?? "")) return "proof_source_unbound";
|
|
5856
|
+
if (evidence.outcome !== "success") return "proof_evidence_outcome_invalid";
|
|
5857
|
+
if (obligation.kind === "external_fact") {
|
|
5858
|
+
if (!evidence.externalOperationRef) return "proof_external_fact_unavailable";
|
|
5859
|
+
if (evidence.externalOperationRef.status !== "completed") return "proof_external_fact_incomplete";
|
|
5860
|
+
}
|
|
5861
|
+
const available = proofHostSurfacesOf(evidence);
|
|
5862
|
+
if (available.length === 0 || !available.some((surface) => spec.supportedSurfaces.includes(surface))) return "proof_producer_capability_unavailable";
|
|
5863
|
+
if (spec.capabilities.length > 0 && !spec.capabilities.some((capability) => evidence.capabilities.includes(capability))) return "proof_producer_capability_unavailable";
|
|
5864
|
+
if (spec.requiredRole !== void 0 && evidence.evidenceRole !== spec.requiredRole) return "proof_role_unbound";
|
|
5865
|
+
if (spec.readbackRequired) {
|
|
5866
|
+
if (!(evidence.operations ?? []).some((entry) => entry.op === "read" || entry.op === "verify")) return "proof_readback_unavailable";
|
|
5867
|
+
if (spec.operationOnSubject && obligation.subjectIds.length > 0) {
|
|
5868
|
+
const subjects = evidence.subjects;
|
|
5869
|
+
if (!obligation.subjectIds.every((subject) => subjects.some((value) => value === subject))) return "proof_subject_unbound";
|
|
5870
|
+
}
|
|
5871
|
+
}
|
|
5872
|
+
if (obligation.kind === "state_verification" && evidence.surfaces.length > 0 && !evidence.surfaces.includes(obligation.surface)) return "proof_surface_unbound";
|
|
5873
|
+
if (obligation.kind === "execution_fact" && !(evidence.operations ?? []).some((entry) => entry.op === obligation.operation)) return "proof_operation_unbound";
|
|
5874
|
+
}
|
|
5875
|
+
/**
|
|
5876
|
+
* The subjects an item's own obligation requires. They come from the item's
|
|
5877
|
+
* frozen verification contract and captured target — never from the proof
|
|
5878
|
+
* manifest, which is exactly what a proof must be checked against.
|
|
5879
|
+
*/
|
|
5880
|
+
function requiredSubjectsOf(item) {
|
|
5881
|
+
const values = /* @__PURE__ */ new Set();
|
|
5882
|
+
const subject = item.verification.subject;
|
|
5883
|
+
if (typeof subject === "string" && subject.length > 0 && subject !== "scope") values.add(subject);
|
|
5884
|
+
const target = item.requestedTarget ?? {};
|
|
5885
|
+
if (item.verification.surface === "scope") {
|
|
5886
|
+
const scope = target.scope;
|
|
5887
|
+
if (typeof scope === "string" && scope.length > 0 && scope !== "scope") values.add(scope);
|
|
5888
|
+
}
|
|
5889
|
+
for (const key of [
|
|
5890
|
+
"artifact_id",
|
|
5891
|
+
"package_id",
|
|
5892
|
+
"service_id",
|
|
5893
|
+
"repository"
|
|
5894
|
+
]) {
|
|
5895
|
+
const value = target[key];
|
|
5896
|
+
if (typeof value === "string" && value.length > 0 && value !== "scope") values.add(value);
|
|
5897
|
+
}
|
|
5898
|
+
return [...values].sort();
|
|
5899
|
+
}
|
|
5900
|
+
/** The frozen coverage digest of a subject set: sorted, then hashed. */
|
|
5901
|
+
function scopeCoverageDigest(subjects) {
|
|
5902
|
+
return createHash("sha256").update("ccg.proofScopeCoverage.v2\n", "utf8").update(JSON.stringify([...subjects].sort()), "utf8").digest("hex");
|
|
5903
|
+
}
|
|
5904
|
+
/**
|
|
5905
|
+
* Operations a fact may perform to discharge one proof kind. `execution_fact`
|
|
5906
|
+
* is bound to the obligation's own declared operation; the readback kinds
|
|
5907
|
+
* accept only an actual read or verify, so a bare successful call never
|
|
5908
|
+
* satisfies them.
|
|
5909
|
+
*/
|
|
5910
|
+
const KIND_OPERATIONS = {
|
|
5911
|
+
subject_readback: ["read", "verify"],
|
|
5912
|
+
scope_coverage: ["run", "verify"],
|
|
5913
|
+
state_verification: ["read", "verify"],
|
|
5914
|
+
input_asset_check: ["read", "verify"],
|
|
5915
|
+
output_visual_readback: ["read", "verify"],
|
|
5916
|
+
object_url_readback: ["read", "verify"],
|
|
5917
|
+
execution_fact: "declared",
|
|
5918
|
+
external_fact: []
|
|
5919
|
+
};
|
|
5920
|
+
/** Whether the fact performed an operation the kind accepts. */
|
|
5921
|
+
function proofOperationMatches(evidence, obligation) {
|
|
5922
|
+
const allowed = KIND_OPERATIONS[obligation.kind];
|
|
5923
|
+
const operations = evidence.operations ?? [];
|
|
5924
|
+
if (allowed === "declared") return operations.some((entry) => entry.op === obligation.operation);
|
|
5925
|
+
if (allowed.length === 0) return true;
|
|
5926
|
+
return operations.some((entry) => allowed.includes(entry.op));
|
|
5927
|
+
}
|
|
5928
|
+
/**
|
|
5929
|
+
* Bind a v2 manifest to the live projection; [] means every obligation binds.
|
|
5930
|
+
*
|
|
5931
|
+
* The binding is the whole chain the review demanded, in one place:
|
|
5932
|
+
* the user's obligation (frozen subject and scope on the ITEM) → the trusted
|
|
5933
|
+
* producer fact (qualified by the same availability rules ordinary evidence
|
|
5934
|
+
* uses) → the declared source → the declared operation and its order relative
|
|
5935
|
+
* to the effect → the real coverage set. Only then is the obligation
|
|
5936
|
+
* discharged. A manifest that describes a different subject than the item
|
|
5937
|
+
* asked about fails even when the manifest and the facts agree with each
|
|
5938
|
+
* other.
|
|
5939
|
+
*/
|
|
5940
|
+
function bindProofV2ToProjection(projection, manifest) {
|
|
5941
|
+
const errors = [];
|
|
5942
|
+
for (const obligation of manifest.obligations) {
|
|
5943
|
+
const item = projection.items.get(obligation.obligationId);
|
|
5944
|
+
if (!item) {
|
|
5945
|
+
errors.push("proof_obligation_unbound");
|
|
5946
|
+
continue;
|
|
5947
|
+
}
|
|
5948
|
+
if (item.status !== "pending") {
|
|
5949
|
+
errors.push("proof_obligation_not_pending");
|
|
5950
|
+
continue;
|
|
5951
|
+
}
|
|
5952
|
+
if (item.verification.surface !== void 0 && item.verification.surface !== obligation.surface) errors.push("proof_surface_unbound");
|
|
5953
|
+
const required = requiredSubjectsOf(item);
|
|
5954
|
+
if (required.length > 0) {
|
|
5955
|
+
if (!obligation.subjectIds.every((subject) => required.includes(subject))) {
|
|
5956
|
+
errors.push("proof_subject_unbound");
|
|
5957
|
+
continue;
|
|
5958
|
+
}
|
|
5959
|
+
if (!required.every((subject) => obligation.subjectIds.includes(subject))) {
|
|
5960
|
+
errors.push("proof_scope_incomplete");
|
|
5961
|
+
continue;
|
|
5962
|
+
}
|
|
5963
|
+
}
|
|
5964
|
+
const cited = [];
|
|
5965
|
+
for (const evidenceId of obligation.evidenceIds) {
|
|
5966
|
+
const evidence = projection.evidence.get(evidenceId);
|
|
5967
|
+
if (!evidence) {
|
|
5968
|
+
errors.push("proof_evidence_unknown");
|
|
5969
|
+
continue;
|
|
5970
|
+
}
|
|
5971
|
+
const availability = evidenceAvailabilityReason(evidence);
|
|
5972
|
+
if (availability !== void 0) {
|
|
5973
|
+
errors.push(availability);
|
|
5974
|
+
continue;
|
|
5975
|
+
}
|
|
5976
|
+
if (evidence.epoch !== projection.epoch) {
|
|
5977
|
+
errors.push("proof_evidence_wrong_epoch");
|
|
5978
|
+
continue;
|
|
5979
|
+
}
|
|
5980
|
+
if (obligation.subjectIds.length > 0 && !evidence.subjects.some((subject) => obligation.subjectIds.includes(subject))) {
|
|
5981
|
+
errors.push("proof_subject_unbound");
|
|
5982
|
+
continue;
|
|
5983
|
+
}
|
|
5984
|
+
if (!proofOperationMatches(evidence, obligation)) {
|
|
5985
|
+
errors.push("proof_operation_unbound");
|
|
5986
|
+
continue;
|
|
5987
|
+
}
|
|
5988
|
+
const rejection = proofV2Rejection(evidence, obligation);
|
|
5989
|
+
if (rejection) {
|
|
5990
|
+
errors.push(rejection);
|
|
5991
|
+
continue;
|
|
5992
|
+
}
|
|
5993
|
+
cited.push(evidence);
|
|
5994
|
+
}
|
|
5995
|
+
if (cited.length === 0 && obligation.evidenceIds.length > 0) continue;
|
|
5996
|
+
if (required.length > 0 && !required.every((subject) => cited.some((fact) => fact.subjects.includes(subject)))) {
|
|
5997
|
+
errors.push("proof_scope_incomplete");
|
|
5998
|
+
continue;
|
|
5999
|
+
}
|
|
6000
|
+
if (obligation.kind === "input_asset_check") {
|
|
6001
|
+
const firstCheck = Math.min(...cited.map((fact) => fact.toolResultSeq));
|
|
6002
|
+
if ([...projection.evidence.values()].some((fact) => fact.evidenceRole === "effect" && required.some((subject) => fact.subjects.includes(subject)) && fact.toolResultSeq < firstCheck)) {
|
|
6003
|
+
errors.push("proof_input_check_after_effect");
|
|
6004
|
+
continue;
|
|
6005
|
+
}
|
|
6006
|
+
}
|
|
6007
|
+
if (obligation.kind === "scope_coverage") {
|
|
6008
|
+
const covered = [...new Set(cited.flatMap((fact) => fact.subjects))].sort();
|
|
6009
|
+
if (obligation.expectedScopeDigest !== void 0 && obligation.expectedScopeDigest !== scopeCoverageDigest(required)) {
|
|
6010
|
+
errors.push("proof_scope_digest_unbound");
|
|
6011
|
+
continue;
|
|
6012
|
+
}
|
|
6013
|
+
if (obligation.observedScopeDigest !== void 0 && obligation.observedScopeDigest !== scopeCoverageDigest(covered)) {
|
|
6014
|
+
errors.push("proof_scope_digest_unbound");
|
|
6015
|
+
continue;
|
|
6016
|
+
}
|
|
6017
|
+
}
|
|
6018
|
+
}
|
|
6019
|
+
return [...new Set(errors)];
|
|
6020
|
+
}
|
|
6021
|
+
function sessionQueryV2(projection, proof) {
|
|
6022
|
+
if (proof) {
|
|
6023
|
+
if (validateProofManifestV2(proof).length) return {
|
|
6024
|
+
sessionRefDigest: projection.sessionRefDigest,
|
|
6025
|
+
epoch: projection.epoch,
|
|
6026
|
+
contractRevision: projection.contractRevision,
|
|
6027
|
+
state: "corrupt",
|
|
6028
|
+
reasonCode: "proof_invalid",
|
|
6029
|
+
cohortId: projection.hostCohortId
|
|
6030
|
+
};
|
|
6031
|
+
if (bindProofV2ToProjection(projection, proof).length) return {
|
|
6032
|
+
sessionRefDigest: projection.sessionRefDigest,
|
|
6033
|
+
epoch: projection.epoch,
|
|
6034
|
+
contractRevision: projection.contractRevision,
|
|
6035
|
+
state: "corrupt",
|
|
6036
|
+
reasonCode: "proof_unbound",
|
|
6037
|
+
cohortId: projection.hostCohortId
|
|
6038
|
+
};
|
|
6039
|
+
}
|
|
6040
|
+
const state = projection.integrity === "valid" ? projection.hostStatus === "supported" ? "valid" : "unknown" : projection.integrity;
|
|
6041
|
+
return {
|
|
6042
|
+
sessionRefDigest: projection.sessionRefDigest,
|
|
6043
|
+
epoch: projection.epoch,
|
|
6044
|
+
contractRevision: projection.contractRevision,
|
|
6045
|
+
state,
|
|
6046
|
+
...proof ? { proof } : {},
|
|
6047
|
+
cohortId: projection.hostCohortId
|
|
6048
|
+
};
|
|
6049
|
+
}
|
|
6050
|
+
/**
|
|
6051
|
+
* The capability report for one proof kind against the facts a cohort actually
|
|
6052
|
+
* produced: `unavailable` with a stable reason when no producer is observable,
|
|
6053
|
+
* never a silent pass.
|
|
6054
|
+
*/
|
|
6055
|
+
function proofCapabilityReport(kind, facts) {
|
|
6056
|
+
for (const fact of facts) {
|
|
6057
|
+
if (fact.outcome !== "success") continue;
|
|
6058
|
+
const probe = {
|
|
6059
|
+
obligationId: "probe",
|
|
6060
|
+
kind,
|
|
6061
|
+
surface: "artifact",
|
|
6062
|
+
subjectIds: fact.subjects.slice(0, 1),
|
|
6063
|
+
sourceIds: [],
|
|
6064
|
+
operation: "verify",
|
|
6065
|
+
evidenceIds: []
|
|
6066
|
+
};
|
|
6067
|
+
if (probe.subjectIds.length === 0) probe.subjectIds = ["probe"];
|
|
6068
|
+
if (proofV2Rejection(fact, probe) === void 0) return { status: "supported" };
|
|
6069
|
+
}
|
|
6070
|
+
return {
|
|
6071
|
+
status: "unavailable",
|
|
6072
|
+
reasonCode: "proof_producer_capability_unavailable"
|
|
6073
|
+
};
|
|
6074
|
+
}
|
|
6075
|
+
|
|
6076
|
+
//#endregion
|
|
6077
|
+
//#region src/domain/checkpoint.ts
|
|
6078
|
+
function stable$1(value) {
|
|
6079
|
+
if (Array.isArray(value)) return `[${value.map(stable$1).join(",")}]`;
|
|
6080
|
+
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(",")}}`;
|
|
6081
|
+
return JSON.stringify(value);
|
|
6082
|
+
}
|
|
6083
|
+
function tuplesEqual(left, right) {
|
|
6084
|
+
return stable$1(left ?? {}) === stable$1(right ?? {});
|
|
6085
|
+
}
|
|
6086
|
+
function transitionsEqual(left, right) {
|
|
6087
|
+
return stable$1(left) === stable$1(right);
|
|
6088
|
+
}
|
|
6089
|
+
function transitionIsSelfConsistent(action, transition) {
|
|
6090
|
+
if (!transition?.parameters || transition.predicateId !== ACTION_MANIFEST.actions[action].predicateId || transition.version !== 1 || transition.predParamsKind !== "inline") return false;
|
|
6091
|
+
const recomputed = predParamsDigest(transition.parameters, resolveAllowlist("product"));
|
|
6092
|
+
return transition.parametersDigest === void 0 || transition.parametersDigest === recomputed;
|
|
6093
|
+
}
|
|
6094
|
+
function evidenceFact(evidence) {
|
|
6095
|
+
return {
|
|
6096
|
+
id: evidence.id,
|
|
6097
|
+
outcome: evidence.outcome,
|
|
6098
|
+
method: evidence.toolName,
|
|
4482
6099
|
operations: (evidence.operations ?? []).map((entry) => entry.op),
|
|
4483
6100
|
executables: evidence.executables ?? [],
|
|
4484
6101
|
subjects: evidence.subjects,
|
|
@@ -4556,6 +6173,37 @@ function evidenceProblem(projection, item, binding) {
|
|
|
4556
6173
|
};
|
|
4557
6174
|
}
|
|
4558
6175
|
}
|
|
6176
|
+
/**
|
|
6177
|
+
* The strict-policy proof obligation (C06). Only the surfaces the USER asked
|
|
6178
|
+
* for add a requirement, and the requirement is a real readback from the v2
|
|
6179
|
+
* capability matrix: a visual verification needs a fact that actually observed
|
|
6180
|
+
* the output, a complete-scope verification needs a fact that actually covered
|
|
6181
|
+
* the scope. Standard policy does not run this, and no ordinary action gains an
|
|
6182
|
+
* approval step.
|
|
6183
|
+
*/
|
|
6184
|
+
function strictProofProblem(projection, item, binding) {
|
|
6185
|
+
const surface = item.verification.surface;
|
|
6186
|
+
if (surface !== "visual" && surface !== "scope") return void 0;
|
|
6187
|
+
const kind = surface === "visual" ? "output_visual_readback" : "scope_coverage";
|
|
6188
|
+
const obligation = {
|
|
6189
|
+
obligationId: item.id,
|
|
6190
|
+
kind,
|
|
6191
|
+
surface,
|
|
6192
|
+
subjectIds: [item.verification.subject ?? item.requestedTarget?.scope ?? "scope"].filter((value) => typeof value === "string" && value.length > 0),
|
|
6193
|
+
sourceIds: [],
|
|
6194
|
+
operation: item.verification.operation ?? "verify",
|
|
6195
|
+
evidenceIds: binding.evidenceIds
|
|
6196
|
+
};
|
|
6197
|
+
const cited = citedEvidence(projection, binding);
|
|
6198
|
+
if (cited.some((fact) => fact.outcome === "success" && fact.subjects.some((subject) => obligation.subjectIds.includes(subject)) && proofV2Rejection(fact, obligation) === void 0)) return void 0;
|
|
6199
|
+
return {
|
|
6200
|
+
itemId: item.id,
|
|
6201
|
+
reason: `strict policy: the requested ${surface} verification needs a real readback fact from the current projection`,
|
|
6202
|
+
reasonCode: "strict_proof_required",
|
|
6203
|
+
offendingEvidenceIds: cited.map((fact) => fact.id),
|
|
6204
|
+
hint: closingHint(projection, item, binding.evidenceIds)
|
|
6205
|
+
};
|
|
6206
|
+
}
|
|
4559
6207
|
function expectedTransitionMatches(action, transition, resolved, observed) {
|
|
4560
6208
|
const expectedPredicate = ACTION_MANIFEST.actions[action].predicateId;
|
|
4561
6209
|
if (transition.predicateId !== expectedPredicate || transition.version !== 1 || transition.predParamsKind !== "inline" || !transition.parameters) return false;
|
|
@@ -4570,14 +6218,14 @@ function expectedTransitionMatches(action, transition, resolved, observed) {
|
|
|
4570
6218
|
"version",
|
|
4571
6219
|
"integrity_digest",
|
|
4572
6220
|
...action === "publish" ? ["registry"] : ["profile"]
|
|
4573
|
-
].every((key) => stable$
|
|
6221
|
+
].every((key) => stable$1(observed[key]) === stable$1(resolved[key]) && stable$1(params[key]) === stable$1(resolved[key]));
|
|
4574
6222
|
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$
|
|
6223
|
+
case "modify": return stable$1(observed.post_digest) === stable$1(params.post_digest);
|
|
6224
|
+
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);
|
|
6225
|
+
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);
|
|
6226
|
+
case "push": return stable$1(observed.remote_oid) === stable$1(resolved.local_oid) && stable$1(params.local_oid) === stable$1(resolved.local_oid);
|
|
6227
|
+
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);
|
|
6228
|
+
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
6229
|
default: return true;
|
|
4582
6230
|
}
|
|
4583
6231
|
}
|
|
@@ -4586,8 +6234,8 @@ function nonStatefulTransitionMatches(action, transition, resolved, observed) {
|
|
|
4586
6234
|
const params = transition.parameters;
|
|
4587
6235
|
const recomputed = predParamsDigest(params, resolveAllowlist("product"));
|
|
4588
6236
|
if (transition.parametersDigest && transition.parametersDigest !== recomputed) return false;
|
|
4589
|
-
if (action === "inspect_remote_updates") return ["remote", "version"].every((key) => stable$
|
|
4590
|
-
return stable$
|
|
6237
|
+
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);
|
|
6238
|
+
return stable$1(params) === stable$1({
|
|
4591
6239
|
expected_outcome: {
|
|
4592
6240
|
k: "e",
|
|
4593
6241
|
v: "success"
|
|
@@ -4694,7 +6342,7 @@ function richStatefulRecord(projection, item, binding) {
|
|
|
4694
6342
|
reason: "resolution fact does not bind an expected transition digest",
|
|
4695
6343
|
reasonCode: "resolution_expected_transition_digest_missing"
|
|
4696
6344
|
} };
|
|
4697
|
-
if (resolution.expectedTransitionDigest !== sha256(stable$
|
|
6345
|
+
if (resolution.expectedTransitionDigest !== sha256(stable$1(resolution.expectedTransition))) return { rejected: {
|
|
4698
6346
|
itemId: item.id,
|
|
4699
6347
|
reason: "resolution expected transition digest does not match its stable payload",
|
|
4700
6348
|
reasonCode: "resolution_expected_transition_digest_mismatch"
|
|
@@ -4789,7 +6437,7 @@ function simpleRecord(projection, item, binding) {
|
|
|
4789
6437
|
const effectAction = effect.semanticAction ?? "generic_run";
|
|
4790
6438
|
const effectTarget = effect.resolvedTarget ?? {};
|
|
4791
6439
|
const effectObserved = effect.observedState ?? {};
|
|
4792
|
-
if (!(Object.entries(binding.resolvedTarget ?? {}).every(([key, value]) => Object.hasOwn(effectTarget, key) && stable$
|
|
6440
|
+
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
6441
|
itemId: item.id,
|
|
4794
6442
|
reason: "binding target does not match the cited effect evidence",
|
|
4795
6443
|
reasonCode: "binding_state_cross_pairing"
|
|
@@ -4828,7 +6476,7 @@ function certifyCheckpoint(projection, bindings, id, commit = true) {
|
|
|
4828
6476
|
if (projection.integrity !== "valid" || projection.hostStatus !== "supported") return {
|
|
4829
6477
|
status: "unknown",
|
|
4830
6478
|
contractRevision: projection.contractRevision,
|
|
4831
|
-
openItems:
|
|
6479
|
+
openItems: certifiableOpenItems(projection).map((item) => item.id),
|
|
4832
6480
|
rejectedBindings: []
|
|
4833
6481
|
};
|
|
4834
6482
|
const rejectedBindings = [];
|
|
@@ -4860,6 +6508,16 @@ function certifyCheckpoint(projection, bindings, id, commit = true) {
|
|
|
4860
6508
|
});
|
|
4861
6509
|
continue;
|
|
4862
6510
|
}
|
|
6511
|
+
const ancestorBlock = ancestorConstraintForBinding(projection, item, binding.resolvedTarget);
|
|
6512
|
+
if (ancestorBlock) {
|
|
6513
|
+
rejectedBindings.push({
|
|
6514
|
+
itemId: item.id,
|
|
6515
|
+
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`,
|
|
6516
|
+
reasonCode: ancestorBlock.reasonCode,
|
|
6517
|
+
hint: closingHint(projection, item)
|
|
6518
|
+
});
|
|
6519
|
+
continue;
|
|
6520
|
+
}
|
|
4863
6521
|
if (item.targetCaptureStatus === "clarification_required") {
|
|
4864
6522
|
rejectedBindings.push({
|
|
4865
6523
|
itemId: item.id,
|
|
@@ -4890,6 +6548,13 @@ function certifyCheckpoint(projection, bindings, id, commit = true) {
|
|
|
4890
6548
|
rejectedBindings.push(problem);
|
|
4891
6549
|
continue;
|
|
4892
6550
|
}
|
|
6551
|
+
if (projection.policy === "strict") {
|
|
6552
|
+
const strictProblem = strictProofProblem(projection, item, binding);
|
|
6553
|
+
if (strictProblem) {
|
|
6554
|
+
rejectedBindings.push(strictProblem);
|
|
6555
|
+
continue;
|
|
6556
|
+
}
|
|
6557
|
+
}
|
|
4893
6558
|
if ((item.semanticAction ?? "generic_run") === "generic_run") {
|
|
4894
6559
|
rejectedBindings.push({
|
|
4895
6560
|
itemId: item.id,
|
|
@@ -4906,48 +6571,95 @@ function certifyCheckpoint(projection, bindings, id, commit = true) {
|
|
|
4906
6571
|
records.push(built.record);
|
|
4907
6572
|
referencedFacts.push(...citedEvidence(projection, binding).map(evidenceFact));
|
|
4908
6573
|
}
|
|
4909
|
-
const
|
|
6574
|
+
const closure = certificateClosure(projection);
|
|
6575
|
+
const open = closure.itemIds.filter((itemId) => !bindings.some((binding) => binding.itemId === itemId));
|
|
4910
6576
|
if (rejectedBindings.length || open.length) return {
|
|
4911
6577
|
status: "incomplete",
|
|
4912
6578
|
contractRevision: projection.contractRevision,
|
|
4913
|
-
openItems:
|
|
6579
|
+
openItems: closure.itemIds,
|
|
4914
6580
|
rejectedBindings
|
|
4915
6581
|
};
|
|
6582
|
+
if (projection.boundaryProtocol === 5 && closure.unitId === void 0) return {
|
|
6583
|
+
status: "incomplete",
|
|
6584
|
+
contractRevision: projection.contractRevision,
|
|
6585
|
+
openItems: closure.itemIds,
|
|
6586
|
+
rejectedBindings: [{
|
|
6587
|
+
itemId: "*",
|
|
6588
|
+
reason: "no current work unit is available for a v2 certificate",
|
|
6589
|
+
reasonCode: "unit_unavailable"
|
|
6590
|
+
}]
|
|
6591
|
+
};
|
|
4916
6592
|
try {
|
|
4917
6593
|
const contractSha256 = currentContractDigest(projection);
|
|
4918
|
-
const openDigest = digestStrings(
|
|
6594
|
+
const openDigest = digestStrings(closure.itemIds);
|
|
4919
6595
|
const evidenceSha256 = evidenceSha256Digest(referencedFacts);
|
|
4920
6596
|
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
|
-
|
|
6597
|
+
const checkpoint = projection.boundaryProtocol === 5 ? (() => {
|
|
6598
|
+
const certification = certificationDigestV2({
|
|
6599
|
+
stopProtocolVersion: STOP_PROTOCOL_VERSION_V2,
|
|
6600
|
+
certificateVersion: CERTIFICATE_VERSION_V2,
|
|
6601
|
+
epoch: projection.epoch,
|
|
6602
|
+
sessionRefDigest: projection.sessionRefDigest,
|
|
6603
|
+
hostLockDigest: projection.hostLockDigest,
|
|
6604
|
+
contractRevision: projection.contractRevision,
|
|
6605
|
+
contractSha256,
|
|
6606
|
+
unitId: closure.unitId,
|
|
6607
|
+
unitClosureDigest: openDigest,
|
|
6608
|
+
evidenceSha256,
|
|
6609
|
+
bindingDigest: bindingDigest$1,
|
|
6610
|
+
goalRef: projection.currentGoalRef ?? null
|
|
6611
|
+
});
|
|
6612
|
+
return {
|
|
6613
|
+
id,
|
|
6614
|
+
stopProtocolVersion: STOP_PROTOCOL_VERSION_V2,
|
|
6615
|
+
certificateVersion: CERTIFICATE_VERSION_V2,
|
|
6616
|
+
epoch: projection.epoch,
|
|
6617
|
+
sessionRefDigest: projection.sessionRefDigest,
|
|
6618
|
+
hostLockDigest: projection.hostLockDigest,
|
|
6619
|
+
contractRevision: projection.contractRevision,
|
|
6620
|
+
contractSha256,
|
|
6621
|
+
openDigest,
|
|
6622
|
+
evidenceSha256,
|
|
6623
|
+
bindingDigest: bindingDigest$1,
|
|
6624
|
+
bindings,
|
|
6625
|
+
...projection.currentGoalRef ? { goalRef: { ...projection.currentGoalRef } } : {},
|
|
6626
|
+
unitId: closure.unitId,
|
|
6627
|
+
unitClosureDigest: openDigest,
|
|
6628
|
+
certificationDigest: certification,
|
|
6629
|
+
result: "certified"
|
|
6630
|
+
};
|
|
6631
|
+
})() : (() => {
|
|
6632
|
+
const certification = certificationDigest({
|
|
6633
|
+
stopProtocolVersion: STOP_PROTOCOL_VERSION,
|
|
6634
|
+
certificateVersion: CERTIFICATE_VERSION,
|
|
6635
|
+
epoch: projection.epoch,
|
|
6636
|
+
sessionRefDigest: projection.sessionRefDigest,
|
|
6637
|
+
hostLockDigest: projection.hostLockDigest,
|
|
6638
|
+
contractRevision: projection.contractRevision,
|
|
6639
|
+
contractSha256,
|
|
6640
|
+
...projection.currentGoalRef ? { goalRef: projection.currentGoalRef } : {},
|
|
6641
|
+
openDigest,
|
|
6642
|
+
evidenceSha256,
|
|
6643
|
+
bindingDigest: bindingDigest$1
|
|
6644
|
+
});
|
|
6645
|
+
return {
|
|
6646
|
+
id,
|
|
6647
|
+
stopProtocolVersion: STOP_PROTOCOL_VERSION,
|
|
6648
|
+
certificateVersion: CERTIFICATE_VERSION,
|
|
6649
|
+
epoch: projection.epoch,
|
|
6650
|
+
sessionRefDigest: projection.sessionRefDigest,
|
|
6651
|
+
hostLockDigest: projection.hostLockDigest,
|
|
6652
|
+
contractRevision: projection.contractRevision,
|
|
6653
|
+
contractSha256,
|
|
6654
|
+
openDigest,
|
|
6655
|
+
evidenceSha256,
|
|
6656
|
+
bindingDigest: bindingDigest$1,
|
|
6657
|
+
bindings,
|
|
6658
|
+
...projection.currentGoalRef ? { goalRef: { ...projection.currentGoalRef } } : {},
|
|
6659
|
+
certificationDigest: certification,
|
|
6660
|
+
result: "certified"
|
|
6661
|
+
};
|
|
6662
|
+
})();
|
|
4951
6663
|
if (commit) {
|
|
4952
6664
|
projection.checkpoints.push(checkpoint);
|
|
4953
6665
|
for (const binding of bindings) projection.items.get(binding.itemId).status = "passed";
|
|
@@ -4964,7 +6676,7 @@ function certifyCheckpoint(projection, bindings, id, commit = true) {
|
|
|
4964
6676
|
return {
|
|
4965
6677
|
status: "incomplete",
|
|
4966
6678
|
contractRevision: projection.contractRevision,
|
|
4967
|
-
openItems:
|
|
6679
|
+
openItems: closure.itemIds,
|
|
4968
6680
|
rejectedBindings: [{
|
|
4969
6681
|
itemId: "*",
|
|
4970
6682
|
reason: error instanceof Error ? error.message : "certificate manifest rejected",
|
|
@@ -5056,9 +6768,6 @@ function bindingActionPlanProblem(projection, item, binding) {
|
|
|
5056
6768
|
}
|
|
5057
6769
|
}
|
|
5058
6770
|
}
|
|
5059
|
-
function openItems(projection) {
|
|
5060
|
-
return [...projection.items.values()].filter((item) => item.status === "pending" && item.kind !== "prohibition").map((item) => item.id);
|
|
5061
|
-
}
|
|
5062
6771
|
|
|
5063
6772
|
//#endregion
|
|
5064
6773
|
//#region src/domain/contract-segment.ts
|
|
@@ -7662,13 +9371,13 @@ function parseArguments$1(raw) {
|
|
|
7662
9371
|
return {};
|
|
7663
9372
|
}
|
|
7664
9373
|
}
|
|
7665
|
-
function asRecord$
|
|
9374
|
+
function asRecord$2(value) {
|
|
7666
9375
|
return typeof value === "object" && value !== null ? value : void 0;
|
|
7667
9376
|
}
|
|
7668
9377
|
function extractTextContent(content) {
|
|
7669
9378
|
const parts = [];
|
|
7670
9379
|
for (const block$1 of content) {
|
|
7671
|
-
const record = asRecord$
|
|
9380
|
+
const record = asRecord$2(block$1);
|
|
7672
9381
|
if (!record) continue;
|
|
7673
9382
|
if (record.type === "text" && typeof record.text === "string") parts.push(record.text);
|
|
7674
9383
|
if (record.type === "tool-result" && Array.isArray(record.content)) parts.push(extractTextContent(record.content));
|
|
@@ -7676,10 +9385,10 @@ function extractTextContent(content) {
|
|
|
7676
9385
|
return parts.join("\n");
|
|
7677
9386
|
}
|
|
7678
9387
|
function metaPaths(meta) {
|
|
7679
|
-
const record = asRecord$
|
|
9388
|
+
const record = asRecord$2(meta);
|
|
7680
9389
|
if (!record) return [];
|
|
7681
9390
|
if (typeof record.path === "string") return [record.path];
|
|
7682
|
-
if (Array.isArray(record.diffs)) return record.diffs.map((diff) => asRecord$
|
|
9391
|
+
if (Array.isArray(record.diffs)) return record.diffs.map((diff) => asRecord$2(diff)?.path).filter((path$1) => typeof path$1 === "string");
|
|
7683
9392
|
return [];
|
|
7684
9393
|
}
|
|
7685
9394
|
function argsPaths(args) {
|
|
@@ -7693,24 +9402,24 @@ function resolveCommandPath(reference, cwd) {
|
|
|
7693
9402
|
if (/^[A-Za-z]:[\\/]/.test(reference) || reference.startsWith("//") || reference.startsWith("\\\\") || reference.startsWith("/") || reference.startsWith("\\")) return reference;
|
|
7694
9403
|
return `${cwd.replace(/[\\/]+$/, "")}/${reference}`;
|
|
7695
9404
|
}
|
|
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
|
|
9405
|
+
function stable(value) {
|
|
9406
|
+
if (Array.isArray(value)) return `[${value.map(stable).join(",")}]`;
|
|
9407
|
+
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
9408
|
return JSON.stringify(value);
|
|
7700
9409
|
}
|
|
7701
9410
|
function structuredGuardMeta(meta, toolName) {
|
|
7702
9411
|
if (toolName !== "context_guard_evidence") return void 0;
|
|
7703
|
-
const outer = asRecord$
|
|
7704
|
-
const value = asRecord$
|
|
9412
|
+
const outer = asRecord$2(meta);
|
|
9413
|
+
const value = asRecord$2(outer?.contextGuard ?? outer?.context_guard);
|
|
7705
9414
|
if (!value) return void 0;
|
|
7706
9415
|
const action = value.semanticAction ?? value.semantic_action;
|
|
7707
9416
|
const role = value.evidenceRole ?? value.evidence_role;
|
|
7708
|
-
const resolved = asRecord$
|
|
7709
|
-
const observed = asRecord$
|
|
7710
|
-
const rawExpected = asRecord$
|
|
7711
|
-
const expectedParameters = asRecord$
|
|
9417
|
+
const resolved = asRecord$2(value.resolvedTarget ?? value.resolved_target);
|
|
9418
|
+
const observed = asRecord$2(value.observedState ?? value.observed_state);
|
|
9419
|
+
const rawExpected = asRecord$2(value.expectedTransition);
|
|
9420
|
+
const expectedParameters = asRecord$2(rawExpected?.parameters);
|
|
7712
9421
|
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
|
|
9422
|
+
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
9423
|
if (typeof value.adapterId !== "string" || typeof value.adapterVersion !== "string") return void 0;
|
|
7715
9424
|
if (SUPPORTED_EVIDENCE_ADAPTERS[value.adapterId] !== value.adapterVersion) return void 0;
|
|
7716
9425
|
if (typeof action !== "string" || typeof role !== "string" || !resolved) return void 0;
|
|
@@ -7807,7 +9516,7 @@ const PERSISTENT_TIMEOUT_INTRO = /^Your command timed out after \d+ seconds or e
|
|
|
7807
9516
|
* the fallback.
|
|
7808
9517
|
*/
|
|
7809
9518
|
function structuredTerminalFacts(meta) {
|
|
7810
|
-
const record = asRecord$
|
|
9519
|
+
const record = asRecord$2(meta);
|
|
7811
9520
|
if (!record) return void 0;
|
|
7812
9521
|
const rawExit = record.exitCode ?? record.exit_code;
|
|
7813
9522
|
const rawSignal = record.signal;
|
|
@@ -7858,10 +9567,10 @@ function extractTerminalFacts(textContent) {
|
|
|
7858
9567
|
};
|
|
7859
9568
|
}
|
|
7860
9569
|
function metaUrls(meta) {
|
|
7861
|
-
const record = asRecord$
|
|
9570
|
+
const record = asRecord$2(meta);
|
|
7862
9571
|
if (!record) return [];
|
|
7863
9572
|
if (typeof record.url === "string") return [sanitizeUrl(record.url)];
|
|
7864
|
-
if (Array.isArray(record.sources)) return record.sources.map((source) => asRecord$
|
|
9573
|
+
if (Array.isArray(record.sources)) return record.sources.map((source) => asRecord$2(source)?.url).filter((url) => typeof url === "string").map((url) => sanitizeUrl(url));
|
|
7865
9574
|
return [];
|
|
7866
9575
|
}
|
|
7867
9576
|
const DETERMINISTIC_CHECK_PATTERNS = [
|
|
@@ -7918,7 +9627,7 @@ function resolveSubjectPaths(values, cwd) {
|
|
|
7918
9627
|
function extractToolSubject(call, result, defaultCwd, hostLock) {
|
|
7919
9628
|
const args = parseArguments$1(call.arguments);
|
|
7920
9629
|
if (call.name === "context_guard_external_operation") {
|
|
7921
|
-
const external = asRecord$
|
|
9630
|
+
const external = asRecord$2(asRecord$2(result.meta)?.contextGuardExternalOperation);
|
|
7922
9631
|
const status = external?.status;
|
|
7923
9632
|
if (typeof external?.id === "string" && typeof external.adapterId === "string" && (status === "running" || status === "pending" || status === "completed" || status === "failed" || status === "unknown")) return {
|
|
7924
9633
|
capabilities: ["external-operation-readback"],
|
|
@@ -7949,7 +9658,7 @@ function extractToolSubject(call, result, defaultCwd, hostLock) {
|
|
|
7949
9658
|
}
|
|
7950
9659
|
const structured = structuredGuardMeta(result.meta, call.name);
|
|
7951
9660
|
if (call.name === "context_guard_evidence" && !structured) {
|
|
7952
|
-
const disposition = asRecord$
|
|
9661
|
+
const disposition = asRecord$2(asRecord$2(result.meta)?.contextGuardDisposition);
|
|
7953
9662
|
return {
|
|
7954
9663
|
capabilities: ["guard-state-readback"],
|
|
7955
9664
|
subjects: [],
|
|
@@ -8165,8 +9874,781 @@ function supersedeItem(items, oldId, replacement) {
|
|
|
8165
9874
|
return true;
|
|
8166
9875
|
}
|
|
8167
9876
|
|
|
9877
|
+
//#endregion
|
|
9878
|
+
//#region src/domain/delivery.ts
|
|
9879
|
+
function assistantTextOf(data) {
|
|
9880
|
+
return (data?.message?.content ?? []).filter((part) => part?.type === "text").map((part) => part?.text ?? "").join("\n");
|
|
9881
|
+
}
|
|
9882
|
+
function integerField(data, field$1) {
|
|
9883
|
+
const value = data?.[field$1];
|
|
9884
|
+
return typeof value === "number" && Number.isSafeInteger(value) ? value : void 0;
|
|
9885
|
+
}
|
|
9886
|
+
/**
|
|
9887
|
+
* Derive the trusted deliveries from the event log. Deterministic: a replay of
|
|
9888
|
+
* identical events yields identical facts.
|
|
9889
|
+
*/
|
|
9890
|
+
function deriveTrustedDeliveries(events) {
|
|
9891
|
+
const turns = /* @__PURE__ */ new Map();
|
|
9892
|
+
const factsFor = (turn) => {
|
|
9893
|
+
let facts = turns.get(turn);
|
|
9894
|
+
if (!facts) {
|
|
9895
|
+
facts = {
|
|
9896
|
+
started: false,
|
|
9897
|
+
steps: /* @__PURE__ */ new Set(),
|
|
9898
|
+
assistants: [],
|
|
9899
|
+
ends: []
|
|
9900
|
+
};
|
|
9901
|
+
turns.set(turn, facts);
|
|
9902
|
+
}
|
|
9903
|
+
return facts;
|
|
9904
|
+
};
|
|
9905
|
+
for (const event of events) switch (event.type) {
|
|
9906
|
+
case "turn/start": {
|
|
9907
|
+
const turn = integerField(event.data, "turn");
|
|
9908
|
+
if (turn !== void 0) factsFor(turn).started = true;
|
|
9909
|
+
break;
|
|
9910
|
+
}
|
|
9911
|
+
case "step/start":
|
|
9912
|
+
case "step/end": {
|
|
9913
|
+
const turn = integerField(event.data, "turn");
|
|
9914
|
+
const step = integerField(event.data, "step");
|
|
9915
|
+
if (turn !== void 0 && step !== void 0) factsFor(turn).steps.add(step);
|
|
9916
|
+
break;
|
|
9917
|
+
}
|
|
9918
|
+
case "assistant/message": {
|
|
9919
|
+
const turn = integerField(event.data, "turn");
|
|
9920
|
+
const step = integerField(event.data, "step");
|
|
9921
|
+
if (turn === void 0 || step === void 0) break;
|
|
9922
|
+
const facts = factsFor(turn);
|
|
9923
|
+
facts.steps.add(step);
|
|
9924
|
+
facts.assistants.push({
|
|
9925
|
+
seq: event.seq,
|
|
9926
|
+
step,
|
|
9927
|
+
text: assistantTextOf(event.data),
|
|
9928
|
+
interrupted: event.data?.interrupted === true
|
|
9929
|
+
});
|
|
9930
|
+
break;
|
|
9931
|
+
}
|
|
9932
|
+
case "turn/end": {
|
|
9933
|
+
const turn = integerField(event.data, "turn");
|
|
9934
|
+
if (turn === void 0) break;
|
|
9935
|
+
const reason = event.data?.reason;
|
|
9936
|
+
factsFor(turn).ends.push({
|
|
9937
|
+
seq: event.seq,
|
|
9938
|
+
kind: typeof reason?.kind === "string" ? reason.kind : ""
|
|
9939
|
+
});
|
|
9940
|
+
break;
|
|
9941
|
+
}
|
|
9942
|
+
default: break;
|
|
9943
|
+
}
|
|
9944
|
+
const deliveries = [];
|
|
9945
|
+
for (const [turn, facts] of turns) {
|
|
9946
|
+
if (!facts.started) continue;
|
|
9947
|
+
if (facts.ends.length !== 1) continue;
|
|
9948
|
+
const end = facts.ends[0];
|
|
9949
|
+
if (end.kind !== "completed") continue;
|
|
9950
|
+
if (facts.steps.size === 0) continue;
|
|
9951
|
+
const finalStep = Math.max(...facts.steps);
|
|
9952
|
+
const inFinalStep = facts.assistants.filter((row) => row.step === finalStep && row.seq < end.seq && !row.interrupted && row.text.trim().length > 0);
|
|
9953
|
+
if (inFinalStep.length === 0) continue;
|
|
9954
|
+
const final = inFinalStep.reduce((left, right) => right.seq > left.seq ? right : left);
|
|
9955
|
+
if (facts.assistants.some((row) => row.seq > final.seq && row.seq < end.seq)) continue;
|
|
9956
|
+
deliveries.push({
|
|
9957
|
+
turn,
|
|
9958
|
+
turnEndSeq: end.seq,
|
|
9959
|
+
responseSeq: final.seq,
|
|
9960
|
+
responseSha256: sha256(final.text)
|
|
9961
|
+
});
|
|
9962
|
+
}
|
|
9963
|
+
return deliveries.sort((left, right) => left.turnEndSeq - right.turnEndSeq);
|
|
9964
|
+
}
|
|
9965
|
+
/**
|
|
9966
|
+
* The information-slot items a delivery closes: obligations captured from a
|
|
9967
|
+
* root message inside the delivered turn, in the unit that turn's input
|
|
9968
|
+
* belonged to (or in one of that unit's delegated sub-units), whose semantic
|
|
9969
|
+
* slot is information (an inquiry or an explanation request). Execution,
|
|
9970
|
+
* constraints, and unknowns are never closed by delivery, and neither are
|
|
9971
|
+
* questions from earlier messages.
|
|
9972
|
+
*
|
|
9973
|
+
* An ATTACHMENT obligation (one with an `asset` identity) closes through its
|
|
9974
|
+
* CURRENT interpretation instead of its original message (0.6.1 W060-01
|
|
9975
|
+
* review): a re-interpreted old asset would otherwise never close, because
|
|
9976
|
+
* its root message can no longer belong to a live turn. The binding is the
|
|
9977
|
+
* interpretation fact itself — the delivery must be the answer of the turn
|
|
9978
|
+
* that recorded the interpretation, and the fact must exist at the delivery
|
|
9979
|
+
* watermark. The final answer — even a real one — still never interprets
|
|
9980
|
+
* images on the model's behalf.
|
|
9981
|
+
*/
|
|
9982
|
+
function informationItemIdsForDelivery(items, delivery, turnRootInputSeqs, eligibleUnitIds, interpretationFacts) {
|
|
9983
|
+
const closed = [];
|
|
9984
|
+
for (const [itemId, item] of items) {
|
|
9985
|
+
if (item.status !== "pending") continue;
|
|
9986
|
+
if (item.kind === "prohibition") continue;
|
|
9987
|
+
const isAssetObligation = item.asset !== void 0 && item.asset !== null;
|
|
9988
|
+
if (item.unitId !== void 0 && eligibleUnitIds !== void 0 && !eligibleUnitIds.has(item.unitId)) continue;
|
|
9989
|
+
const informationSlot = item.taskKind === "inquiry" || item.authorityDisposition === "informational" && item.kind === "requirement";
|
|
9990
|
+
item.asset !== void 0 && item.asset;
|
|
9991
|
+
if (isAssetObligation) {
|
|
9992
|
+
if (!(interpretationFacts ?? []).some((fact) => fact.itemId === itemId && fact.turn === delivery.turn && fact.resultSeq <= delivery.turnEndSeq)) continue;
|
|
9993
|
+
closed.push(itemId);
|
|
9994
|
+
continue;
|
|
9995
|
+
}
|
|
9996
|
+
if (informationSlot) {
|
|
9997
|
+
const interpretedThisTurn = (interpretationFacts ?? []).some((fact) => fact.itemId === itemId && fact.turn === delivery.turn && fact.resultSeq <= delivery.turnEndSeq);
|
|
9998
|
+
const sourceSeq = /^m(\d+)(?::|$)/.exec(item.sourceMessageId);
|
|
9999
|
+
if (!interpretedThisTurn && (!sourceSeq || !turnRootInputSeqs.has(Number(sourceSeq[1])))) continue;
|
|
10000
|
+
closed.push(itemId);
|
|
10001
|
+
continue;
|
|
10002
|
+
}
|
|
10003
|
+
}
|
|
10004
|
+
return closed;
|
|
10005
|
+
}
|
|
10006
|
+
|
|
10007
|
+
//#endregion
|
|
10008
|
+
//#region src/domain/host-selection.ts
|
|
10009
|
+
/**
|
|
10010
|
+
* The default question-tool allowlist. The real names are a host tool-bundle
|
|
10011
|
+
* surface: native acceptance pins the audited names for the running cohort,
|
|
10012
|
+
* and the runtime may override this list per cohort.
|
|
10013
|
+
*/
|
|
10014
|
+
const DEFAULT_QUESTION_TOOL_NAMES = ["question", "ask_user"];
|
|
10015
|
+
function parseQuestionCall(rawArguments) {
|
|
10016
|
+
if (typeof rawArguments !== "string") return void 0;
|
|
10017
|
+
let args;
|
|
10018
|
+
try {
|
|
10019
|
+
args = JSON.parse(rawArguments);
|
|
10020
|
+
} catch {
|
|
10021
|
+
return;
|
|
10022
|
+
}
|
|
10023
|
+
if (!args || typeof args !== "object" || Array.isArray(args)) return void 0;
|
|
10024
|
+
const record = args;
|
|
10025
|
+
const rawOptions = record.options ?? record.choices;
|
|
10026
|
+
if (!Array.isArray(rawOptions) || rawOptions.length === 0) return void 0;
|
|
10027
|
+
const options = [];
|
|
10028
|
+
for (const entry of rawOptions) {
|
|
10029
|
+
if (typeof entry !== "string" || !entry.trim()) return void 0;
|
|
10030
|
+
options.push(entry.trim());
|
|
10031
|
+
}
|
|
10032
|
+
return {
|
|
10033
|
+
questionId: typeof record.question_id === "string" ? record.question_id : typeof record.questionId === "string" ? record.questionId : void 0,
|
|
10034
|
+
question: typeof record.question === "string" ? record.question : void 0,
|
|
10035
|
+
options
|
|
10036
|
+
};
|
|
10037
|
+
}
|
|
10038
|
+
function parseSelectionAnswer(content, options) {
|
|
10039
|
+
if (!Array.isArray(content)) return void 0;
|
|
10040
|
+
const text = content.filter((part) => !!part && typeof part === "object").filter((part) => part.type === "text").map((part) => part.text ?? "").join("\n");
|
|
10041
|
+
if (!text.trim()) return void 0;
|
|
10042
|
+
const trimmed = text.trim();
|
|
10043
|
+
if (options.includes(trimmed)) return trimmed;
|
|
10044
|
+
try {
|
|
10045
|
+
const parsed = JSON.parse(trimmed);
|
|
10046
|
+
const answer = parsed.answer ?? parsed.selected ?? parsed.value;
|
|
10047
|
+
if (typeof answer === "string" && options.includes(answer.trim())) return answer.trim();
|
|
10048
|
+
} catch {}
|
|
10049
|
+
}
|
|
10050
|
+
function looksLikeDirectory(value) {
|
|
10051
|
+
return /^[~.]?(?:[\\/][^\n]*)+$/.test(value);
|
|
10052
|
+
}
|
|
10053
|
+
/**
|
|
10054
|
+
* Derive the trusted selections from the durable log. Deterministic: a replay
|
|
10055
|
+
* of identical events yields identical selections.
|
|
10056
|
+
*/
|
|
10057
|
+
function deriveTrustedSelections(events, options) {
|
|
10058
|
+
const names = new Set(options.questionToolNames);
|
|
10059
|
+
const pending = /* @__PURE__ */ new Map();
|
|
10060
|
+
const selections = [];
|
|
10061
|
+
for (const event of events) {
|
|
10062
|
+
if (event.type === "tool/call") {
|
|
10063
|
+
const data$1 = event.data ?? {};
|
|
10064
|
+
const name = String(data$1.name ?? "");
|
|
10065
|
+
if (!names.has(name)) continue;
|
|
10066
|
+
const shape = parseQuestionCall(data$1.arguments);
|
|
10067
|
+
if (!shape) continue;
|
|
10068
|
+
pending.set(String(data$1.callId ?? ""), {
|
|
10069
|
+
callId: String(data$1.callId ?? ""),
|
|
10070
|
+
seq: event.seq,
|
|
10071
|
+
turn: typeof data$1.turn === "number" ? data$1.turn : void 0,
|
|
10072
|
+
toolName: name,
|
|
10073
|
+
shape
|
|
10074
|
+
});
|
|
10075
|
+
continue;
|
|
10076
|
+
}
|
|
10077
|
+
if (event.type !== "tool/result") continue;
|
|
10078
|
+
const data = event.data ?? {};
|
|
10079
|
+
const callId = String(data.message?.source?.callId ?? "");
|
|
10080
|
+
const call = pending.get(callId);
|
|
10081
|
+
if (!call) continue;
|
|
10082
|
+
pending.delete(callId);
|
|
10083
|
+
if (data.error !== void 0) continue;
|
|
10084
|
+
const selected = parseSelectionAnswer(data.message?.content, call.shape.options);
|
|
10085
|
+
if (!selected) continue;
|
|
10086
|
+
selections.push({
|
|
10087
|
+
callId,
|
|
10088
|
+
resultSeq: event.seq,
|
|
10089
|
+
turn: call.turn,
|
|
10090
|
+
toolName: call.toolName,
|
|
10091
|
+
questionId: call.shape.questionId,
|
|
10092
|
+
question: call.shape.question,
|
|
10093
|
+
options: call.shape.options,
|
|
10094
|
+
selected,
|
|
10095
|
+
kind: looksLikeDirectory(selected) ? "directory" : "value"
|
|
10096
|
+
});
|
|
10097
|
+
}
|
|
10098
|
+
return selections;
|
|
10099
|
+
}
|
|
10100
|
+
|
|
10101
|
+
//#endregion
|
|
10102
|
+
//#region src/domain/release.ts
|
|
10103
|
+
/**
|
|
10104
|
+
* Explicit release adoption and single-use tickets (0.6.0 C10 / DS06-F).
|
|
10105
|
+
*
|
|
10106
|
+
* A release is never implicit. "release", a loaded Skill, or an installation
|
|
10107
|
+
* never activates this profile: a root user must explicitly ADOPT a release
|
|
10108
|
+
* contract that names the exact candidate, and every effect must then match
|
|
10109
|
+
* that contract and spend a one-shot reservation.
|
|
10110
|
+
*
|
|
10111
|
+
* Three durable records carry the state machine (P0 §5), all written through
|
|
10112
|
+
* the plugin-notice channel the host already persists:
|
|
10113
|
+
*
|
|
10114
|
+
* - `contract` — the adopted scope: operations, the exact candidate, the
|
|
10115
|
+
* readiness/closure references and an optional expiry.
|
|
10116
|
+
* - `reservation` — written BEFORE any effect; the operation is `in_flight`
|
|
10117
|
+
* from that moment, so a crash cannot be mistaken for "never
|
|
10118
|
+
* started" and the operation is never blindly re-sent.
|
|
10119
|
+
* - `settlement` — written after the effect. Its outcome distinguishes a
|
|
10120
|
+
* PROVEN no-effect (`not_effected`, which releases the lock)
|
|
10121
|
+
* from an UNKNOWN effect (`unknown`/`failed`/`unconfirmed`,
|
|
10122
|
+
* which keeps the lock until a trusted readback reconciles
|
|
10123
|
+
* it) and from a `settled` release.
|
|
10124
|
+
*
|
|
10125
|
+
* CANDIDATE IDENTITY IS TYPED, NOT CONFLATED. A release artifact has several
|
|
10126
|
+
* genuinely different identities — the commit it was built from, the SHA-256 of
|
|
10127
|
+
* the exact bytes, npm's SHA-512 SRI, the package name, the version, the
|
|
10128
|
+
* repository, the ref and the target registry. Each is a separate field and is
|
|
10129
|
+
* compared with its own observed value read from a trusted producer. Comparing,
|
|
10130
|
+
* say, a 64-hex SHA-256 against an SRI can never succeed, so a legitimate
|
|
10131
|
+
* release would have been permanently refused; and accepting a model-supplied
|
|
10132
|
+
* SHA instead of the artifact's embedded one would bind nothing. Every field
|
|
10133
|
+
* the contract declares must be OBSERVED, so omitting evidence is a refusal,
|
|
10134
|
+
* never a bypass.
|
|
10135
|
+
*
|
|
10136
|
+
* COVERAGE SURFACE (frozen wording): only the surfaces Guard itself routes can
|
|
10137
|
+
* be protected. Operations with no Guard execution surface are refused before
|
|
10138
|
+
* any effect, and the plugin never suggests falling back to a plain shell
|
|
10139
|
+
* command. A trusted in-process caller that bypasses Guard entirely is a host
|
|
10140
|
+
* trust boundary and is disclosed as such in the documentation, not pretended
|
|
10141
|
+
* away.
|
|
10142
|
+
*/
|
|
10143
|
+
const RELEASE_CONTRACT_PREFIX = "Context Guard release contract v1: ";
|
|
10144
|
+
const RELEASE_RESERVATION_PREFIX = "Context Guard release reservation v1: ";
|
|
10145
|
+
const RELEASE_SETTLEMENT_PREFIX = "Context Guard release settlement v1: ";
|
|
10146
|
+
const RELEASE_REVOCATION_PREFIX = "Context Guard release revocation v1: ";
|
|
10147
|
+
const RELEASE_OPERATIONS = [
|
|
10148
|
+
"npm_publish",
|
|
10149
|
+
"git_tag",
|
|
10150
|
+
"github_release_create",
|
|
10151
|
+
"github_release_update",
|
|
10152
|
+
"github_release_delete",
|
|
10153
|
+
"composite_runner"
|
|
10154
|
+
];
|
|
10155
|
+
/**
|
|
10156
|
+
* The routing table for this release. `git_tag` and the GitHub Release
|
|
10157
|
+
* operations have no Guard-owned execution route yet; the coordinator
|
|
10158
|
+
* explicitly approved that staged scope reduction on 2026-09-14, and the new
|
|
10159
|
+
* route is the way each of them becomes protectable. A composite runner stays
|
|
10160
|
+
* opaque by construction.
|
|
10161
|
+
*/
|
|
10162
|
+
const RELEASE_OPERATION_SURFACES = {
|
|
10163
|
+
npm_publish: {
|
|
10164
|
+
surface: "context_guard_action",
|
|
10165
|
+
protectable: true,
|
|
10166
|
+
reasonCode: "release_operation_protectable",
|
|
10167
|
+
attribution: "implemented"
|
|
10168
|
+
},
|
|
10169
|
+
git_tag: {
|
|
10170
|
+
surface: "none",
|
|
10171
|
+
protectable: false,
|
|
10172
|
+
reasonCode: "release_operation_unrouted",
|
|
10173
|
+
attribution: "scope_reduction"
|
|
10174
|
+
},
|
|
10175
|
+
github_release_create: {
|
|
10176
|
+
surface: "none",
|
|
10177
|
+
protectable: false,
|
|
10178
|
+
reasonCode: "release_operation_unrouted",
|
|
10179
|
+
attribution: "scope_reduction"
|
|
10180
|
+
},
|
|
10181
|
+
github_release_update: {
|
|
10182
|
+
surface: "none",
|
|
10183
|
+
protectable: false,
|
|
10184
|
+
reasonCode: "release_operation_unrouted",
|
|
10185
|
+
attribution: "scope_reduction"
|
|
10186
|
+
},
|
|
10187
|
+
github_release_delete: {
|
|
10188
|
+
surface: "none",
|
|
10189
|
+
protectable: false,
|
|
10190
|
+
reasonCode: "release_operation_unrouted",
|
|
10191
|
+
attribution: "scope_reduction"
|
|
10192
|
+
},
|
|
10193
|
+
composite_runner: {
|
|
10194
|
+
surface: "none",
|
|
10195
|
+
protectable: false,
|
|
10196
|
+
reasonCode: "release_runner_opaque",
|
|
10197
|
+
attribution: "host_boundary"
|
|
10198
|
+
}
|
|
10199
|
+
};
|
|
10200
|
+
const FULL_SHA40 = /^[0-9a-f]{40}$/;
|
|
10201
|
+
const SHA256 = /^[0-9a-f]{64}$/;
|
|
10202
|
+
const SRI = /^sha(?:256|384|512)-[A-Za-z0-9+/]+={0,2}$/;
|
|
10203
|
+
/**
|
|
10204
|
+
* How strongly an outcome resolves the reservation. A `settled` release is
|
|
10205
|
+
* never downgraded by a later record, while a stronger record reconciles a
|
|
10206
|
+
* weaker one — that is how a trusted readback recovers an earlier unconfirmed
|
|
10207
|
+
* attempt instead of being discarded.
|
|
10208
|
+
*/
|
|
10209
|
+
const OUTCOME_STRENGTH = {
|
|
10210
|
+
not_effected: 0,
|
|
10211
|
+
unknown: 1,
|
|
10212
|
+
failed: 1,
|
|
10213
|
+
unconfirmed: 2,
|
|
10214
|
+
settled: 3
|
|
10215
|
+
};
|
|
10216
|
+
function asRecord$1(value) {
|
|
10217
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
10218
|
+
}
|
|
10219
|
+
function optionalString(value) {
|
|
10220
|
+
return typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
|
|
10221
|
+
}
|
|
10222
|
+
/**
|
|
10223
|
+
* Normalize a candidate release contract from a root adoption payload. Every
|
|
10224
|
+
* field is validated: an unparsable or partial adoption is refused rather than
|
|
10225
|
+
* approximated, because a half-specified contract would authorize an
|
|
10226
|
+
* unspecified candidate.
|
|
10227
|
+
*/
|
|
10228
|
+
function normalizeReleaseContract(raw, adoptedBy, adoptedAtRevision = 0) {
|
|
10229
|
+
const errors = [];
|
|
10230
|
+
const value = asRecord$1(raw);
|
|
10231
|
+
if (!value) return { errors: ["release_contract_malformed"] };
|
|
10232
|
+
const operations = [];
|
|
10233
|
+
if (!Array.isArray(value.operations) || value.operations.length === 0) errors.push("release_operations_missing");
|
|
10234
|
+
else for (const entry of value.operations) {
|
|
10235
|
+
if (typeof entry !== "string" || !RELEASE_OPERATIONS.includes(entry)) {
|
|
10236
|
+
errors.push("release_operation_unknown");
|
|
10237
|
+
continue;
|
|
10238
|
+
}
|
|
10239
|
+
const operation = entry;
|
|
10240
|
+
if (!RELEASE_OPERATION_SURFACES[operation].protectable) {
|
|
10241
|
+
errors.push(RELEASE_OPERATION_SURFACES[operation].reasonCode);
|
|
10242
|
+
continue;
|
|
10243
|
+
}
|
|
10244
|
+
if (!operations.includes(operation)) operations.push(operation);
|
|
10245
|
+
}
|
|
10246
|
+
const candidate = asRecord$1(value.candidate);
|
|
10247
|
+
if (!candidate) errors.push("release_candidate_missing");
|
|
10248
|
+
const CANDIDATE_FIELDS = [
|
|
10249
|
+
"fullSha40",
|
|
10250
|
+
"ref",
|
|
10251
|
+
"repository",
|
|
10252
|
+
"packageId",
|
|
10253
|
+
"version",
|
|
10254
|
+
"artifactSha256",
|
|
10255
|
+
"artifactSri",
|
|
10256
|
+
"registry",
|
|
10257
|
+
"artifactDigest"
|
|
10258
|
+
];
|
|
10259
|
+
for (const key of Object.keys(candidate ?? {})) if (!CANDIDATE_FIELDS.includes(key)) errors.push("release_candidate_field_unknown");
|
|
10260
|
+
const fullSha40 = optionalString(candidate?.fullSha40) ?? "";
|
|
10261
|
+
if (!FULL_SHA40.test(fullSha40)) errors.push("release_candidate_sha_invalid");
|
|
10262
|
+
const ref = optionalString(candidate?.ref);
|
|
10263
|
+
const repository = optionalString(candidate?.repository);
|
|
10264
|
+
const packageId = optionalString(candidate?.packageId);
|
|
10265
|
+
const version = optionalString(candidate?.version);
|
|
10266
|
+
let artifactSha256 = optionalString(candidate?.artifactSha256);
|
|
10267
|
+
let artifactSri = optionalString(candidate?.artifactSri);
|
|
10268
|
+
const legacyDigest = optionalString(candidate?.artifactDigest);
|
|
10269
|
+
if (legacyDigest !== void 0) if (SHA256.test(legacyDigest)) artifactSha256 ??= legacyDigest;
|
|
10270
|
+
else if (SRI.test(legacyDigest)) artifactSri ??= legacyDigest;
|
|
10271
|
+
else errors.push("release_candidate_artifact_digest_invalid");
|
|
10272
|
+
const registry = optionalString(candidate?.registry);
|
|
10273
|
+
if (artifactSha256 !== void 0 && !SHA256.test(artifactSha256)) errors.push("release_candidate_sha256_invalid");
|
|
10274
|
+
if (artifactSri !== void 0 && !SRI.test(artifactSri)) errors.push("release_candidate_sri_invalid");
|
|
10275
|
+
const readinessRefs = Array.isArray(value.readinessRefs) ? value.readinessRefs.filter((entry) => typeof entry === "string" && entry.length > 0) : [];
|
|
10276
|
+
const closureCertRef = optionalString(value.closureCertRef);
|
|
10277
|
+
let expiresAtEpochMs;
|
|
10278
|
+
if (value.expiresAtEpochMs !== void 0) if (typeof value.expiresAtEpochMs !== "number" || !Number.isSafeInteger(value.expiresAtEpochMs) || value.expiresAtEpochMs <= 0) errors.push("release_expiry_invalid");
|
|
10279
|
+
else expiresAtEpochMs = value.expiresAtEpochMs;
|
|
10280
|
+
if (errors.length) return { errors: [...new Set(errors)] };
|
|
10281
|
+
const suppliedId = optionalString(value.contractId);
|
|
10282
|
+
const body = {
|
|
10283
|
+
operations: [...operations].sort(),
|
|
10284
|
+
candidate: {
|
|
10285
|
+
fullSha40,
|
|
10286
|
+
...ref ? { ref } : {},
|
|
10287
|
+
...repository ? { repository } : {},
|
|
10288
|
+
...packageId ? { packageId } : {},
|
|
10289
|
+
...version ? { version } : {},
|
|
10290
|
+
...artifactSha256 ? { artifactSha256 } : {},
|
|
10291
|
+
...artifactSri ? { artifactSri } : {},
|
|
10292
|
+
...registry ? { registry } : {}
|
|
10293
|
+
},
|
|
10294
|
+
readinessRefs: [...readinessRefs].sort(),
|
|
10295
|
+
...closureCertRef ? { closureCertRef } : {},
|
|
10296
|
+
...expiresAtEpochMs !== void 0 ? { expiresAtEpochMs } : {}
|
|
10297
|
+
};
|
|
10298
|
+
return {
|
|
10299
|
+
contract: {
|
|
10300
|
+
contractId: suppliedId ?? `rel-${sha256(JSON.stringify(body)).slice(0, 16)}`,
|
|
10301
|
+
adoptedBy,
|
|
10302
|
+
adoptedAtRevision,
|
|
10303
|
+
operations: operations.sort(),
|
|
10304
|
+
candidate: body.candidate,
|
|
10305
|
+
readinessRefs: body.readinessRefs,
|
|
10306
|
+
...closureCertRef ? { closureCertRef } : {},
|
|
10307
|
+
...expiresAtEpochMs !== void 0 ? { expiresAtEpochMs } : {}
|
|
10308
|
+
},
|
|
10309
|
+
errors: []
|
|
10310
|
+
};
|
|
10311
|
+
}
|
|
10312
|
+
function normalizeReservation(raw) {
|
|
10313
|
+
const value = asRecord$1(raw);
|
|
10314
|
+
if (!value) return void 0;
|
|
10315
|
+
const contractId = optionalString(value.contractId);
|
|
10316
|
+
const operation = optionalString(value.operation);
|
|
10317
|
+
const callId = optionalString(value.callId);
|
|
10318
|
+
const startedAtSeq = value.startedAtSeq;
|
|
10319
|
+
if (!contractId || !callId) return void 0;
|
|
10320
|
+
if (!operation || !RELEASE_OPERATIONS.includes(operation)) return void 0;
|
|
10321
|
+
if (typeof startedAtSeq !== "number" || !Number.isSafeInteger(startedAtSeq)) return void 0;
|
|
10322
|
+
const observedArtifactSri = optionalString(value.observedArtifactSri);
|
|
10323
|
+
return {
|
|
10324
|
+
contractId,
|
|
10325
|
+
operation,
|
|
10326
|
+
callId,
|
|
10327
|
+
startedAtSeq,
|
|
10328
|
+
status: "in_flight",
|
|
10329
|
+
...observedArtifactSri ? { observedArtifactSri } : {}
|
|
10330
|
+
};
|
|
10331
|
+
}
|
|
10332
|
+
const RELEASE_OUTCOMES = [
|
|
10333
|
+
"settled",
|
|
10334
|
+
"unconfirmed",
|
|
10335
|
+
"unknown",
|
|
10336
|
+
"failed",
|
|
10337
|
+
"not_effected"
|
|
10338
|
+
];
|
|
10339
|
+
function normalizeSettlement(raw) {
|
|
10340
|
+
const value = asRecord$1(raw);
|
|
10341
|
+
if (!value) return void 0;
|
|
10342
|
+
const contractId = optionalString(value.contractId);
|
|
10343
|
+
const operation = optionalString(value.operation);
|
|
10344
|
+
const callId = optionalString(value.callId);
|
|
10345
|
+
const settledAtSeq = value.settledAtSeq;
|
|
10346
|
+
const outcome = optionalString(value.outcome);
|
|
10347
|
+
if (!contractId || !callId) return void 0;
|
|
10348
|
+
if (!operation || !RELEASE_OPERATIONS.includes(operation)) return void 0;
|
|
10349
|
+
if (typeof settledAtSeq !== "number" || !Number.isSafeInteger(settledAtSeq)) return void 0;
|
|
10350
|
+
if (!outcome || !RELEASE_OUTCOMES.includes(outcome)) return void 0;
|
|
10351
|
+
const readbackRaw = asRecord$1(value.readback);
|
|
10352
|
+
const kind = readbackRaw?.kind;
|
|
10353
|
+
return {
|
|
10354
|
+
contractId,
|
|
10355
|
+
operation,
|
|
10356
|
+
callId,
|
|
10357
|
+
settledAtSeq,
|
|
10358
|
+
readback: readbackRaw && (kind === "npm_integrity" || kind === "git_ref" || kind === "github_release") && optionalString(readbackRaw.identity) ? {
|
|
10359
|
+
kind,
|
|
10360
|
+
identity: optionalString(readbackRaw.identity)
|
|
10361
|
+
} : "unavailable",
|
|
10362
|
+
outcome
|
|
10363
|
+
};
|
|
10364
|
+
}
|
|
10365
|
+
/** The adopted, not-revoked contract that covers an operation, newest first. */
|
|
10366
|
+
function releaseContractFor(projection, operation, contractId) {
|
|
10367
|
+
const contracts = projection.releaseContracts.filter((contract) => contract.revokedAtSeq === void 0 && (contractId === void 0 || contract.contractId === contractId) && contract.operations.includes(operation));
|
|
10368
|
+
return contracts.length ? contracts[contracts.length - 1] : void 0;
|
|
10369
|
+
}
|
|
10370
|
+
/** Whether a contract was explicitly revoked by a durable root command. */
|
|
10371
|
+
function isContractRevoked(projection, contractId) {
|
|
10372
|
+
return projection.releaseContracts.some((contract) => contract.contractId === contractId && contract.revokedAtSeq !== void 0);
|
|
10373
|
+
}
|
|
10374
|
+
/**
|
|
10375
|
+
* The reconciled settlement per (contract, operation, callId): the strongest
|
|
10376
|
+
* outcome wins, ties resolve to the later record. A `settled` release is never
|
|
10377
|
+
* revoked by a later weaker record.
|
|
10378
|
+
*/
|
|
10379
|
+
function reconciledSettlements(projection, contractId, operation) {
|
|
10380
|
+
const byCall = /* @__PURE__ */ new Map();
|
|
10381
|
+
for (const settlement of projection.releaseSettlements) {
|
|
10382
|
+
if (settlement.contractId !== contractId || settlement.operation !== operation) continue;
|
|
10383
|
+
const existing = byCall.get(settlement.callId);
|
|
10384
|
+
if (!existing) {
|
|
10385
|
+
byCall.set(settlement.callId, settlement);
|
|
10386
|
+
continue;
|
|
10387
|
+
}
|
|
10388
|
+
const stronger = OUTCOME_STRENGTH[settlement.outcome] > OUTCOME_STRENGTH[existing.outcome];
|
|
10389
|
+
const newer = OUTCOME_STRENGTH[settlement.outcome] === OUTCOME_STRENGTH[existing.outcome] && settlement.settledAtSeq >= existing.settledAtSeq;
|
|
10390
|
+
if (stronger || newer) byCall.set(settlement.callId, settlement);
|
|
10391
|
+
}
|
|
10392
|
+
return [...byCall.values()];
|
|
10393
|
+
}
|
|
10394
|
+
/** Whether a settlement releases the one-shot lock: settled, or proven no-effect. */
|
|
10395
|
+
function releasesLock(settlement) {
|
|
10396
|
+
return settlement.outcome === "settled" || settlement.outcome === "not_effected";
|
|
10397
|
+
}
|
|
10398
|
+
/** The in-flight (unresolved) reservation for one contract operation, if any. */
|
|
10399
|
+
function inFlightReservation(projection, contractId, operation) {
|
|
10400
|
+
const settled = reconciledSettlements(projection, contractId, operation);
|
|
10401
|
+
for (const reservation of projection.releaseReservations) {
|
|
10402
|
+
if (reservation.contractId !== contractId || reservation.operation !== operation) continue;
|
|
10403
|
+
const resolution = settled.find((settlement) => settlement.callId === reservation.callId);
|
|
10404
|
+
if (!resolution || !releasesLock(resolution)) return reservation;
|
|
10405
|
+
}
|
|
10406
|
+
}
|
|
10407
|
+
/** Whether a contract operation has already been consumed by a settled effect. */
|
|
10408
|
+
function settledOperations(projection, contractId) {
|
|
10409
|
+
const consumed = [];
|
|
10410
|
+
for (const settlement of projection.releaseSettlements) {
|
|
10411
|
+
if (settlement.contractId !== contractId) continue;
|
|
10412
|
+
if (settlement.outcome !== "settled") continue;
|
|
10413
|
+
if (!consumed.includes(settlement.operation)) consumed.push(settlement.operation);
|
|
10414
|
+
}
|
|
10415
|
+
return consumed.sort();
|
|
10416
|
+
}
|
|
10417
|
+
const CANDIDATE_FIELD_CODES = [
|
|
10418
|
+
{
|
|
10419
|
+
field: "fullSha40",
|
|
10420
|
+
label: "commit",
|
|
10421
|
+
unresolvedCode: "release_candidate_sha_unresolved",
|
|
10422
|
+
mismatchCode: "release_candidate_sha_mismatch"
|
|
10423
|
+
},
|
|
10424
|
+
{
|
|
10425
|
+
field: "ref",
|
|
10426
|
+
label: "ref",
|
|
10427
|
+
unresolvedCode: "release_candidate_ref_unresolved",
|
|
10428
|
+
mismatchCode: "release_candidate_ref_mismatch"
|
|
10429
|
+
},
|
|
10430
|
+
{
|
|
10431
|
+
field: "repository",
|
|
10432
|
+
label: "repository",
|
|
10433
|
+
unresolvedCode: "release_candidate_repository_unresolved",
|
|
10434
|
+
mismatchCode: "release_candidate_repository_mismatch"
|
|
10435
|
+
},
|
|
10436
|
+
{
|
|
10437
|
+
field: "packageId",
|
|
10438
|
+
label: "package",
|
|
10439
|
+
unresolvedCode: "release_candidate_package_unresolved",
|
|
10440
|
+
mismatchCode: "release_candidate_package_mismatch"
|
|
10441
|
+
},
|
|
10442
|
+
{
|
|
10443
|
+
field: "version",
|
|
10444
|
+
label: "version",
|
|
10445
|
+
unresolvedCode: "release_candidate_version_unresolved",
|
|
10446
|
+
mismatchCode: "release_candidate_version_mismatch"
|
|
10447
|
+
},
|
|
10448
|
+
{
|
|
10449
|
+
field: "artifactSha256",
|
|
10450
|
+
label: "artifact SHA-256",
|
|
10451
|
+
unresolvedCode: "release_artifact_sha256_unresolved",
|
|
10452
|
+
mismatchCode: "release_candidate_artifact_mismatch"
|
|
10453
|
+
},
|
|
10454
|
+
{
|
|
10455
|
+
field: "artifactSri",
|
|
10456
|
+
label: "artifact SRI",
|
|
10457
|
+
unresolvedCode: "release_artifact_sri_unresolved",
|
|
10458
|
+
mismatchCode: "release_candidate_artifact_sri_mismatch"
|
|
10459
|
+
},
|
|
10460
|
+
{
|
|
10461
|
+
field: "registry",
|
|
10462
|
+
label: "registry",
|
|
10463
|
+
unresolvedCode: "release_candidate_registry_unresolved",
|
|
10464
|
+
mismatchCode: "release_candidate_registry_mismatch"
|
|
10465
|
+
}
|
|
10466
|
+
];
|
|
10467
|
+
/** A readiness reference resolves to a real, already-established fact. */
|
|
10468
|
+
function readinessResolves(projection, ref) {
|
|
10469
|
+
if (projection.checkpoints.some((checkpoint) => checkpoint.id === ref && checkpoint.result === "certified")) return true;
|
|
10470
|
+
if (projection.boundaries.some((boundary) => boundary.id === ref)) return true;
|
|
10471
|
+
return projection.items.get(ref)?.status === "passed";
|
|
10472
|
+
}
|
|
10473
|
+
/**
|
|
10474
|
+
* The pre-effect release decision. Order matters: an unprotectable surface and
|
|
10475
|
+
* a damaged release state are refused before expiry or candidate checks,
|
|
10476
|
+
* because running an unprotected operation is never made acceptable by a valid
|
|
10477
|
+
* ticket, and because unreadable release state must not authorize anything.
|
|
10478
|
+
*/
|
|
10479
|
+
function releasePreEffectDecision(projection, request) {
|
|
10480
|
+
const surface = RELEASE_OPERATION_SURFACES[request.operation];
|
|
10481
|
+
if (!surface.protectable) return {
|
|
10482
|
+
status: "denied",
|
|
10483
|
+
reasonCode: surface.reasonCode
|
|
10484
|
+
};
|
|
10485
|
+
if (projection.releaseStateDamaged) return {
|
|
10486
|
+
status: "denied",
|
|
10487
|
+
reasonCode: "release_state_damaged"
|
|
10488
|
+
};
|
|
10489
|
+
const contract = releaseContractFor(projection, request.operation, request.contractId);
|
|
10490
|
+
if (!contract) {
|
|
10491
|
+
if (projection.releaseContracts.some((entry) => entry.revokedAtSeq !== void 0 && entry.operations.includes(request.operation) && (request.contractId === void 0 || entry.contractId === request.contractId))) return {
|
|
10492
|
+
status: "denied",
|
|
10493
|
+
reasonCode: "release_contract_revoked"
|
|
10494
|
+
};
|
|
10495
|
+
if (request.contractId !== void 0 && isContractRevoked(projection, request.contractId)) return {
|
|
10496
|
+
status: "denied",
|
|
10497
|
+
reasonCode: "release_contract_revoked"
|
|
10498
|
+
};
|
|
10499
|
+
return {
|
|
10500
|
+
status: "denied",
|
|
10501
|
+
reasonCode: projection.releaseContracts.length === 0 ? "release_contract_required" : "release_operation_not_adopted"
|
|
10502
|
+
};
|
|
10503
|
+
}
|
|
10504
|
+
if (contract.expiresAtEpochMs !== void 0) {
|
|
10505
|
+
if (request.nowEpochMs === void 0) return {
|
|
10506
|
+
status: "denied",
|
|
10507
|
+
reasonCode: "release_expiry_unevaluable",
|
|
10508
|
+
contractId: contract.contractId
|
|
10509
|
+
};
|
|
10510
|
+
if (request.nowEpochMs >= contract.expiresAtEpochMs) return {
|
|
10511
|
+
status: "denied",
|
|
10512
|
+
reasonCode: "release_contract_expired",
|
|
10513
|
+
contractId: contract.contractId
|
|
10514
|
+
};
|
|
10515
|
+
}
|
|
10516
|
+
if (settledOperations(projection, contract.contractId).includes(request.operation)) return {
|
|
10517
|
+
status: "denied",
|
|
10518
|
+
reasonCode: "release_operation_consumed",
|
|
10519
|
+
contractId: contract.contractId
|
|
10520
|
+
};
|
|
10521
|
+
if (inFlightReservation(projection, contract.contractId, request.operation)) return {
|
|
10522
|
+
status: "denied",
|
|
10523
|
+
reasonCode: "release_operation_in_flight",
|
|
10524
|
+
contractId: contract.contractId
|
|
10525
|
+
};
|
|
10526
|
+
for (const ref of contract.readinessRefs) if (!readinessResolves(projection, ref)) return {
|
|
10527
|
+
status: "denied",
|
|
10528
|
+
reasonCode: "release_readiness_unresolved",
|
|
10529
|
+
contractId: contract.contractId
|
|
10530
|
+
};
|
|
10531
|
+
const frozen = contract.frozenClosure;
|
|
10532
|
+
const closure = frozen !== void 0 ? projection.checkpoints.find((checkpoint) => checkpoint.id === frozen.id) : void 0;
|
|
10533
|
+
if (!frozen || frozen.contractRevision !== contract.adoptedAtRevision || frozen.id !== contract.closureCertRef || !closure || closure.result !== "certified" || closure.certificationDigest !== frozen.certificationDigest || closure.epoch !== projection.epoch) return {
|
|
10534
|
+
status: "denied",
|
|
10535
|
+
reasonCode: "release_closure_unresolved",
|
|
10536
|
+
contractId: contract.contractId
|
|
10537
|
+
};
|
|
10538
|
+
if (contract.candidate.artifactSha256 === void 0 && contract.candidate.artifactSri === void 0) return {
|
|
10539
|
+
status: "denied",
|
|
10540
|
+
reasonCode: "release_artifact_identity_required",
|
|
10541
|
+
contractId: contract.contractId
|
|
10542
|
+
};
|
|
10543
|
+
const candidate = contract.candidate;
|
|
10544
|
+
const observedIdentity = request.observed ?? {};
|
|
10545
|
+
const compared = [];
|
|
10546
|
+
for (const entry of CANDIDATE_FIELD_CODES) {
|
|
10547
|
+
const declared = candidate[entry.field];
|
|
10548
|
+
if (typeof declared !== "string") continue;
|
|
10549
|
+
compared.push([entry, declared]);
|
|
10550
|
+
}
|
|
10551
|
+
for (const [entry, declared] of compared) {
|
|
10552
|
+
const observed = observedIdentity[entry.field];
|
|
10553
|
+
if (observed === void 0) return {
|
|
10554
|
+
status: "denied",
|
|
10555
|
+
reasonCode: entry.unresolvedCode,
|
|
10556
|
+
contractId: contract.contractId
|
|
10557
|
+
};
|
|
10558
|
+
if (observed !== declared) return {
|
|
10559
|
+
status: "denied",
|
|
10560
|
+
reasonCode: entry.mismatchCode,
|
|
10561
|
+
contractId: contract.contractId
|
|
10562
|
+
};
|
|
10563
|
+
}
|
|
10564
|
+
if (candidate.ref !== void 0 && observedIdentity.refSha !== void 0 && observedIdentity.fullSha40 !== void 0 && observedIdentity.refSha !== observedIdentity.fullSha40) return {
|
|
10565
|
+
status: "denied",
|
|
10566
|
+
reasonCode: "release_ref_commit_mismatch",
|
|
10567
|
+
contractId: contract.contractId
|
|
10568
|
+
};
|
|
10569
|
+
const resolved = request.resolvedTarget;
|
|
10570
|
+
if (resolved === void 0) return {
|
|
10571
|
+
status: "denied",
|
|
10572
|
+
reasonCode: "release_target_unresolved",
|
|
10573
|
+
contractId: contract.contractId
|
|
10574
|
+
};
|
|
10575
|
+
if (resolved.version === void 0) return {
|
|
10576
|
+
status: "denied",
|
|
10577
|
+
reasonCode: "release_target_unresolved",
|
|
10578
|
+
contractId: contract.contractId
|
|
10579
|
+
};
|
|
10580
|
+
if (candidate.packageId !== void 0 && resolved.artifact_id !== candidate.packageId) return {
|
|
10581
|
+
status: "denied",
|
|
10582
|
+
reasonCode: "release_target_package_mismatch",
|
|
10583
|
+
contractId: contract.contractId
|
|
10584
|
+
};
|
|
10585
|
+
if (candidate.version !== void 0 && resolved.version !== candidate.version) return {
|
|
10586
|
+
status: "denied",
|
|
10587
|
+
reasonCode: "release_target_version_mismatch",
|
|
10588
|
+
contractId: contract.contractId
|
|
10589
|
+
};
|
|
10590
|
+
if (candidate.registry !== void 0 && resolved.registry !== candidate.registry) return {
|
|
10591
|
+
status: "denied",
|
|
10592
|
+
reasonCode: "release_target_registry_mismatch",
|
|
10593
|
+
contractId: contract.contractId
|
|
10594
|
+
};
|
|
10595
|
+
return {
|
|
10596
|
+
status: "granted",
|
|
10597
|
+
reasonCode: "release_contract_granted",
|
|
10598
|
+
contractId: contract.contractId
|
|
10599
|
+
};
|
|
10600
|
+
}
|
|
10601
|
+
/**
|
|
10602
|
+
* Whether a trusted readback settles the attempt: the readback must name the
|
|
10603
|
+
* SAME artifact identity the contract froze. A registry that answers with a
|
|
10604
|
+
* different integrity proves the wrong bytes are published, which is an
|
|
10605
|
+
* unknown outcome for this contract, never a settlement.
|
|
10606
|
+
*/
|
|
10607
|
+
function readbackSettlesContract(contract, readback, reservationSri) {
|
|
10608
|
+
if (readback === "unavailable") return "unconfirmed";
|
|
10609
|
+
if (readback.kind !== "npm_integrity") return "unconfirmed";
|
|
10610
|
+
const expected = contract.candidate.artifactSri ?? reservationSri;
|
|
10611
|
+
if (expected === void 0) return "unconfirmed";
|
|
10612
|
+
return readback.identity === expected ? "settled" : "mismatch";
|
|
10613
|
+
}
|
|
10614
|
+
/**
|
|
10615
|
+
* The reservation for one call, including revoked contracts: reconciling an
|
|
10616
|
+
* operation that was already in flight when its contract was revoked is the
|
|
10617
|
+
* recovery case, not an authority question.
|
|
10618
|
+
*/
|
|
10619
|
+
function reservationFor(projection, contractId, callId) {
|
|
10620
|
+
return projection.releaseReservations.find((entry) => entry.contractId === contractId && entry.callId === callId);
|
|
10621
|
+
}
|
|
10622
|
+
/** A contract by id, INCLUDING revoked ones, for recovery lookups. */
|
|
10623
|
+
function contractById(projection, contractId) {
|
|
10624
|
+
return projection.releaseContracts.find((entry) => entry.contractId === contractId);
|
|
10625
|
+
}
|
|
10626
|
+
/** The coverage report for one contract: which adopted operations Guard can protect. */
|
|
10627
|
+
function releaseCoverage(contract) {
|
|
10628
|
+
return [...contract.operations].sort().map((operation) => ({
|
|
10629
|
+
operation,
|
|
10630
|
+
...RELEASE_OPERATION_SURFACES[operation]
|
|
10631
|
+
}));
|
|
10632
|
+
}
|
|
10633
|
+
|
|
8168
10634
|
//#endregion
|
|
8169
10635
|
//#region src/domain/derive.ts
|
|
10636
|
+
/**
|
|
10637
|
+
* Audited delegation tool names (C04/DS06-B). A tool result from one of these
|
|
10638
|
+
* is a subagent's answer: bounded evidence for the unit that asked for it, and
|
|
10639
|
+
* never a parent completion. The real names are a host tool-bundle surface —
|
|
10640
|
+
* native acceptance pins the audited cohort, exactly like the question-tool
|
|
10641
|
+
* allowlist — so this list is the production default and can be overridden by
|
|
10642
|
+
* an audited cohort.
|
|
10643
|
+
*/
|
|
10644
|
+
const DEFAULT_DELEGATION_TOOL_NAMES = [
|
|
10645
|
+
"task",
|
|
10646
|
+
"delegate",
|
|
10647
|
+
"delegate_task",
|
|
10648
|
+
"subagent",
|
|
10649
|
+
"subagent_fork",
|
|
10650
|
+
"spawn_agent"
|
|
10651
|
+
];
|
|
8170
10652
|
const CAPTURE_V042_NOTICE = "Context Guard capture boundary: v0.4.2";
|
|
8171
10653
|
const PROTOCOL_V3_NOTICE = "Context Guard protocol boundary: v3.0.0";
|
|
8172
10654
|
/**
|
|
@@ -8177,6 +10659,15 @@ const PROTOCOL_V3_NOTICE = "Context Guard protocol boundary: v3.0.0";
|
|
|
8177
10659
|
* their historical meaning for replay.
|
|
8178
10660
|
*/
|
|
8179
10661
|
const PROTOCOL_V4_NOTICE = "Context Guard protocol boundary: v4.0.0";
|
|
10662
|
+
/**
|
|
10663
|
+
* 0.6.0 first-step boundary: same placement discipline as v4. It cuts the
|
|
10664
|
+
* work-unit, delivery, and certificate-v2 semantics (P0 §1): messages before
|
|
10665
|
+
* it keep their historical rules, messages after it are captured into work
|
|
10666
|
+
* units and close through unit-closure certificates and trusted deliveries.
|
|
10667
|
+
* An old binary ignores this notice (plugin source, unmatched pattern), so the
|
|
10668
|
+
* fail direction on rollback is closed, never a misread.
|
|
10669
|
+
*/
|
|
10670
|
+
const PROTOCOL_V5_NOTICE = "Context Guard protocol boundary: v5.0.0";
|
|
8180
10671
|
function isProtocolBoundaryNotice(event, notice = PROTOCOL_V3_NOTICE) {
|
|
8181
10672
|
if (event.type !== "user/message") return false;
|
|
8182
10673
|
const data = asRecord(event.data);
|
|
@@ -8196,6 +10687,213 @@ function parseArguments(raw) {
|
|
|
8196
10687
|
function asRecord(value) {
|
|
8197
10688
|
return typeof value === "object" && value !== null ? value : void 0;
|
|
8198
10689
|
}
|
|
10690
|
+
/** Stable JSON, used for the release adoption digest. */
|
|
10691
|
+
function stableJson(value) {
|
|
10692
|
+
if (Array.isArray(value)) return `[${value.map(stableJson).join(",")}]`;
|
|
10693
|
+
if (value && typeof value === "object") return `{${Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, entry]) => `${JSON.stringify(key)}:${stableJson(entry)}`).join(",")}}`;
|
|
10694
|
+
return JSON.stringify(value);
|
|
10695
|
+
}
|
|
10696
|
+
/**
|
|
10697
|
+
* Bounded release diagnostic ledger (last 16 entries). A rejected record also
|
|
10698
|
+
* marks the release state damaged: an unreadable reservation, settlement or
|
|
10699
|
+
* contract must block release operations rather than being silently forgotten,
|
|
10700
|
+
* and it must not touch the projection's own integrity, which governs ordinary
|
|
10701
|
+
* work.
|
|
10702
|
+
*/
|
|
10703
|
+
function pushReleaseDiagnostic(projection, seq, reasonCode, damaging = false) {
|
|
10704
|
+
if (damaging) projection.releaseStateDamaged = true;
|
|
10705
|
+
if (projection.releaseDiagnostics.some((entry) => entry.seq === seq && entry.reasonCode === reasonCode)) return;
|
|
10706
|
+
projection.releaseDiagnostics.push({
|
|
10707
|
+
seq,
|
|
10708
|
+
reasonCode
|
|
10709
|
+
});
|
|
10710
|
+
if (projection.releaseDiagnostics.length > 16) projection.releaseDiagnostics.shift();
|
|
10711
|
+
}
|
|
10712
|
+
function assetReceiptMatches(receipt, asset) {
|
|
10713
|
+
const record = asRecord(receipt);
|
|
10714
|
+
return record !== void 0 && record.message_seq === asset.messageSeq && record.part_index === asset.partIndex && record.media_sha256 === asset.mediaSha256;
|
|
10715
|
+
}
|
|
10716
|
+
/**
|
|
10717
|
+
* Atomically supersede one unresolved clause by its recorded interpretation
|
|
10718
|
+
* partition (0.6.1 review round 10). Every declared sub-span becomes its own
|
|
10719
|
+
* obligation bound to the exact sub-span: information sub-spans become
|
|
10720
|
+
* delivery-closable informational obligations; declared-unknown and
|
|
10721
|
+
* undeclared sub-spans become pending unresolved obligations that keep the
|
|
10722
|
+
* clause's execution and unknown demands open. Returns the ids of the
|
|
10723
|
+
* created information sub-items.
|
|
10724
|
+
*/
|
|
10725
|
+
function supersedeClauseByPartition(projection, item, receipt) {
|
|
10726
|
+
const extent = itemExtentOf(item);
|
|
10727
|
+
const information = readPartitionSpans(receipt.information_spans);
|
|
10728
|
+
const unknown = readPartitionSpans(receipt.unknown_spans);
|
|
10729
|
+
if (!information || !unknown) return [];
|
|
10730
|
+
for (const span of [...information, ...unknown]) if (span.start < extent.start || span.end > extent.end) return [];
|
|
10731
|
+
const ordered = [...information, ...unknown].sort((left, right) => left.start - right.start || left.end - right.end);
|
|
10732
|
+
for (let index = 1; index < ordered.length; index += 1) if (ordered[index].start < ordered[index - 1].end) return [];
|
|
10733
|
+
const complement = [];
|
|
10734
|
+
let cursor = extent.start;
|
|
10735
|
+
for (const span of ordered) {
|
|
10736
|
+
if (span.start > cursor) complement.push({
|
|
10737
|
+
start: cursor,
|
|
10738
|
+
end: span.start
|
|
10739
|
+
});
|
|
10740
|
+
cursor = Math.max(cursor, span.end);
|
|
10741
|
+
}
|
|
10742
|
+
if (cursor < extent.end) complement.push({
|
|
10743
|
+
start: cursor,
|
|
10744
|
+
end: extent.end
|
|
10745
|
+
});
|
|
10746
|
+
const partIndex = (item.spans ?? [])[0]?.partIndex ?? 0;
|
|
10747
|
+
const rawTextSha256 = item.rawTextSha256;
|
|
10748
|
+
const revisionBase = projection.contractRevision;
|
|
10749
|
+
const informationIds = [];
|
|
10750
|
+
const makeSubItem = (span, informational, offset$1) => {
|
|
10751
|
+
const revision = revisionBase + 1 + offset$1;
|
|
10752
|
+
const kind = "requirement";
|
|
10753
|
+
const id = `${informational ? "R" : "R"}${nextNumericId(projection.items, "R")}`;
|
|
10754
|
+
const sub = {
|
|
10755
|
+
id,
|
|
10756
|
+
revision,
|
|
10757
|
+
kind,
|
|
10758
|
+
sourceMessageId: item.sourceMessageId,
|
|
10759
|
+
normalizedText: item.normalizedText,
|
|
10760
|
+
textSha256: item.textSha256,
|
|
10761
|
+
status: "pending",
|
|
10762
|
+
verification: {
|
|
10763
|
+
enforced: false,
|
|
10764
|
+
surface: "scope",
|
|
10765
|
+
subject: item.verification.subject ?? "scope"
|
|
10766
|
+
},
|
|
10767
|
+
semanticAction: "generic_run",
|
|
10768
|
+
requestedTarget: { scope: item.verification.subject ?? "scope" },
|
|
10769
|
+
targetCaptureStatus: "resolved",
|
|
10770
|
+
authority: item.authority,
|
|
10771
|
+
taskKind: informational ? "inquiry" : "action",
|
|
10772
|
+
directive: informational ? "informational" : void 0,
|
|
10773
|
+
executee: "unresolved",
|
|
10774
|
+
authorityDisposition: informational ? "informational" : "unresolved",
|
|
10775
|
+
interpretationFingerprint: `partition:${item.id}:${span.start}:${span.end}`,
|
|
10776
|
+
rawTextSha256,
|
|
10777
|
+
spans: [{
|
|
10778
|
+
partIndex,
|
|
10779
|
+
start: span.start,
|
|
10780
|
+
end: span.end,
|
|
10781
|
+
class: "instruction"
|
|
10782
|
+
}],
|
|
10783
|
+
unitId: item.unitId,
|
|
10784
|
+
clarifiesItemId: item.id,
|
|
10785
|
+
interpretedFromUnresolved: item.id
|
|
10786
|
+
};
|
|
10787
|
+
projection.items.set(id, sub);
|
|
10788
|
+
projection.contractRevision = Math.max(projection.contractRevision, revision);
|
|
10789
|
+
return sub;
|
|
10790
|
+
};
|
|
10791
|
+
let offset = 0;
|
|
10792
|
+
for (const span of information) {
|
|
10793
|
+
informationIds.push(makeSubItem(span, true, offset).id);
|
|
10794
|
+
offset += 1;
|
|
10795
|
+
}
|
|
10796
|
+
for (const span of [...unknown, ...complement]) {
|
|
10797
|
+
makeSubItem(span, false, offset);
|
|
10798
|
+
offset += 1;
|
|
10799
|
+
}
|
|
10800
|
+
if (informationIds.length > 0) {
|
|
10801
|
+
item.status = "superseded";
|
|
10802
|
+
item.supersededBy = informationIds[0];
|
|
10803
|
+
}
|
|
10804
|
+
return informationIds;
|
|
10805
|
+
}
|
|
10806
|
+
/** The next numeric id for a prefix, shared with nextId's numbering. */
|
|
10807
|
+
function nextNumericId(items, prefix) {
|
|
10808
|
+
let max = 0;
|
|
10809
|
+
for (const item of items.values()) {
|
|
10810
|
+
if (!item.id.startsWith(prefix)) continue;
|
|
10811
|
+
const num = Number(item.id.slice(prefix.length));
|
|
10812
|
+
if (Number.isInteger(num) && num > max) max = num;
|
|
10813
|
+
}
|
|
10814
|
+
return max + 1;
|
|
10815
|
+
}
|
|
10816
|
+
function readPartitionSpans(raw) {
|
|
10817
|
+
if (!Array.isArray(raw)) return void 0;
|
|
10818
|
+
const spans = [];
|
|
10819
|
+
for (const entry of raw) {
|
|
10820
|
+
const record = asRecord(entry);
|
|
10821
|
+
const start = record?.start;
|
|
10822
|
+
const end = record?.end;
|
|
10823
|
+
if (typeof start !== "number" || !Number.isSafeInteger(start) || typeof end !== "number" || !Number.isSafeInteger(end) || start >= end) return void 0;
|
|
10824
|
+
spans.push({
|
|
10825
|
+
start,
|
|
10826
|
+
end
|
|
10827
|
+
});
|
|
10828
|
+
}
|
|
10829
|
+
return spans;
|
|
10830
|
+
}
|
|
10831
|
+
function itemExtentOf(item) {
|
|
10832
|
+
const spans = item.spans ?? [];
|
|
10833
|
+
if (spans.length === 0) return {
|
|
10834
|
+
start: 0,
|
|
10835
|
+
end: 0
|
|
10836
|
+
};
|
|
10837
|
+
return {
|
|
10838
|
+
start: Math.min(...spans.map((span) => span.start)),
|
|
10839
|
+
end: Math.max(...spans.map((span) => span.end))
|
|
10840
|
+
};
|
|
10841
|
+
}
|
|
10842
|
+
/**
|
|
10843
|
+
* Replay validation of a clause-kind interpretation: the CALL's partition
|
|
10844
|
+
* (from the persisted tool/call arguments) must be present and structurally
|
|
10845
|
+
* valid against the obligation's extent, the receipt's echoed spans must
|
|
10846
|
+
* match the contract's spans, and the receipt's partition must EQUAL the
|
|
10847
|
+
* call's partition. A receipt that redraws the partition — replacing a
|
|
10848
|
+
* submitted unknown span with an information claim — is tampering.
|
|
10849
|
+
*/
|
|
10850
|
+
function clauseCallReceiptMatches(callInformation, callUnknown, recorded, item) {
|
|
10851
|
+
const spans = item.spans ?? [];
|
|
10852
|
+
const echoed = recorded.spans;
|
|
10853
|
+
if (!Array.isArray(echoed) || echoed.length !== spans.length) return false;
|
|
10854
|
+
if (!spans.every((span, index) => {
|
|
10855
|
+
const echo = asRecord(echoed[index]);
|
|
10856
|
+
return echo !== void 0 && echo.part_index === span.partIndex && echo.start === span.start && echo.end === span.end;
|
|
10857
|
+
})) return false;
|
|
10858
|
+
if (callInformation === void 0 || callUnknown === void 0 || callInformation.length === 0) return false;
|
|
10859
|
+
const extent = itemExtentOf(item);
|
|
10860
|
+
const allCall = [...callInformation, ...callUnknown];
|
|
10861
|
+
for (const span of allCall) if (span.start < extent.start || span.end > extent.end) return false;
|
|
10862
|
+
const orderedCall = [...allCall].sort((left, right) => left.start - right.start || left.end - right.end);
|
|
10863
|
+
for (let index = 1; index < orderedCall.length; index += 1) if (orderedCall[index].start < orderedCall[index - 1].end) return false;
|
|
10864
|
+
const receiptInformation = readPartitionSpans(recorded.information_spans);
|
|
10865
|
+
const receiptUnknown = readPartitionSpans(recorded.unknown_spans);
|
|
10866
|
+
if (receiptInformation === void 0 || receiptUnknown === void 0) return false;
|
|
10867
|
+
return samePartition(receiptInformation, receiptUnknown, callInformation, callUnknown);
|
|
10868
|
+
}
|
|
10869
|
+
function samePartition(leftInformation, leftUnknown, rightInformation, rightUnknown) {
|
|
10870
|
+
const normalize = (information, unknown) => {
|
|
10871
|
+
const ordered = [...information.map((span) => ({
|
|
10872
|
+
...span,
|
|
10873
|
+
information: true
|
|
10874
|
+
})), ...unknown.map((span) => ({
|
|
10875
|
+
...span,
|
|
10876
|
+
information: false
|
|
10877
|
+
}))].sort((left, right) => left.start - right.start || left.end - right.end);
|
|
10878
|
+
return JSON.stringify(ordered.map((span) => [
|
|
10879
|
+
span.start,
|
|
10880
|
+
span.end,
|
|
10881
|
+
span.information
|
|
10882
|
+
]));
|
|
10883
|
+
};
|
|
10884
|
+
return normalize(leftInformation, leftUnknown) === normalize(rightInformation, rightUnknown);
|
|
10885
|
+
}
|
|
10886
|
+
/**
|
|
10887
|
+
* Whether a recorded certificate is exactly the certificate this log re-derives.
|
|
10888
|
+
*
|
|
10889
|
+
* The comparison is by FIELD SEMANTICS, not by JSON text: a tool output is a
|
|
10890
|
+
* JSON object whose property order is an artifact of serialization, so
|
|
10891
|
+
* `JSON.stringify` equality made an identical certificate replay as corrupt
|
|
10892
|
+
* whenever the writer emitted `unit_id` before `goal_ref` (or vice versa). The
|
|
10893
|
+
* field set is still exact — an extra, missing, or renamed field stays a
|
|
10894
|
+
* mismatch — and values are compared by canonical encoding, so tampering is as
|
|
10895
|
+
* detectable as before.
|
|
10896
|
+
*/
|
|
8199
10897
|
function recordedCertificateMatches(recorded, checkpoint) {
|
|
8200
10898
|
const value = asRecord(recorded);
|
|
8201
10899
|
if (!value) return false;
|
|
@@ -8214,6 +10912,10 @@ function recordedCertificateMatches(recorded, checkpoint) {
|
|
|
8214
10912
|
certification_digest: checkpoint.certificationDigest,
|
|
8215
10913
|
goal_ref: checkpoint.goalRef ?? null
|
|
8216
10914
|
};
|
|
10915
|
+
if (checkpoint.unitId !== void 0) {
|
|
10916
|
+
exact.unit_id = checkpoint.unitId;
|
|
10917
|
+
exact.unit_closure_digest = checkpoint.unitClosureDigest;
|
|
10918
|
+
}
|
|
8217
10919
|
const normalized = {
|
|
8218
10920
|
...value,
|
|
8219
10921
|
goal_ref: goal ? {
|
|
@@ -8221,7 +10923,61 @@ function recordedCertificateMatches(recorded, checkpoint) {
|
|
|
8221
10923
|
revision: goal.revision
|
|
8222
10924
|
} : value.goal_ref
|
|
8223
10925
|
};
|
|
8224
|
-
|
|
10926
|
+
const expectedKeys = Object.keys(exact).sort();
|
|
10927
|
+
const actualKeys = Object.keys(normalized).sort();
|
|
10928
|
+
if (expectedKeys.length !== actualKeys.length) return false;
|
|
10929
|
+
return expectedKeys.every((key, index) => key === actualKeys[index] && stableJson(normalized[key]) === stableJson(exact[key]));
|
|
10930
|
+
}
|
|
10931
|
+
/**
|
|
10932
|
+
* The proof binding state the log itself implies for one checkpoint call. This
|
|
10933
|
+
* is the same computation the signing tool performs, replayed against the
|
|
10934
|
+
* projection derived up to that call.
|
|
10935
|
+
*/
|
|
10936
|
+
function replayProofState(projection, proof) {
|
|
10937
|
+
if (proof === void 0) return {
|
|
10938
|
+
status: "absent",
|
|
10939
|
+
reason_codes: []
|
|
10940
|
+
};
|
|
10941
|
+
const structural = validateProofManifestV2(proof);
|
|
10942
|
+
if (structural.length) return {
|
|
10943
|
+
status: "invalid",
|
|
10944
|
+
reason_codes: [...structural].sort()
|
|
10945
|
+
};
|
|
10946
|
+
const binding = bindProofV2ToProjection(projection, proof);
|
|
10947
|
+
return binding.length ? {
|
|
10948
|
+
status: "rejected",
|
|
10949
|
+
reason_codes: [...binding].sort()
|
|
10950
|
+
} : {
|
|
10951
|
+
status: "bound",
|
|
10952
|
+
reason_codes: []
|
|
10953
|
+
};
|
|
10954
|
+
}
|
|
10955
|
+
/** Bounded set equality for reason-code lists, order-insensitive. */
|
|
10956
|
+
function sameStringSet(recorded, expected) {
|
|
10957
|
+
if (!Array.isArray(recorded)) return false;
|
|
10958
|
+
const left = [...new Set(recorded.filter((entry) => typeof entry === "string"))].sort();
|
|
10959
|
+
const right = [...new Set(expected)].sort();
|
|
10960
|
+
return left.length === right.length && left.every((value, index) => value === right[index]);
|
|
10961
|
+
}
|
|
10962
|
+
/**
|
|
10963
|
+
* Freeze the closure certificate the adopter relied on, resolved AT the
|
|
10964
|
+
* adoption watermark. Only the checkpoints restored so far existed then, so a
|
|
10965
|
+
* certificate that appears LATER in the log can never ratify an earlier
|
|
10966
|
+
* adoption; an unresolvable reference is recorded as unresolved rather than
|
|
10967
|
+
* left open for a future entry to satisfy.
|
|
10968
|
+
*/
|
|
10969
|
+
function freezeAdoptionClosure(projection, contract) {
|
|
10970
|
+
const ref = contract.closureCertRef;
|
|
10971
|
+
const closure = ref !== void 0 ? projection.checkpoints.find((checkpoint) => checkpoint.id === ref && checkpoint.result === "certified") : void 0;
|
|
10972
|
+
return closure === void 0 ? contract : {
|
|
10973
|
+
...contract,
|
|
10974
|
+
frozenClosure: {
|
|
10975
|
+
id: closure.id,
|
|
10976
|
+
certificationDigest: closure.certificationDigest,
|
|
10977
|
+
epoch: closure.epoch,
|
|
10978
|
+
contractRevision: closure.contractRevision
|
|
10979
|
+
}
|
|
10980
|
+
};
|
|
8225
10981
|
}
|
|
8226
10982
|
function restoreHistoricalCheckpoint(recorded, bindings, id) {
|
|
8227
10983
|
const stringField = (name) => typeof recorded[name] === "string" ? recorded[name] : void 0;
|
|
@@ -8241,6 +10997,7 @@ function restoreHistoricalCheckpoint(recorded, bindings, id) {
|
|
|
8241
10997
|
"certification_digest"
|
|
8242
10998
|
].some((name) => !stringField(name))) return void 0;
|
|
8243
10999
|
if (goal && (typeof goal.id !== "string" || !Number.isSafeInteger(goal.revision))) return void 0;
|
|
11000
|
+
if (recorded.unit_id !== void 0 && (typeof recorded.unit_id !== "string" || !stringField("unit_closure_digest"))) return void 0;
|
|
8244
11001
|
return {
|
|
8245
11002
|
id,
|
|
8246
11003
|
stopProtocolVersion: stringField("stop_protocol_version"),
|
|
@@ -8258,6 +11015,10 @@ function restoreHistoricalCheckpoint(recorded, bindings, id) {
|
|
|
8258
11015
|
id: goal.id,
|
|
8259
11016
|
revision: goal.revision
|
|
8260
11017
|
} } : {},
|
|
11018
|
+
...typeof recorded.unit_id === "string" ? {
|
|
11019
|
+
unitId: recorded.unit_id,
|
|
11020
|
+
unitClosureDigest: stringField("unit_closure_digest")
|
|
11021
|
+
} : {},
|
|
8261
11022
|
certificationDigest: stringField("certification_digest"),
|
|
8262
11023
|
result: "certified"
|
|
8263
11024
|
};
|
|
@@ -8295,11 +11056,39 @@ function resolveArtifact(path$1, scope) {
|
|
|
8295
11056
|
* an item whose action/target could not be derived deterministically stays
|
|
8296
11057
|
* `legacy_authority_unclassified` instead of being retroactively authorized.
|
|
8297
11058
|
*/
|
|
8298
|
-
function captureRootText(projection, text, seq, scope, legacy, priorRootMessages, prefix = `m${seq}`, coordinationSplit = true) {
|
|
11059
|
+
function captureRootText(projection, text, seq, scope, legacy, priorRootMessages, prefix = `m${seq}`, coordinationSplit = true, unitId, clarification = false) {
|
|
8299
11060
|
const blocks = segmentAuthorityBlocks(text, priorRootMessages);
|
|
11061
|
+
const provenance = legacy ? void 0 : {
|
|
11062
|
+
rawTextSha256: sha256(text),
|
|
11063
|
+
rawText: text
|
|
11064
|
+
};
|
|
11065
|
+
let coveredSpans = 0;
|
|
11066
|
+
let blockCursor = 0;
|
|
8300
11067
|
for (const block$1 of blocks) {
|
|
8301
11068
|
if (!block$1.capture) continue;
|
|
8302
|
-
|
|
11069
|
+
let blockOffset;
|
|
11070
|
+
if (provenance) {
|
|
11071
|
+
const at = provenance.rawText.indexOf(block$1.text, blockCursor);
|
|
11072
|
+
if (at >= 0) {
|
|
11073
|
+
blockCursor = at + 1;
|
|
11074
|
+
blockOffset = utf8ByteOffset(provenance.rawText, at);
|
|
11075
|
+
}
|
|
11076
|
+
}
|
|
11077
|
+
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 ? {
|
|
11078
|
+
...provenance,
|
|
11079
|
+
blockOffset,
|
|
11080
|
+
blockText: block$1.text,
|
|
11081
|
+
blockAuthority: block$1.authority
|
|
11082
|
+
} : void 0, clarification ? text : void 0);
|
|
11083
|
+
}
|
|
11084
|
+
if (provenance) {
|
|
11085
|
+
projection.coverage.push({
|
|
11086
|
+
seq,
|
|
11087
|
+
rawTextSha256: provenance.rawTextSha256,
|
|
11088
|
+
byteLength: utf8ByteLength(provenance.rawText),
|
|
11089
|
+
coveredSpans
|
|
11090
|
+
});
|
|
11091
|
+
if (projection.coverage.length > 16) projection.coverage.shift();
|
|
8303
11092
|
}
|
|
8304
11093
|
priorRootMessages.push(text);
|
|
8305
11094
|
if (priorRootMessages.length > 16) priorRootMessages.shift();
|
|
@@ -8310,20 +11099,45 @@ function captureRootText(projection, text, seq, scope, legacy, priorRootMessages
|
|
|
8310
11099
|
* item, so evidence for one file cannot close a message that also covers other
|
|
8311
11100
|
* files or embeds prohibitions.
|
|
8312
11101
|
*/
|
|
8313
|
-
function insertItems(projection, text, sourceMessageId, scope, authority = "root_instruction", legacy = false, legacyAuthorityProven = false, coordinationSplit = true) {
|
|
11102
|
+
function insertItems(projection, text, sourceMessageId, scope, authority = "root_instruction", legacy = false, legacyAuthorityProven = false, coordinationSplit = true, unitId, provenance, clarificationText) {
|
|
8314
11103
|
const before = new Set(projection.items.keys());
|
|
11104
|
+
let coveredSpans = 0;
|
|
11105
|
+
const usedOccurrences = /* @__PURE__ */ new Set();
|
|
8315
11106
|
for (const segment of segmentClauses(text, { coordinationSplit })) {
|
|
8316
11107
|
if (classifyUserInteraction(segment.body) === "conversational") continue;
|
|
8317
11108
|
if (segment.kind === "requirement" && segment.paths.length === 0 && isInstructionFraming(segment.body)) continue;
|
|
11109
|
+
let span;
|
|
11110
|
+
if (provenance) {
|
|
11111
|
+
let at = provenance.blockText.indexOf(segment.text);
|
|
11112
|
+
while (at >= 0 && usedOccurrences.has(at)) at = provenance.blockText.indexOf(segment.text, at + 1);
|
|
11113
|
+
if (at >= 0) {
|
|
11114
|
+
usedOccurrences.add(at);
|
|
11115
|
+
const start = (provenance.blockOffset ?? 0) + utf8ByteOffset(provenance.blockText, at);
|
|
11116
|
+
span = {
|
|
11117
|
+
partIndex: 0,
|
|
11118
|
+
start,
|
|
11119
|
+
end: start + utf8ByteLength(segment.text),
|
|
11120
|
+
class: spanClassOf(segment.kind, segment.interpretation.directive, provenance.blockAuthority)
|
|
11121
|
+
};
|
|
11122
|
+
}
|
|
11123
|
+
}
|
|
11124
|
+
if (span) coveredSpans += 1;
|
|
8318
11125
|
if (segment.paths.length === 0) {
|
|
8319
|
-
insert(projection, segment, sourceMessageId, scope.cwd || "scope", "scope"
|
|
11126
|
+
insert(projection, segment, sourceMessageId, scope.cwd || "scope", "scope", unitId, provenance ? {
|
|
11127
|
+
rawTextSha256: provenance.rawTextSha256,
|
|
11128
|
+
span
|
|
11129
|
+
} : void 0);
|
|
8320
11130
|
continue;
|
|
8321
11131
|
}
|
|
8322
|
-
for (const path$1 of segment.paths) insert(projection, segment, sourceMessageId, resolveArtifact(path$1, scope), "artifact"
|
|
11132
|
+
for (const path$1 of segment.paths) insert(projection, segment, sourceMessageId, resolveArtifact(path$1, scope), "artifact", unitId, provenance ? {
|
|
11133
|
+
rawTextSha256: provenance.rawTextSha256,
|
|
11134
|
+
span
|
|
11135
|
+
} : void 0);
|
|
8323
11136
|
}
|
|
8324
11137
|
for (const [id, item] of projection.items) {
|
|
8325
11138
|
if (before.has(id)) continue;
|
|
8326
11139
|
if (item.kind !== "requirement" || item.waitAuthorization || item.authorityDisposition === "conditional_wait") continue;
|
|
11140
|
+
if (item.authorityDisposition !== void 0 && item.authorityDisposition !== "executable_now") continue;
|
|
8327
11141
|
for (const [otherId, other] of projection.items) {
|
|
8328
11142
|
if (otherId === id || other.status !== "pending") continue;
|
|
8329
11143
|
if (!other.waitAuthorization || other.kind !== "requirement") continue;
|
|
@@ -8335,6 +11149,24 @@ function insertItems(projection, text, sourceMessageId, scope, authority = "root
|
|
|
8335
11149
|
break;
|
|
8336
11150
|
}
|
|
8337
11151
|
}
|
|
11152
|
+
if (clarificationText) for (const [id, item] of projection.items) {
|
|
11153
|
+
if (before.has(id)) continue;
|
|
11154
|
+
if (item.kind === "prohibition" || item.status !== "pending") continue;
|
|
11155
|
+
if (item.authorityDisposition !== "executable_now") continue;
|
|
11156
|
+
if (!item.semanticAction || item.semanticAction === "generic_run") continue;
|
|
11157
|
+
for (const [otherId, other] of projection.items) {
|
|
11158
|
+
if (otherId === id || !before.has(otherId)) continue;
|
|
11159
|
+
if (other.status !== "pending" || other.kind === "prohibition") continue;
|
|
11160
|
+
if (other.waitAuthorization || other.legacyFlags?.length) continue;
|
|
11161
|
+
if (!(other.semanticAction === "generic_run" && (other.authorityDisposition === "executable_now" || other.authorityDisposition === "unresolved"))) continue;
|
|
11162
|
+
if (other.normalizedText.length < 4) continue;
|
|
11163
|
+
if (!clarificationText.includes(other.normalizedText)) continue;
|
|
11164
|
+
if (other.verification.subject !== item.verification.subject) continue;
|
|
11165
|
+
supersedeItem(projection.items, otherId, item);
|
|
11166
|
+
item.clarifiesItemId = otherId;
|
|
11167
|
+
break;
|
|
11168
|
+
}
|
|
11169
|
+
}
|
|
8338
11170
|
for (const [id, item] of projection.items) {
|
|
8339
11171
|
if (before.has(id)) continue;
|
|
8340
11172
|
if (legacy) if (legacyAuthorityProven && item.semanticAction !== void 0 && item.semanticAction !== "generic_run" && item.targetCaptureStatus === "resolved") {
|
|
@@ -8347,17 +11179,24 @@ function insertItems(projection, text, sourceMessageId, scope, authority = "root
|
|
|
8347
11179
|
}
|
|
8348
11180
|
else item.authority = authority;
|
|
8349
11181
|
}
|
|
11182
|
+
return coveredSpans;
|
|
8350
11183
|
}
|
|
8351
|
-
function insert(projection, segment, sourceMessageId, subject, surface) {
|
|
11184
|
+
function insert(projection, segment, sourceMessageId, subject, surface, unitId, provenance) {
|
|
8352
11185
|
const revision = projection.contractRevision + 1;
|
|
8353
11186
|
const id = nextId(projection.items, segment.kind);
|
|
8354
11187
|
const method = extractMethod(segment.body);
|
|
8355
11188
|
const operation = extractOperation(segment.body);
|
|
8356
11189
|
const item = captureItem(segment.kind, segment.body, sourceMessageId, id, revision, subject, surface, method, operation, segment.interpretation);
|
|
11190
|
+
if (unitId !== void 0) item.unitId = unitId;
|
|
11191
|
+
if (provenance) {
|
|
11192
|
+
item.rawTextSha256 = provenance.rawTextSha256;
|
|
11193
|
+
if (provenance.span) item.spans = [provenance.span];
|
|
11194
|
+
}
|
|
8357
11195
|
const duplicate = [...projection.items.values()].find((existing) => existing.kind === segment.kind && existing.status === "pending" && existing.textSha256 === item.textSha256 && existing.verification.subject === subject);
|
|
8358
11196
|
if (duplicate) supersedeItem(projection.items, duplicate.id, item);
|
|
8359
11197
|
else projection.items.set(id, item);
|
|
8360
11198
|
projection.contractRevision = item.revision;
|
|
11199
|
+
return item;
|
|
8361
11200
|
}
|
|
8362
11201
|
/**
|
|
8363
11202
|
* Pure, deterministic re-derivation of the guard projection from the DSH
|
|
@@ -8368,6 +11207,7 @@ function insert(projection, segment, sourceMessageId, subject, surface) {
|
|
|
8368
11207
|
*/
|
|
8369
11208
|
function deriveProjection(sourceEvents, config, scope, durableConfirmed, hostLock = DEFAULT_HOST_LOCK) {
|
|
8370
11209
|
const projection = createProjection();
|
|
11210
|
+
projection.policy = config.policy ?? "standard";
|
|
8371
11211
|
if (scope.sessionHeader) projection.sessionRefDigest = sessionRefDigest(scope.sessionHeader);
|
|
8372
11212
|
projection.hostLockDigest = hostLock.digest;
|
|
8373
11213
|
projection.hostStatus = hostLock.status;
|
|
@@ -8380,14 +11220,45 @@ function deriveProjection(sourceEvents, config, scope, durableConfirmed, hostLoc
|
|
|
8380
11220
|
let enablementTransitioned = false;
|
|
8381
11221
|
let lastCompactionSeq = -1;
|
|
8382
11222
|
const pendingCalls = /* @__PURE__ */ new Map();
|
|
11223
|
+
const v5BoundarySeq = sourceEvents.find((event) => isProtocolBoundaryNotice(event, PROTOCOL_V5_NOTICE))?.seq;
|
|
8383
11224
|
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;
|
|
11225
|
+
const protocolBoundarySeq = sourceEvents.find((event) => isProtocolBoundaryNotice(event) || isProtocolBoundaryNotice(event, PROTOCOL_V4_NOTICE) || isProtocolBoundaryNotice(event, PROTOCOL_V5_NOTICE))?.seq;
|
|
11226
|
+
const captureBoundarySeq = sourceEvents.find((event) => isProtocolBoundaryNotice(event, CAPTURE_V042_NOTICE) || isProtocolBoundaryNotice(event, PROTOCOL_V4_NOTICE) || isProtocolBoundaryNotice(event, PROTOCOL_V5_NOTICE))?.seq;
|
|
8386
11227
|
const priorRootMessages = [];
|
|
8387
11228
|
let realRootInputSeen = false;
|
|
11229
|
+
const trustedDeliveries = (v5BoundarySeq !== void 0 ? deriveTrustedDeliveries(sourceEvents) : []).filter((delivery) => delivery.turnEndSeq > v5BoundarySeq);
|
|
11230
|
+
let deliveryCursor = 0;
|
|
11231
|
+
const interpretationFacts = [];
|
|
11232
|
+
const applyDeliveriesUpTo = (seq) => {
|
|
11233
|
+
while (deliveryCursor < trustedDeliveries.length && trustedDeliveries[deliveryCursor].turnEndSeq <= seq) {
|
|
11234
|
+
const delivery = trustedDeliveries[deliveryCursor];
|
|
11235
|
+
deliveryCursor += 1;
|
|
11236
|
+
const inputSeqs = turnRootInputSeqs.get(delivery.turn);
|
|
11237
|
+
if (!inputSeqs) continue;
|
|
11238
|
+
const owningUnitId = turnUnitIds.get(delivery.turn);
|
|
11239
|
+
const eligibleUnitIds = owningUnitId === void 0 ? void 0 : new Set([owningUnitId, ...unitDescendantIds(projection, owningUnitId)]);
|
|
11240
|
+
for (const itemId of informationItemIdsForDelivery(projection.items, delivery, inputSeqs, eligibleUnitIds, interpretationFacts)) {
|
|
11241
|
+
const item = projection.items.get(itemId);
|
|
11242
|
+
if (!item || item.status !== "pending") continue;
|
|
11243
|
+
const sourceSeq = /^m(\d+)(?::|$)/.exec(item.sourceMessageId);
|
|
11244
|
+
if (!sourceSeq || Number(sourceSeq[1]) <= v5BoundarySeq) continue;
|
|
11245
|
+
item.status = "answered";
|
|
11246
|
+
item.answeredBy = {
|
|
11247
|
+
turn: delivery.turn,
|
|
11248
|
+
responseSeq: delivery.responseSeq,
|
|
11249
|
+
responseSha256: delivery.responseSha256
|
|
11250
|
+
};
|
|
11251
|
+
}
|
|
11252
|
+
}
|
|
11253
|
+
};
|
|
11254
|
+
const turnRootInputSeqs = /* @__PURE__ */ new Map();
|
|
11255
|
+
const turnUnitIds = /* @__PURE__ */ new Map();
|
|
11256
|
+
let activeTurn;
|
|
11257
|
+
const unitSemanticsActive = () => v5BoundarySeq !== void 0 && !scope.sessionHeader?.parentSession && !scope.sessionHeader?.delegationDepth && scope.sessionHeader?.origin !== "subagent";
|
|
8388
11258
|
for (const event of sourceEvents) {
|
|
8389
11259
|
projection.enabled = enabled;
|
|
8390
11260
|
projection.lastObservedSourceSeq = Math.max(projection.lastObservedSourceSeq, event.seq);
|
|
11261
|
+
applyDeliveriesUpTo(event.seq);
|
|
8391
11262
|
switch (event.type) {
|
|
8392
11263
|
case "command/run": {
|
|
8393
11264
|
const data = asRecord(event.data);
|
|
@@ -8408,6 +11279,26 @@ function deriveProjection(sourceEvents, config, scope, durableConfirmed, hostLoc
|
|
|
8408
11279
|
item.supersededBy = `CLEAR:${revision}`;
|
|
8409
11280
|
}
|
|
8410
11281
|
projection.contractRevision = revision;
|
|
11282
|
+
} else if (subcommand === "release") {
|
|
11283
|
+
const rest = typeof data.args === "string" ? data.args.trim().slice(7).trim() : "";
|
|
11284
|
+
const revoke = /^revoke(?:\s+(\S+))?$/.exec(rest);
|
|
11285
|
+
if (revoke) {
|
|
11286
|
+
const contractId = revoke[1] ?? "";
|
|
11287
|
+
const contract = projection.releaseContracts.find((entry) => entry.contractId === contractId);
|
|
11288
|
+
if (!contract) pushReleaseDiagnostic(projection, event.seq, "release_contract_revocation_unknown");
|
|
11289
|
+
else if (contract.revokedAtSeq === void 0) contract.revokedAtSeq = event.seq;
|
|
11290
|
+
break;
|
|
11291
|
+
}
|
|
11292
|
+
const match = /^adopt(?:\s+([\s\S]+))?$/.exec(rest);
|
|
11293
|
+
if (match) {
|
|
11294
|
+
const payload = parseArguments((match[1] ?? "").trim());
|
|
11295
|
+
const normalized = normalizeReleaseContract(payload, {
|
|
11296
|
+
seq: event.seq,
|
|
11297
|
+
digest: sha256(stableJson(payload))
|
|
11298
|
+
}, projection.contractRevision);
|
|
11299
|
+
if (!normalized.contract) for (const code of normalized.errors) pushReleaseDiagnostic(projection, event.seq, code);
|
|
11300
|
+
else if (!projection.releaseContracts.some((contract) => contract.contractId === normalized.contract.contractId)) projection.releaseContracts.push(freezeAdoptionClosure(projection, normalized.contract));
|
|
11301
|
+
} else if (rest.length > 0 && !/^status$/.test(rest)) pushReleaseDiagnostic(projection, event.seq, "release_subcommand_unknown");
|
|
8411
11302
|
}
|
|
8412
11303
|
break;
|
|
8413
11304
|
}
|
|
@@ -8417,10 +11308,22 @@ function deriveProjection(sourceEvents, config, scope, durableConfirmed, hostLoc
|
|
|
8417
11308
|
break;
|
|
8418
11309
|
case "turn/start": {
|
|
8419
11310
|
const started = asRecord(event.data);
|
|
8420
|
-
if (typeof started?.turn === "number" && Number.isSafeInteger(started.turn))
|
|
11311
|
+
if (typeof started?.turn === "number" && Number.isSafeInteger(started.turn)) {
|
|
11312
|
+
projection.hostTurn = started.turn;
|
|
11313
|
+
activeTurn = started.turn;
|
|
11314
|
+
}
|
|
11315
|
+
break;
|
|
11316
|
+
}
|
|
11317
|
+
case "turn/end": {
|
|
11318
|
+
const ended = asRecord(event.data);
|
|
11319
|
+
if (typeof ended?.turn === "number" && Number.isSafeInteger(ended.turn)) activeTurn = void 0;
|
|
8421
11320
|
break;
|
|
8422
11321
|
}
|
|
8423
11322
|
case "user/message": {
|
|
11323
|
+
if (isProtocolBoundaryNotice(event, PROTOCOL_V5_NOTICE)) {
|
|
11324
|
+
projection.boundaryProtocol = 5;
|
|
11325
|
+
break;
|
|
11326
|
+
}
|
|
8424
11327
|
if (isProtocolBoundaryNotice(event) || isProtocolBoundaryNotice(event, CAPTURE_V042_NOTICE) || isProtocolBoundaryNotice(event, PROTOCOL_V4_NOTICE)) break;
|
|
8425
11328
|
{
|
|
8426
11329
|
const record = asRecord(event.data);
|
|
@@ -8444,20 +11347,79 @@ function deriveProjection(sourceEvents, config, scope, durableConfirmed, hostLoc
|
|
|
8444
11347
|
if (rootSeq !== void 0) projection.handledControlSeqs.add(rootSeq);
|
|
8445
11348
|
break;
|
|
8446
11349
|
}
|
|
11350
|
+
if (recordSource?.kind === "plugin" && recordSource.plugin === "context-guard") {
|
|
11351
|
+
if (recordText.startsWith(RELEASE_CONTRACT_PREFIX)) {
|
|
11352
|
+
const payload = parseArguments(recordText.slice(RELEASE_CONTRACT_PREFIX.length));
|
|
11353
|
+
const adoptionSeq = typeof payload.adoptedBySeq === "number" && Number.isSafeInteger(payload.adoptedBySeq) ? payload.adoptedBySeq : event.seq;
|
|
11354
|
+
const normalized = normalizeReleaseContract(asRecord(payload.contract) ?? payload, {
|
|
11355
|
+
seq: adoptionSeq,
|
|
11356
|
+
digest: sha256(stableJson(payload.contract ?? null))
|
|
11357
|
+
}, projection.contractRevision);
|
|
11358
|
+
if (!normalized.contract) for (const code of normalized.errors) pushReleaseDiagnostic(projection, event.seq, code, true);
|
|
11359
|
+
else if (!projection.releaseContracts.some((contract) => contract.contractId === normalized.contract.contractId)) projection.releaseContracts.push(freezeAdoptionClosure(projection, normalized.contract));
|
|
11360
|
+
break;
|
|
11361
|
+
}
|
|
11362
|
+
if (recordText.startsWith(RELEASE_RESERVATION_PREFIX)) {
|
|
11363
|
+
const reservation = normalizeReservation(parseArguments(recordText.slice(RELEASE_RESERVATION_PREFIX.length)));
|
|
11364
|
+
if (!reservation) pushReleaseDiagnostic(projection, event.seq, "release_reservation_malformed", true);
|
|
11365
|
+
else if (!projection.releaseReservations.some((entry) => entry.callId === reservation.callId)) projection.releaseReservations.push({
|
|
11366
|
+
...reservation,
|
|
11367
|
+
startedAtSeq: event.seq
|
|
11368
|
+
});
|
|
11369
|
+
break;
|
|
11370
|
+
}
|
|
11371
|
+
if (recordText.startsWith(RELEASE_REVOCATION_PREFIX)) {
|
|
11372
|
+
const payload = asRecord(parseArguments(recordText.slice(RELEASE_REVOCATION_PREFIX.length)));
|
|
11373
|
+
const contractId = typeof payload?.contractId === "string" ? payload.contractId : "";
|
|
11374
|
+
const contract = projection.releaseContracts.find((entry) => entry.contractId === contractId);
|
|
11375
|
+
if (!contract) pushReleaseDiagnostic(projection, event.seq, "release_contract_revocation_unknown");
|
|
11376
|
+
else if (contract.revokedAtSeq === void 0) contract.revokedAtSeq = event.seq;
|
|
11377
|
+
break;
|
|
11378
|
+
}
|
|
11379
|
+
if (recordText.startsWith(RELEASE_SETTLEMENT_PREFIX)) {
|
|
11380
|
+
const settlement = normalizeSettlement(parseArguments(recordText.slice(RELEASE_SETTLEMENT_PREFIX.length)));
|
|
11381
|
+
if (!settlement) pushReleaseDiagnostic(projection, event.seq, "release_settlement_malformed", true);
|
|
11382
|
+
else {
|
|
11383
|
+
const pinned = {
|
|
11384
|
+
...settlement,
|
|
11385
|
+
settledAtSeq: event.seq
|
|
11386
|
+
};
|
|
11387
|
+
const key = (row) => `${row.contractId}\u0000${row.operation}\u0000${row.callId}`;
|
|
11388
|
+
const index = projection.releaseSettlements.findIndex((entry) => key(entry) === key(pinned));
|
|
11389
|
+
if (index < 0) projection.releaseSettlements.push(pinned);
|
|
11390
|
+
else if (OUTCOME_STRENGTH[pinned.outcome] >= OUTCOME_STRENGTH[projection.releaseSettlements[index].outcome]) projection.releaseSettlements[index] = pinned;
|
|
11391
|
+
}
|
|
11392
|
+
break;
|
|
11393
|
+
}
|
|
11394
|
+
}
|
|
8447
11395
|
}
|
|
8448
11396
|
if (!enabled) break;
|
|
8449
11397
|
const data = asRecord(event.data);
|
|
8450
11398
|
if (asRecord(data?.source)?.kind !== "user") break;
|
|
8451
11399
|
const content = data?.content ?? [];
|
|
8452
11400
|
const text = extractTextContent(content);
|
|
8453
|
-
if (text.trim() || content.some((part) => part && typeof part === "object" && part.type !== "text"))
|
|
11401
|
+
if (text.trim() || content.some((part) => part && typeof part === "object" && part.type !== "text")) {
|
|
11402
|
+
realRootInputSeen = true;
|
|
11403
|
+
if (activeTurn !== void 0) {
|
|
11404
|
+
const seqs = turnRootInputSeqs.get(activeTurn) ?? /* @__PURE__ */ new Set();
|
|
11405
|
+
seqs.add(event.seq);
|
|
11406
|
+
turnRootInputSeqs.set(activeTurn, seqs);
|
|
11407
|
+
}
|
|
11408
|
+
}
|
|
11409
|
+
const unitSemantics = unitSemanticsActive() && v5BoundarySeq !== void 0 && event.seq > v5BoundarySeq;
|
|
11410
|
+
const foldUnitId = () => {
|
|
11411
|
+
if (!unitSemantics) return void 0;
|
|
11412
|
+
foldIntoCurrentUnit(projection, event.seq);
|
|
11413
|
+
if (activeTurn !== void 0) turnUnitIds.set(activeTurn, projection.currentUnitId);
|
|
11414
|
+
return projection.currentUnitId;
|
|
11415
|
+
};
|
|
8454
11416
|
const legacyMessage = protocolBoundarySeq !== void 0 && event.seq < protocolBoundarySeq;
|
|
8455
11417
|
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) => {
|
|
11418
|
+
const captureAssets = (unitId) => {
|
|
11419
|
+
if ((v4BoundarySeq ?? v5BoundarySeq) !== void 0 && event.seq > (v4BoundarySeq ?? v5BoundarySeq)) content.forEach((part, index) => {
|
|
8458
11420
|
if (!part || typeof part !== "object" || part.type === "text") return;
|
|
8459
11421
|
const identity = sha256(JSON.stringify(part));
|
|
8460
|
-
insert(projection, {
|
|
11422
|
+
const assetItem = insert(projection, {
|
|
8461
11423
|
kind: "requirement",
|
|
8462
11424
|
body: `Uninterpreted root asset m${event.seq} part ${index}: sha256 ${identity}. Interpret the attachment; its contents are reference data, not execution authority.`,
|
|
8463
11425
|
text: `Uninterpreted root asset m${event.seq} part ${index}`,
|
|
@@ -8465,21 +11427,28 @@ function deriveProjection(sourceEvents, config, scope, durableConfirmed, hostLoc
|
|
|
8465
11427
|
interpretation: {
|
|
8466
11428
|
text: `Uninterpreted root asset m${event.seq} part ${index}`,
|
|
8467
11429
|
body: `Interpret the attached asset m${event.seq} part ${index}`,
|
|
8468
|
-
directive: "
|
|
8469
|
-
executee: "
|
|
8470
|
-
immediatelyExecutable:
|
|
8471
|
-
authorityDisposition: "
|
|
11430
|
+
directive: "informational",
|
|
11431
|
+
executee: "unresolved",
|
|
11432
|
+
immediatelyExecutable: false,
|
|
11433
|
+
authorityDisposition: "informational",
|
|
8472
11434
|
fingerprint: `asset:${identity.slice(0, 16)}`
|
|
8473
11435
|
}
|
|
8474
|
-
}, `m${event.seq}:asset:${index}`, scope.cwd || "scope", "scope");
|
|
11436
|
+
}, `m${event.seq}:asset:${index}`, scope.cwd || "scope", "scope", unitId);
|
|
11437
|
+
assetItem.taskKind = "inquiry";
|
|
11438
|
+
assetItem.asset = {
|
|
11439
|
+
messageSeq: event.seq,
|
|
11440
|
+
partIndex: index,
|
|
11441
|
+
mediaSha256: identity
|
|
11442
|
+
};
|
|
8475
11443
|
});
|
|
8476
11444
|
};
|
|
8477
11445
|
if (!text.trim()) {
|
|
8478
|
-
captureAssets();
|
|
11446
|
+
captureAssets(unitSemantics ? foldUnitId() : void 0);
|
|
8479
11447
|
break;
|
|
8480
11448
|
}
|
|
8481
11449
|
if (!scope.sessionHeader?.parentSession && !scope.sessionHeader?.delegationDepth && scope.sessionHeader?.origin !== "subagent") {
|
|
8482
|
-
const
|
|
11450
|
+
const confirmGrammarSeq = v4BoundarySeq ?? v5BoundarySeq;
|
|
11451
|
+
const parsed = confirmGrammarSeq !== void 0 && event.seq > confirmGrammarSeq ? parseConfirmationMessage(text) : (() => {
|
|
8483
11452
|
const match = CONFIRM_LINE_PATTERN.exec(text.trim());
|
|
8484
11453
|
return match ? {
|
|
8485
11454
|
kind: "confirm",
|
|
@@ -8489,12 +11458,14 @@ function deriveProjection(sourceEvents, config, scope, durableConfirmed, hostLoc
|
|
|
8489
11458
|
})();
|
|
8490
11459
|
if (parsed.kind === "confirm") {
|
|
8491
11460
|
if (confirmRebind(projection, parsed.proposalId, `m${event.seq}`, durableConfirmed)) {
|
|
8492
|
-
|
|
8493
|
-
|
|
11461
|
+
const unitId = unitSemantics ? foldUnitId() : void 0;
|
|
11462
|
+
captureAssets(unitId);
|
|
11463
|
+
if (parsed.remainder) captureRootText(projection, parsed.remainder, event.seq, scope, legacyMessage, priorRootMessages, `m${event.seq}:r`, coordinationSplit, unitId, unitSemantics);
|
|
8494
11464
|
break;
|
|
8495
11465
|
}
|
|
8496
11466
|
} else if (parsed.kind !== "none") {
|
|
8497
|
-
|
|
11467
|
+
const unitId = unitSemantics ? foldUnitId() : void 0;
|
|
11468
|
+
captureAssets(unitId);
|
|
8498
11469
|
projection.lastConfirmationRejection = {
|
|
8499
11470
|
eventSeq: event.seq,
|
|
8500
11471
|
kind: parsed.kind,
|
|
@@ -8502,14 +11473,23 @@ function deriveProjection(sourceEvents, config, scope, durableConfirmed, hostLoc
|
|
|
8502
11473
|
};
|
|
8503
11474
|
const stripped = text.split(/\r?\n/).filter((line) => !CONFIRM_LINE_PATTERN.test(line.trim())).join("\n");
|
|
8504
11475
|
if (!stripped.trim()) break;
|
|
8505
|
-
captureRootText(projection, stripped, event.seq, scope, legacyMessage, priorRootMessages, `m${event.seq}`, coordinationSplit);
|
|
11476
|
+
captureRootText(projection, stripped, event.seq, scope, legacyMessage, priorRootMessages, `m${event.seq}`, coordinationSplit, unitId, unitSemantics);
|
|
8506
11477
|
break;
|
|
8507
11478
|
}
|
|
8508
11479
|
}
|
|
8509
|
-
|
|
11480
|
+
const directiveBearing = text.trim().length > 0 && !isInformationalMessage(text) && classifyUserInteraction(text) !== "conversational";
|
|
11481
|
+
let captureUnitId;
|
|
11482
|
+
if (unitSemantics && directiveBearing) {
|
|
11483
|
+
if (!explicitlyLinkedToCurrentUnit(projection, text) && opensNewUnit(projection, text, true, currentUnitHasOpenWork(projection))) {
|
|
11484
|
+
const parentUnitId = opensChildUnit(projection, text) ? projection.currentUnitId : void 0;
|
|
11485
|
+
captureUnitId = openUnit(projection, event.seq, text.slice(0, 200), parentUnitId).unitId;
|
|
11486
|
+
} else captureUnitId = foldUnitId();
|
|
11487
|
+
if (activeTurn !== void 0) turnUnitIds.set(activeTurn, projection.currentUnitId);
|
|
11488
|
+
}
|
|
11489
|
+
captureAssets(captureUnitId ?? (unitSemantics ? foldUnitId() : void 0));
|
|
8510
11490
|
if (isInformationalMessage(text)) break;
|
|
8511
11491
|
if (classifyUserInteraction(text) === "conversational") break;
|
|
8512
|
-
captureRootText(projection, text, event.seq, scope, legacyMessage, priorRootMessages, `m${event.seq}`, coordinationSplit);
|
|
11492
|
+
captureRootText(projection, text, event.seq, scope, legacyMessage, priorRootMessages, `m${event.seq}`, coordinationSplit, captureUnitId, unitSemantics);
|
|
8513
11493
|
break;
|
|
8514
11494
|
}
|
|
8515
11495
|
case "goal/change": {
|
|
@@ -8546,10 +11526,13 @@ function deriveProjection(sourceEvents, config, scope, durableConfirmed, hostLoc
|
|
|
8546
11526
|
const call = {
|
|
8547
11527
|
name: String(data?.name ?? ""),
|
|
8548
11528
|
arguments: String(data?.arguments ?? ""),
|
|
8549
|
-
rootCallId: typeof data?.rootCallId === "string" ? data.rootCallId : void 0
|
|
11529
|
+
rootCallId: typeof data?.rootCallId === "string" ? data.rootCallId : void 0,
|
|
11530
|
+
...typeof data?.turn === "number" && Number.isSafeInteger(data.turn) ? { turn: data.turn } : {},
|
|
11531
|
+
...projection.currentUnitId !== void 0 ? { unitIdAtCall: projection.currentUnitId } : {}
|
|
8550
11532
|
};
|
|
8551
11533
|
if (call.name === "context_guard_checkpoint") {
|
|
8552
11534
|
const args = parseArguments(call.arguments);
|
|
11535
|
+
if (asRecord(args.proof)) call.proof = args.proof;
|
|
8553
11536
|
call.bindings = Array.isArray(args.bindings) ? args.bindings.map((binding) => {
|
|
8554
11537
|
const record = asRecord(binding);
|
|
8555
11538
|
const transition = asRecord(record?.expected_transition);
|
|
@@ -8639,6 +11622,18 @@ function deriveProjection(sourceEvents, config, scope, durableConfirmed, hostLoc
|
|
|
8639
11622
|
}
|
|
8640
11623
|
break;
|
|
8641
11624
|
}
|
|
11625
|
+
const recordedProof = asRecord(recorded.proof_state);
|
|
11626
|
+
const recomputedProof = replayProofState(projection, call.proof);
|
|
11627
|
+
if ((recordedProof !== void 0 || call.proof !== void 0) && (recordedProof === void 0 || String(recordedProof.status ?? "") !== recomputedProof.status || !sameStringSet(recordedProof.reason_codes, recomputedProof.reason_codes))) {
|
|
11628
|
+
projection.integrity = "corrupt";
|
|
11629
|
+
projection.integrityViolations.push("proof_replay_mismatch");
|
|
11630
|
+
break;
|
|
11631
|
+
}
|
|
11632
|
+
if (recomputedProof.status === "invalid" || recomputedProof.status === "rejected") {
|
|
11633
|
+
projection.integrity = "corrupt";
|
|
11634
|
+
projection.integrityViolations.push("proof_replay_mismatch");
|
|
11635
|
+
break;
|
|
11636
|
+
}
|
|
8642
11637
|
if (!asRecord(recorded.certificate)) {
|
|
8643
11638
|
for (const binding of call.bindings ?? []) {
|
|
8644
11639
|
const item = projection.items.get(binding.itemId);
|
|
@@ -8670,6 +11665,47 @@ function deriveProjection(sourceEvents, config, scope, durableConfirmed, hostLoc
|
|
|
8670
11665
|
} else certifyCheckpoint(projection, call.bindings ?? [], id, true);
|
|
8671
11666
|
break;
|
|
8672
11667
|
}
|
|
11668
|
+
if (call.name === "context_guard_interpret") {
|
|
11669
|
+
if (!call.rootCallId && !data?.error) {
|
|
11670
|
+
const callArgs = parseArguments(call.arguments);
|
|
11671
|
+
const requested = typeof callArgs.item_id === "string" ? callArgs.item_id.trim() : "";
|
|
11672
|
+
const recorded = parseArguments(textContent);
|
|
11673
|
+
if (recorded.status === "recorded") {
|
|
11674
|
+
const item = requested ? projection.items.get(requested) : void 0;
|
|
11675
|
+
const resultTurn = typeof data?.turn === "number" && Number.isSafeInteger(data.turn) ? data.turn : void 0;
|
|
11676
|
+
const callInformation = readPartitionSpans(callArgs.information_spans);
|
|
11677
|
+
const callUnknown = readPartitionSpans(callArgs.unknown_spans);
|
|
11678
|
+
if (!(requested !== "" && item !== void 0 && item.status === "pending" && recorded.item_id === requested && recorded.item_revision === item.revision && (item.asset !== void 0 ? recorded.kind === "asset" && !Object.hasOwn(recorded, "information_spans") && !Object.hasOwn(recorded, "unknown_spans") && assetReceiptMatches(recorded.asset, item.asset) : recorded.kind === "clause" && clauseCallReceiptMatches(callInformation, callUnknown, recorded, item)))) {
|
|
11679
|
+
projection.integrity = "corrupt";
|
|
11680
|
+
projection.integrityViolations.push("interpretation_receipt_mismatch");
|
|
11681
|
+
break;
|
|
11682
|
+
}
|
|
11683
|
+
if (call.turn === void 0 || resultTurn === void 0) break;
|
|
11684
|
+
if (call.turn !== resultTurn) {
|
|
11685
|
+
projection.integrity = "corrupt";
|
|
11686
|
+
projection.integrityViolations.push("interpretation_receipt_mismatch");
|
|
11687
|
+
break;
|
|
11688
|
+
}
|
|
11689
|
+
if (item.asset !== void 0) {
|
|
11690
|
+
const existing = interpretationFacts.findIndex((fact) => fact.itemId === requested);
|
|
11691
|
+
if (existing >= 0) interpretationFacts.splice(existing, 1);
|
|
11692
|
+
interpretationFacts.push({
|
|
11693
|
+
itemId: requested,
|
|
11694
|
+
resultSeq: event.seq,
|
|
11695
|
+
turn: call.turn
|
|
11696
|
+
});
|
|
11697
|
+
} else {
|
|
11698
|
+
const informationSubItemIds = supersedeClauseByPartition(projection, item, asRecord(recorded));
|
|
11699
|
+
for (const subItemId of informationSubItemIds) if (interpretationFacts.findIndex((fact) => fact.itemId === subItemId) < 0) interpretationFacts.push({
|
|
11700
|
+
itemId: subItemId,
|
|
11701
|
+
resultSeq: event.seq,
|
|
11702
|
+
turn: call.turn
|
|
11703
|
+
});
|
|
11704
|
+
}
|
|
11705
|
+
}
|
|
11706
|
+
}
|
|
11707
|
+
break;
|
|
11708
|
+
}
|
|
8673
11709
|
if (call.name === "context_guard_boundary") {
|
|
8674
11710
|
const recorded = parseArguments(textContent);
|
|
8675
11711
|
const candidate = call.boundaryRequest ? qualifyBoundary(projection, call.boundaryRequest) : void 0;
|
|
@@ -8689,7 +11725,8 @@ function deriveProjection(sourceEvents, config, scope, durableConfirmed, hostLoc
|
|
|
8689
11725
|
break;
|
|
8690
11726
|
}
|
|
8691
11727
|
evidenceCounter += 1;
|
|
8692
|
-
const
|
|
11728
|
+
const delegated = DEFAULT_DELEGATION_TOOL_NAMES.includes(call.name);
|
|
11729
|
+
const baseEvidence = withDurability(evidenceFromPersistedToolResult({
|
|
8693
11730
|
callId,
|
|
8694
11731
|
name: call.name,
|
|
8695
11732
|
arguments: call.arguments,
|
|
@@ -8703,7 +11740,17 @@ function deriveProjection(sourceEvents, config, scope, durableConfirmed, hostLoc
|
|
|
8703
11740
|
meta: data?.meta,
|
|
8704
11741
|
textContent
|
|
8705
11742
|
}, epoch, `E${String(evidenceCounter).padStart(4, "0")}`, scope.cwd || void 0, hostLock), durableConfirmed);
|
|
11743
|
+
const evidence = delegated ? {
|
|
11744
|
+
...baseEvidence,
|
|
11745
|
+
delegatedSubtask: true
|
|
11746
|
+
} : baseEvidence;
|
|
8706
11747
|
projection.evidence.set(evidence.id, evidence);
|
|
11748
|
+
if (delegated && call.unitIdAtCall !== void 0) recordDelegation(projection, call.unitIdAtCall, {
|
|
11749
|
+
callId,
|
|
11750
|
+
resultSeq: event.seq,
|
|
11751
|
+
toolName: call.name,
|
|
11752
|
+
status: data?.error !== void 0 ? "failed" : "completed"
|
|
11753
|
+
});
|
|
8707
11754
|
if (evidence.externalOperationRef) projection.externalOperations.set(evidence.externalOperationRef.id, evidence.externalOperationRef);
|
|
8708
11755
|
break;
|
|
8709
11756
|
}
|
|
@@ -8712,13 +11759,60 @@ function deriveProjection(sourceEvents, config, scope, durableConfirmed, hostLoc
|
|
|
8712
11759
|
}
|
|
8713
11760
|
projection.enabled = enabled;
|
|
8714
11761
|
projection.epoch = epoch;
|
|
11762
|
+
if (interpretationFacts.length > 64) interpretationFacts.splice(0, interpretationFacts.length - 64);
|
|
11763
|
+
projection.interpretationFacts = interpretationFacts;
|
|
11764
|
+
projection.trustedSelections = deriveTrustedSelections(sourceEvents, { questionToolNames: DEFAULT_QUESTION_TOOL_NAMES });
|
|
11765
|
+
if (projection.trustedSelections.length > 16) projection.trustedSelections = projection.trustedSelections.slice(-16);
|
|
11766
|
+
const approvalAsked = /* @__PURE__ */ new Map();
|
|
11767
|
+
for (const event of sourceEvents) {
|
|
11768
|
+
const data = asRecord(event.data);
|
|
11769
|
+
if (event.type === "approval/asked") {
|
|
11770
|
+
const id = typeof data?.id === "string" ? data.id : "";
|
|
11771
|
+
const toolName = typeof data?.toolName === "string" ? data.toolName : void 0;
|
|
11772
|
+
if (id) approvalAsked.set(id, {
|
|
11773
|
+
id,
|
|
11774
|
+
seq: event.seq,
|
|
11775
|
+
toolName
|
|
11776
|
+
});
|
|
11777
|
+
continue;
|
|
11778
|
+
}
|
|
11779
|
+
if (event.type === "approval/decided") {
|
|
11780
|
+
const id = typeof data?.id === "string" ? data.id : "";
|
|
11781
|
+
const asked = approvalAsked.get(id);
|
|
11782
|
+
if (!asked) continue;
|
|
11783
|
+
const outcome = String(data?.outcome ?? "");
|
|
11784
|
+
if (![
|
|
11785
|
+
"allowed-once",
|
|
11786
|
+
"rejected",
|
|
11787
|
+
"cancelled",
|
|
11788
|
+
"unavailable"
|
|
11789
|
+
].includes(outcome)) continue;
|
|
11790
|
+
projection.approvals.push({
|
|
11791
|
+
id: asked.id,
|
|
11792
|
+
seq: asked.seq,
|
|
11793
|
+
toolName: asked.toolName,
|
|
11794
|
+
outcome
|
|
11795
|
+
});
|
|
11796
|
+
approvalAsked.delete(id);
|
|
11797
|
+
}
|
|
11798
|
+
}
|
|
11799
|
+
if (projection.approvals.length > 16) projection.approvals = projection.approvals.slice(-16);
|
|
11800
|
+
if (!projection.releaseStateDamaged) projection.releaseStateDamaged = projection.releaseSettlements.some((settlement) => {
|
|
11801
|
+
if (settlement.readback === "unavailable") return false;
|
|
11802
|
+
const contract = projection.releaseContracts.find((entry) => entry.contractId === settlement.contractId);
|
|
11803
|
+
if (!contract || settlement.readback.kind !== "npm_integrity") return false;
|
|
11804
|
+
const reservation = projection.releaseReservations.find((entry) => entry.contractId === settlement.contractId && entry.operation === settlement.operation && entry.callId === settlement.callId);
|
|
11805
|
+
const expected = contract.candidate.artifactSri ?? reservation?.observedArtifactSri;
|
|
11806
|
+
return expected !== void 0 && expected !== settlement.readback.identity;
|
|
11807
|
+
});
|
|
8715
11808
|
return {
|
|
8716
11809
|
projection,
|
|
8717
11810
|
compacted,
|
|
8718
11811
|
enablementTransitioned,
|
|
8719
11812
|
lastCompactionSeq,
|
|
8720
11813
|
realRootInputSeen,
|
|
8721
|
-
protocolV4Present: v4BoundarySeq !== void 0
|
|
11814
|
+
protocolV4Present: v4BoundarySeq !== void 0,
|
|
11815
|
+
boundaryV5: v5BoundarySeq !== void 0
|
|
8722
11816
|
};
|
|
8723
11817
|
}
|
|
8724
11818
|
|
|
@@ -8770,21 +11864,34 @@ function claimedBatchHasRealRootInput(messages) {
|
|
|
8770
11864
|
* persisted step batch; guidance is compact and never claims a recovery that
|
|
8771
11865
|
* did not happen. `opt-in` reaches this path only after its explicit `on` command. Delegated sessions receive neither: their
|
|
8772
11866
|
* scope arrives through the parent's delegation prompt (A04).
|
|
11867
|
+
*
|
|
11868
|
+
* A session without a v5 boundary receives the 0.6 boundary: it cuts the
|
|
11869
|
+
* work-unit/delivery/certificate-v2 semantics at exactly this message. A
|
|
11870
|
+
* session that already has v5 injects nothing.
|
|
8773
11871
|
*/
|
|
8774
11872
|
function previewFirstStepInjection(input, claimedRealInput) {
|
|
8775
|
-
if (!input.enabled || input.
|
|
11873
|
+
if (!input.enabled || input.delegated) return void 0;
|
|
8776
11874
|
if (!claimedRealInput) return void 0;
|
|
11875
|
+
if (input.boundaryV5Present) return void 0;
|
|
8777
11876
|
return {
|
|
8778
|
-
boundary:
|
|
8779
|
-
guidance:
|
|
11877
|
+
boundary: PROTOCOL_V5_NOTICE,
|
|
11878
|
+
guidance: firstStepGuidance(input.policy ?? "standard")
|
|
8780
11879
|
};
|
|
8781
11880
|
}
|
|
8782
11881
|
/**
|
|
8783
11882
|
* Compact first-step guidance: protection has started, what it protects, and
|
|
8784
|
-
* the
|
|
8785
|
-
*
|
|
11883
|
+
* when the guarded producer path is needed. 0.6.1 (W060-05): the stateful
|
|
11884
|
+
* workflow is stated CONDITIONALLY — only an obligation whose own clause
|
|
11885
|
+
* demands a certified stateful action runs through prepare/producer/checkpoint.
|
|
11886
|
+
* The 0.6.0 text demanded that order for every stateful action unconditionally,
|
|
11887
|
+
* which ordinary business work correctly read as a Guard approval gate.
|
|
11888
|
+
* Ordinary answers, investigations, and ordinary tool work are never gated, and
|
|
11889
|
+
* missing Guard evidence is never a reason to repeat a completed action.
|
|
8786
11890
|
*/
|
|
8787
|
-
|
|
11891
|
+
function firstStepGuidance(policy = "standard") {
|
|
11892
|
+
return "Context Guard is now protecting this session: requirements from your messages stay open until they are certified with matching durable evidence. Ordinary answers, investigations, and ordinary tool work need no Guard approval. When a requirement itself calls for a certified stateful action (install, apply, create, modify, restart, commit, push, publish, pull, fetch), call context_guard_prepare before it to see the supported command shape and the required resolution/effect/state order, run the action through the guarded path, and close items with context_guard_checkpoint; never repeat an already-completed action to mint missing evidence." + (policy === "strict" ? " Under strict policy, a verification the user explicitly requested (a visual readback or a complete-scope check) must be discharged by a real readback fact." : "") + " Ordinary answers and investigations need no certification.";
|
|
11893
|
+
}
|
|
11894
|
+
const FIRST_STEP_GUIDANCE = firstStepGuidance("standard");
|
|
8788
11895
|
/**
|
|
8789
11896
|
* Lifecycle phase derived from durable facts. `enabled` is the log-derived
|
|
8790
11897
|
* enablement (`always`, or the explicit `on`/`off` command sequence), and
|
|
@@ -9079,6 +12186,22 @@ async function executeRevalidatedGitEffect(resolved, manifest, target, currentSt
|
|
|
9079
12186
|
status: "rejected",
|
|
9080
12187
|
...checked.reasonCode ? { reasonCode: checked.reasonCode } : {}
|
|
9081
12188
|
};
|
|
12189
|
+
const text = (key) => {
|
|
12190
|
+
const value = currentStateTuple[key];
|
|
12191
|
+
return typeof value === "string" ? value : value === void 0 ? void 0 : Buffer.from(value).toString("utf8");
|
|
12192
|
+
};
|
|
12193
|
+
if (manifest.action === "push" && text("source_oid") !== void 0 && text("source_oid") === text("destination_oid")) return {
|
|
12194
|
+
status: "rejected",
|
|
12195
|
+
reasonCode: "effect_already_applied"
|
|
12196
|
+
};
|
|
12197
|
+
if (manifest.action === "pull" && text("pre_head_oid") !== void 0 && text("pre_head_oid") === text("upstream_oid")) return {
|
|
12198
|
+
status: "rejected",
|
|
12199
|
+
reasonCode: "effect_already_applied"
|
|
12200
|
+
};
|
|
12201
|
+
if (manifest.action === "fetch" && text("tracking_oid") !== void 0 && text("tracking_oid") === text("upstream_oid")) return {
|
|
12202
|
+
status: "rejected",
|
|
12203
|
+
reasonCode: "effect_already_applied"
|
|
12204
|
+
};
|
|
9082
12205
|
await runner("git", manifest.argv.slice(1), target.repository);
|
|
9083
12206
|
return { status: "executed" };
|
|
9084
12207
|
}
|
|
@@ -9693,214 +12816,4 @@ function verifyComposedHostLockDump(text, expected, roots) {
|
|
|
9693
12816
|
}
|
|
9694
12817
|
|
|
9695
12818
|
//#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 };
|
|
12819
|
+
export { extractToolSubject as $, isFrozenV042RebindResponse as $n, sha256 as $r, proofDigest as $t, lifecyclePhase as A, decisionBoundaryKey as An, STATEFUL_ACTIONS as Ar, compareHostVersions as At, RELEASE_RESERVATION_PREFIX as B, effectuateBoundary as Bn, semanticActionFromCommand as Br, certifyCheckpoint as Bt, gitCommandMatchesTarget as C, isVerifyingCapability as Cn, npmEscapedPackageName as Cr, evaluateToolSurfaceCapability as Ct, FIRST_STEP_GUIDANCE as D, classifyCompletionClaim as Dn, CERTIFICATE_VERSION as Dr, MIN_SUPPORTED_HOST_VERSION as Dt, verifiedLinearCommitReadback as E, NO_PROGRESS_TURNS_BEFORE_STOP as En, BOUNDED_ARTIFACT_TYPES as Er, LATEST_SUPPORTED_HOST_VERSION as Et, PROTOCOL_V4_NOTICE as F, observeAssistantOutcome as Fn, boundedArtifactChoiceMatches as Fr, RC015_HOST_PACKAGES as Ft, readbackSettlesContract as G, confirmRebind as Gn, validateManifest as Gr, PROOF_PROTOCOL_VERSION as Gt, contractById as H, qualifyBoundary as Hn, validateActionManifest as Hr, PROOF_KINDS as Ht, PROTOCOL_V5_NOTICE as I, progressFingerprint as In, isStatefulAction as Ir, RC1_HOST_PACKAGES as It, releasePreEffectDecision as J, proposeRebindV042 as Jn, canonicalizePath as Jr, bindProofV2ToProjection as Jt, releaseContractFor as K, proposeRebind as Kn, classifyTaskIntent as Kr, PROOF_PROTOCOL_VERSION_V2 as Kt, deriveProjection as L, goalCompletionDenial as Ln, requestedIdentityKey as Lr, ALPHA3_HOST_PACKAGES as Lt, CAPTURE_V042_NOTICE as M, isWholeTaskCompletionClaim as Mn, STOP_PROTOCOL_VERSION_V2 as Mr, parseHostVersion as Mt, DEFAULT_DELEGATION_TOOL_NAMES as N, latestAssistantText as Nn, SUPPORTED_EVIDENCE_ADAPTERS as Nr, satisfiesSupportedHostRange as Nt, claimedBatchHasRealRootInput as O, decideTurnBoundary as On, CERTIFICATE_VERSION_V2 as Or, SUPPORTED_HOST_RANGE as Ot, PROTOCOL_V3_NOTICE as P, latestRootInstruction as Pn, actionCompatible as Pr, RC015_RC2_HOST_PACKAGES as Pt, extractTextContent as Q, CONFIRM_LINE_PATTERN as Qn, sanitizeUrl as Qr, proofCapabilityReport as Qt, RELEASE_OPERATIONS as R, hasCurrentCertificate as Rn, requestedTargetAuthorizesMutation as Rr, authorityCaptureCounts as Rt, executeRevalidatedGitEffect as S, evidenceMatchesItem as Sn, canonicalRegistryBase as Sr, evaluateHostLock as St, revalidateGitPrestate as T, NO_PROGRESS_RECORD_PREFIX as Tn, ACTION_MANIFEST_VERSION as Tr, selectHostCohort as Tt, inFlightReservation as U, currentContractDigest as Un, validateActionTarget as Ur, PROOF_KINDS_V2 as Ut, RELEASE_SETTLEMENT_PREFIX as V, isCurrentAcceptedBoundary as Vn, semanticActionFromText as Vr, PROOF_CAPABILITY_MATRIX as Vt, normalizeReleaseContract as W, createProjection as Wn, COMMAND_SURFACE_MANIFEST as Wr, PROOF_MANIFEST_DOMAIN_V2 as Wt, supersedeItem as X, rebindResponse as Xn, normalizeClause as Xr, createProofManifest as Xt, reservationFor as Y, rebindAttemptKey as Yn, digestStrings as Yr, canonicalProjection as Yt, evidenceFromPersistedToolResult as Z, replayRebindResult as Zn, sanitizeClauseText as Zr, createProofManifestV2 as Zt, GIT_COMMAND_MANIFEST_IDS as _, openItems as _n, kindOfScope as _r, LEGACY_HOST_COHORTS as _t, injectActiveProfileHostLock as a, requiredSubjectsOf as an, captureClause as ar, parseShellCommand as at, commitTreeSnapshotDigest as b, bindingSatisfies as bn, semanticActionOfScope as br, evaluateExternalWaitCapability as bt, packageRowsFromPnpmLock as c, sessionQueryV2 as cn, extractArtifactPaths as cr, ACTIVE_HOST_LAUNCHER_VERSION as ct, resolveInstalledHostLock as d, certifiableOpenItems as dn, isInformationalMessage as dr, BASE_HOST_PACKAGES as dt, proofDigestV2 as en, parseConfirmationMessage as er, isDeterministicCheck as et, verifyComposedHostLockDump as f, certificateClosure as fn, segmentClauses as fr, DEFAULT_HOST_LOCK as ft, snapshotSessionEvents as g, closingHint as gn, isOpenObligation as gr, HOST_COHORTS as gt, SessionApiError as h, MIN_RECOVERY_CHAR_BUDGET as hn, isExecutableItem as hr, HOST_CAPABILITY_PACKAGE_GROUPS as ht, hostLockRowsFromComposedDump as i, proofV2Rejection as in, relevantEvidence as ir, parsePwshCommand as it, previewFirstStepInjection as j, isRootPauseRequest as jn, STOP_PROTOCOL_VERSION as jr, evaluateMinimumHostVersion as jt, firstStepGuidance as k, decideTurnStopping as kn, SEMANTIC_ACTIONS as kr, SUPPORTED_HOST_VERSIONS as kt, readActiveHostGraph as l, validateProofManifest as ln, extractMethod as lr, ALPHA2_DSHMARKET_139_HOST_PACKAGES as lt, SESSION_EVENT_ENVELOPE_INVALID as m, DEFAULT_RECOVERY_CHAR_BUDGET as mn, interpretMessage as mr, GOAL_HOST_PACKAGES as mt, combineHostPolicy as n, proofHostSurfacesOf as nn, evidenceAvailabilityReason as nr, canonicalArgvFromCommand as nt, inspectTargetHostGraph as o, scopeCoverageDigest as on, captureItem as or, ACTIVE_HOST_COHORT_ID as ot, SESSION_API_UNSUPPORTED as p, unitDescendantIds as pn, interpretClause as pr, EXPECTED_HOST_PACKAGES as pt, releaseCoverage as q, proposeRebindOutcome as qn, classifyUserInteraction as qr, bindProofToProjection as qt, hostLockContextFromComposedDump as r, proofOperationMatches as rn, itemDiagnosis as rr, isRunExecutable as rt, packageRowsFromActiveGraph as s, sessionQuery as sn, classifyClause as sr, ACTIVE_HOST_COHORT_IDS as st, HostProfileError as t, proofEvidenceConstraints as tn, deriveItemDiagnosis as tr, withDurability as tt, resolveActiveProfileHostLock as u, validateProofManifestV2 as un, extractOperation as ur, ALPHA2_HOST_PACKAGES as ut, GIT_COMMAND_TEMPLATES as v, recoveryDigest as vn, maskCodeSpans as vr, bindExecutableIdentity as vt, parseGitCommandManifest as w, CONTROL_RECORD_PREFIX as wn, ACTION_MANIFEST as wr, hostVersionFromPackages as wt, createGitPrestateEnvelope as x, evidenceCoverage as xn, statefulActionsOfScope as xr, evaluateHostCapability as xt, commitIndexSnapshotDigest as y, renderRecoveryPacket as yn, namedActions as yr, bindLiveGoalCapability as yt, RELEASE_OPERATION_SURFACES as z, availableBoundaryQualifications as zn, requestedTargetMatchesResolved as zr, segmentAuthorityBlocks as zt };
|