project-tiny-context-harness 0.8.0 → 0.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +40 -32
- package/assets/README.md +141 -60
- package/assets/README.zh-CN.md +48 -40
- package/assets/agents/AGENTS_CORE.md +16 -12
- package/assets/context_templates/architecture.md +5 -5
- package/assets/context_templates/area.md +5 -5
- package/assets/context_templates/global.md +6 -6
- package/assets/context_templates/screen-contract.md +18 -18
- package/assets/github/harness.yml +2 -2
- package/assets/skills/context_development_engineer/SKILL.md +7 -5
- package/assets/skills/context_full_project_export/SKILL.md +42 -42
- package/assets/skills/context_uiux_design/SKILL.md +27 -13
- package/assets/skills/design-resource-authoring/SKILL.md +7 -7
- package/assets/skills/design-resource-authoring/references/downstream-handoff.md +12 -12
- package/assets/skills/design-resource-authoring/references/open-design-provider.md +21 -21
- package/assets/skills/design-resource-authoring/references/resource-selection.md +3 -3
- package/assets/skills/long-task-workflow/SKILL.md +17 -17
- package/assets/skills/long-task-workflow/agents/openai.yaml +4 -4
- package/assets/skills/long-task-workflow/references/authority-lifecycle.md +11 -9
- package/assets/skills/long-task-workflow/references/contract-authoring.md +19 -15
- package/assets/skills/long-task-workflow/references/evidence-design.md +18 -17
- package/assets/skills/long-task-workflow/references/source-authoring.md +15 -12
- package/assets/skills/normal-long-task/SKILL.md +12 -12
- package/dist/commands/design-resource.js +2 -2
- package/dist/commands/index.js +26 -26
- package/dist/commands/long-task-authoring.js +83 -9
- package/dist/commands/long-task.js +15 -15
- package/dist/lib/context-graph-snapshot.js +7 -0
- package/dist/lib/design-md.js +5 -4
- package/dist/lib/design-resource-handoff-shape-evidence.js +3 -0
- package/dist/lib/design-resource-handoff-shape-primitives.js +1 -3
- package/dist/lib/design-resource-handoff-types.d.ts +2 -0
- package/dist/lib/design-resource-handoff-validation-coverage.js +1 -0
- package/dist/lib/design-resource-handoff-validation-structure.js +2 -0
- package/dist/lib/doctor.js +2 -2
- package/dist/lib/execution-target-capabilities.d.ts +2 -0
- package/dist/lib/execution-target-capabilities.js +31 -0
- package/dist/lib/long-task-acceptance-shape.js +81 -20
- package/dist/lib/long-task-applicability-shape.d.ts +4 -0
- package/dist/lib/long-task-applicability-shape.js +54 -0
- package/dist/lib/long-task-assertions-v2.js +8 -0
- package/dist/lib/long-task-authoring-preflight-diagnostics.js +5 -0
- package/dist/lib/long-task-authoring-preflight-repair-order.js +3 -0
- package/dist/lib/long-task-authoring-preflight.js +12 -8
- package/dist/lib/long-task-authority-materials.js +46 -4
- package/dist/lib/long-task-authority-policy.d.ts +54 -0
- package/dist/lib/long-task-authority-policy.js +61 -0
- package/dist/lib/long-task-authority-types.d.ts +12 -2
- package/dist/lib/long-task-authority.js +9 -2
- package/dist/lib/long-task-check-shape.js +6 -1
- package/dist/lib/long-task-claim-definitions.js +27 -42
- package/dist/lib/long-task-claims.js +275 -146
- package/dist/lib/long-task-contract-types.d.ts +42 -8
- package/dist/lib/long-task-control-fields.d.ts +12 -0
- package/dist/lib/long-task-control-fields.js +66 -0
- package/dist/lib/long-task-control-types.d.ts +1 -0
- package/dist/lib/long-task-control-types.js +1 -0
- package/dist/lib/long-task-counterfactual-claim-policy.js +7 -0
- package/dist/lib/long-task-counterfactual-sandbox.js +1 -2
- package/dist/lib/long-task-counterfactual-types.d.ts +12 -0
- package/dist/lib/long-task-delivery-compiler.js +1 -0
- package/dist/lib/long-task-delivery-preflight.js +24 -0
- package/dist/lib/long-task-delivery-shape.d.ts +1 -0
- package/dist/lib/long-task-delivery-shape.js +1 -0
- 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 +46 -0
- package/dist/lib/long-task-design-resource-handoff.js +48 -7
- package/dist/lib/long-task-evidence-capability-codec.js +37 -0
- package/dist/lib/long-task-evidence-capability-policy.js +80 -38
- package/dist/lib/long-task-evidence-capability-runtime.js +36 -0
- package/dist/lib/long-task-evidence-capability-types.d.ts +13 -1
- package/dist/lib/long-task-evidence-findings.js +3 -0
- package/dist/lib/long-task-evidence-sensitivity-policy.js +38 -0
- package/dist/lib/long-task-evidence-v2.js +7 -1
- package/dist/lib/long-task-final-integrity.d.ts +16 -0
- package/dist/lib/long-task-final-integrity.js +98 -0
- package/dist/lib/long-task-final-v2.js +17 -34
- package/dist/lib/long-task-outcome-parser.js +22 -4
- package/dist/lib/long-task-playwright-case-evidence.d.ts +2 -0
- package/dist/lib/long-task-playwright-case-evidence.js +10 -2
- package/dist/lib/long-task-playwright-evidence.d.ts +1 -0
- package/dist/lib/long-task-playwright-evidence.js +32 -0
- package/dist/lib/long-task-product-shape.d.ts +3 -1
- package/dist/lib/long-task-product-shape.js +100 -4
- package/dist/lib/long-task-requirement-shape.js +3 -0
- package/dist/lib/long-task-root-shape.js +12 -6
- package/dist/lib/long-task-runner-freeze.js +16 -0
- package/dist/lib/long-task-runtime-types.d.ts +11 -2
- package/dist/lib/long-task-semantic-assurance-policy.d.ts +4 -0
- package/dist/lib/long-task-semantic-assurance-policy.js +52 -0
- package/dist/lib/long-task-semantic-contract-types.d.ts +18 -1
- package/dist/lib/long-task-semantic-drift-migration.js +33 -2
- package/dist/lib/long-task-semantic-mutation.d.ts +7 -0
- package/dist/lib/long-task-semantic-mutation.js +81 -0
- 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-source-authority-types.d.ts +2 -0
- package/dist/lib/long-task-source-item-parser.d.ts +1 -1
- package/dist/lib/long-task-source-item-parser.js +150 -104
- package/dist/lib/long-task-source-markers.d.ts +12 -0
- package/dist/lib/long-task-source-markers.js +146 -0
- package/dist/lib/long-task-source-target-continuity.d.ts +1 -1
- package/dist/lib/long-task-source-target-continuity.js +80 -55
- package/dist/lib/long-task-source-target-index.js +7 -29
- package/dist/lib/long-task-status-v2.d.ts +1 -0
- package/dist/lib/long-task-status-v2.js +2 -2
- package/dist/lib/long-task-target-policy.js +20 -0
- package/dist/lib/long-task-ui-design-policy.js +27 -1
- package/dist/lib/long-task-ui-surface-shape.js +21 -1
- package/dist/lib/long-task-ui-surface-types.d.ts +7 -0
- package/dist/lib/long-task-verification-preview.d.ts +10 -0
- package/dist/lib/long-task-verifier-dependency-closure.d.ts +3 -0
- package/dist/lib/long-task-verifier-dependency-closure.js +179 -0
- package/dist/lib/long-task-verifier-v2.js +12 -2
- package/dist/lib/long-task-workspace.js +5 -1
- package/dist/schemas/long-task-delivery-v2/long-task-delivery-v2.schema.json +99 -25
- package/migrations/README.md +8 -8
- package/package.json +84 -84
|
@@ -1,18 +1,21 @@
|
|
|
1
1
|
const NON_RESULT_SENSITIVITY_PREFIXES = [
|
|
2
2
|
"requirement.",
|
|
3
3
|
"control.",
|
|
4
|
+
"control_relation",
|
|
4
5
|
"obligation.",
|
|
5
6
|
"non_completing.",
|
|
6
7
|
"forbidden_shortcut.",
|
|
7
8
|
];
|
|
8
9
|
export function validateClaimEvidenceSensitivity(contract, globalChecks, outcomes, report) {
|
|
9
10
|
validateGlobalStructuredSensitivity(contract, globalChecks, report);
|
|
11
|
+
validateGlobalSemanticWitnesses(contract, globalChecks, report);
|
|
10
12
|
const weakOutcomes = new Set(contract.risk.facts.weak_observability);
|
|
11
13
|
for (const outcome of outcomes) {
|
|
12
14
|
const population = outcome.acceptance.population;
|
|
13
15
|
const counterfactualsByCheck = groupCounterfactuals(outcome);
|
|
14
16
|
for (const check of outcome.acceptance.checks) {
|
|
15
17
|
const counterfactuals = counterfactualsByCheck.get(check.key) ?? [];
|
|
18
|
+
validateBehavioralSemanticWitnesses(outcome.key, check, counterfactuals, report);
|
|
16
19
|
if (check.evidence_adapter === "structured_json_v2") {
|
|
17
20
|
const assertionClaims = new Set(assertions(check).flatMap((assertion) => assertion.claims));
|
|
18
21
|
const populationClaims = new Set(population?.check_key === check.key ? population.claims : []);
|
|
@@ -35,6 +38,41 @@ export function validateClaimEvidenceSensitivity(contract, globalChecks, outcome
|
|
|
35
38
|
}
|
|
36
39
|
}
|
|
37
40
|
}
|
|
41
|
+
function validateGlobalSemanticWitnesses(contract, checks, report) {
|
|
42
|
+
for (const check of checks)
|
|
43
|
+
validateBehavioralSemanticWitnesses("GLOBAL", check, contract.global.acceptance.counterfactual_controls.filter((control) => control.check_key === check.key), report);
|
|
44
|
+
}
|
|
45
|
+
function validateBehavioralSemanticWitnesses(scope, check, counterfactuals, report) {
|
|
46
|
+
for (const assertion of assertions(check)) {
|
|
47
|
+
if (!assertion.claims.length || !isBehavioralAssertion(check, assertion))
|
|
48
|
+
continue;
|
|
49
|
+
const witnesses = counterfactuals.filter((control) => isNarrowSemanticMutation(control.mutation.type) &&
|
|
50
|
+
control.expected_assertion_failures.includes(assertion.key) &&
|
|
51
|
+
assertion.claims.every((claim) => control.claims.includes(claim)));
|
|
52
|
+
if (!witnesses.length) {
|
|
53
|
+
issue(report, `behavioral_semantic_counterfactual_required:${scope}:${check.key}:${assertion.key}`);
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
if (!witnesses.some((control) => control.preserved_assertions.some((reference) => isLivenessAssertion(check, reference))))
|
|
57
|
+
issue(report, `behavioral_counterfactual_liveness_witness_required:${scope}:${check.key}:${assertion.key}`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
function isNarrowSemanticMutation(type) {
|
|
61
|
+
return type === "replace_json_value" || type === "replace_text";
|
|
62
|
+
}
|
|
63
|
+
function isBehavioralAssertion(check, assertion) {
|
|
64
|
+
return !(check.proof_surface === "implementation_structure" &&
|
|
65
|
+
assertion.operator === "exists" &&
|
|
66
|
+
assertion.evidence_capabilities.every((capability) => capability === "presence"));
|
|
67
|
+
}
|
|
68
|
+
function isLivenessAssertion(check, reference) {
|
|
69
|
+
const assertion = check.positive_assertions.find((candidate) => candidate.key === reference);
|
|
70
|
+
return Boolean(assertion &&
|
|
71
|
+
!assertion.claims.length &&
|
|
72
|
+
assertion.operator === "equals" &&
|
|
73
|
+
assertion.expected === true &&
|
|
74
|
+
assertion.evidence_capabilities.includes("target_runtime"));
|
|
75
|
+
}
|
|
38
76
|
function validateGlobalStructuredSensitivity(contract, checks, report) {
|
|
39
77
|
for (const check of checks) {
|
|
40
78
|
if (check.evidence_adapter !== "structured_json_v2")
|
|
@@ -5,6 +5,7 @@ import { executeCheckRunner } from "./long-task-check-runner.js";
|
|
|
5
5
|
import { createCounterfactualSandbox } from "./long-task-counterfactual-sandbox.js";
|
|
6
6
|
import { assertionFinding, checkFinding, classifyCheckStatus, evaluateAssertionResults, } from "./long-task-evidence-findings.js";
|
|
7
7
|
import { classifyPlaywrightCounterfactual } from "./long-task-playwright-counterfactual-policy.js";
|
|
8
|
+
import { applyNarrowSemanticMutation } from "./long-task-semantic-mutation.js";
|
|
8
9
|
import { evaluateEvidenceCapabilities } from "./long-task-evidence-capability-policy.js";
|
|
9
10
|
import { resolveInsideRepository } from "./long-task-workspace.js";
|
|
10
11
|
export async function evaluateCheckEvidence(check, raw, snapshotRoot, outcome) {
|
|
@@ -33,6 +34,7 @@ export async function evaluateCheckEvidence(check, raw, snapshotRoot, outcome) {
|
|
|
33
34
|
assertion_key: result.key,
|
|
34
35
|
polarity: result.polarity,
|
|
35
36
|
proof_surface: check.proof_surface,
|
|
37
|
+
applicability_ref: result.applicability_ref ?? null,
|
|
36
38
|
}))
|
|
37
39
|
: [])
|
|
38
40
|
: [];
|
|
@@ -45,6 +47,7 @@ export async function evaluateCheckEvidence(check, raw, snapshotRoot, outcome) {
|
|
|
45
47
|
assertion_key: null,
|
|
46
48
|
polarity: "population",
|
|
47
49
|
proof_surface: check.proof_surface,
|
|
50
|
+
applicability_ref: null,
|
|
48
51
|
})));
|
|
49
52
|
return {
|
|
50
53
|
internal_id: check.internal_id,
|
|
@@ -141,9 +144,12 @@ async function evaluateCounterfactualSet(entries, snapshotRoot, manifest) {
|
|
|
141
144
|
for (const target of control.mutation.paths)
|
|
142
145
|
await rm(resolveInsideRepository(root, target, "counterfactual.remove_paths"), { recursive: true, force: true });
|
|
143
146
|
}
|
|
144
|
-
else {
|
|
147
|
+
else if (control.mutation.type === "replace_file") {
|
|
145
148
|
await copyFile(resolveInsideRepository(root, control.mutation.fixture_path, "counterfactual.fixture"), resolveInsideRepository(root, control.mutation.path, "counterfactual.path"));
|
|
146
149
|
}
|
|
150
|
+
else {
|
|
151
|
+
await applyNarrowSemanticMutation(root, control.mutation);
|
|
152
|
+
}
|
|
147
153
|
const raw = await executeCheckRunner(check, root);
|
|
148
154
|
const result = await evaluateCheckEvidence(check, raw, root, entry.evidenceOutcome);
|
|
149
155
|
const playwrightClassification = result.evidence_adapter === "playwright_json_v1"
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { CompiledDeliveryContractV2, LongTaskFindingV2 } from "./long-task-delivery-types.js";
|
|
2
|
+
import { type ActiveAuthorityIdentityExpectation, type ActiveLongTaskAuthorityV3 } from "./long-task-state.js";
|
|
3
|
+
import { currentGitState } from "./long-task-workspace.js";
|
|
4
|
+
export interface FinalGateProtectedInputIdentity {
|
|
5
|
+
compiled_identity: string;
|
|
6
|
+
raw_contract_sha256: string;
|
|
7
|
+
}
|
|
8
|
+
export declare function captureFinalGateProtectedInputIdentity(repository: string, compiled: CompiledDeliveryContractV2): Promise<FinalGateProtectedInputIdentity>;
|
|
9
|
+
export declare function finalGateIntegrityFindings(input: {
|
|
10
|
+
repository: string;
|
|
11
|
+
active: ActiveLongTaskAuthorityV3;
|
|
12
|
+
accepted_authority: ActiveAuthorityIdentityExpectation;
|
|
13
|
+
candidate: Awaited<ReturnType<typeof currentGitState>>;
|
|
14
|
+
workspace_identity_before: string;
|
|
15
|
+
protected_inputs_before: FinalGateProtectedInputIdentity;
|
|
16
|
+
}): Promise<LongTaskFindingV2[]>;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { compileDeliveryContract } from "./long-task-delivery-compiler.js";
|
|
4
|
+
import { assertProtectedRepositoryFile } from "./long-task-protected-files.js";
|
|
5
|
+
import { activeAuthorityIdentityMatches, assertMatchingActiveBinding, loadActiveLongTaskAuthority, } from "./long-task-state.js";
|
|
6
|
+
import { captureWorkspaceFingerprint, currentGitState, } from "./long-task-workspace.js";
|
|
7
|
+
import { sha256Hex } from "./strict-codec.js";
|
|
8
|
+
export async function captureFinalGateProtectedInputIdentity(repository, compiled) {
|
|
9
|
+
const file = await assertProtectedRepositoryFile(repository, path.join(repository, ...compiled.contract_file.split("/")), "final_gate_delivery_contract");
|
|
10
|
+
return {
|
|
11
|
+
compiled_identity: compiled.compiled_identity,
|
|
12
|
+
raw_contract_sha256: sha256Hex(await readFile(file)),
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
export async function finalGateIntegrityFindings(input) {
|
|
16
|
+
const findings = [];
|
|
17
|
+
const after = await captureWorkspaceFingerprint(input.repository);
|
|
18
|
+
const gitAfter = await currentGitState(input.repository);
|
|
19
|
+
if (after.identity !== input.workspace_identity_before ||
|
|
20
|
+
gitAfter.head !== input.candidate.head ||
|
|
21
|
+
gitAfter.tree !== input.candidate.tree ||
|
|
22
|
+
gitAfter.dirty.length)
|
|
23
|
+
findings.push(workspaceChangedFinding());
|
|
24
|
+
if (await activeAuthorityChanged(input))
|
|
25
|
+
findings.push(activeAuthorityChangedFinding());
|
|
26
|
+
findings.push(...(await protectedInputFindings(input)));
|
|
27
|
+
return findings;
|
|
28
|
+
}
|
|
29
|
+
async function activeAuthorityChanged(input) {
|
|
30
|
+
try {
|
|
31
|
+
const currentActive = (await loadActiveLongTaskAuthority(input.repository))
|
|
32
|
+
.authority;
|
|
33
|
+
return (!currentActive ||
|
|
34
|
+
!activeAuthorityIdentityMatches(currentActive, input.accepted_authority));
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
async function protectedInputFindings(input) {
|
|
41
|
+
try {
|
|
42
|
+
const refreshed = await compileDeliveryContract(input.active.workdir, input.repository, {
|
|
43
|
+
live_gate: true,
|
|
44
|
+
initial_task_base: input.active.initial_task_base,
|
|
45
|
+
authority_revision: input.active.authority_revision,
|
|
46
|
+
require_completion_gate: true,
|
|
47
|
+
});
|
|
48
|
+
await assertMatchingActiveBinding(refreshed);
|
|
49
|
+
const after = await captureFinalGateProtectedInputIdentity(input.repository, refreshed);
|
|
50
|
+
return after.compiled_identity !==
|
|
51
|
+
input.protected_inputs_before.compiled_identity ||
|
|
52
|
+
after.raw_contract_sha256 !==
|
|
53
|
+
input.protected_inputs_before.raw_contract_sha256
|
|
54
|
+
? [
|
|
55
|
+
protectedInputsChangedFinding({
|
|
56
|
+
before: input.protected_inputs_before,
|
|
57
|
+
after,
|
|
58
|
+
}),
|
|
59
|
+
]
|
|
60
|
+
: [];
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
return [
|
|
64
|
+
protectedInputsChangedFinding({
|
|
65
|
+
before: input.protected_inputs_before,
|
|
66
|
+
after_error: error instanceof Error ? error.message : String(error),
|
|
67
|
+
}),
|
|
68
|
+
];
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
function workspaceChangedFinding() {
|
|
72
|
+
return {
|
|
73
|
+
code: "workspace_changed_during_final_gate",
|
|
74
|
+
outcome_key: null,
|
|
75
|
+
check_key: null,
|
|
76
|
+
message: "The workspace changed while Live Final Gate was running.",
|
|
77
|
+
next_action: "Stop concurrent mutation and rerun the complete Live Final Gate.",
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
function activeAuthorityChangedFinding() {
|
|
81
|
+
return {
|
|
82
|
+
code: "active_authority_changed_during_final_gate",
|
|
83
|
+
outcome_key: null,
|
|
84
|
+
check_key: null,
|
|
85
|
+
message: "Active Authority changed while Live Final Gate was running.",
|
|
86
|
+
next_action: "Review the new Authority Revision and rerun the complete Live Final Gate.",
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
function protectedInputsChangedFinding(actual) {
|
|
90
|
+
return {
|
|
91
|
+
code: "protected_inputs_changed_during_final_gate",
|
|
92
|
+
outcome_key: null,
|
|
93
|
+
check_key: null,
|
|
94
|
+
message: "Protected Contract, Source, controlling Context, verifier/runner, verification-input or workdir input identity changed while Final Gate was running.",
|
|
95
|
+
actual,
|
|
96
|
+
next_action: "Stop concurrent protected-input mutation, review any required Authority Revision, and rerun the complete Final Gate.",
|
|
97
|
+
};
|
|
98
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { compileDeliveryContract } from "./long-task-delivery-compiler.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { captureFinalGateProtectedInputIdentity, finalGateIntegrityFindings, } from "./long-task-final-integrity.js";
|
|
3
|
+
import { assertMatchingActiveBinding, loadActiveLongTaskAuthority, writeFinalReceipt, } from "./long-task-state.js";
|
|
4
|
+
import { assertVerifierAuthorityCurrent, deliveryCompileFreshness, } from "./long-task-freshness.js";
|
|
4
5
|
import { allCompiledChecks, runDeliveryChecks, } from "./long-task-verifier-v2.js";
|
|
5
6
|
import { captureWorkspaceFingerprint, createWorkspaceSnapshot, currentGitState, repositoryRoot, } from "./long-task-workspace.js";
|
|
6
7
|
export async function runDeliveryFinalGate(workdirInput) {
|
|
@@ -21,6 +22,11 @@ export async function runDeliveryFinalGate(workdirInput) {
|
|
|
21
22
|
const candidate = await currentGitState(repository);
|
|
22
23
|
if (candidate.dirty.length)
|
|
23
24
|
throw new Error(`final_gate_requires_clean_candidate_commit:${candidate.dirty.join(",")}`);
|
|
25
|
+
const acceptedCompiled = active.authority_snapshot;
|
|
26
|
+
await assertMatchingActiveBinding(acceptedCompiled);
|
|
27
|
+
const staleAuthorityInputs = await deliveryCompileFreshness(acceptedCompiled);
|
|
28
|
+
if (staleAuthorityInputs.length)
|
|
29
|
+
throw new Error(`final_gate_protected_input_stale:${staleAuthorityInputs.join(",")}`);
|
|
24
30
|
const compiled = await compileDeliveryContract(active.workdir, repository, {
|
|
25
31
|
live_gate: true,
|
|
26
32
|
initial_task_base: active.initial_task_base,
|
|
@@ -28,42 +34,19 @@ export async function runDeliveryFinalGate(workdirInput) {
|
|
|
28
34
|
require_completion_gate: true,
|
|
29
35
|
});
|
|
30
36
|
await assertMatchingActiveBinding(compiled);
|
|
37
|
+
const protectedInputsBefore = await captureFinalGateProtectedInputIdentity(repository, compiled);
|
|
31
38
|
const before = await captureWorkspaceFingerprint(repository);
|
|
32
39
|
const snapshot = await createWorkspaceSnapshot(repository, active.workdir, `final-${compiled.task.id}`);
|
|
33
40
|
try {
|
|
34
41
|
const run = await runDeliveryChecks(compiled, snapshot, allCompiledChecks(compiled), true, true);
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
outcome_key: null,
|
|
44
|
-
check_key: null,
|
|
45
|
-
message: "The workspace changed while Live Final Gate was running.",
|
|
46
|
-
next_action: "Stop concurrent mutation and rerun the complete Live Final Gate.",
|
|
47
|
-
});
|
|
48
|
-
let activeAuthorityChanged = false;
|
|
49
|
-
try {
|
|
50
|
-
const currentActive = (await loadActiveLongTaskAuthority(repository))
|
|
51
|
-
.authority;
|
|
52
|
-
activeAuthorityChanged =
|
|
53
|
-
!currentActive ||
|
|
54
|
-
!activeAuthorityIdentityMatches(currentActive, acceptedAuthority);
|
|
55
|
-
}
|
|
56
|
-
catch {
|
|
57
|
-
activeAuthorityChanged = true;
|
|
58
|
-
}
|
|
59
|
-
if (activeAuthorityChanged)
|
|
60
|
-
run.findings.push({
|
|
61
|
-
code: "active_authority_changed_during_final_gate",
|
|
62
|
-
outcome_key: null,
|
|
63
|
-
check_key: null,
|
|
64
|
-
message: "Active Authority changed while Live Final Gate was running.",
|
|
65
|
-
next_action: "Review the new Authority Revision and rerun the complete Live Final Gate.",
|
|
66
|
-
});
|
|
42
|
+
run.findings.push(...(await finalGateIntegrityFindings({
|
|
43
|
+
repository,
|
|
44
|
+
active,
|
|
45
|
+
accepted_authority: acceptedAuthority,
|
|
46
|
+
candidate,
|
|
47
|
+
workspace_identity_before: before.identity,
|
|
48
|
+
protected_inputs_before: protectedInputsBefore,
|
|
49
|
+
})));
|
|
67
50
|
const outcomeResults = projectOutcomes(compiled.outcomes.map((outcome) => outcome.key), run.check_results, run.findings);
|
|
68
51
|
const globalChecks = run.check_results.filter((check) => check.outcome_key === null);
|
|
69
52
|
const globalHardFailure = globalChecks.some((check) => check.status !== "passed" && check.status !== "blocked_external");
|
|
@@ -1,22 +1,33 @@
|
|
|
1
1
|
import { parseCheck } from "./long-task-check-shape.js";
|
|
2
2
|
import { normalizeRepositoryFile } from "./long-task-paths.js";
|
|
3
3
|
import { parseRequirements } from "./long-task-requirement-shape.js";
|
|
4
|
-
import { array, boolean, key, literal, nullable, object, parseBindings, parseControls, parseCounterfactuals,
|
|
4
|
+
import { array, boolean, key, literal, nullable, object, parseBindings, parseControls, parseCounterfactuals, parseApplicableKeyedStatements, parseClaimApplicability, parseControlRelationClosure, parseControlRelations, parseObligations, parseOwner, parsePopulation, parseRollback, repositoryPatterns, string, strings, } from "./long-task-delivery-shape.js";
|
|
5
5
|
import { parseStrictYaml } from "./strict-codec.js";
|
|
6
6
|
import { assertNoSemanticDriftMigration, semanticDriftOutcomeMigrationFields, } from "./long-task-semantic-drift-migration.js";
|
|
7
7
|
import { parseSurfaceBindings } from "./long-task-ui-surface-shape.js";
|
|
8
8
|
export function parseOutcome(value, label) {
|
|
9
9
|
assertNoSemanticDriftMigration(semanticDriftOutcomeMigrationFields(value, label));
|
|
10
|
-
const row = object(value, label, [
|
|
10
|
+
const row = object(value, label, [
|
|
11
|
+
"key",
|
|
12
|
+
"title",
|
|
13
|
+
"stage",
|
|
14
|
+
"applicability",
|
|
15
|
+
"product",
|
|
16
|
+
"technical",
|
|
17
|
+
"acceptance",
|
|
18
|
+
], ["depends_on"]);
|
|
11
19
|
const product = object(row.product, `${label}.product`, [
|
|
12
20
|
"observable_result",
|
|
21
|
+
"result_applicability_refs",
|
|
13
22
|
"success_path_required",
|
|
14
23
|
"degradation_path_required",
|
|
15
24
|
"owner",
|
|
25
|
+
"control_relation_closure",
|
|
16
26
|
], [
|
|
17
27
|
"requirements",
|
|
18
28
|
"owner_surfaces",
|
|
19
29
|
"controls",
|
|
30
|
+
"control_relations",
|
|
20
31
|
"surface_bindings",
|
|
21
32
|
"non_completing_outcomes",
|
|
22
33
|
]);
|
|
@@ -36,8 +47,10 @@ export function parseOutcome(value, label) {
|
|
|
36
47
|
depends_on: Object.hasOwn(row, "depends_on")
|
|
37
48
|
? strings(row.depends_on, `${label}.depends_on`).map((item, index) => key(item, `${label}.depends_on[${index}]`))
|
|
38
49
|
: [],
|
|
50
|
+
applicability: parseClaimApplicability(row.applicability, `${label}.applicability`),
|
|
39
51
|
product: {
|
|
40
52
|
observable_result: string(product.observable_result, `${label}.product.observable_result`),
|
|
53
|
+
result_applicability_refs: strings(product.result_applicability_refs, `${label}.product.result_applicability_refs`).map((item, index) => key(item, `${label}.product.result_applicability_refs[${index}]`)),
|
|
41
54
|
success_path_required: boolean(product.success_path_required, `${label}.product.success_path_required`),
|
|
42
55
|
degradation_path_required: boolean(product.degradation_path_required, `${label}.product.degradation_path_required`),
|
|
43
56
|
owner: parseOwner(product.owner, `${label}.product.owner`),
|
|
@@ -50,11 +63,15 @@ export function parseOutcome(value, label) {
|
|
|
50
63
|
controls: Object.hasOwn(product, "controls")
|
|
51
64
|
? parseControls(product.controls, `${label}.product.controls`)
|
|
52
65
|
: [],
|
|
66
|
+
control_relation_closure: parseControlRelationClosure(product.control_relation_closure, `${label}.product.control_relation_closure`),
|
|
67
|
+
control_relations: Object.hasOwn(product, "control_relations")
|
|
68
|
+
? parseControlRelations(product.control_relations, `${label}.product.control_relations`)
|
|
69
|
+
: [],
|
|
53
70
|
surface_bindings: Object.hasOwn(product, "surface_bindings")
|
|
54
71
|
? parseSurfaceBindings(product.surface_bindings, `${label}.product.surface_bindings`)
|
|
55
72
|
: [],
|
|
56
73
|
non_completing_outcomes: Object.hasOwn(product, "non_completing_outcomes")
|
|
57
|
-
?
|
|
74
|
+
? parseApplicableKeyedStatements(product.non_completing_outcomes, `${label}.product.non_completing_outcomes`)
|
|
58
75
|
: [],
|
|
59
76
|
},
|
|
60
77
|
technical: {
|
|
@@ -69,7 +86,7 @@ export function parseOutcome(value, label) {
|
|
|
69
86
|
? repositoryPatterns(technical.forbidden_paths, `${label}.technical.forbidden_paths`)
|
|
70
87
|
: [],
|
|
71
88
|
forbidden_shortcuts: Object.hasOwn(technical, "forbidden_shortcuts")
|
|
72
|
-
?
|
|
89
|
+
? parseApplicableKeyedStatements(technical.forbidden_shortcuts, `${label}.technical.forbidden_shortcuts`)
|
|
73
90
|
: [],
|
|
74
91
|
bindings: Object.hasOwn(technical, "bindings")
|
|
75
92
|
? parseBindings(technical.bindings, `${label}.technical.bindings`)
|
|
@@ -100,6 +117,7 @@ export function parseOutcomeFragment(raw, relative) {
|
|
|
100
117
|
"title",
|
|
101
118
|
"stage",
|
|
102
119
|
"depends_on",
|
|
120
|
+
"applicability",
|
|
103
121
|
"product",
|
|
104
122
|
"technical",
|
|
105
123
|
"acceptance",
|
|
@@ -12,6 +12,7 @@ export interface PlaywrightCaseInstance {
|
|
|
12
12
|
status: string;
|
|
13
13
|
given_keys: string[];
|
|
14
14
|
action_keys: string[];
|
|
15
|
+
attachment_names: string[];
|
|
15
16
|
}
|
|
16
17
|
export interface PlaywrightCase {
|
|
17
18
|
id: string;
|
|
@@ -31,6 +32,7 @@ export interface PlaywrightCase {
|
|
|
31
32
|
interrupted_instances: number;
|
|
32
33
|
given_keys: string[];
|
|
33
34
|
action_keys: string[];
|
|
35
|
+
attachment_names: string[];
|
|
34
36
|
}
|
|
35
37
|
export declare function collectCases(report: Record<string, unknown>, declaredIds: Set<string>): {
|
|
36
38
|
cases: PlaywrightCaseInstance[];
|
|
@@ -75,6 +75,15 @@ function playwrightCase(id, test) {
|
|
|
75
75
|
resultStatuses.length > 0 &&
|
|
76
76
|
resultStatuses.at(-1) === "passed";
|
|
77
77
|
const traces = results.map((result) => scenarioTrace(result));
|
|
78
|
+
const attachmentNames = [
|
|
79
|
+
...new Set(results.flatMap((result) => Array.isArray(result.attachments)
|
|
80
|
+
? result.attachments
|
|
81
|
+
.map(record)
|
|
82
|
+
.filter(Boolean)
|
|
83
|
+
.map((attachment) => attachment.name)
|
|
84
|
+
.filter((name) => typeof name === "string")
|
|
85
|
+
: [])),
|
|
86
|
+
].sort();
|
|
78
87
|
const givenKeys = traces[0]?.given_keys ?? [];
|
|
79
88
|
const actionKeys = traces[0]?.action_keys ?? [];
|
|
80
89
|
const traceConsistent = traces.every((trace) => same(trace.given_keys, givenKeys) && same(trace.action_keys, actionKeys));
|
|
@@ -91,6 +100,7 @@ function playwrightCase(id, test) {
|
|
|
91
100
|
status,
|
|
92
101
|
given_keys: traceConsistent ? givenKeys : [],
|
|
93
102
|
action_keys: traceConsistent ? actionKeys : [],
|
|
103
|
+
attachment_names: attachmentNames,
|
|
94
104
|
};
|
|
95
105
|
}
|
|
96
106
|
export function declaredCaseIds(check) {
|
|
@@ -99,8 +109,6 @@ export function declaredCaseIds(check) {
|
|
|
99
109
|
...check.positive_assertions,
|
|
100
110
|
...check.negative_assertions,
|
|
101
111
|
]) {
|
|
102
|
-
if (!assertion.claims.length)
|
|
103
|
-
continue;
|
|
104
112
|
const match = /^playwright\.case\.([a-z0-9][a-z0-9-]*)\.passed$/u.exec(assertion.observation);
|
|
105
113
|
if (match)
|
|
106
114
|
result.add(assertion.key);
|
|
@@ -5,3 +5,4 @@ export interface PlaywrightEvidence {
|
|
|
5
5
|
error: string | null;
|
|
6
6
|
}
|
|
7
7
|
export declare function extractPlaywrightEvidence(check: CompiledCheckV2, report: Record<string, unknown>, exitCode: number): PlaywrightEvidence;
|
|
8
|
+
export declare function designMethodAttachmentName(target: string, method: string, condition: string, kind: "record" | "observation"): string;
|
|
@@ -105,6 +105,32 @@ function evidenceRecordsForCases(check, cases) {
|
|
|
105
105
|
comparison_artifact_path: target.comparison_artifact_path,
|
|
106
106
|
});
|
|
107
107
|
}
|
|
108
|
+
if (assertion?.evidence_capabilities.includes("design_method") &&
|
|
109
|
+
item.executed) {
|
|
110
|
+
for (const target of check.design_conformance_targets ?? []) {
|
|
111
|
+
const binding = target.verification_method_bindings.find((candidate) => candidate.assertion_ref === assertion.key);
|
|
112
|
+
if (!binding)
|
|
113
|
+
continue;
|
|
114
|
+
const requiredAttachments = binding.evidence_artifacts.flatMap((artifact) => [
|
|
115
|
+
designMethodAttachmentName(target.key, binding.method, artifact.condition_key, "record"),
|
|
116
|
+
designMethodAttachmentName(target.key, binding.method, artifact.condition_key, "observation"),
|
|
117
|
+
]);
|
|
118
|
+
if (!requiredAttachments.every((name) => item.attachment_names.includes(name)))
|
|
119
|
+
continue;
|
|
120
|
+
evidenceRecords.push({
|
|
121
|
+
assertion_key: assertion.key,
|
|
122
|
+
capability: "design_method",
|
|
123
|
+
design_target_ref: target.key,
|
|
124
|
+
target_ref: target.target_ref,
|
|
125
|
+
method: binding.method,
|
|
126
|
+
cells: binding.evidence_artifacts.map((artifact) => ({
|
|
127
|
+
condition_key: artifact.condition_key,
|
|
128
|
+
artifact_path: artifact.path,
|
|
129
|
+
observation_artifact_path: artifact.observation_path,
|
|
130
|
+
})),
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
}
|
|
108
134
|
}
|
|
109
135
|
return evidenceRecords;
|
|
110
136
|
}
|
|
@@ -167,10 +193,16 @@ function aggregateCases(instances) {
|
|
|
167
193
|
rows.every((row) => same(row.action_keys, rows[0].action_keys))
|
|
168
194
|
? rows[0].action_keys
|
|
169
195
|
: [],
|
|
196
|
+
attachment_names: rows.length === 0
|
|
197
|
+
? []
|
|
198
|
+
: rows[0].attachment_names.filter((name) => rows.every((row) => row.attachment_names.includes(name))),
|
|
170
199
|
};
|
|
171
200
|
})
|
|
172
201
|
.sort((left, right) => left.id.localeCompare(right.id));
|
|
173
202
|
}
|
|
203
|
+
export function designMethodAttachmentName(target, method, condition, kind) {
|
|
204
|
+
return `ty-context-design-method:${target}:${method}:${condition}:${kind}`;
|
|
205
|
+
}
|
|
174
206
|
function integer(value) {
|
|
175
207
|
return Number.isInteger(value) && Number(value) >= 0 ? Number(value) : null;
|
|
176
208
|
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import type { DeliveryControlV2, DeliveryObligationV2, DeliveryOwnerV2, KeyedPathV2, KeyedStatementV2 } from "./long-task-delivery-types.js";
|
|
1
|
+
import type { DeliveryControlRelationClosureV2, DeliveryControlRelationV2, DeliveryControlV2, DeliveryObligationV2, DeliveryOwnerV2, KeyedPathV2, KeyedStatementV2 } from "./long-task-delivery-types.js";
|
|
2
2
|
export declare function parseKeyedStatements(value: unknown, label: string): KeyedStatementV2[];
|
|
3
3
|
export declare function parseKeyedPaths(value: unknown, label: string): KeyedPathV2[];
|
|
4
4
|
export declare function parseControls(value: unknown, label: string): DeliveryControlV2[];
|
|
5
|
+
export declare function parseControlRelations(value: unknown, label: string): DeliveryControlRelationV2[];
|
|
6
|
+
export declare function parseControlRelationClosure(value: unknown, label: string): DeliveryControlRelationClosureV2;
|
|
5
7
|
export declare function parseOwner(value: unknown, label: string): DeliveryOwnerV2;
|
|
6
8
|
export declare function parseObligations(value: unknown, label: string): DeliveryObligationV2[];
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { CONTROL_FIELD_NAMES } from "./long-task-control-fields.js";
|
|
2
|
+
import { parseKeyRefs } from "./long-task-applicability-shape.js";
|
|
1
3
|
import { parseRequiredProofSurfaces } from "./long-task-required-proof-surfaces.js";
|
|
2
|
-
import { array, key, object, repositoryFiles, repositoryPattern, repositoryPatterns, string, text, } from "./long-task-shape-primitives.js";
|
|
4
|
+
import { array, fail, key, literal, object, repositoryFiles, repositoryPattern, repositoryPatterns, string, text, } from "./long-task-shape-primitives.js";
|
|
3
5
|
export function parseKeyedStatements(value, label) {
|
|
4
6
|
return array(value, label).map((item, index) => {
|
|
5
7
|
const itemLabel = `${label}[${index}]`;
|
|
@@ -23,9 +25,10 @@ export function parseKeyedPaths(value, label) {
|
|
|
23
25
|
export function parseControls(value, label) {
|
|
24
26
|
return array(value, label).map((item, index) => {
|
|
25
27
|
const itemLabel = `${label}[${index}]`;
|
|
26
|
-
const row = object(item, itemLabel, ["key", "
|
|
28
|
+
const row = object(item, itemLabel, ["key", "field_coverage"], [
|
|
27
29
|
"surface",
|
|
28
30
|
"region",
|
|
31
|
+
"location",
|
|
29
32
|
"control_type",
|
|
30
33
|
"label_content",
|
|
31
34
|
"user_task",
|
|
@@ -46,11 +49,11 @@ export function parseControls(value, label) {
|
|
|
46
49
|
"feedback",
|
|
47
50
|
"accessibility",
|
|
48
51
|
]);
|
|
49
|
-
|
|
52
|
+
const control = {
|
|
50
53
|
key: key(row.key, `${itemLabel}.key`),
|
|
51
54
|
surface: optionalText(row, "surface", itemLabel),
|
|
52
55
|
region: optionalText(row, "region", itemLabel),
|
|
53
|
-
location:
|
|
56
|
+
location: optionalText(row, "location", itemLabel),
|
|
54
57
|
control_type: optionalText(row, "control_type", itemLabel),
|
|
55
58
|
label_content: optionalText(row, "label_content", itemLabel),
|
|
56
59
|
user_task: optionalText(row, "user_task", itemLabel),
|
|
@@ -70,9 +73,44 @@ export function parseControls(value, label) {
|
|
|
70
73
|
permission: optionalText(row, "permission", itemLabel),
|
|
71
74
|
feedback: optionalText(row, "feedback", itemLabel),
|
|
72
75
|
accessibility: optionalText(row, "accessibility", itemLabel),
|
|
76
|
+
field_coverage: parseControlFieldCoverage(row.field_coverage, `${itemLabel}.field_coverage`),
|
|
77
|
+
};
|
|
78
|
+
validateControlFieldCoverage(control, itemLabel);
|
|
79
|
+
return control;
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
export function parseControlRelations(value, label) {
|
|
83
|
+
return array(value, label).map((item, index) => {
|
|
84
|
+
const itemLabel = `${label}[${index}]`;
|
|
85
|
+
const row = object(item, itemLabel, [
|
|
86
|
+
"key",
|
|
87
|
+
"statement",
|
|
88
|
+
"control_refs",
|
|
89
|
+
"required_proof_surfaces",
|
|
90
|
+
"applicability_refs",
|
|
91
|
+
]);
|
|
92
|
+
return {
|
|
93
|
+
key: key(row.key, `${itemLabel}.key`),
|
|
94
|
+
statement: string(row.statement, `${itemLabel}.statement`),
|
|
95
|
+
control_refs: parseKeyRefs(row.control_refs, `${itemLabel}.control_refs`),
|
|
96
|
+
required_proof_surfaces: parseRequiredProofSurfaces(row.required_proof_surfaces, `${itemLabel}.required_proof_surfaces`),
|
|
97
|
+
applicability_refs: parseKeyRefs(row.applicability_refs, `${itemLabel}.applicability_refs`),
|
|
73
98
|
};
|
|
74
99
|
});
|
|
75
100
|
}
|
|
101
|
+
export function parseControlRelationClosure(value, label) {
|
|
102
|
+
const row = object(value, label, ["state", "statement"], ["applicability_refs"]);
|
|
103
|
+
const state = literal(row.state, ["specified", "not_applicable", "unresolved"], `${label}.state`);
|
|
104
|
+
return {
|
|
105
|
+
state,
|
|
106
|
+
statement: string(row.statement, `${label}.statement`),
|
|
107
|
+
applicability_refs: state === "unresolved"
|
|
108
|
+
? Object.hasOwn(row, "applicability_refs")
|
|
109
|
+
? parseKeyRefs(row.applicability_refs, `${label}.applicability_refs`)
|
|
110
|
+
: []
|
|
111
|
+
: parseKeyRefs(row.applicability_refs, `${label}.applicability_refs`),
|
|
112
|
+
};
|
|
113
|
+
}
|
|
76
114
|
export function parseOwner(value, label) {
|
|
77
115
|
const row = object(value, label, ["label", "context_refs", "path_globs"]);
|
|
78
116
|
return {
|
|
@@ -88,14 +126,72 @@ export function parseObligations(value, label) {
|
|
|
88
126
|
"key",
|
|
89
127
|
"statement",
|
|
90
128
|
"required_proof_surfaces",
|
|
129
|
+
"applicability_refs",
|
|
91
130
|
]);
|
|
92
131
|
return {
|
|
93
132
|
key: key(row.key, `${itemLabel}.key`),
|
|
94
133
|
statement: string(row.statement, `${itemLabel}.statement`),
|
|
95
134
|
required_proof_surfaces: parseRequiredProofSurfaces(row.required_proof_surfaces, `${itemLabel}.required_proof_surfaces`),
|
|
135
|
+
applicability_refs: parseKeyRefs(row.applicability_refs, `${itemLabel}.applicability_refs`),
|
|
96
136
|
};
|
|
97
137
|
});
|
|
98
138
|
}
|
|
139
|
+
function parseControlFieldCoverage(value, label) {
|
|
140
|
+
return array(value, label).map((item, index) => {
|
|
141
|
+
const itemLabel = `${label}[${index}]`;
|
|
142
|
+
const row = object(item, itemLabel, ["fields", "state"], ["statement", "applicability_refs"]);
|
|
143
|
+
const fields = array(row.fields, `${itemLabel}.fields`).map((field, fieldIndex) => literal(field, CONTROL_FIELD_NAMES, `${itemLabel}.fields[${fieldIndex}]`));
|
|
144
|
+
if (!fields.length)
|
|
145
|
+
fail(`${itemLabel}.fields`, "must not be empty");
|
|
146
|
+
const state = literal(row.state, ["specified", "not_applicable", "unresolved"], `${itemLabel}.state`);
|
|
147
|
+
if (state === "specified") {
|
|
148
|
+
if (Object.hasOwn(row, "statement"))
|
|
149
|
+
fail(`${itemLabel}.statement`, "forbidden for specified fields");
|
|
150
|
+
return {
|
|
151
|
+
fields,
|
|
152
|
+
state,
|
|
153
|
+
applicability_refs: parseRequiredApplicabilityRefs(row, itemLabel),
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
const statement = string(row.statement, `${itemLabel}.statement`);
|
|
157
|
+
if (state === "not_applicable")
|
|
158
|
+
return {
|
|
159
|
+
fields,
|
|
160
|
+
state,
|
|
161
|
+
statement,
|
|
162
|
+
applicability_refs: parseRequiredApplicabilityRefs(row, itemLabel),
|
|
163
|
+
};
|
|
164
|
+
if (Object.hasOwn(row, "applicability_refs") &&
|
|
165
|
+
array(row.applicability_refs, `${itemLabel}.applicability_refs`).length)
|
|
166
|
+
fail(`${itemLabel}.applicability_refs`, "must be empty while unresolved");
|
|
167
|
+
return { fields, state, statement, applicability_refs: [] };
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
function parseRequiredApplicabilityRefs(row, label) {
|
|
171
|
+
const refs = parseKeyRefs(row.applicability_refs, `${label}.applicability_refs`);
|
|
172
|
+
if (!refs.length)
|
|
173
|
+
fail(`${label}.applicability_refs`, "must not be empty");
|
|
174
|
+
return refs;
|
|
175
|
+
}
|
|
176
|
+
function validateControlFieldCoverage(control, label) {
|
|
177
|
+
const owners = new Map();
|
|
178
|
+
for (const [index, entry] of control.field_coverage.entries()) {
|
|
179
|
+
for (const field of entry.fields) {
|
|
180
|
+
const previous = owners.get(field);
|
|
181
|
+
if (previous !== undefined)
|
|
182
|
+
fail(`${label}.field_coverage[${index}].fields`, `field ${field} already owned by entry ${previous}`);
|
|
183
|
+
owners.set(field, index);
|
|
184
|
+
const hasStatement = Boolean(control[field].trim());
|
|
185
|
+
if (entry.state === "specified" && !hasStatement)
|
|
186
|
+
fail(`${label}.${field}`, "must be non-empty when field state is specified");
|
|
187
|
+
if (entry.state !== "specified" && hasStatement)
|
|
188
|
+
fail(`${label}.${field}`, `must be empty when field state is ${entry.state}`);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
const missing = CONTROL_FIELD_NAMES.filter((field) => !owners.has(field));
|
|
192
|
+
if (missing.length)
|
|
193
|
+
fail(`${label}.field_coverage`, `missing fields ${missing.join(",")}`);
|
|
194
|
+
}
|
|
99
195
|
function optionalText(row, field, label) {
|
|
100
196
|
return Object.hasOwn(row, field) ? text(row[field], `${label}.${field}`) : "";
|
|
101
197
|
}
|