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
|
@@ -9,7 +9,8 @@ import { assertRiskNotDowngraded, buildAuthorityRevisionProposal, enforceAuthori
|
|
|
9
9
|
import { validateActualRiskSurfaces } from "./long-task-risk-surfaces.js";
|
|
10
10
|
import { captureVerifierIdentity } from "./long-task-verifier-identity.js";
|
|
11
11
|
import { verifierAuthorityDiff } from "./long-task-verifier-authority.js";
|
|
12
|
-
import { changedWorkspacePaths, currentGitTree, repoRelative, repositoryRoot, } from "./long-task-workspace.js";
|
|
12
|
+
import { changedWorkspacePaths, changedWorkspacePathsFromHead, currentGitTree, repoRelative, repositoryRoot, } from "./long-task-workspace.js";
|
|
13
|
+
import { assertWorkspaceScope, classifyWorkspaceScope, firstLockManagedWorkspacePaths, protectedWorkspacePaths, } from "./long-task-workspace-scope.js";
|
|
13
14
|
export async function compileDeliveryContract(workdirInput, projectRootInput = process.cwd(), options = {}) {
|
|
14
15
|
const repository = await repositoryRoot(projectRootInput);
|
|
15
16
|
const workdir = path.resolve(workdirInput);
|
|
@@ -35,18 +36,38 @@ export async function compileDeliveryContract(workdirInput, projectRootInput = p
|
|
|
35
36
|
const current = validation.workspace;
|
|
36
37
|
const globalChecks = validation.global_checks;
|
|
37
38
|
const outcomes = validation.outcomes;
|
|
39
|
+
const contractFiles = Object.fromEntries(Object.entries(parsed.contract_files).map(([relative, hash]) => [
|
|
40
|
+
repoRelative(repository, path.join(workdir, ...relative.split("/"))),
|
|
41
|
+
hash,
|
|
42
|
+
]));
|
|
38
43
|
const contractHash = sha256Hex(canonicalValueJson(contract));
|
|
39
44
|
const authorityMaterials = computeAuthorityMaterials(contract, sourceHashes, sourceItems, context);
|
|
40
45
|
const previous = options.live_gate
|
|
41
46
|
? null
|
|
42
47
|
: (options.previous_authority ?? null);
|
|
43
|
-
const
|
|
44
|
-
|
|
48
|
+
const existingInitialTaskBase = options.initial_task_base ?? previous?.initial_task_base ?? null;
|
|
49
|
+
const changedPaths = existingInitialTaskBase
|
|
50
|
+
? changedWorkspacePaths(existingInitialTaskBase.workspace_manifest, current)
|
|
51
|
+
: await changedWorkspacePathsFromHead(repository, workdir);
|
|
52
|
+
const firstLockManagedPaths = existingInitialTaskBase
|
|
53
|
+
? []
|
|
54
|
+
: await firstLockManagedWorkspacePaths(repository, changedPaths);
|
|
55
|
+
assertWorkspaceScope(classifyWorkspaceScope(contract, changedPaths, protectedWorkspacePaths({
|
|
56
|
+
contract_files: contractFiles,
|
|
57
|
+
source_hashes: sourceHashes,
|
|
58
|
+
context_hashes: context.sha256,
|
|
59
|
+
checks: [
|
|
60
|
+
...globalChecks,
|
|
61
|
+
...outcomes.flatMap((outcome) => outcome.acceptance.checks),
|
|
62
|
+
],
|
|
63
|
+
additional_files: firstLockManagedPaths,
|
|
64
|
+
})));
|
|
65
|
+
const initialTaskBase = existingInitialTaskBase ?? {
|
|
45
66
|
git_commit: current.git_head,
|
|
46
67
|
git_tree: await currentGitTree(repository),
|
|
47
68
|
workspace_manifest: current,
|
|
48
69
|
};
|
|
49
|
-
await validateActualRiskSurfaces(repository,
|
|
70
|
+
await validateActualRiskSurfaces(repository, changedPaths, contract);
|
|
50
71
|
const verifier = await captureVerifierIdentity(repository, options.require_completion_gate !== false);
|
|
51
72
|
const allChecks = [
|
|
52
73
|
...globalChecks,
|
|
@@ -95,10 +116,7 @@ export async function compileDeliveryContract(workdirInput, projectRootInput = p
|
|
|
95
116
|
workdir,
|
|
96
117
|
contract_file: repoRelative(repository, contractFile),
|
|
97
118
|
contract_sha256: contractHash,
|
|
98
|
-
contract_files:
|
|
99
|
-
repoRelative(repository, path.join(workdir, ...relative.split("/"))),
|
|
100
|
-
hash,
|
|
101
|
-
])),
|
|
119
|
+
contract_files: contractFiles,
|
|
102
120
|
source_hashes: sourceHashes,
|
|
103
121
|
source_items: sourceItems,
|
|
104
122
|
context_snapshot: normalizeContextAuthoritySnapshot(context),
|
|
@@ -6,5 +6,6 @@ export * from "./long-task-evidence-capability-types.js";
|
|
|
6
6
|
export * from "./long-task-risk-types.js";
|
|
7
7
|
export * from "./long-task-runtime-types.js";
|
|
8
8
|
export * from "./long-task-semantic-contract-types.js";
|
|
9
|
+
export * from "./long-task-ui-surface-types.js";
|
|
9
10
|
export * from "./long-task-source-authority-types.js";
|
|
10
11
|
export * from "./long-task-workspace-runtime-types.js";
|
|
@@ -6,5 +6,6 @@ export * from "./long-task-evidence-capability-types.js";
|
|
|
6
6
|
export * from "./long-task-risk-types.js";
|
|
7
7
|
export * from "./long-task-runtime-types.js";
|
|
8
8
|
export * from "./long-task-semantic-contract-types.js";
|
|
9
|
+
export * from "./long-task-ui-surface-types.js";
|
|
9
10
|
export * from "./long-task-source-authority-types.js";
|
|
10
11
|
export * from "./long-task-workspace-runtime-types.js";
|
|
@@ -7,6 +7,7 @@ import { validateEvidenceAdapterCompatibility } from "./long-task-evidence-adapt
|
|
|
7
7
|
import { validateEvidenceCapabilityDeclarations } from "./long-task-evidence-capability-policy.js";
|
|
8
8
|
import { validateDeliveryStages } from "./long-task-stage-policy.js";
|
|
9
9
|
import { validateExecutionTargets, validateExternalConfirmationImpacts, } from "./long-task-target-policy.js";
|
|
10
|
+
import { validateUiSurfaceBindings } from "./long-task-ui-surface-policy.js";
|
|
10
11
|
export function validateDeliveryContractStructure(contract) {
|
|
11
12
|
validateUniqueKeys(contract);
|
|
12
13
|
validateDependencies(contract);
|
|
@@ -17,6 +18,7 @@ export function validateDeliveryContractStructure(contract) {
|
|
|
17
18
|
allow_uncovered: true,
|
|
18
19
|
});
|
|
19
20
|
validateSourceClaimMappings(contract, claims);
|
|
21
|
+
validateUiSurfaceBindings(contract, claims);
|
|
20
22
|
assertCompiledClaimsCovered(claims);
|
|
21
23
|
validateDeliveryStages(contract);
|
|
22
24
|
validateExecutionTargets(contract);
|
|
@@ -40,6 +42,7 @@ export function deliveryContractStructureDiagnostics(contract) {
|
|
|
40
42
|
});
|
|
41
43
|
if (claims) {
|
|
42
44
|
validateSourceClaimMappings(contract, claims, report);
|
|
45
|
+
validateUiSurfaceBindings(contract, claims, report);
|
|
43
46
|
validateExternalConfirmationImpacts(contract, claims, report);
|
|
44
47
|
capture(diagnostics, () => assertCompiledClaimsCovered(claims));
|
|
45
48
|
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { containsDesignResourceHandoff } from "./design-resource-handoff-parser.js";
|
|
4
|
+
import { preflightDesignResourceHandoff } from "./design-resource-handoff-validation.js";
|
|
5
|
+
import { assertProtectedRepositoryFile } from "./long-task-protected-files.js";
|
|
6
|
+
export async function validateLongTaskDesignResourceHandoffs(contract, repository) {
|
|
7
|
+
const handoffs = await loadHandoffs(contract, repository);
|
|
8
|
+
const indexed = indexHandoffTargets(handoffs);
|
|
9
|
+
const contractTargets = contract.outcomes.flatMap((outcome) => outcome.product.surface_bindings.flatMap((binding) => binding.design_targets.map((target) => ({
|
|
10
|
+
outcome_key: outcome.key,
|
|
11
|
+
binding,
|
|
12
|
+
target,
|
|
13
|
+
}))));
|
|
14
|
+
const consumed = new Set();
|
|
15
|
+
for (const contractTarget of contractTargets) {
|
|
16
|
+
const indexedTarget = indexed.get(contractTarget.target.key);
|
|
17
|
+
if (!indexedTarget)
|
|
18
|
+
invalid("target_handoff_missing", contractTarget.target.key);
|
|
19
|
+
consumed.add(contractTarget.target.key);
|
|
20
|
+
validateTargetIdentity(contractTarget, indexedTarget);
|
|
21
|
+
validateCoverageClaims(contract, contractTarget, indexedTarget);
|
|
22
|
+
validateBlockerBindings(contract, contractTarget, indexedTarget);
|
|
23
|
+
}
|
|
24
|
+
for (const key of indexed.keys())
|
|
25
|
+
if (!consumed.has(key))
|
|
26
|
+
invalid("handoff_target_unbound", key);
|
|
27
|
+
}
|
|
28
|
+
async function loadHandoffs(contract, repository) {
|
|
29
|
+
const results = [];
|
|
30
|
+
for (const sourcePath of contract.task.source_paths) {
|
|
31
|
+
if (!sourcePath.toLowerCase().endsWith(".md"))
|
|
32
|
+
continue;
|
|
33
|
+
const file = await assertProtectedRepositoryFile(repository, path.resolve(repository, ...sourcePath.split("/")), `design_resource_handoff_source:${sourcePath}`);
|
|
34
|
+
const content = await readFile(file, "utf8");
|
|
35
|
+
if (containsDesignResourceHandoff(content))
|
|
36
|
+
results.push(await preflightDesignResourceHandoff(repository, sourcePath));
|
|
37
|
+
}
|
|
38
|
+
return results;
|
|
39
|
+
}
|
|
40
|
+
function indexHandoffTargets(handoffs) {
|
|
41
|
+
const indexed = new Map();
|
|
42
|
+
for (const preflight of handoffs)
|
|
43
|
+
for (const target of preflight.handoff.targets) {
|
|
44
|
+
if (indexed.has(target.key))
|
|
45
|
+
invalid("handoff_target_duplicate", target.key);
|
|
46
|
+
indexed.set(target.key, { preflight, target });
|
|
47
|
+
}
|
|
48
|
+
return indexed;
|
|
49
|
+
}
|
|
50
|
+
function validateTargetIdentity(contractTarget, indexed) {
|
|
51
|
+
const { target } = contractTarget;
|
|
52
|
+
const handoffTarget = indexed.target;
|
|
53
|
+
if (target.interpretation !== handoffTarget.interpretation)
|
|
54
|
+
invalid("target_interpretation_mismatch", `${target.key}:${target.interpretation}:${handoffTarget.interpretation}`);
|
|
55
|
+
assertSameSet(target.condition_keys, handoffTarget.condition_refs, "target_conditions_mismatch", target.key);
|
|
56
|
+
const resourcePaths = handoffTarget.resource_refs.map((ref) => indexed.preflight.handoff.resources.find((item) => item.key === ref)
|
|
57
|
+
.path);
|
|
58
|
+
assertSameSet(target.source_paths, [indexed.preflight.handoff_path, ...resourcePaths], "target_source_paths_mismatch", target.key);
|
|
59
|
+
}
|
|
60
|
+
function validateCoverageClaims(contract, contractTarget, indexed) {
|
|
61
|
+
const target = contractTarget.target;
|
|
62
|
+
const check = contract.outcomes
|
|
63
|
+
.find((item) => item.key === contractTarget.outcome_key)
|
|
64
|
+
.acceptance.checks.find((item) => item.key === target.conformance_check_ref);
|
|
65
|
+
const assertion = check.positive_assertions.find((item) => item.key === target.conformance_assertion_ref);
|
|
66
|
+
const claims = new Map(contract.source_claims.map((item) => [item.key, item]));
|
|
67
|
+
const rows = indexed.preflight.handoff.coverage.filter((row) => row.disposition === "covered" && row.target_refs.includes(target.key));
|
|
68
|
+
if (!rows.length)
|
|
69
|
+
invalid("target_covered_claims_required", target.key);
|
|
70
|
+
const claimsBySourceItem = new Map();
|
|
71
|
+
for (const row of rows)
|
|
72
|
+
for (const sourceItemRef of row.source_item_refs) {
|
|
73
|
+
const localClaims = designSourceItemClaims(contract, contractTarget, indexed, sourceItemRef, claims);
|
|
74
|
+
claimsBySourceItem.set(sourceItemRef, localClaims);
|
|
75
|
+
for (const localClaimRef of localClaims)
|
|
76
|
+
if (!assertion.claims.includes(localClaimRef))
|
|
77
|
+
invalid("coverage_claim_not_in_conformance_assertion", `${target.key}:${sourceItemRef}:${localClaimRef}`);
|
|
78
|
+
}
|
|
79
|
+
validateVerificationMethodBindings(target, check, rows, claimsBySourceItem);
|
|
80
|
+
}
|
|
81
|
+
function validateBlockerBindings(contract, contractTarget, indexed) {
|
|
82
|
+
const bound = new Map(contractTarget.binding.acceptance_blockers.map((item) => [item.key, item]));
|
|
83
|
+
const claims = new Map(contract.source_claims.map((item) => [item.key, item]));
|
|
84
|
+
const confirmations = new Map(contract.global.acceptance.external_confirmations.map((item) => [
|
|
85
|
+
item.key,
|
|
86
|
+
item,
|
|
87
|
+
]));
|
|
88
|
+
const availableMethods = new Set(contractTarget.target.verification_method_bindings.map((item) => item.method));
|
|
89
|
+
for (const blocker of indexed.preflight.handoff.acceptance_blockers) {
|
|
90
|
+
if (!blocker.target_refs.includes(contractTarget.target.key))
|
|
91
|
+
continue;
|
|
92
|
+
const contractBlocker = bound.get(blocker.key);
|
|
93
|
+
if (!contractBlocker)
|
|
94
|
+
invalid("acceptance_blocker_unbound", `${contractTarget.target.key}:${blocker.key}`);
|
|
95
|
+
assertSameSet(contractBlocker.source_item_refs, blocker.source_item_refs, "acceptance_blocker_source_items_mismatch", blocker.key);
|
|
96
|
+
assertSameSet(contractBlocker.verification_methods, blocker.verification_methods, "acceptance_blocker_methods_mismatch", blocker.key);
|
|
97
|
+
for (const method of blocker.verification_methods)
|
|
98
|
+
if (!availableMethods.has(method))
|
|
99
|
+
invalid("acceptance_blocker_method_unbound", `${blocker.key}:${method}`);
|
|
100
|
+
const fullClaimRefs = blocker.source_item_refs.flatMap((sourceItemRef) => {
|
|
101
|
+
const local = designSourceItemClaims(contract, contractTarget, indexed, sourceItemRef, claims);
|
|
102
|
+
return local.map((claimRef) => `${contractTarget.outcome_key}.${claimRef}`);
|
|
103
|
+
});
|
|
104
|
+
if (contractBlocker.status === "machine_claim") {
|
|
105
|
+
const expectedLocal = fullClaimRefs.map((claimRef) => claimRef.slice(contractTarget.outcome_key.length + 1));
|
|
106
|
+
for (const claimRef of expectedLocal)
|
|
107
|
+
if (!contractBlocker.refs.includes(claimRef))
|
|
108
|
+
invalid("acceptance_blocker_machine_claim_lineage_missing", `${blocker.key}:${claimRef}`);
|
|
109
|
+
}
|
|
110
|
+
else
|
|
111
|
+
for (const confirmationRef of contractBlocker.refs) {
|
|
112
|
+
const confirmation = confirmations.get(confirmationRef);
|
|
113
|
+
if (!confirmation)
|
|
114
|
+
invalid("acceptance_blocker_confirmation_unknown", `${blocker.key}:${confirmationRef}`);
|
|
115
|
+
for (const claimRef of fullClaimRefs)
|
|
116
|
+
if (!confirmation.impact_claims.includes(claimRef))
|
|
117
|
+
invalid("acceptance_blocker_confirmation_lineage_missing", `${blocker.key}:${confirmationRef}:${claimRef}`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
function designSourceItemClaims(contract, contractTarget, indexed, sourceItemRef, claims = new Map(contract.source_claims.map((item) => [item.key, item]))) {
|
|
122
|
+
const claim = claims.get(sourceItemRef);
|
|
123
|
+
if (!claim)
|
|
124
|
+
invalid("coverage_source_claim_unknown", `${contractTarget.target.key}:${sourceItemRef}`);
|
|
125
|
+
if (claim.source_ref.split("#")[0] !== indexed.preflight.handoff_path)
|
|
126
|
+
invalid("coverage_source_claim_file_mismatch", `${contractTarget.target.key}:${sourceItemRef}`);
|
|
127
|
+
if (claim.disposition.type !== "claim")
|
|
128
|
+
invalid("coverage_source_claim_disposition_required", `${contractTarget.target.key}:${sourceItemRef}:${claim.disposition.type}`);
|
|
129
|
+
const prefix = `${contractTarget.outcome_key}.`;
|
|
130
|
+
return claim.disposition.refs.map((claimRef) => {
|
|
131
|
+
if (!claimRef.startsWith(prefix))
|
|
132
|
+
invalid("coverage_claim_outcome_mismatch", `${contractTarget.target.key}:${sourceItemRef}:${claimRef}`);
|
|
133
|
+
return claimRef.slice(prefix.length);
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
function validateVerificationMethodBindings(target, check, rows, claimsBySourceItem) {
|
|
137
|
+
const expectedMethods = new Set(rows.flatMap((row) => row.verification_methods));
|
|
138
|
+
const bindings = target.verification_method_bindings;
|
|
139
|
+
assertSameSet(bindings.map((item) => item.method), [...expectedMethods], "verification_methods_mismatch", target.key);
|
|
140
|
+
const assertionRefs = bindings.map((item) => item.assertion_ref);
|
|
141
|
+
if (new Set(assertionRefs).size !== assertionRefs.length)
|
|
142
|
+
invalid("verification_method_assertion_duplicate", target.key);
|
|
143
|
+
if (assertionRefs.includes(target.conformance_assertion_ref))
|
|
144
|
+
invalid("verification_method_assertion_must_be_independent", target.key);
|
|
145
|
+
for (const binding of bindings) {
|
|
146
|
+
const assertion = check.positive_assertions.find((item) => item.key === binding.assertion_ref);
|
|
147
|
+
if (!assertion)
|
|
148
|
+
invalid("verification_method_assertion_unknown", `${target.key}:${binding.method}:${binding.assertion_ref}`);
|
|
149
|
+
const sourceItems = new Set(rows
|
|
150
|
+
.filter((row) => row.verification_methods.includes(binding.method))
|
|
151
|
+
.flatMap((row) => row.source_item_refs));
|
|
152
|
+
for (const sourceItemRef of sourceItems)
|
|
153
|
+
for (const claimRef of claimsBySourceItem.get(sourceItemRef) ?? [])
|
|
154
|
+
if (!assertion.claims.includes(claimRef))
|
|
155
|
+
invalid("verification_method_claim_not_asserted", `${target.key}:${binding.method}:${sourceItemRef}:${claimRef}`);
|
|
156
|
+
for (const capability of requiredCapabilities(binding.method))
|
|
157
|
+
if (!assertion.evidence_capabilities.includes(capability))
|
|
158
|
+
invalid("verification_method_capability_required", `${target.key}:${binding.method}:${capability}`);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
function requiredCapabilities(method) {
|
|
162
|
+
if (method === "interaction_trace")
|
|
163
|
+
return ["interaction_trace", "target_runtime"];
|
|
164
|
+
if (method === "component_state")
|
|
165
|
+
return ["design_conformance", "interaction_trace", "target_runtime"];
|
|
166
|
+
return ["design_conformance", "target_runtime"];
|
|
167
|
+
}
|
|
168
|
+
function assertSameSet(actual, expected, code, detail) {
|
|
169
|
+
const left = [...new Set(actual)].sort();
|
|
170
|
+
const right = [...new Set(expected)].sort();
|
|
171
|
+
if (left.length !== right.length ||
|
|
172
|
+
left.some((item, index) => item !== right[index]))
|
|
173
|
+
invalid(code, `${detail}:${left.join(",")}:${right.join(",")}`);
|
|
174
|
+
}
|
|
175
|
+
function invalid(code, detail) {
|
|
176
|
+
throw new Error(`delivery_contract_invalid:design_resource_${code}:${detail}`);
|
|
177
|
+
}
|
|
@@ -141,6 +141,25 @@ function decodeRecord(value, index) {
|
|
|
141
141
|
artifact_path: nonEmpty(row.artifact_path, `${label}.artifact_path`),
|
|
142
142
|
artifact_sha256: sha(row.artifact_sha256, `${label}.artifact_sha256`),
|
|
143
143
|
};
|
|
144
|
+
case "design_conformance":
|
|
145
|
+
exact(row, label, [
|
|
146
|
+
"assertion_key",
|
|
147
|
+
"capability",
|
|
148
|
+
"design_target_ref",
|
|
149
|
+
"target_ref",
|
|
150
|
+
"condition_keys",
|
|
151
|
+
"actual_artifact_path",
|
|
152
|
+
"comparison_artifact_path",
|
|
153
|
+
]);
|
|
154
|
+
return {
|
|
155
|
+
...base,
|
|
156
|
+
capability,
|
|
157
|
+
design_target_ref: key(row.design_target_ref, `${label}.design_target_ref`),
|
|
158
|
+
target_ref: key(row.target_ref, `${label}.target_ref`),
|
|
159
|
+
condition_keys: keys(row.condition_keys, `${label}.condition_keys`),
|
|
160
|
+
actual_artifact_path: nonEmpty(row.actual_artifact_path, `${label}.actual_artifact_path`),
|
|
161
|
+
comparison_artifact_path: nonEmpty(row.comparison_artifact_path, `${label}.comparison_artifact_path`),
|
|
162
|
+
};
|
|
144
163
|
case "target_runtime":
|
|
145
164
|
exact(row, label, [
|
|
146
165
|
"assertion_key",
|
|
@@ -26,12 +26,16 @@ export function validateEvidenceCapabilityDeclarations(contract, report) {
|
|
|
26
26
|
if (assertion.evidence_capabilities.includes("visual_render") &&
|
|
27
27
|
!check.artifact_globs.length)
|
|
28
28
|
issue(report, "visual_render_artifact_required", label);
|
|
29
|
+
if (assertion.evidence_capabilities.includes("design_conformance") &&
|
|
30
|
+
!check.artifact_globs.length)
|
|
31
|
+
issue(report, "design_conformance_artifact_required", label);
|
|
29
32
|
if (assertion.evidence_capabilities.includes("interaction_trace") &&
|
|
30
33
|
!check.scenario.when.length)
|
|
31
34
|
issue(report, "interaction_trace_actions_required", label);
|
|
32
35
|
if (check.runner.type === "playwright_test" &&
|
|
33
36
|
assertion.evidence_capabilities.some((capability) => capability !== "presence" &&
|
|
34
37
|
capability !== "interaction_trace" &&
|
|
38
|
+
capability !== "design_conformance" &&
|
|
35
39
|
capability !== "target_runtime"))
|
|
36
40
|
issue(report, "playwright_evidence_capability_unsupported", label);
|
|
37
41
|
if (["boundary_invocation", "external_side_effect"].some((capability) => assertion.evidence_capabilities.includes(capability)) &&
|
|
@@ -25,12 +25,37 @@ export function validateRuntimeEvidenceRecord(check, record, artifactHashes) {
|
|
|
25
25
|
return artifactHashes[record.artifact_path] === record.artifact_sha256
|
|
26
26
|
? null
|
|
27
27
|
: "artifact_hash_mismatch";
|
|
28
|
+
case "design_conformance":
|
|
29
|
+
return validateDesignConformance(check, record, artifactHashes);
|
|
28
30
|
case "target_runtime":
|
|
29
31
|
return validateTargetRuntime(check, record);
|
|
30
32
|
case "input_variation":
|
|
31
33
|
return validateInputVariation(record);
|
|
32
34
|
}
|
|
33
35
|
}
|
|
36
|
+
function validateDesignConformance(check, record, artifactHashes) {
|
|
37
|
+
const target = (check.design_conformance_targets ?? []).find((item) => item.key === record.design_target_ref &&
|
|
38
|
+
designTargetUsesAssertion(item, record.assertion_key));
|
|
39
|
+
if (!target)
|
|
40
|
+
return "design_target_unknown";
|
|
41
|
+
if (target.target_ref !== record.target_ref ||
|
|
42
|
+
target.target_ref !== check.execution_target.target_ref)
|
|
43
|
+
return "target_mismatch";
|
|
44
|
+
if (!same([...target.condition_keys].sort(), [...record.condition_keys].sort()))
|
|
45
|
+
return "design_conditions_mismatch";
|
|
46
|
+
if (target.actual_artifact_path !== record.actual_artifact_path ||
|
|
47
|
+
target.comparison_artifact_path !== record.comparison_artifact_path)
|
|
48
|
+
return "design_artifact_path_mismatch";
|
|
49
|
+
if (!artifactHashes[record.actual_artifact_path])
|
|
50
|
+
return "actual_artifact_missing";
|
|
51
|
+
if (!artifactHashes[record.comparison_artifact_path])
|
|
52
|
+
return "comparison_artifact_missing";
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
function designTargetUsesAssertion(target, assertionKey) {
|
|
56
|
+
return (target.conformance_assertion_ref === assertionKey ||
|
|
57
|
+
target.verification_method_bindings.some((item) => item.assertion_ref === assertionKey));
|
|
58
|
+
}
|
|
34
59
|
function validateInteractionTrace(check, record) {
|
|
35
60
|
if (record.target_ref !== check.execution_target.target_ref)
|
|
36
61
|
return "target_mismatch";
|
|
@@ -55,6 +55,14 @@ export interface VisualRenderEvidenceV2 extends EvidenceRecordBaseV2 {
|
|
|
55
55
|
artifact_path: string;
|
|
56
56
|
artifact_sha256: string;
|
|
57
57
|
}
|
|
58
|
+
export interface DesignConformanceEvidenceV2 extends EvidenceRecordBaseV2 {
|
|
59
|
+
capability: "design_conformance";
|
|
60
|
+
design_target_ref: string;
|
|
61
|
+
target_ref: string;
|
|
62
|
+
condition_keys: string[];
|
|
63
|
+
actual_artifact_path: string;
|
|
64
|
+
comparison_artifact_path: string;
|
|
65
|
+
}
|
|
58
66
|
export interface TargetRuntimeEvidenceV2 extends EvidenceRecordBaseV2 {
|
|
59
67
|
capability: "target_runtime";
|
|
60
68
|
target_ref: string;
|
|
@@ -70,5 +78,5 @@ export interface InputVariationEvidenceV2 extends EvidenceRecordBaseV2 {
|
|
|
70
78
|
}>;
|
|
71
79
|
failure_case_observed: boolean;
|
|
72
80
|
}
|
|
73
|
-
export type EvidenceCapabilityRecordV2 = InteractionTraceEvidenceV2 | StateDeltaEvidenceV2 | CrossSurfaceConsistencyEvidenceV2 | DurableReadbackEvidenceV2 | BoundaryInvocationEvidenceV2 | ExternalSideEffectEvidenceV2 | FailureInjectionEvidenceV2 | VisualRenderEvidenceV2 | TargetRuntimeEvidenceV2 | InputVariationEvidenceV2;
|
|
81
|
+
export type EvidenceCapabilityRecordV2 = InteractionTraceEvidenceV2 | StateDeltaEvidenceV2 | CrossSurfaceConsistencyEvidenceV2 | DurableReadbackEvidenceV2 | BoundaryInvocationEvidenceV2 | ExternalSideEffectEvidenceV2 | FailureInjectionEvidenceV2 | VisualRenderEvidenceV2 | DesignConformanceEvidenceV2 | TargetRuntimeEvidenceV2 | InputVariationEvidenceV2;
|
|
74
82
|
export {};
|
|
@@ -4,6 +4,7 @@ import { parseRequirements } from "./long-task-requirement-shape.js";
|
|
|
4
4
|
import { array, boolean, key, literal, nullable, object, parseBindings, parseControls, parseCounterfactuals, parseKeyedStatements, 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
|
+
import { parseSurfaceBindings } from "./long-task-ui-surface-shape.js";
|
|
7
8
|
export function parseOutcome(value, label) {
|
|
8
9
|
assertNoSemanticDriftMigration(semanticDriftOutcomeMigrationFields(value, label));
|
|
9
10
|
const row = object(value, label, ["key", "title", "stage", "product", "technical", "acceptance"], ["depends_on"]);
|
|
@@ -12,7 +13,13 @@ export function parseOutcome(value, label) {
|
|
|
12
13
|
"success_path_required",
|
|
13
14
|
"degradation_path_required",
|
|
14
15
|
"owner",
|
|
15
|
-
], [
|
|
16
|
+
], [
|
|
17
|
+
"requirements",
|
|
18
|
+
"owner_surfaces",
|
|
19
|
+
"controls",
|
|
20
|
+
"surface_bindings",
|
|
21
|
+
"non_completing_outcomes",
|
|
22
|
+
]);
|
|
16
23
|
const technical = object(row.technical, `${label}.technical`, ["expected_change_paths"], [
|
|
17
24
|
"obligations",
|
|
18
25
|
"allowed_support_paths",
|
|
@@ -43,6 +50,9 @@ export function parseOutcome(value, label) {
|
|
|
43
50
|
controls: Object.hasOwn(product, "controls")
|
|
44
51
|
? parseControls(product.controls, `${label}.product.controls`)
|
|
45
52
|
: [],
|
|
53
|
+
surface_bindings: Object.hasOwn(product, "surface_bindings")
|
|
54
|
+
? parseSurfaceBindings(product.surface_bindings, `${label}.product.surface_bindings`)
|
|
55
|
+
: [],
|
|
46
56
|
non_completing_outcomes: Object.hasOwn(product, "non_completing_outcomes")
|
|
47
57
|
? parseKeyedStatements(product.non_completing_outcomes, `${label}.product.non_completing_outcomes`)
|
|
48
58
|
: [],
|
|
@@ -46,4 +46,3 @@ export declare function matchesRepoPattern(fileInput: string, patternInput: stri
|
|
|
46
46
|
export declare function proveRepositoryPatternSubset(candidateInput: string, ownerInput: string): PatternContainment;
|
|
47
47
|
export declare function classifyRepositoryPatternOverlap(leftInput: string, rightInput: string): PatternOverlap;
|
|
48
48
|
export declare function repositoryPatternsMayOverlap(left: string, right: string): boolean;
|
|
49
|
-
export declare function findScopeEscapes(changedPaths: string[], allowedPatterns: string[], forbiddenPatterns: string[]): string[];
|
|
@@ -250,10 +250,6 @@ export function classifyRepositoryPatternOverlap(leftInput, rightInput) {
|
|
|
250
250
|
export function repositoryPatternsMayOverlap(left, right) {
|
|
251
251
|
return (classifyRepositoryPatternOverlap(left, right).status !== "proven_disjoint");
|
|
252
252
|
}
|
|
253
|
-
export function findScopeEscapes(changedPaths, allowedPatterns, forbiddenPatterns) {
|
|
254
|
-
return changedPaths.filter((file) => forbiddenPatterns.some((pattern) => matchesRepoPattern(file, pattern)) ||
|
|
255
|
-
!allowedPatterns.some((pattern) => matchesRepoPattern(file, pattern)));
|
|
256
|
-
}
|
|
257
253
|
function segmentSubset(candidate, owner) {
|
|
258
254
|
if (candidate.raw === owner.raw)
|
|
259
255
|
return { status: "proven_subset" };
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { CompiledCheckV2 } from "./long-task-delivery-types.js";
|
|
2
|
+
export interface PlaywrightCaseInstance {
|
|
3
|
+
id: string;
|
|
4
|
+
project_id: string;
|
|
5
|
+
executed: boolean;
|
|
6
|
+
passed: boolean;
|
|
7
|
+
skipped: boolean;
|
|
8
|
+
flaky: boolean;
|
|
9
|
+
unexpected: boolean;
|
|
10
|
+
timed_out: boolean;
|
|
11
|
+
interrupted: boolean;
|
|
12
|
+
status: string;
|
|
13
|
+
given_keys: string[];
|
|
14
|
+
action_keys: string[];
|
|
15
|
+
}
|
|
16
|
+
export interface PlaywrightCase {
|
|
17
|
+
id: string;
|
|
18
|
+
executed: boolean;
|
|
19
|
+
passed: boolean;
|
|
20
|
+
skipped: boolean;
|
|
21
|
+
flaky: boolean;
|
|
22
|
+
unexpected: boolean;
|
|
23
|
+
status: string;
|
|
24
|
+
project_ids: string[];
|
|
25
|
+
executed_instances: number;
|
|
26
|
+
failed_instances: number;
|
|
27
|
+
skipped_instances: number;
|
|
28
|
+
flaky_instances: number;
|
|
29
|
+
unexpected_instances: number;
|
|
30
|
+
timed_out_instances: number;
|
|
31
|
+
interrupted_instances: number;
|
|
32
|
+
given_keys: string[];
|
|
33
|
+
action_keys: string[];
|
|
34
|
+
}
|
|
35
|
+
export declare function collectCases(report: Record<string, unknown>, declaredIds: Set<string>): {
|
|
36
|
+
cases: PlaywrightCaseInstance[];
|
|
37
|
+
unbound_unexpected_instances: number;
|
|
38
|
+
error: string | null;
|
|
39
|
+
};
|
|
40
|
+
export declare function declaredCaseIds(check: CompiledCheckV2): Set<string>;
|
|
41
|
+
export declare function duplicateCaseInstance(values: PlaywrightCaseInstance[]): PlaywrightCaseInstance | null;
|
|
42
|
+
export declare function record(value: unknown): Record<string, unknown> | null;
|
|
43
|
+
export declare function same(left: string[], right: string[]): boolean;
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
export function collectCases(report, declaredIds) {
|
|
2
|
+
const cases = [];
|
|
3
|
+
const collection = { unbound_unexpected_instances: 0 };
|
|
4
|
+
const error = visitSuites(report.suites, cases, declaredIds, collection);
|
|
5
|
+
return { cases, error, ...collection };
|
|
6
|
+
}
|
|
7
|
+
function visitSuites(value, cases, declaredIds, collection) {
|
|
8
|
+
if (!Array.isArray(value))
|
|
9
|
+
return null;
|
|
10
|
+
for (const suiteValue of value) {
|
|
11
|
+
const suite = record(suiteValue);
|
|
12
|
+
if (!suite)
|
|
13
|
+
continue;
|
|
14
|
+
const nestedError = visitSuites(suite.suites, cases, declaredIds, collection);
|
|
15
|
+
if (nestedError)
|
|
16
|
+
return nestedError;
|
|
17
|
+
if (!Array.isArray(suite.specs))
|
|
18
|
+
continue;
|
|
19
|
+
for (const specValue of suite.specs) {
|
|
20
|
+
const spec = record(specValue);
|
|
21
|
+
if (!spec || !Array.isArray(spec.tests))
|
|
22
|
+
continue;
|
|
23
|
+
for (const testValue of spec.tests) {
|
|
24
|
+
const test = record(testValue);
|
|
25
|
+
if (!test)
|
|
26
|
+
continue;
|
|
27
|
+
const title = [spec.title, test.title]
|
|
28
|
+
.filter((item) => typeof item === "string")
|
|
29
|
+
.join(" ");
|
|
30
|
+
const ids = declaredIdsInTitle(title, declaredIds);
|
|
31
|
+
if (ids.length > 1)
|
|
32
|
+
return `playwright_test_multiple_ac_ids:${ids.join(",")}`;
|
|
33
|
+
const instance = playwrightCase(ids[0] ?? "unbound", test);
|
|
34
|
+
if (ids.length === 1)
|
|
35
|
+
cases.push(instance);
|
|
36
|
+
else if (instance.unexpected)
|
|
37
|
+
collection.unbound_unexpected_instances += 1;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
function declaredIdsInTitle(title, declared) {
|
|
44
|
+
const explicit = [...title.matchAll(/\[ac:([a-z0-9][a-z0-9-]*)\]/gu)].map((match) => match[1]);
|
|
45
|
+
const legacy = [...title.matchAll(/\[([a-z0-9][a-z0-9-]*)\]/gu)].map((match) => match[1]);
|
|
46
|
+
return [
|
|
47
|
+
...new Set([...explicit, ...legacy].filter((id) => declared.has(id))),
|
|
48
|
+
].sort();
|
|
49
|
+
}
|
|
50
|
+
function playwrightCase(id, test) {
|
|
51
|
+
const status = typeof test.status === "string" ? test.status : "invalid";
|
|
52
|
+
const projectId = typeof test.projectId === "string"
|
|
53
|
+
? test.projectId
|
|
54
|
+
: typeof test.projectName === "string"
|
|
55
|
+
? test.projectName
|
|
56
|
+
: "default";
|
|
57
|
+
const results = Array.isArray(test.results)
|
|
58
|
+
? test.results.map(record).filter(Boolean)
|
|
59
|
+
: [];
|
|
60
|
+
const resultStatuses = results
|
|
61
|
+
.map((result) => result.status)
|
|
62
|
+
.filter((value) => typeof value === "string");
|
|
63
|
+
const skipped = status === "skipped" ||
|
|
64
|
+
(resultStatuses.length > 0 &&
|
|
65
|
+
resultStatuses.every((value) => value === "skipped"));
|
|
66
|
+
const executed = !skipped && resultStatuses.length > 0;
|
|
67
|
+
const flaky = status === "flaky";
|
|
68
|
+
const unexpected = status === "unexpected";
|
|
69
|
+
const timedOut = resultStatuses.includes("timedOut");
|
|
70
|
+
const interrupted = resultStatuses.includes("interrupted");
|
|
71
|
+
const passed = executed &&
|
|
72
|
+
!flaky &&
|
|
73
|
+
!unexpected &&
|
|
74
|
+
status === "expected" &&
|
|
75
|
+
resultStatuses.length > 0 &&
|
|
76
|
+
resultStatuses.at(-1) === "passed";
|
|
77
|
+
const traces = results.map((result) => scenarioTrace(result));
|
|
78
|
+
const givenKeys = traces[0]?.given_keys ?? [];
|
|
79
|
+
const actionKeys = traces[0]?.action_keys ?? [];
|
|
80
|
+
const traceConsistent = traces.every((trace) => same(trace.given_keys, givenKeys) && same(trace.action_keys, actionKeys));
|
|
81
|
+
return {
|
|
82
|
+
id,
|
|
83
|
+
project_id: projectId,
|
|
84
|
+
executed,
|
|
85
|
+
passed,
|
|
86
|
+
skipped,
|
|
87
|
+
flaky,
|
|
88
|
+
unexpected,
|
|
89
|
+
timed_out: timedOut,
|
|
90
|
+
interrupted,
|
|
91
|
+
status,
|
|
92
|
+
given_keys: traceConsistent ? givenKeys : [],
|
|
93
|
+
action_keys: traceConsistent ? actionKeys : [],
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
export function declaredCaseIds(check) {
|
|
97
|
+
const result = new Set();
|
|
98
|
+
for (const assertion of [
|
|
99
|
+
...check.positive_assertions,
|
|
100
|
+
...check.negative_assertions,
|
|
101
|
+
]) {
|
|
102
|
+
if (!assertion.claims.length)
|
|
103
|
+
continue;
|
|
104
|
+
const match = /^playwright\.case\.([a-z0-9][a-z0-9-]*)\.passed$/u.exec(assertion.observation);
|
|
105
|
+
if (match)
|
|
106
|
+
result.add(assertion.key);
|
|
107
|
+
}
|
|
108
|
+
return result;
|
|
109
|
+
}
|
|
110
|
+
export function duplicateCaseInstance(values) {
|
|
111
|
+
const seen = new Set();
|
|
112
|
+
for (const value of values) {
|
|
113
|
+
const identity = `${value.id}\0${value.project_id}`;
|
|
114
|
+
if (seen.has(identity))
|
|
115
|
+
return value;
|
|
116
|
+
seen.add(identity);
|
|
117
|
+
}
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
export function record(value) {
|
|
121
|
+
return value && typeof value === "object" && !Array.isArray(value)
|
|
122
|
+
? value
|
|
123
|
+
: null;
|
|
124
|
+
}
|
|
125
|
+
function scenarioTrace(result) {
|
|
126
|
+
const givenKeys = [];
|
|
127
|
+
const actionKeys = [];
|
|
128
|
+
const visit = (value) => {
|
|
129
|
+
if (!Array.isArray(value))
|
|
130
|
+
return;
|
|
131
|
+
for (const item of value) {
|
|
132
|
+
const step = record(item);
|
|
133
|
+
if (!step)
|
|
134
|
+
continue;
|
|
135
|
+
if (typeof step.title === "string") {
|
|
136
|
+
const given = /^\[given:([a-z0-9][a-z0-9-]*)\]$/u.exec(step.title);
|
|
137
|
+
const action = /^\[action:([a-z0-9][a-z0-9-]*)\]$/u.exec(step.title);
|
|
138
|
+
if (given)
|
|
139
|
+
givenKeys.push(given[1]);
|
|
140
|
+
if (action)
|
|
141
|
+
actionKeys.push(action[1]);
|
|
142
|
+
}
|
|
143
|
+
visit(step.steps);
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
visit(result.steps);
|
|
147
|
+
return { given_keys: givenKeys, action_keys: actionKeys };
|
|
148
|
+
}
|
|
149
|
+
export function same(left, right) {
|
|
150
|
+
return (left.length === right.length &&
|
|
151
|
+
left.every((value, index) => value === right[index]));
|
|
152
|
+
}
|