project-tiny-context-harness 0.7.8 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.md +393 -375
- package/assets/README.md +567 -552
- package/assets/README.zh-CN.md +339 -321
- package/assets/agents/.gitkeep +1 -1
- package/assets/agents/AGENTS_CORE.md +66 -58
- package/assets/context_templates/architecture.md +33 -33
- package/assets/context_templates/area.md +39 -39
- package/assets/context_templates/context.toml +30 -30
- package/assets/context_templates/deployment.md +35 -35
- package/assets/context_templates/global.md +52 -51
- package/assets/context_templates/product-surface-contract.md +70 -70
- package/assets/context_templates/screen-contract.md +186 -177
- package/assets/context_templates/verification.md +32 -32
- package/assets/github/.gitkeep +1 -1
- package/assets/github/harness.yml +41 -41
- package/assets/make/.gitkeep +1 -1
- package/assets/make/ty-context.mk +48 -48
- package/assets/skills/context_development_engineer/SKILL.md +135 -112
- package/assets/skills/context_full_project_export/SKILL.md +70 -70
- package/assets/skills/context_harness_upgrade/SKILL.md +60 -60
- package/assets/skills/context_product_plan/SKILL.md +88 -87
- package/assets/skills/context_surface_contract/SKILL.md +191 -191
- package/assets/skills/context_uiux_design/SKILL.md +158 -154
- package/assets/skills/design-resource-authoring/SKILL.md +84 -79
- package/assets/skills/design-resource-authoring/references/downstream-handoff.md +136 -108
- package/assets/skills/design-resource-authoring/references/open-design-provider.md +133 -127
- package/assets/skills/design-resource-authoring/references/resource-selection.md +173 -173
- package/assets/skills/design-system-authoring/SKILL.md +57 -57
- package/assets/skills/design-system-authoring/agents/openai.yaml +6 -6
- package/assets/skills/design-system-authoring/references/authority-adoption.md +48 -47
- package/assets/skills/design-system-authoring/references/open-design-design-system-provider.md +110 -110
- package/assets/skills/long-task-workflow/SKILL.md +100 -87
- package/assets/skills/long-task-workflow/agents/openai.yaml +4 -4
- package/assets/skills/long-task-workflow/references/authority-lifecycle.md +76 -59
- package/assets/skills/long-task-workflow/references/contract-authoring.md +116 -103
- package/assets/skills/long-task-workflow/references/evidence-design.md +78 -75
- package/assets/skills/long-task-workflow/references/source-authoring.md +101 -98
- package/assets/skills/normal-long-task/SKILL.md +12 -12
- package/assets/skills/source-plan-authoring/SKILL.md +14 -14
- package/assets/tools/validate_context.py +442 -442
- package/dist/commands/design-resource.d.ts +1 -0
- package/dist/commands/design-resource.js +32 -0
- package/dist/commands/index.js +4 -0
- package/dist/commands/long-task-command-args.d.ts +1 -0
- package/dist/commands/long-task-command-args.js +6 -0
- package/dist/commands/long-task-revision.js +16 -4
- package/dist/commands/long-task.js +12 -5
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/lib/design-md.js +3 -1
- package/dist/lib/design-resource-handoff-file-primitives.d.ts +4 -0
- package/dist/lib/design-resource-handoff-file-primitives.js +14 -0
- package/dist/lib/design-resource-handoff-file-validation.d.ts +2 -0
- package/dist/lib/design-resource-handoff-file-validation.js +142 -0
- package/dist/lib/design-resource-handoff-parser.d.ts +3 -0
- package/dist/lib/design-resource-handoff-parser.js +29 -0
- package/dist/lib/design-resource-handoff-policy.d.ts +3 -0
- package/dist/lib/design-resource-handoff-policy.js +36 -0
- package/dist/lib/design-resource-handoff-shape-evidence.d.ts +4 -0
- package/dist/lib/design-resource-handoff-shape-evidence.js +84 -0
- package/dist/lib/design-resource-handoff-shape-primitives.d.ts +9 -0
- package/dist/lib/design-resource-handoff-shape-primitives.js +44 -0
- package/dist/lib/design-resource-handoff-shape-structure.d.ts +5 -0
- package/dist/lib/design-resource-handoff-shape-structure.js +118 -0
- package/dist/lib/design-resource-handoff-shape.d.ts +2 -0
- package/dist/lib/design-resource-handoff-shape.js +75 -0
- package/dist/lib/design-resource-handoff-types.d.ts +139 -0
- package/dist/lib/design-resource-handoff-types.js +46 -0
- package/dist/lib/design-resource-handoff-validation-coverage.d.ts +4 -0
- package/dist/lib/design-resource-handoff-validation-coverage.js +168 -0
- package/dist/lib/design-resource-handoff-validation-primitives.d.ts +11 -0
- package/dist/lib/design-resource-handoff-validation-primitives.js +26 -0
- package/dist/lib/design-resource-handoff-validation-structure.d.ts +6 -0
- package/dist/lib/design-resource-handoff-validation-structure.js +93 -0
- package/dist/lib/design-resource-handoff-validation.d.ts +3 -0
- package/dist/lib/design-resource-handoff-validation.js +78 -0
- package/dist/lib/design-resource-handoff-web-dependency-validation.d.ts +3 -0
- package/dist/lib/design-resource-handoff-web-dependency-validation.js +70 -0
- package/dist/lib/long-task-activation-validation.js +14 -2
- package/dist/lib/long-task-authoring-preflight-diagnostics.js +7 -0
- package/dist/lib/long-task-authoring-preflight.js +39 -1
- package/dist/lib/long-task-authority-material-diff.js +14 -0
- package/dist/lib/long-task-authority-materials.js +21 -0
- package/dist/lib/long-task-authority-policy.d.ts +33 -0
- package/dist/lib/long-task-authority-policy.js +36 -0
- package/dist/lib/long-task-authority-revision-analysis.d.ts +1 -0
- package/dist/lib/long-task-authority-revision-analysis.js +30 -0
- package/dist/lib/long-task-authority-revision-brief.d.ts +3 -0
- package/dist/lib/long-task-authority-revision-brief.js +74 -0
- package/dist/lib/long-task-authority-revision-details.js +16 -16
- package/dist/lib/long-task-authority-revision-diagnosis.d.ts +1 -1
- package/dist/lib/long-task-authority-revision-diagnosis.js +8 -0
- package/dist/lib/long-task-authority-revision-enforcement.js +7 -3
- package/dist/lib/long-task-authority-revision-summary.d.ts +3 -0
- package/dist/lib/long-task-authority-revision-summary.js +133 -21
- package/dist/lib/long-task-authority-revision-types.d.ts +19 -1
- package/dist/lib/long-task-authority-revision.js +5 -0
- package/dist/lib/long-task-authority-types.d.ts +2 -0
- package/dist/lib/long-task-authority.js +10 -2
- package/dist/lib/long-task-check-execution-policy.js +2 -0
- package/dist/lib/long-task-claim-definitions.js +1 -5
- package/dist/lib/long-task-contract-types.d.ts +2 -0
- package/dist/lib/long-task-counterfactual-sandbox.d.ts +7 -0
- package/dist/lib/long-task-counterfactual-sandbox.js +27 -1
- package/dist/lib/long-task-delivery-compiler.js +26 -8
- package/dist/lib/long-task-delivery-types.d.ts +1 -0
- package/dist/lib/long-task-delivery-types.js +1 -0
- package/dist/lib/long-task-delivery-validation.js +3 -0
- package/dist/lib/long-task-design-resource-handoff.d.ts +2 -0
- package/dist/lib/long-task-design-resource-handoff.js +177 -0
- package/dist/lib/long-task-evidence-capability-codec.js +19 -0
- package/dist/lib/long-task-evidence-capability-policy.js +4 -0
- package/dist/lib/long-task-evidence-capability-runtime.js +25 -0
- package/dist/lib/long-task-evidence-capability-types.d.ts +9 -1
- package/dist/lib/long-task-outcome-parser.js +11 -1
- package/dist/lib/long-task-paths.d.ts +0 -1
- package/dist/lib/long-task-paths.js +0 -4
- package/dist/lib/long-task-playwright-case-evidence.d.ts +43 -0
- package/dist/lib/long-task-playwright-case-evidence.js +152 -0
- package/dist/lib/long-task-playwright-evidence.js +19 -152
- package/dist/lib/long-task-runner-freeze.d.ts +2 -2
- package/dist/lib/long-task-runner-freeze.js +2 -1
- package/dist/lib/long-task-runtime-types.d.ts +2 -0
- package/dist/lib/long-task-semantic-contract-types.d.ts +1 -1
- package/dist/lib/long-task-semantic-drift-migration.js +6 -1
- package/dist/lib/long-task-shape-primitives.d.ts +1 -1
- package/dist/lib/long-task-shape-primitives.js +1 -0
- package/dist/lib/long-task-status-v2.js +12 -7
- package/dist/lib/long-task-ui-design-policy.d.ts +17 -0
- package/dist/lib/long-task-ui-design-policy.js +128 -0
- package/dist/lib/long-task-ui-surface-policy.d.ts +4 -0
- package/dist/lib/long-task-ui-surface-policy.js +109 -0
- package/dist/lib/long-task-ui-surface-shape.d.ts +2 -0
- package/dist/lib/long-task-ui-surface-shape.js +94 -0
- package/dist/lib/long-task-ui-surface-types.d.ts +44 -0
- package/dist/lib/long-task-ui-surface-types.js +1 -0
- package/dist/lib/long-task-ui-surface-validation.d.ts +11 -0
- package/dist/lib/long-task-ui-surface-validation.js +62 -0
- package/dist/lib/long-task-verification-preview.d.ts +55 -0
- package/dist/lib/long-task-verification-preview.js +120 -0
- package/dist/lib/long-task-verifier-v2.js +11 -18
- package/dist/lib/long-task-workspace-scope.d.ts +38 -0
- package/dist/lib/long-task-workspace-scope.js +121 -0
- package/dist/lib/long-task-workspace.d.ts +1 -0
- package/dist/lib/long-task-workspace.js +73 -9
- package/dist/schemas/long-task-delivery-v2/long-task-delivery-v2.schema.json +84 -2
- package/migrations/README.md +7 -7
- package/package.json +3 -3
- package/source-mappings.yaml +25 -25
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { collectCases, declaredCaseIds, duplicateCaseInstance, record, same, } from "./long-task-playwright-case-evidence.js";
|
|
1
2
|
export function extractPlaywrightEvidence(check, report, exitCode) {
|
|
2
3
|
const stats = record(report.stats);
|
|
3
4
|
if (!stats)
|
|
@@ -61,6 +62,10 @@ export function extractPlaywrightEvidence(check, report, exitCode) {
|
|
|
61
62
|
}
|
|
62
63
|
}
|
|
63
64
|
observations["playwright.case_ids"] = cases.map((item) => item.id).sort();
|
|
65
|
+
const evidenceRecords = evidenceRecordsForCases(check, cases);
|
|
66
|
+
return { observations, evidence_records: evidenceRecords, error: null };
|
|
67
|
+
}
|
|
68
|
+
function evidenceRecordsForCases(check, cases) {
|
|
64
69
|
const evidenceRecords = [];
|
|
65
70
|
for (const item of cases) {
|
|
66
71
|
const assertion = [
|
|
@@ -86,127 +91,22 @@ export function extractPlaywrightEvidence(check, report, exitCode) {
|
|
|
86
91
|
session_id: `playwright:${item.id}:${item.project_ids.join(",")}`,
|
|
87
92
|
cold_start: check.execution_target.entrypoint === "root",
|
|
88
93
|
});
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
const suite = record(suiteValue);
|
|
103
|
-
if (!suite)
|
|
104
|
-
continue;
|
|
105
|
-
const nestedError = visitSuites(suite.suites, cases, declaredIds, collection);
|
|
106
|
-
if (nestedError)
|
|
107
|
-
return nestedError;
|
|
108
|
-
if (!Array.isArray(suite.specs))
|
|
109
|
-
continue;
|
|
110
|
-
for (const specValue of suite.specs) {
|
|
111
|
-
const spec = record(specValue);
|
|
112
|
-
if (!spec || !Array.isArray(spec.tests))
|
|
113
|
-
continue;
|
|
114
|
-
for (const testValue of spec.tests) {
|
|
115
|
-
const test = record(testValue);
|
|
116
|
-
if (!test)
|
|
117
|
-
continue;
|
|
118
|
-
const title = [spec.title, test.title]
|
|
119
|
-
.filter((item) => typeof item === "string")
|
|
120
|
-
.join(" ");
|
|
121
|
-
const ids = declaredIdsInTitle(title, declaredIds);
|
|
122
|
-
if (ids.length > 1)
|
|
123
|
-
return `playwright_test_multiple_ac_ids:${ids.join(",")}`;
|
|
124
|
-
const instance = playwrightCase(ids[0] ?? "unbound", test);
|
|
125
|
-
if (ids.length === 1)
|
|
126
|
-
cases.push(instance);
|
|
127
|
-
else if (instance.unexpected)
|
|
128
|
-
collection.unbound_unexpected_instances += 1;
|
|
129
|
-
}
|
|
94
|
+
if (assertion?.evidence_capabilities.includes("design_conformance") &&
|
|
95
|
+
item.executed) {
|
|
96
|
+
for (const target of (check.design_conformance_targets ?? []).filter((candidate) => candidate.conformance_assertion_ref === assertion.key ||
|
|
97
|
+
candidate.verification_method_bindings.some((binding) => binding.assertion_ref === assertion.key)))
|
|
98
|
+
evidenceRecords.push({
|
|
99
|
+
assertion_key: assertion.key,
|
|
100
|
+
capability: "design_conformance",
|
|
101
|
+
design_target_ref: target.key,
|
|
102
|
+
target_ref: target.target_ref,
|
|
103
|
+
condition_keys: target.condition_keys,
|
|
104
|
+
actual_artifact_path: target.actual_artifact_path,
|
|
105
|
+
comparison_artifact_path: target.comparison_artifact_path,
|
|
106
|
+
});
|
|
130
107
|
}
|
|
131
108
|
}
|
|
132
|
-
return
|
|
133
|
-
}
|
|
134
|
-
function declaredIdsInTitle(title, declared) {
|
|
135
|
-
const explicit = [...title.matchAll(/\[ac:([a-z0-9][a-z0-9-]*)\]/gu)].map((match) => match[1]);
|
|
136
|
-
const legacy = [...title.matchAll(/\[([a-z0-9][a-z0-9-]*)\]/gu)].map((match) => match[1]);
|
|
137
|
-
return [
|
|
138
|
-
...new Set([...explicit, ...legacy].filter((id) => declared.has(id))),
|
|
139
|
-
].sort();
|
|
140
|
-
}
|
|
141
|
-
function playwrightCase(id, test) {
|
|
142
|
-
const status = typeof test.status === "string" ? test.status : "invalid";
|
|
143
|
-
const projectId = typeof test.projectId === "string"
|
|
144
|
-
? test.projectId
|
|
145
|
-
: typeof test.projectName === "string"
|
|
146
|
-
? test.projectName
|
|
147
|
-
: "default";
|
|
148
|
-
const results = Array.isArray(test.results)
|
|
149
|
-
? test.results.map(record).filter(Boolean)
|
|
150
|
-
: [];
|
|
151
|
-
const resultStatuses = results
|
|
152
|
-
.map((result) => result.status)
|
|
153
|
-
.filter((value) => typeof value === "string");
|
|
154
|
-
const skipped = status === "skipped" ||
|
|
155
|
-
(resultStatuses.length > 0 &&
|
|
156
|
-
resultStatuses.every((value) => value === "skipped"));
|
|
157
|
-
const executed = !skipped && resultStatuses.length > 0;
|
|
158
|
-
const flaky = status === "flaky";
|
|
159
|
-
const unexpected = status === "unexpected";
|
|
160
|
-
const timedOut = resultStatuses.includes("timedOut");
|
|
161
|
-
const interrupted = resultStatuses.includes("interrupted");
|
|
162
|
-
const passed = executed &&
|
|
163
|
-
!flaky &&
|
|
164
|
-
!unexpected &&
|
|
165
|
-
status === "expected" &&
|
|
166
|
-
resultStatuses.length > 0 &&
|
|
167
|
-
resultStatuses.at(-1) === "passed";
|
|
168
|
-
const traces = results.map((result) => scenarioTrace(result));
|
|
169
|
-
const givenKeys = traces[0]?.given_keys ?? [];
|
|
170
|
-
const actionKeys = traces[0]?.action_keys ?? [];
|
|
171
|
-
const traceConsistent = traces.every((trace) => same(trace.given_keys, givenKeys) && same(trace.action_keys, actionKeys));
|
|
172
|
-
return {
|
|
173
|
-
id,
|
|
174
|
-
project_id: projectId,
|
|
175
|
-
executed,
|
|
176
|
-
passed,
|
|
177
|
-
skipped,
|
|
178
|
-
flaky,
|
|
179
|
-
unexpected,
|
|
180
|
-
timed_out: timedOut,
|
|
181
|
-
interrupted,
|
|
182
|
-
status,
|
|
183
|
-
given_keys: traceConsistent ? givenKeys : [],
|
|
184
|
-
action_keys: traceConsistent ? actionKeys : [],
|
|
185
|
-
};
|
|
186
|
-
}
|
|
187
|
-
function declaredCaseIds(check) {
|
|
188
|
-
const result = new Set();
|
|
189
|
-
for (const assertion of [
|
|
190
|
-
...check.positive_assertions,
|
|
191
|
-
...check.negative_assertions,
|
|
192
|
-
]) {
|
|
193
|
-
if (!assertion.claims.length)
|
|
194
|
-
continue;
|
|
195
|
-
const match = /^playwright\.case\.([a-z0-9][a-z0-9-]*)\.passed$/u.exec(assertion.observation);
|
|
196
|
-
if (match)
|
|
197
|
-
result.add(assertion.key);
|
|
198
|
-
}
|
|
199
|
-
return result;
|
|
200
|
-
}
|
|
201
|
-
function duplicateCaseInstance(values) {
|
|
202
|
-
const seen = new Set();
|
|
203
|
-
for (const value of values) {
|
|
204
|
-
const identity = `${value.id}\0${value.project_id}`;
|
|
205
|
-
if (seen.has(identity))
|
|
206
|
-
return value;
|
|
207
|
-
seen.add(identity);
|
|
208
|
-
}
|
|
209
|
-
return null;
|
|
109
|
+
return evidenceRecords;
|
|
210
110
|
}
|
|
211
111
|
function aggregateCases(instances) {
|
|
212
112
|
const grouped = new Map();
|
|
@@ -271,42 +171,9 @@ function aggregateCases(instances) {
|
|
|
271
171
|
})
|
|
272
172
|
.sort((left, right) => left.id.localeCompare(right.id));
|
|
273
173
|
}
|
|
274
|
-
function record(value) {
|
|
275
|
-
return value && typeof value === "object" && !Array.isArray(value)
|
|
276
|
-
? value
|
|
277
|
-
: null;
|
|
278
|
-
}
|
|
279
174
|
function integer(value) {
|
|
280
175
|
return Number.isInteger(value) && Number(value) >= 0 ? Number(value) : null;
|
|
281
176
|
}
|
|
282
|
-
function scenarioTrace(result) {
|
|
283
|
-
const givenKeys = [];
|
|
284
|
-
const actionKeys = [];
|
|
285
|
-
const visit = (value) => {
|
|
286
|
-
if (!Array.isArray(value))
|
|
287
|
-
return;
|
|
288
|
-
for (const item of value) {
|
|
289
|
-
const step = record(item);
|
|
290
|
-
if (!step)
|
|
291
|
-
continue;
|
|
292
|
-
if (typeof step.title === "string") {
|
|
293
|
-
const given = /^\[given:([a-z0-9][a-z0-9-]*)\]$/u.exec(step.title);
|
|
294
|
-
const action = /^\[action:([a-z0-9][a-z0-9-]*)\]$/u.exec(step.title);
|
|
295
|
-
if (given)
|
|
296
|
-
givenKeys.push(given[1]);
|
|
297
|
-
if (action)
|
|
298
|
-
actionKeys.push(action[1]);
|
|
299
|
-
}
|
|
300
|
-
visit(step.steps);
|
|
301
|
-
}
|
|
302
|
-
};
|
|
303
|
-
visit(result.steps);
|
|
304
|
-
return { given_keys: givenKeys, action_keys: actionKeys };
|
|
305
|
-
}
|
|
306
|
-
function same(left, right) {
|
|
307
|
-
return (left.length === right.length &&
|
|
308
|
-
left.every((value, index) => value === right[index]));
|
|
309
|
-
}
|
|
310
177
|
function invalid(error) {
|
|
311
178
|
return { observations: {}, evidence_records: [], error };
|
|
312
179
|
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import type { CompiledCheckV2, DeliveryCheckV2, ExecutionTargetV2, WorkspaceManifestV2 } from "./long-task-delivery-types.js";
|
|
2
|
-
export declare function freezeDeliveryCheck(check: DeliveryCheckV2, outcomeKey: string | null, repository: string, baseline: WorkspaceManifestV2, executionTarget: ExecutionTargetV2, knownExecutionTargets: ExecutionTargetV2[]): Promise<CompiledCheckV2>;
|
|
1
|
+
import type { CompiledCheckV2, CompiledDesignTargetV2, DeliveryCheckV2, ExecutionTargetV2, WorkspaceManifestV2 } from "./long-task-delivery-types.js";
|
|
2
|
+
export declare function freezeDeliveryCheck(check: DeliveryCheckV2, outcomeKey: string | null, repository: string, baseline: WorkspaceManifestV2, executionTarget: ExecutionTargetV2, knownExecutionTargets: ExecutionTargetV2[], designConformanceTargets: CompiledDesignTargetV2[]): Promise<CompiledCheckV2>;
|
|
@@ -7,7 +7,7 @@ import { nearestRunnerFile, npmCliPath, npxCliPath, } from "./long-task-runner-f
|
|
|
7
7
|
import { canonicalValueJson, sha256Hex } from "./strict-codec.js";
|
|
8
8
|
import { repoRelative, resolveInsideRepository, } from "./long-task-workspace.js";
|
|
9
9
|
import { evidenceAdapterForRunner } from "./long-task-evidence-adapter-policy.js";
|
|
10
|
-
export async function freezeDeliveryCheck(check, outcomeKey, repository, baseline, executionTarget, knownExecutionTargets) {
|
|
10
|
+
export async function freezeDeliveryCheck(check, outcomeKey, repository, baseline, executionTarget, knownExecutionTargets, designConformanceTargets) {
|
|
11
11
|
const prefix = outcomeKey ? `CHECK.${outcomeKey}` : "CHECK.GLOBAL";
|
|
12
12
|
const expectedOutputs = check.expected_output_paths.map((pattern, index) => assertRepositoryPattern(repository, pattern, `${check.key}.expected_output_paths[${index}]`));
|
|
13
13
|
for (const [index, pattern] of check.input_paths.entries()) {
|
|
@@ -32,6 +32,7 @@ export async function freezeDeliveryCheck(check, outcomeKey, repository, baselin
|
|
|
32
32
|
verification_input_hashes: verificationInputHashes,
|
|
33
33
|
execution_target_definition: executionTarget,
|
|
34
34
|
known_execution_targets: knownExecutionTargets,
|
|
35
|
+
design_conformance_targets: designConformanceTargets,
|
|
35
36
|
};
|
|
36
37
|
return {
|
|
37
38
|
...compiled,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { DeliveryCheckV2, DeliveryContractV2, DeliveryOutcomeV2, DeliveryRunnerV2, ProofSurface } from "./long-task-contract-types.js";
|
|
2
2
|
import type { EvidenceCapabilityV2, ExecutionTargetV2 } from "./long-task-semantic-contract-types.js";
|
|
3
|
+
import type { CompiledDesignTargetV2 } from "./long-task-ui-surface-types.js";
|
|
3
4
|
import type { AuthorityHashesV2, ContextAuthoritySnapshotV2, InitialTaskBaseV2, NextAuthorityMaterialsV2, VerifierIdentityV2 } from "./long-task-authority-types.js";
|
|
4
5
|
import type { EvidenceAdapter } from "./long-task-evidence-adapter-types.js";
|
|
5
6
|
import type { EvidenceCapabilityRecordV2 } from "./long-task-evidence-capability-types.js";
|
|
@@ -25,6 +26,7 @@ export interface CompiledCheckV2 extends Omit<DeliveryCheckV2, "runner"> {
|
|
|
25
26
|
raw_execution_identity: string;
|
|
26
27
|
execution_target_definition: ExecutionTargetV2;
|
|
27
28
|
known_execution_targets: ExecutionTargetV2[];
|
|
29
|
+
design_conformance_targets: CompiledDesignTargetV2[];
|
|
28
30
|
}
|
|
29
31
|
export interface ProductClaimV2 {
|
|
30
32
|
id: string;
|
|
@@ -25,7 +25,7 @@ export interface DeliveryStageV2 {
|
|
|
25
25
|
gate_outcome: string;
|
|
26
26
|
}
|
|
27
27
|
export type DeliveryJourneyRoleV2 = "success" | "degradation" | "recovery" | "stage_gate" | "conformance";
|
|
28
|
-
export type EvidenceCapabilityV2 = "presence" | "interaction_trace" | "state_delta" | "cross_surface_consistency" | "durable_readback" | "boundary_invocation" | "external_side_effect" | "failure_injection" | "visual_render" | "target_runtime" | "input_variation";
|
|
28
|
+
export type EvidenceCapabilityV2 = "presence" | "interaction_trace" | "state_delta" | "cross_surface_consistency" | "durable_readback" | "boundary_invocation" | "external_side_effect" | "failure_injection" | "visual_render" | "design_conformance" | "target_runtime" | "input_variation";
|
|
29
29
|
export interface CheckExecutionTargetV2 {
|
|
30
30
|
target_ref: string;
|
|
31
31
|
entrypoint: "root" | "internal";
|
|
@@ -37,8 +37,13 @@ export function assertNoSemanticDriftMigration(fields) {
|
|
|
37
37
|
function collectOutcome(outcome, label, missing) {
|
|
38
38
|
required(outcome, label, ["stage"], missing);
|
|
39
39
|
const product = row(outcome.product);
|
|
40
|
-
if (product)
|
|
40
|
+
if (product) {
|
|
41
41
|
required(product, `${label}.product`, ["success_path_required", "degradation_path_required"], missing);
|
|
42
|
+
if (Array.isArray(product.controls) &&
|
|
43
|
+
product.controls.length > 0 &&
|
|
44
|
+
!Object.hasOwn(product, "surface_bindings"))
|
|
45
|
+
missing.push(`${label}.product.surface_bindings`);
|
|
46
|
+
}
|
|
42
47
|
const acceptance = row(outcome.acceptance);
|
|
43
48
|
collectChecks(acceptance?.checks, `${label}.acceptance.checks`, missing);
|
|
44
49
|
}
|
|
@@ -16,5 +16,5 @@ export declare function key(value: unknown, label: string): string;
|
|
|
16
16
|
export declare function nullable<T>(value: unknown, parser: (value: unknown) => T): T | null;
|
|
17
17
|
export declare const PROOF_SURFACES: readonly ["ui_browser", "runtime_behavior", "api_contract", "data_state", "security_boundary", "population_coverage", "implementation_structure"];
|
|
18
18
|
export declare const JOURNEY_ROLES: readonly ["success", "degradation", "recovery", "stage_gate", "conformance"];
|
|
19
|
-
export declare const EVIDENCE_CAPABILITIES: readonly ["presence", "interaction_trace", "state_delta", "cross_surface_consistency", "durable_readback", "boundary_invocation", "external_side_effect", "failure_injection", "visual_render", "target_runtime", "input_variation"];
|
|
19
|
+
export declare const EVIDENCE_CAPABILITIES: readonly ["presence", "interaction_trace", "state_delta", "cross_surface_consistency", "durable_readback", "boundary_invocation", "external_side_effect", "failure_injection", "visual_render", "design_conformance", "target_runtime", "input_variation"];
|
|
20
20
|
export declare function fail(label: string, message: string): never;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { compileDeliveryContract } from "./long-task-delivery-compiler.js";
|
|
3
|
+
import { authorityRevisionDecisionNextAction } from "./long-task-authority-revision-brief.js";
|
|
3
4
|
import { projectAuthorityRevisionDecision } from "./long-task-authority-revision-summary.js";
|
|
4
5
|
import { runDeliveryFinalGate } from "./long-task-final-v2.js";
|
|
5
6
|
import { deliveryCompileFreshness } from "./long-task-freshness.js";
|
|
@@ -83,10 +84,10 @@ export async function resumeDeliveryTask(workdir) {
|
|
|
83
84
|
if (active.workdir !== path.resolve(workdir))
|
|
84
85
|
throw new Error("active_task_workdir_mismatch");
|
|
85
86
|
const compiled = active.authority_snapshot;
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
87
|
+
// Status capture runs git write-tree; finish it before git status can refresh
|
|
88
|
+
// the same repository index inside currentGitState.
|
|
89
|
+
const status = await readDeliveryStatusForAuthority(active);
|
|
90
|
+
const git = await currentGitState(compiled.repository_root);
|
|
90
91
|
return {
|
|
91
92
|
schema_version: "long-task-resume-v2",
|
|
92
93
|
task: {
|
|
@@ -312,11 +313,15 @@ function acceptedScopeMessage(workflowStatus, confirmations, targetProfile) {
|
|
|
312
313
|
}
|
|
313
314
|
function nextAction(status) {
|
|
314
315
|
if (status.pending_authority_revision)
|
|
315
|
-
return
|
|
316
|
+
return authorityRevisionDecisionNextAction(status.pending_authority_revision);
|
|
316
317
|
if (status.findings.length)
|
|
317
318
|
return status.findings.at(-1).next_action;
|
|
318
|
-
if (status.ready_outcomes.length)
|
|
319
|
-
|
|
319
|
+
if (status.ready_outcomes.length) {
|
|
320
|
+
const outcome = status.ready_outcomes[0];
|
|
321
|
+
if (status.outcomes[outcome] === "progress_stale")
|
|
322
|
+
return `Targeted evidence for ready Outcome ${outcome} is stale. This is a freshness fact, not an immediate per-edit rerun instruction: coalesce relevant changes, use the cheapest reliable project-owned feedback, and refresh the declared Check before relying on this Outcome or entering Final Gate.`;
|
|
323
|
+
return `Implement and verify ready Outcome: ${outcome}.`;
|
|
324
|
+
}
|
|
320
325
|
return "Create a clean candidate commit; Stop will run the authoritative Live Final Gate.";
|
|
321
326
|
}
|
|
322
327
|
function message(error) {
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { CompiledClaimsV2 } from "./long-task-claims.js";
|
|
2
|
+
import type { DeliveryCheckV2, DeliveryContractV2, DeliveryOutcomeV2, ProductClaimV2 } from "./long-task-delivery-types.js";
|
|
3
|
+
import { type Reporter } from "./long-task-ui-surface-validation.js";
|
|
4
|
+
interface UiDesignBindingContext {
|
|
5
|
+
outcome: DeliveryOutcomeV2;
|
|
6
|
+
binding: DeliveryOutcomeV2["product"]["surface_bindings"][number];
|
|
7
|
+
outcomeClaims: Map<string, ProductClaimV2>;
|
|
8
|
+
claims: CompiledClaimsV2;
|
|
9
|
+
checks: Map<string, DeliveryCheckV2>;
|
|
10
|
+
confirmations: Map<string, DeliveryContractV2["global"]["acceptance"]["external_confirmations"][number]>;
|
|
11
|
+
carriers: string[];
|
|
12
|
+
designTargetKeys: Set<string>;
|
|
13
|
+
designAssertionRefs: Set<string>;
|
|
14
|
+
report?: Reporter;
|
|
15
|
+
}
|
|
16
|
+
export declare function validateUiDesignBinding(context: UiDesignBindingContext): void;
|
|
17
|
+
export {};
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { matchesRepoPattern } from "./long-task-paths.js";
|
|
2
|
+
import { issue, unique, validateCarrierInputs, validateRootJourney, validateTargetLocalClaimProof, } from "./long-task-ui-surface-validation.js";
|
|
3
|
+
export function validateUiDesignBinding(context) {
|
|
4
|
+
validateDesignTargets(context);
|
|
5
|
+
validateBlockers(context);
|
|
6
|
+
}
|
|
7
|
+
function validateDesignTargets({ outcome, binding, outcomeClaims, checks, carriers, designTargetKeys, designAssertionRefs, report, }) {
|
|
8
|
+
for (const target of binding.design_targets) {
|
|
9
|
+
const label = `${outcome.key}:${binding.key}:${target.key}`;
|
|
10
|
+
if (designTargetKeys.has(target.key))
|
|
11
|
+
issue(report, "ui_design_target_key_duplicate", label);
|
|
12
|
+
designTargetKeys.add(target.key);
|
|
13
|
+
const assertionIdentity = `${target.conformance_check_ref}\0${target.conformance_assertion_ref}`;
|
|
14
|
+
if (designAssertionRefs.has(assertionIdentity))
|
|
15
|
+
issue(report, "ui_design_target_assertion_duplicate", label);
|
|
16
|
+
designAssertionRefs.add(assertionIdentity);
|
|
17
|
+
unique(target.verification_method_bindings.map((item) => item.method), "ui_design_target_verification_method_duplicate", label, report);
|
|
18
|
+
unique(target.verification_method_bindings.map((item) => item.assertion_ref), "ui_design_target_verification_assertion_duplicate", label, report);
|
|
19
|
+
if (!target.verification_method_bindings.length)
|
|
20
|
+
issue(report, "ui_design_target_verification_methods_required", label);
|
|
21
|
+
for (const [name, values] of [
|
|
22
|
+
["source_paths", target.source_paths],
|
|
23
|
+
["condition_keys", target.condition_keys],
|
|
24
|
+
["claim_refs", target.claim_refs],
|
|
25
|
+
]) {
|
|
26
|
+
unique(values, `ui_design_target_${name}_duplicate`, label, report);
|
|
27
|
+
if (!values.length)
|
|
28
|
+
issue(report, `ui_design_target_${name}_required`, label);
|
|
29
|
+
}
|
|
30
|
+
for (const claimRef of target.claim_refs) {
|
|
31
|
+
const claim = outcomeClaims.get(claimRef);
|
|
32
|
+
if (!claim ||
|
|
33
|
+
claim.kind !== "control" ||
|
|
34
|
+
!binding.control_refs.some((control) => claimRef.startsWith(`control.${control}.`)))
|
|
35
|
+
issue(report, "ui_design_target_control_claim_required", `${label}:${claimRef}`);
|
|
36
|
+
}
|
|
37
|
+
const check = checks.get(target.conformance_check_ref);
|
|
38
|
+
if (!check) {
|
|
39
|
+
issue(report, "ui_design_target_conformance_check_unknown", `${label}:${target.conformance_check_ref}`);
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
validateRootJourney(binding, check, label, report);
|
|
43
|
+
validateCarrierInputs(check, carriers, label, report);
|
|
44
|
+
validateDesignAssertion(target, check, label, report);
|
|
45
|
+
for (const methodBinding of target.verification_method_bindings) {
|
|
46
|
+
const identity = `${target.conformance_check_ref}\0${methodBinding.assertion_ref}`;
|
|
47
|
+
if (methodBinding.assertion_ref !== target.conformance_assertion_ref &&
|
|
48
|
+
designAssertionRefs.has(identity))
|
|
49
|
+
issue(report, "ui_design_target_assertion_duplicate", `${label}:${methodBinding.assertion_ref}`);
|
|
50
|
+
if (methodBinding.assertion_ref !== target.conformance_assertion_ref)
|
|
51
|
+
designAssertionRefs.add(identity);
|
|
52
|
+
if (!check.positive_assertions.some((item) => item.key === methodBinding.assertion_ref))
|
|
53
|
+
issue(report, "ui_design_target_verification_assertion_unknown", `${label}:${methodBinding.method}:${methodBinding.assertion_ref}`);
|
|
54
|
+
}
|
|
55
|
+
validateDesignFiles(target, check, label, report);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function validateDesignAssertion(target, check, label, report) {
|
|
59
|
+
const assertion = check.positive_assertions.find((item) => item.key === target.conformance_assertion_ref);
|
|
60
|
+
if (!assertion) {
|
|
61
|
+
issue(report, "ui_design_target_conformance_assertion_unknown", `${label}:${target.conformance_assertion_ref}`);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
for (const claimRef of target.claim_refs)
|
|
65
|
+
if (!assertion.claims.includes(claimRef))
|
|
66
|
+
issue(report, "ui_design_target_claim_not_asserted", `${label}:${claimRef}`);
|
|
67
|
+
for (const capability of [
|
|
68
|
+
"design_conformance",
|
|
69
|
+
"interaction_trace",
|
|
70
|
+
"target_runtime",
|
|
71
|
+
])
|
|
72
|
+
if (!assertion.evidence_capabilities.includes(capability))
|
|
73
|
+
issue(report, "ui_design_target_capability_required", `${label}:${capability}`);
|
|
74
|
+
}
|
|
75
|
+
function validateDesignFiles(target, check, label, report) {
|
|
76
|
+
for (const source of target.source_paths)
|
|
77
|
+
if (!check.verification_inputs.some((pattern) => matchesRepoPattern(source, pattern)))
|
|
78
|
+
issue(report, "ui_design_target_verification_input_missing", `${label}:${source}`);
|
|
79
|
+
if (target.actual_artifact_path === target.comparison_artifact_path)
|
|
80
|
+
issue(report, "ui_design_target_artifacts_must_differ", label);
|
|
81
|
+
for (const artifact of [
|
|
82
|
+
target.actual_artifact_path,
|
|
83
|
+
target.comparison_artifact_path,
|
|
84
|
+
])
|
|
85
|
+
if (!check.artifact_globs.some((pattern) => matchesRepoPattern(artifact, pattern)))
|
|
86
|
+
issue(report, "ui_design_target_artifact_glob_missing", `${label}:${artifact}`);
|
|
87
|
+
}
|
|
88
|
+
function validateBlockers(context) {
|
|
89
|
+
const { outcome, binding, outcomeClaims, claims, checks, confirmations, report, } = context;
|
|
90
|
+
const label = `${outcome.key}:${binding.key}`;
|
|
91
|
+
unique(binding.acceptance_blockers.map((item) => item.key), "ui_design_blocker_key_duplicate", label, report);
|
|
92
|
+
for (const blocker of binding.acceptance_blockers) {
|
|
93
|
+
const blockerLabel = `${label}:${blocker.key}`;
|
|
94
|
+
unique(blocker.refs, "ui_design_blocker_ref_duplicate", blockerLabel, report);
|
|
95
|
+
if (!blocker.refs.length)
|
|
96
|
+
issue(report, "ui_design_blocker_ref_required", blockerLabel);
|
|
97
|
+
unique(blocker.source_item_refs, "ui_design_blocker_source_item_duplicate", blockerLabel, report);
|
|
98
|
+
unique(blocker.verification_methods, "ui_design_blocker_verification_method_duplicate", blockerLabel, report);
|
|
99
|
+
if (!blocker.source_item_refs.length)
|
|
100
|
+
issue(report, "ui_design_blocker_source_item_required", blockerLabel);
|
|
101
|
+
if (!blocker.verification_methods.length)
|
|
102
|
+
issue(report, "ui_design_blocker_verification_method_required", blockerLabel);
|
|
103
|
+
if (blocker.status === "machine_claim")
|
|
104
|
+
validateMachineBlocker(context, blocker.refs, blockerLabel);
|
|
105
|
+
if (blocker.status === "external_confirmation")
|
|
106
|
+
validateExternalBlocker(outcome, confirmations, blocker.refs, blockerLabel, report);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
function validateMachineBlocker({ outcome, binding, outcomeClaims, claims, checks, report, }, claimRefs, label) {
|
|
110
|
+
for (const claimRef of claimRefs) {
|
|
111
|
+
const claim = outcomeClaims.get(claimRef);
|
|
112
|
+
if (!claim)
|
|
113
|
+
issue(report, "ui_design_blocker_machine_claim_unknown", `${label}:${claimRef}`);
|
|
114
|
+
else
|
|
115
|
+
validateTargetLocalClaimProof(outcome, claim, binding.target_ref, null, claims, checks, label, report);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
function validateExternalBlocker(outcome, confirmations, confirmationRefs, label, report) {
|
|
119
|
+
for (const confirmationRef of confirmationRefs) {
|
|
120
|
+
const confirmation = confirmations.get(confirmationRef);
|
|
121
|
+
if (!confirmation)
|
|
122
|
+
issue(report, "ui_design_blocker_confirmation_unknown", `${label}:${confirmationRef}`);
|
|
123
|
+
else if (!confirmation.blocks_target)
|
|
124
|
+
issue(report, "ui_design_blocker_confirmation_must_block_target", `${label}:${confirmationRef}`);
|
|
125
|
+
else if (!confirmation.impact_claims.some((claim) => claim.startsWith(`${outcome.key}.`)))
|
|
126
|
+
issue(report, "ui_design_blocker_confirmation_impact_mismatch", `${label}:${confirmationRef}`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { CompiledClaimsV2 } from "./long-task-claims.js";
|
|
2
|
+
import type { DeliveryContractV2 } from "./long-task-delivery-types.js";
|
|
3
|
+
import { type Reporter } from "./long-task-ui-surface-validation.js";
|
|
4
|
+
export declare function validateUiSurfaceBindings(contract: DeliveryContractV2, claims: CompiledClaimsV2, report?: Reporter): void;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { validateUiDesignBinding } from "./long-task-ui-design-policy.js";
|
|
2
|
+
import { bindingCarriers, controlClaims, expectedControlProofSurface, issue, unique, validateCarrierInputs, validateRootJourney, validateTargetLocalClaimProof, } from "./long-task-ui-surface-validation.js";
|
|
3
|
+
export function validateUiSurfaceBindings(contract, claims, report) {
|
|
4
|
+
const targets = new Map(contract.task.execution_targets.map((target) => [target.key, target]));
|
|
5
|
+
const requiredTargets = new Set(contract.task.target_profile.required_target_refs);
|
|
6
|
+
const confirmations = new Map(contract.global.acceptance.external_confirmations.map((item) => [
|
|
7
|
+
item.key,
|
|
8
|
+
item,
|
|
9
|
+
]));
|
|
10
|
+
for (const outcome of contract.outcomes)
|
|
11
|
+
validateOutcomeBindings(outcome, claims, targets, requiredTargets, confirmations, report);
|
|
12
|
+
}
|
|
13
|
+
function validateOutcomeBindings(outcome, claims, targets, requiredTargets, confirmations, report) {
|
|
14
|
+
const surfaceBindings = outcome.product.surface_bindings ?? [];
|
|
15
|
+
if (outcome.product.controls.length && !surfaceBindings.length)
|
|
16
|
+
issue(report, "ui_surface_binding_required", outcome.key);
|
|
17
|
+
if (!outcome.product.controls.length && surfaceBindings.length)
|
|
18
|
+
issue(report, "ui_surface_binding_without_controls", outcome.key);
|
|
19
|
+
unique(surfaceBindings.map((item) => item.key), "ui_surface_binding_key_duplicate", outcome.key, report);
|
|
20
|
+
const controls = new Map(outcome.product.controls.map((control) => [control.key, control]));
|
|
21
|
+
const ownerSurfaces = new Set(outcome.product.owner_surfaces);
|
|
22
|
+
const technicalBindings = new Map(outcome.technical.bindings.map((binding) => [binding.key, binding]));
|
|
23
|
+
const checks = new Map(outcome.acceptance.checks.map((check) => [check.key, check]));
|
|
24
|
+
const outcomeClaims = new Map((claims.by_outcome[outcome.key] ?? []).map((claim) => [
|
|
25
|
+
claim.local_key,
|
|
26
|
+
claim,
|
|
27
|
+
]));
|
|
28
|
+
const boundControls = new Set();
|
|
29
|
+
const boundControlTargets = new Set();
|
|
30
|
+
const designTargetKeys = new Set();
|
|
31
|
+
const designAssertionRefs = new Set();
|
|
32
|
+
for (const binding of surfaceBindings) {
|
|
33
|
+
const label = `${outcome.key}:${binding.key}`;
|
|
34
|
+
if (!ownerSurfaces.has(binding.surface_ref))
|
|
35
|
+
issue(report, "ui_surface_binding_owner_surface_unknown", `${label}:${binding.surface_ref}`);
|
|
36
|
+
const target = targets.get(binding.target_ref);
|
|
37
|
+
if (!target || target.role !== "product")
|
|
38
|
+
issue(report, "ui_surface_binding_product_target_required", `${label}:${binding.target_ref}`);
|
|
39
|
+
if (!requiredTargets.has(binding.target_ref))
|
|
40
|
+
issue(report, "ui_surface_binding_required_target_required", `${label}:${binding.target_ref}`);
|
|
41
|
+
validateBoundControls(binding, controls, boundControls, boundControlTargets, label, report);
|
|
42
|
+
const carriers = bindingCarriers(binding.route_binding_ref, binding.component_binding_refs, technicalBindings, label, report);
|
|
43
|
+
const rootCheck = checks.get(binding.root_journey_check_ref);
|
|
44
|
+
if (!rootCheck)
|
|
45
|
+
issue(report, "ui_surface_binding_root_check_unknown", `${label}:${binding.root_journey_check_ref}`);
|
|
46
|
+
else {
|
|
47
|
+
validateRootJourney(binding, rootCheck, label, report);
|
|
48
|
+
validateCarrierInputs(rootCheck, carriers, label, report);
|
|
49
|
+
validateRootBoundControlAssertions(binding, rootCheck, controls, label, report);
|
|
50
|
+
}
|
|
51
|
+
for (const controlRef of binding.control_refs)
|
|
52
|
+
for (const claim of controlClaims(outcomeClaims, controlRef))
|
|
53
|
+
validateTargetLocalClaimProof(outcome, claim, binding.target_ref, expectedControlProofSurface(target?.runtime_family), claims, checks, label, report);
|
|
54
|
+
validateUiDesignBinding({
|
|
55
|
+
outcome,
|
|
56
|
+
binding,
|
|
57
|
+
outcomeClaims,
|
|
58
|
+
claims,
|
|
59
|
+
checks,
|
|
60
|
+
confirmations,
|
|
61
|
+
carriers,
|
|
62
|
+
designTargetKeys,
|
|
63
|
+
designAssertionRefs,
|
|
64
|
+
report,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
for (const control of outcome.product.controls)
|
|
68
|
+
if (!boundControls.has(control.key))
|
|
69
|
+
issue(report, "ui_surface_binding_control_unbound", `${outcome.key}:${control.key}`);
|
|
70
|
+
}
|
|
71
|
+
function validateBoundControls(binding, controls, boundControls, boundControlTargets, label, report) {
|
|
72
|
+
unique(binding.control_refs, "ui_surface_binding_control_ref_duplicate", label, report);
|
|
73
|
+
if (!binding.control_refs.length)
|
|
74
|
+
issue(report, "ui_surface_binding_control_ref_required", label);
|
|
75
|
+
for (const controlRef of binding.control_refs) {
|
|
76
|
+
const control = controls.get(controlRef);
|
|
77
|
+
if (!control)
|
|
78
|
+
issue(report, "ui_surface_binding_control_unknown", `${label}:${controlRef}`);
|
|
79
|
+
else if (control.surface.trim() && control.surface !== binding.surface_ref)
|
|
80
|
+
issue(report, "ui_surface_binding_control_surface_mismatch", `${label}:${controlRef}:${control.surface}`);
|
|
81
|
+
const identity = `${controlRef}\0${binding.target_ref}`;
|
|
82
|
+
if (boundControlTargets.has(identity))
|
|
83
|
+
issue(report, "ui_surface_binding_control_target_duplicate", `${label}:${controlRef}:${binding.target_ref}`);
|
|
84
|
+
boundControlTargets.add(identity);
|
|
85
|
+
boundControls.add(controlRef);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
function validateRootBoundControlAssertions(binding, check, controls, label, report) {
|
|
89
|
+
for (const controlRef of binding.control_refs) {
|
|
90
|
+
const control = controls.get(controlRef);
|
|
91
|
+
if (!control)
|
|
92
|
+
continue;
|
|
93
|
+
const claimRef = rootJourneyClaimRef(control);
|
|
94
|
+
if (!check.positive_assertions.some((assertion) => assertion.claims.includes(claimRef) &&
|
|
95
|
+
assertion.evidence_capabilities.includes("interaction_trace") &&
|
|
96
|
+
assertion.evidence_capabilities.includes("target_runtime")))
|
|
97
|
+
issue(report, "ui_surface_binding_root_control_proof_missing", `${label}:${controlRef}:${claimRef}`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
function rootJourneyClaimRef(control) {
|
|
101
|
+
const field = control.navigation_result?.trim()
|
|
102
|
+
? "navigation_result"
|
|
103
|
+
: control.interaction?.trim()
|
|
104
|
+
? "interaction"
|
|
105
|
+
: control.trigger?.trim()
|
|
106
|
+
? "trigger"
|
|
107
|
+
: "location";
|
|
108
|
+
return `control.${control.key}.${field}`;
|
|
109
|
+
}
|