@sun-asterisk/sungen 3.2.34 → 3.3.0-alpha.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/dist/basis/anchor-grammar.d.ts +32 -0
- package/dist/basis/anchor-grammar.d.ts.map +1 -0
- package/dist/basis/anchor-grammar.js +82 -0
- package/dist/basis/anchor-grammar.js.map +1 -0
- package/dist/basis/anchor-hash.d.ts +6 -0
- package/dist/basis/anchor-hash.d.ts.map +1 -0
- package/dist/basis/anchor-hash.js +28 -0
- package/dist/basis/anchor-hash.js.map +1 -0
- package/dist/basis/bm51-tables.d.ts +20 -0
- package/dist/basis/bm51-tables.d.ts.map +1 -0
- package/dist/basis/bm51-tables.js +71 -0
- package/dist/basis/bm51-tables.js.map +1 -0
- package/dist/basis/extract-spec-anchors.d.ts +39 -0
- package/dist/basis/extract-spec-anchors.d.ts.map +1 -0
- package/dist/basis/extract-spec-anchors.js +81 -0
- package/dist/basis/extract-spec-anchors.js.map +1 -0
- package/dist/basis/migration-sentinel.d.ts +47 -0
- package/dist/basis/migration-sentinel.d.ts.map +1 -0
- package/dist/basis/migration-sentinel.js +219 -0
- package/dist/basis/migration-sentinel.js.map +1 -0
- package/dist/basis/spec-frontmatter.d.ts +18 -0
- package/dist/basis/spec-frontmatter.d.ts.map +1 -0
- package/dist/basis/spec-frontmatter.js +55 -0
- package/dist/basis/spec-frontmatter.js.map +1 -0
- package/dist/basis/spec-sections.d.ts +18 -0
- package/dist/basis/spec-sections.d.ts.map +1 -0
- package/dist/basis/spec-sections.js +27 -0
- package/dist/basis/spec-sections.js.map +1 -0
- package/dist/basis/test-basis-schema.d.ts +28 -0
- package/dist/basis/test-basis-schema.d.ts.map +1 -0
- package/dist/basis/test-basis-schema.js +141 -0
- package/dist/basis/test-basis-schema.js.map +1 -0
- package/dist/capabilities/discover.d.ts +4 -0
- package/dist/capabilities/discover.d.ts.map +1 -1
- package/dist/capabilities/discover.js +16 -2
- package/dist/capabilities/discover.js.map +1 -1
- package/dist/capabilities/registry.d.ts +15 -0
- package/dist/capabilities/registry.d.ts.map +1 -1
- package/dist/capabilities/registry.js.map +1 -1
- package/dist/cli/commands/audit.d.ts.map +1 -1
- package/dist/cli/commands/audit.js +10 -5
- package/dist/cli/commands/audit.js.map +1 -1
- package/dist/cli/commands/capability.d.ts.map +1 -1
- package/dist/cli/commands/capability.js +6 -2
- package/dist/cli/commands/capability.js.map +1 -1
- package/dist/cli/commands/contract-derive-command.d.ts +11 -0
- package/dist/cli/commands/contract-derive-command.d.ts.map +1 -0
- package/dist/cli/commands/contract-derive-command.js +99 -0
- package/dist/cli/commands/contract-derive-command.js.map +1 -0
- package/dist/cli/commands/contract.d.ts +17 -0
- package/dist/cli/commands/contract.d.ts.map +1 -0
- package/dist/cli/commands/contract.js +162 -0
- package/dist/cli/commands/contract.js.map +1 -0
- package/dist/cli/commands/defect.d.ts +7 -0
- package/dist/cli/commands/defect.d.ts.map +1 -0
- package/dist/cli/commands/defect.js +68 -0
- package/dist/cli/commands/defect.js.map +1 -0
- package/dist/cli/commands/delivery.d.ts.map +1 -1
- package/dist/cli/commands/delivery.js +96 -12
- package/dist/cli/commands/delivery.js.map +1 -1
- package/dist/cli/commands/generate.d.ts.map +1 -1
- package/dist/cli/commands/generate.js +3 -1
- package/dist/cli/commands/generate.js.map +1 -1
- package/dist/cli/commands/journey.d.ts.map +1 -1
- package/dist/cli/commands/journey.js +43 -15
- package/dist/cli/commands/journey.js.map +1 -1
- package/dist/cli/commands/next.d.ts +8 -0
- package/dist/cli/commands/next.d.ts.map +1 -1
- package/dist/cli/commands/next.js +68 -8
- package/dist/cli/commands/next.js.map +1 -1
- package/dist/cli/commands/triage.d.ts +7 -0
- package/dist/cli/commands/triage.d.ts.map +1 -0
- package/dist/cli/commands/triage.js +98 -0
- package/dist/cli/commands/triage.js.map +1 -0
- package/dist/cli/commands/update.d.ts +11 -0
- package/dist/cli/commands/update.d.ts.map +1 -1
- package/dist/cli/commands/update.js +31 -6
- package/dist/cli/commands/update.js.map +1 -1
- package/dist/cli/index.js +24 -33
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/project-sungen-invocation.d.ts +7 -0
- package/dist/cli/project-sungen-invocation.d.ts.map +1 -0
- package/dist/cli/project-sungen-invocation.js +76 -0
- package/dist/cli/project-sungen-invocation.js.map +1 -0
- package/dist/contract/approve-contract.d.ts +31 -0
- package/dist/contract/approve-contract.d.ts.map +1 -0
- package/dist/contract/approve-contract.js +87 -0
- package/dist/contract/approve-contract.js.map +1 -0
- package/dist/contract/assertion-matcher-vocabulary.d.ts +24 -0
- package/dist/contract/assertion-matcher-vocabulary.d.ts.map +1 -0
- package/dist/contract/assertion-matcher-vocabulary.js +100 -0
- package/dist/contract/assertion-matcher-vocabulary.js.map +1 -0
- package/dist/contract/build-contract-verdict.d.ts +32 -0
- package/dist/contract/build-contract-verdict.d.ts.map +1 -0
- package/dist/contract/build-contract-verdict.js +123 -0
- package/dist/contract/build-contract-verdict.js.map +1 -0
- package/dist/contract/build-trace-conformance.d.ts +18 -0
- package/dist/contract/build-trace-conformance.d.ts.map +1 -0
- package/dist/contract/build-trace-conformance.js +153 -0
- package/dist/contract/build-trace-conformance.js.map +1 -0
- package/dist/contract/classify-contract-change.d.ts +23 -0
- package/dist/contract/classify-contract-change.d.ts.map +1 -0
- package/dist/contract/classify-contract-change.js +98 -0
- package/dist/contract/classify-contract-change.js.map +1 -0
- package/dist/contract/classify-contract-unit.d.ts +18 -0
- package/dist/contract/classify-contract-unit.d.ts.map +1 -0
- package/dist/contract/classify-contract-unit.js +49 -0
- package/dist/contract/classify-contract-unit.js.map +1 -0
- package/dist/contract/classify-trace-conformance.d.ts +32 -0
- package/dist/contract/classify-trace-conformance.d.ts.map +1 -0
- package/dist/contract/classify-trace-conformance.js +117 -0
- package/dist/contract/classify-trace-conformance.js.map +1 -0
- package/dist/contract/contract-git.d.ts +24 -0
- package/dist/contract/contract-git.d.ts.map +1 -0
- package/dist/contract/contract-git.js +144 -0
- package/dist/contract/contract-git.js.map +1 -0
- package/dist/contract/contract-hash.d.ts +7 -0
- package/dist/contract/contract-hash.d.ts.map +1 -0
- package/dist/contract/contract-hash.js +38 -0
- package/dist/contract/contract-hash.js.map +1 -0
- package/dist/contract/contract-report-file.d.ts +4 -0
- package/dist/contract/contract-report-file.d.ts.map +1 -0
- package/dist/contract/contract-report-file.js +60 -0
- package/dist/contract/contract-report-file.js.map +1 -0
- package/dist/contract/contract-schema.d.ts +71 -0
- package/dist/contract/contract-schema.d.ts.map +1 -0
- package/dist/contract/contract-schema.js +102 -0
- package/dist/contract/contract-schema.js.map +1 -0
- package/dist/contract/contract-units.d.ts +12 -0
- package/dist/contract/contract-units.d.ts.map +1 -0
- package/dist/contract/contract-units.js +96 -0
- package/dist/contract/contract-units.js.map +1 -0
- package/dist/contract/contracts-lock-file.d.ts +24 -0
- package/dist/contract/contracts-lock-file.d.ts.map +1 -0
- package/dist/contract/contracts-lock-file.js +140 -0
- package/dist/contract/contracts-lock-file.js.map +1 -0
- package/dist/contract/data-normalize.d.ts +10 -0
- package/dist/contract/data-normalize.d.ts.map +1 -0
- package/dist/contract/data-normalize.js +110 -0
- package/dist/contract/data-normalize.js.map +1 -0
- package/dist/contract/derive-free-feature.d.ts +38 -0
- package/dist/contract/derive-free-feature.d.ts.map +1 -0
- package/dist/contract/derive-free-feature.js +154 -0
- package/dist/contract/derive-free-feature.js.map +1 -0
- package/dist/contract/field-semantic-redaction.d.ts +21 -0
- package/dist/contract/field-semantic-redaction.d.ts.map +1 -0
- package/dist/contract/field-semantic-redaction.js +118 -0
- package/dist/contract/field-semantic-redaction.js.map +1 -0
- package/dist/contract/free-feature-blocks.d.ts +10 -0
- package/dist/contract/free-feature-blocks.d.ts.map +1 -0
- package/dist/contract/free-feature-blocks.js +94 -0
- package/dist/contract/free-feature-blocks.js.map +1 -0
- package/dist/contract/lock-secret-guard.d.ts +22 -0
- package/dist/contract/lock-secret-guard.d.ts.map +1 -0
- package/dist/contract/lock-secret-guard.js +134 -0
- package/dist/contract/lock-secret-guard.js.map +1 -0
- package/dist/contract/lock-vs-git-head.d.ts +26 -0
- package/dist/contract/lock-vs-git-head.d.ts.map +1 -0
- package/dist/contract/lock-vs-git-head.js +28 -0
- package/dist/contract/lock-vs-git-head.js.map +1 -0
- package/dist/contract/normalize-contract-step.d.ts +21 -0
- package/dist/contract/normalize-contract-step.d.ts.map +1 -0
- package/dist/contract/normalize-contract-step.js +70 -0
- package/dist/contract/normalize-contract-step.js.map +1 -0
- package/dist/contract/normalize-feature-steps.d.ts +7 -0
- package/dist/contract/normalize-feature-steps.d.ts.map +1 -0
- package/dist/contract/normalize-feature-steps.js +127 -0
- package/dist/contract/normalize-feature-steps.js.map +1 -0
- package/dist/contract/oracle-strength.d.ts +12 -0
- package/dist/contract/oracle-strength.d.ts.map +1 -0
- package/dist/contract/oracle-strength.js +88 -0
- package/dist/contract/oracle-strength.js.map +1 -0
- package/dist/contract/project-contract-blockers.d.ts +10 -0
- package/dist/contract/project-contract-blockers.d.ts.map +1 -0
- package/dist/contract/project-contract-blockers.js +73 -0
- package/dist/contract/project-contract-blockers.js.map +1 -0
- package/dist/contract/record-contract-approval.d.ts +21 -0
- package/dist/contract/record-contract-approval.d.ts.map +1 -0
- package/dist/contract/record-contract-approval.js +98 -0
- package/dist/contract/record-contract-approval.js.map +1 -0
- package/dist/contract/render-free-gherkin.d.ts +40 -0
- package/dist/contract/render-free-gherkin.d.ts.map +1 -0
- package/dist/contract/render-free-gherkin.js +141 -0
- package/dist/contract/render-free-gherkin.js.map +1 -0
- package/dist/contract/render-trace-sentence.d.ts +20 -0
- package/dist/contract/render-trace-sentence.d.ts.map +1 -0
- package/dist/contract/render-trace-sentence.js +109 -0
- package/dist/contract/render-trace-sentence.js.map +1 -0
- package/dist/contract/render-trace-target.d.ts +14 -0
- package/dist/contract/render-trace-target.d.ts.map +1 -0
- package/dist/contract/render-trace-target.js +70 -0
- package/dist/contract/render-trace-target.js.map +1 -0
- package/dist/contract/resolve-unit-trace-identities.d.ts +8 -0
- package/dist/contract/resolve-unit-trace-identities.d.ts.map +1 -0
- package/dist/contract/resolve-unit-trace-identities.js +78 -0
- package/dist/contract/resolve-unit-trace-identities.js.map +1 -0
- package/dist/contract/trace/collect-trace-evidence.d.ts +33 -0
- package/dist/contract/trace/collect-trace-evidence.d.ts.map +1 -0
- package/dist/contract/trace/collect-trace-evidence.js +161 -0
- package/dist/contract/trace/collect-trace-evidence.js.map +1 -0
- package/dist/contract/trace/identity-cache.d.ts +19 -0
- package/dist/contract/trace/identity-cache.d.ts.map +1 -0
- package/dist/contract/trace/identity-cache.js +104 -0
- package/dist/contract/trace/identity-cache.js.map +1 -0
- package/dist/contract/trace/select-final-attempt.d.ts +27 -0
- package/dist/contract/trace/select-final-attempt.d.ts.map +1 -0
- package/dist/contract/trace/select-final-attempt.js +116 -0
- package/dist/contract/trace/select-final-attempt.js.map +1 -0
- package/dist/contract/trace/snapshot-html-renderer.d.ts +31 -0
- package/dist/contract/trace/snapshot-html-renderer.d.ts.map +1 -0
- package/dist/contract/trace/snapshot-html-renderer.js +172 -0
- package/dist/contract/trace/snapshot-html-renderer.js.map +1 -0
- package/dist/contract/trace/snapshot-resolver.d.ts +21 -0
- package/dist/contract/trace/snapshot-resolver.d.ts.map +1 -0
- package/dist/contract/trace/snapshot-resolver.js +65 -0
- package/dist/contract/trace/snapshot-resolver.js.map +1 -0
- package/dist/contract/trace/snapshot-store.d.ts +66 -0
- package/dist/contract/trace/snapshot-store.d.ts.map +1 -0
- package/dist/contract/trace/snapshot-store.js +173 -0
- package/dist/contract/trace/snapshot-store.js.map +1 -0
- package/dist/contract/trace/target-identity.d.ts +74 -0
- package/dist/contract/trace/target-identity.d.ts.map +1 -0
- package/dist/contract/trace/target-identity.js +68 -0
- package/dist/contract/trace/target-identity.js.map +1 -0
- package/dist/contract/trace/test-step-join.d.ts +65 -0
- package/dist/contract/trace/test-step-join.d.ts.map +1 -0
- package/dist/contract/trace/test-step-join.js +80 -0
- package/dist/contract/trace/test-step-join.js.map +1 -0
- package/dist/contract/trace/trace-binding.d.ts +21 -0
- package/dist/contract/trace/trace-binding.d.ts.map +1 -0
- package/dist/contract/trace/trace-binding.js +88 -0
- package/dist/contract/trace/trace-binding.js.map +1 -0
- package/dist/contract/trace/trace-file-reader.d.ts +41 -0
- package/dist/contract/trace/trace-file-reader.d.ts.map +1 -0
- package/dist/contract/trace/trace-file-reader.js +161 -0
- package/dist/contract/trace/trace-file-reader.js.map +1 -0
- package/dist/contract/trace/trace-fingerprint.d.ts +22 -0
- package/dist/contract/trace/trace-fingerprint.d.ts.map +1 -0
- package/dist/contract/trace/trace-fingerprint.js +96 -0
- package/dist/contract/trace/trace-fingerprint.js.map +1 -0
- package/dist/contract/trace/trace-identity-resolution.d.ts +26 -0
- package/dist/contract/trace/trace-identity-resolution.d.ts.map +1 -0
- package/dist/contract/trace/trace-identity-resolution.js +84 -0
- package/dist/contract/trace/trace-identity-resolution.js.map +1 -0
- package/dist/contract/trace/trace-run-record.d.ts +30 -0
- package/dist/contract/trace/trace-run-record.d.ts.map +1 -0
- package/dist/contract/trace/trace-run-record.js +121 -0
- package/dist/contract/trace/trace-run-record.js.map +1 -0
- package/dist/contract/trace/trace-steps.d.ts +19 -0
- package/dist/contract/trace/trace-steps.d.ts.map +1 -0
- package/dist/contract/trace/trace-steps.js +79 -0
- package/dist/contract/trace/trace-steps.js.map +1 -0
- package/dist/contract/trace/trace-zip-limits.d.ts +45 -0
- package/dist/contract/trace/trace-zip-limits.d.ts.map +1 -0
- package/dist/contract/trace/trace-zip-limits.js +52 -0
- package/dist/contract/trace/trace-zip-limits.js.map +1 -0
- package/dist/contract/trace/trace-zip-reader.d.ts +10 -0
- package/dist/contract/trace/trace-zip-reader.d.ts.map +1 -0
- package/dist/contract/trace/trace-zip-reader.js +186 -0
- package/dist/contract/trace/trace-zip-reader.js.map +1 -0
- package/dist/contract/verify-approval-decisions.d.ts +5 -0
- package/dist/contract/verify-approval-decisions.d.ts.map +1 -0
- package/dist/contract/verify-approval-decisions.js +104 -0
- package/dist/contract/verify-approval-decisions.js.map +1 -0
- package/dist/decisions/append-decision.d.ts +12 -0
- package/dist/decisions/append-decision.d.ts.map +1 -0
- package/dist/decisions/append-decision.js +73 -0
- package/dist/decisions/append-decision.js.map +1 -0
- package/dist/decisions/confirmed-decision-commands.d.ts +41 -0
- package/dist/decisions/confirmed-decision-commands.d.ts.map +1 -0
- package/dist/decisions/confirmed-decision-commands.js +190 -0
- package/dist/decisions/confirmed-decision-commands.js.map +1 -0
- package/dist/decisions/decision-file.d.ts +31 -0
- package/dist/decisions/decision-file.d.ts.map +1 -0
- package/dist/decisions/decision-file.js +139 -0
- package/dist/decisions/decision-file.js.map +1 -0
- package/dist/decisions/decision-identity.d.ts +12 -0
- package/dist/decisions/decision-identity.d.ts.map +1 -0
- package/dist/decisions/decision-identity.js +78 -0
- package/dist/decisions/decision-identity.js.map +1 -0
- package/dist/decisions/decision-integrity-check.d.ts +12 -0
- package/dist/decisions/decision-integrity-check.d.ts.map +1 -0
- package/dist/decisions/decision-integrity-check.js +102 -0
- package/dist/decisions/decision-integrity-check.js.map +1 -0
- package/dist/decisions/decision-schema.d.ts +42 -0
- package/dist/decisions/decision-schema.d.ts.map +1 -0
- package/dist/decisions/decision-schema.js +75 -0
- package/dist/decisions/decision-schema.js.map +1 -0
- package/dist/decisions/decision-source.d.ts +2 -0
- package/dist/decisions/decision-source.d.ts.map +1 -0
- package/dist/decisions/decision-source.js +18 -0
- package/dist/decisions/decision-source.js.map +1 -0
- package/dist/decisions/exclusive-file-create.d.ts +3 -0
- package/dist/decisions/exclusive-file-create.d.ts.map +1 -0
- package/dist/decisions/exclusive-file-create.js +84 -0
- package/dist/decisions/exclusive-file-create.js.map +1 -0
- package/dist/decisions/git-head-trust.d.ts +7 -0
- package/dist/decisions/git-head-trust.d.ts.map +1 -0
- package/dist/decisions/git-head-trust.js +76 -0
- package/dist/decisions/git-head-trust.js.map +1 -0
- package/dist/decisions/human-confirmation.d.ts +35 -0
- package/dist/decisions/human-confirmation.d.ts.map +1 -0
- package/dist/decisions/human-confirmation.js +161 -0
- package/dist/decisions/human-confirmation.js.map +1 -0
- package/dist/decisions/legacy-waiver-migration.d.ts +31 -0
- package/dist/decisions/legacy-waiver-migration.d.ts.map +1 -0
- package/dist/decisions/legacy-waiver-migration.js +170 -0
- package/dist/decisions/legacy-waiver-migration.js.map +1 -0
- package/dist/decisions/read-decisions.d.ts +19 -0
- package/dist/decisions/read-decisions.d.ts.map +1 -0
- package/dist/decisions/read-decisions.js +90 -0
- package/dist/decisions/read-decisions.js.map +1 -0
- package/dist/decisions/viewpoint-baseline-adapter.d.ts +7 -0
- package/dist/decisions/viewpoint-baseline-adapter.d.ts.map +1 -0
- package/dist/decisions/viewpoint-baseline-adapter.js +109 -0
- package/dist/decisions/viewpoint-baseline-adapter.js.map +1 -0
- package/dist/decisions/waiver-decisions.d.ts +24 -0
- package/dist/decisions/waiver-decisions.d.ts.map +1 -0
- package/dist/decisions/waiver-decisions.js +46 -0
- package/dist/decisions/waiver-decisions.js.map +1 -0
- package/dist/exporters/csv-exporter.d.ts +4 -0
- package/dist/exporters/csv-exporter.d.ts.map +1 -1
- package/dist/exporters/csv-exporter.js +1 -1
- package/dist/exporters/csv-exporter.js.map +1 -1
- package/dist/exporters/feature-parser.d.ts +8 -0
- package/dist/exporters/feature-parser.d.ts.map +1 -1
- package/dist/exporters/feature-parser.js +12 -0
- package/dist/exporters/feature-parser.js.map +1 -1
- package/dist/exporters/matrix/build.d.ts +3 -0
- package/dist/exporters/matrix/build.d.ts.map +1 -1
- package/dist/exporters/matrix/build.js +59 -19
- package/dist/exporters/matrix/build.js.map +1 -1
- package/dist/exporters/matrix/carry-manual-results.d.ts +19 -0
- package/dist/exporters/matrix/carry-manual-results.d.ts.map +1 -1
- package/dist/exporters/matrix/carry-manual-results.js +76 -6
- package/dist/exporters/matrix/carry-manual-results.js.map +1 -1
- package/dist/exporters/matrix/export.d.ts +8 -2
- package/dist/exporters/matrix/export.d.ts.map +1 -1
- package/dist/exporters/matrix/export.js +16 -3
- package/dist/exporters/matrix/export.js.map +1 -1
- package/dist/exporters/matrix/gates.d.ts +6 -1
- package/dist/exporters/matrix/gates.d.ts.map +1 -1
- package/dist/exporters/matrix/gates.js +57 -128
- package/dist/exporters/matrix/gates.js.map +1 -1
- package/dist/exporters/matrix/render-csv.d.ts.map +1 -1
- package/dist/exporters/matrix/render-csv.js +4 -1
- package/dist/exporters/matrix/render-csv.js.map +1 -1
- package/dist/exporters/matrix/render-gate-overrides-sheet.d.ts +13 -0
- package/dist/exporters/matrix/render-gate-overrides-sheet.d.ts.map +1 -0
- package/dist/exporters/matrix/render-gate-overrides-sheet.js +44 -0
- package/dist/exporters/matrix/render-gate-overrides-sheet.js.map +1 -0
- package/dist/exporters/matrix/render-xlsx.d.ts +1 -0
- package/dist/exporters/matrix/render-xlsx.d.ts.map +1 -1
- package/dist/exporters/matrix/render-xlsx.js +6 -0
- package/dist/exporters/matrix/render-xlsx.js.map +1 -1
- package/dist/exporters/matrix/types.d.ts +9 -1
- package/dist/exporters/matrix/types.d.ts.map +1 -1
- package/dist/exporters/matrix/types.js.map +1 -1
- package/dist/exporters/matrix/xlsx-cells.js +1 -1
- package/dist/exporters/matrix/xlsx-cells.js.map +1 -1
- package/dist/exporters/playwright-report-parser.d.ts.map +1 -1
- package/dist/exporters/playwright-report-parser.js +1 -0
- package/dist/exporters/playwright-report-parser.js.map +1 -1
- package/dist/exporters/scenario-merger.d.ts.map +1 -1
- package/dist/exporters/scenario-merger.js +2 -0
- package/dist/exporters/scenario-merger.js.map +1 -1
- package/dist/exporters/types.d.ts +5 -0
- package/dist/exporters/types.d.ts.map +1 -1
- package/dist/generators/test-generator/code-generator.d.ts.map +1 -1
- package/dist/generators/test-generator/code-generator.js +13 -0
- package/dist/generators/test-generator/code-generator.js.map +1 -1
- package/dist/graph/evaluate-graph.d.ts +23 -0
- package/dist/graph/evaluate-graph.d.ts.map +1 -0
- package/dist/graph/evaluate-graph.js +52 -0
- package/dist/graph/evaluate-graph.js.map +1 -0
- package/dist/graph/locate-finding.d.ts +20 -0
- package/dist/graph/locate-finding.d.ts.map +1 -0
- package/dist/graph/locate-finding.js +137 -0
- package/dist/graph/locate-finding.js.map +1 -0
- package/dist/graph/node-decl.d.ts +66 -0
- package/dist/graph/node-decl.d.ts.map +1 -0
- package/dist/graph/node-decl.js +8 -0
- package/dist/graph/node-decl.js.map +1 -0
- package/dist/graph/nodes/core-node-catalog.d.ts +7 -0
- package/dist/graph/nodes/core-node-catalog.d.ts.map +1 -0
- package/dist/graph/nodes/core-node-catalog.js +13 -0
- package/dist/graph/nodes/core-node-catalog.js.map +1 -0
- package/dist/graph/nodes/test-case-contract-nodes.d.ts +21 -0
- package/dist/graph/nodes/test-case-contract-nodes.d.ts.map +1 -0
- package/dist/graph/nodes/test-case-contract-nodes.js +59 -0
- package/dist/graph/nodes/test-case-contract-nodes.js.map +1 -0
- package/dist/graph/nodes/test-case-nodes.d.ts +26 -0
- package/dist/graph/nodes/test-case-nodes.d.ts.map +1 -0
- package/dist/graph/nodes/test-case-nodes.js +66 -0
- package/dist/graph/nodes/test-case-nodes.js.map +1 -0
- package/dist/graph/nodes/unit-audit-nodes.d.ts +18 -0
- package/dist/graph/nodes/unit-audit-nodes.d.ts.map +1 -0
- package/dist/graph/nodes/unit-audit-nodes.js +95 -0
- package/dist/graph/nodes/unit-audit-nodes.js.map +1 -0
- package/dist/graph/nodes/unit-contract-node.d.ts +21 -0
- package/dist/graph/nodes/unit-contract-node.d.ts.map +1 -0
- package/dist/graph/nodes/unit-contract-node.js +88 -0
- package/dist/graph/nodes/unit-contract-node.js.map +1 -0
- package/dist/graph/nodes/unit-design-nodes.d.ts +14 -0
- package/dist/graph/nodes/unit-design-nodes.d.ts.map +1 -0
- package/dist/graph/nodes/unit-design-nodes.js +91 -0
- package/dist/graph/nodes/unit-design-nodes.js.map +1 -0
- package/dist/graph/nodes/unit-execution-nodes.d.ts +16 -0
- package/dist/graph/nodes/unit-execution-nodes.d.ts.map +1 -0
- package/dist/graph/nodes/unit-execution-nodes.js +79 -0
- package/dist/graph/nodes/unit-execution-nodes.js.map +1 -0
- package/dist/graph/test-case-identity.d.ts +13 -0
- package/dist/graph/test-case-identity.d.ts.map +1 -0
- package/dist/graph/test-case-identity.js +106 -0
- package/dist/graph/test-case-identity.js.map +1 -0
- package/dist/graph/test-case-results.d.ts +11 -0
- package/dist/graph/test-case-results.d.ts.map +1 -0
- package/dist/graph/test-case-results.js +137 -0
- package/dist/graph/test-case-results.js.map +1 -0
- package/dist/graph/test-case-satisfied.d.ts +25 -0
- package/dist/graph/test-case-satisfied.d.ts.map +1 -0
- package/dist/graph/test-case-satisfied.js +51 -0
- package/dist/graph/test-case-satisfied.js.map +1 -0
- package/dist/graph/test-case-state-shape.d.ts +67 -0
- package/dist/graph/test-case-state-shape.d.ts.map +1 -0
- package/dist/graph/test-case-state-shape.js +3 -0
- package/dist/graph/test-case-state-shape.js.map +1 -0
- package/dist/graph/test-case-states.d.ts +9 -0
- package/dist/graph/test-case-states.d.ts.map +1 -0
- package/dist/graph/test-case-states.js +116 -0
- package/dist/graph/test-case-states.js.map +1 -0
- package/dist/graph/unit-artifact-probes.d.ts +19 -0
- package/dist/graph/unit-artifact-probes.d.ts.map +1 -0
- package/dist/graph/unit-artifact-probes.js +133 -0
- package/dist/graph/unit-artifact-probes.js.map +1 -0
- package/dist/graph/unit-state-facts.d.ts +43 -0
- package/dist/graph/unit-state-facts.d.ts.map +1 -0
- package/dist/graph/unit-state-facts.js +87 -0
- package/dist/graph/unit-state-facts.js.map +1 -0
- package/dist/harness/audit.d.ts +11 -0
- package/dist/harness/audit.d.ts.map +1 -1
- package/dist/harness/audit.js +34 -9
- package/dist/harness/audit.js.map +1 -1
- package/dist/harness/catalog/gate-rules.yaml +647 -0
- package/dist/harness/counted-assertions.d.ts +2 -0
- package/dist/harness/counted-assertions.d.ts.map +1 -0
- package/dist/harness/counted-assertions.js +38 -0
- package/dist/harness/counted-assertions.js.map +1 -0
- package/dist/harness/defect-draft.d.ts +14 -0
- package/dist/harness/defect-draft.d.ts.map +1 -0
- package/dist/harness/defect-draft.js +143 -0
- package/dist/harness/defect-draft.js.map +1 -0
- package/dist/harness/defects.d.ts +24 -0
- package/dist/harness/defects.d.ts.map +1 -0
- package/dist/harness/defects.js +118 -0
- package/dist/harness/defects.js.map +1 -0
- package/dist/harness/gates/audit-gate-terms.d.ts +41 -0
- package/dist/harness/gates/audit-gate-terms.d.ts.map +1 -0
- package/dist/harness/gates/audit-gate-terms.js +72 -0
- package/dist/harness/gates/audit-gate-terms.js.map +1 -0
- package/dist/harness/gates/audit-gate-verdict.d.ts +31 -0
- package/dist/harness/gates/audit-gate-verdict.d.ts.map +1 -0
- package/dist/harness/gates/audit-gate-verdict.js +101 -0
- package/dist/harness/gates/audit-gate-verdict.js.map +1 -0
- package/dist/harness/gates/audit-input-hash.d.ts +4 -0
- package/dist/harness/gates/audit-input-hash.d.ts.map +1 -0
- package/dist/harness/gates/audit-input-hash.js +96 -0
- package/dist/harness/gates/audit-input-hash.js.map +1 -0
- package/dist/harness/gates/audit-report-verification.d.ts +41 -0
- package/dist/harness/gates/audit-report-verification.d.ts.map +1 -0
- package/dist/harness/gates/audit-report-verification.js +236 -0
- package/dist/harness/gates/audit-report-verification.js.map +1 -0
- package/dist/harness/gates/decisions-hash.d.ts +13 -0
- package/dist/harness/gates/decisions-hash.d.ts.map +1 -0
- package/dist/harness/gates/decisions-hash.js +72 -0
- package/dist/harness/gates/decisions-hash.js.map +1 -0
- package/dist/harness/gates/delivery-checklist.d.ts +21 -0
- package/dist/harness/gates/delivery-checklist.d.ts.map +1 -0
- package/dist/harness/gates/delivery-checklist.js +128 -0
- package/dist/harness/gates/delivery-checklist.js.map +1 -0
- package/dist/harness/gates/delivery-decision-gate.d.ts +33 -0
- package/dist/harness/gates/delivery-decision-gate.d.ts.map +1 -0
- package/dist/harness/gates/delivery-decision-gate.js +116 -0
- package/dist/harness/gates/delivery-decision-gate.js.map +1 -0
- package/dist/harness/gates/delivery-decision-step.d.ts +19 -0
- package/dist/harness/gates/delivery-decision-step.d.ts.map +1 -0
- package/dist/harness/gates/delivery-decision-step.js +86 -0
- package/dist/harness/gates/delivery-decision-step.js.map +1 -0
- package/dist/harness/gates/gate-override-report.d.ts +59 -0
- package/dist/harness/gates/gate-override-report.d.ts.map +1 -0
- package/dist/harness/gates/gate-override-report.js +86 -0
- package/dist/harness/gates/gate-override-report.js.map +1 -0
- package/dist/harness/gates/gate-override-scope.d.ts +18 -0
- package/dist/harness/gates/gate-override-scope.d.ts.map +1 -0
- package/dist/harness/gates/gate-override-scope.js +98 -0
- package/dist/harness/gates/gate-override-scope.js.map +1 -0
- package/dist/harness/gates/gate-override-validation.d.ts +25 -0
- package/dist/harness/gates/gate-override-validation.d.ts.map +1 -0
- package/dist/harness/gates/gate-override-validation.js +81 -0
- package/dist/harness/gates/gate-override-validation.js.map +1 -0
- package/dist/harness/gates/gate-rule-catalog.d.ts +24 -0
- package/dist/harness/gates/gate-rule-catalog.d.ts.map +1 -0
- package/dist/harness/gates/gate-rule-catalog.js +112 -0
- package/dist/harness/gates/gate-rule-catalog.js.map +1 -0
- package/dist/harness/gates/gate-terms.d.ts +33 -0
- package/dist/harness/gates/gate-terms.d.ts.map +1 -0
- package/dist/harness/gates/gate-terms.js +41 -0
- package/dist/harness/gates/gate-terms.js.map +1 -0
- package/dist/harness/gates/in-run-decision-check.d.ts +21 -0
- package/dist/harness/gates/in-run-decision-check.d.ts.map +1 -0
- package/dist/harness/gates/in-run-decision-check.js +138 -0
- package/dist/harness/gates/in-run-decision-check.js.map +1 -0
- package/dist/harness/gates/journey-gate-inputs.d.ts +37 -0
- package/dist/harness/gates/journey-gate-inputs.d.ts.map +1 -0
- package/dist/harness/gates/journey-gate-inputs.js +81 -0
- package/dist/harness/gates/journey-gate-inputs.js.map +1 -0
- package/dist/harness/gates/migrated-waiver-review.d.ts +6 -0
- package/dist/harness/gates/migrated-waiver-review.d.ts.map +1 -0
- package/dist/harness/gates/migrated-waiver-review.js +116 -0
- package/dist/harness/gates/migrated-waiver-review.js.map +1 -0
- package/dist/harness/gates/resolve-rule.d.ts +59 -0
- package/dist/harness/gates/resolve-rule.d.ts.map +1 -0
- package/dist/harness/gates/resolve-rule.js +41 -0
- package/dist/harness/gates/resolve-rule.js.map +1 -0
- package/dist/harness/gates/unit-gate-overrides.d.ts +25 -0
- package/dist/harness/gates/unit-gate-overrides.d.ts.map +1 -0
- package/dist/harness/gates/unit-gate-overrides.js +122 -0
- package/dist/harness/gates/unit-gate-overrides.js.map +1 -0
- package/dist/harness/journey.d.ts +43 -1
- package/dist/harness/journey.d.ts.map +1 -1
- package/dist/harness/journey.js +207 -33
- package/dist/harness/journey.js.map +1 -1
- package/dist/harness/manifest.d.ts +12 -0
- package/dist/harness/manifest.d.ts.map +1 -1
- package/dist/harness/manifest.js +73 -45
- package/dist/harness/manifest.js.map +1 -1
- package/dist/harness/next-step.d.ts +37 -6
- package/dist/harness/next-step.d.ts.map +1 -1
- package/dist/harness/next-step.js +34 -174
- package/dist/harness/next-step.js.map +1 -1
- package/dist/harness/parse.d.ts +25 -0
- package/dist/harness/parse.d.ts.map +1 -1
- package/dist/harness/parse.js +38 -13
- package/dist/harness/parse.js.map +1 -1
- package/dist/harness/provenance.d.ts +29 -0
- package/dist/harness/provenance.d.ts.map +1 -1
- package/dist/harness/provenance.js +109 -9
- package/dist/harness/provenance.js.map +1 -1
- package/dist/harness/script-check.d.ts.map +1 -1
- package/dist/harness/script-check.js +29 -6
- package/dist/harness/script-check.js.map +1 -1
- package/dist/harness/sensors.d.ts +14 -3
- package/dist/harness/sensors.d.ts.map +1 -1
- package/dist/harness/sensors.js +64 -44
- package/dist/harness/sensors.js.map +1 -1
- package/dist/harness/triage-signals.d.ts +20 -0
- package/dist/harness/triage-signals.d.ts.map +1 -0
- package/dist/harness/triage-signals.js +30 -0
- package/dist/harness/triage-signals.js.map +1 -0
- package/dist/harness/triage-unit.d.ts +23 -0
- package/dist/harness/triage-unit.d.ts.map +1 -0
- package/dist/harness/triage-unit.js +142 -0
- package/dist/harness/triage-unit.js.map +1 -0
- package/dist/harness/triage.d.ts +28 -0
- package/dist/harness/triage.d.ts.map +1 -0
- package/dist/harness/triage.js +99 -0
- package/dist/harness/triage.js.map +1 -0
- package/dist/harness/viewpoint-ledger.d.ts.map +1 -1
- package/dist/harness/viewpoint-ledger.js +2 -1
- package/dist/harness/viewpoint-ledger.js.map +1 -1
- package/dist/hooks/bash-write-targets.d.ts +15 -0
- package/dist/hooks/bash-write-targets.d.ts.map +1 -0
- package/dist/hooks/bash-write-targets.js +173 -0
- package/dist/hooks/bash-write-targets.js.map +1 -0
- package/dist/hooks/claude-hook-entry.d.ts +11 -0
- package/dist/hooks/claude-hook-entry.d.ts.map +1 -0
- package/dist/hooks/claude-hook-entry.js +137 -0
- package/dist/hooks/claude-hook-entry.js.map +1 -0
- package/dist/hooks/claude-transcript-turn.d.ts +11 -0
- package/dist/hooks/claude-transcript-turn.d.ts.map +1 -0
- package/dist/hooks/claude-transcript-turn.js +143 -0
- package/dist/hooks/claude-transcript-turn.js.map +1 -0
- package/dist/hooks/guard-delivery-contract-rule.d.ts +10 -0
- package/dist/hooks/guard-delivery-contract-rule.d.ts.map +1 -0
- package/dist/hooks/guard-delivery-contract-rule.js +152 -0
- package/dist/hooks/guard-delivery-contract-rule.js.map +1 -0
- package/dist/hooks/guard-protected-paths.d.ts +9 -0
- package/dist/hooks/guard-protected-paths.d.ts.map +1 -0
- package/dist/hooks/guard-protected-paths.js +107 -0
- package/dist/hooks/guard-protected-paths.js.map +1 -0
- package/dist/hooks/guard-rules.d.ts +37 -0
- package/dist/hooks/guard-rules.d.ts.map +1 -0
- package/dist/hooks/guard-rules.js +61 -0
- package/dist/hooks/guard-rules.js.map +1 -0
- package/dist/hooks/guard-tty-spoof-rule.d.ts +15 -0
- package/dist/hooks/guard-tty-spoof-rule.d.ts.map +1 -0
- package/dist/hooks/guard-tty-spoof-rule.js +21 -0
- package/dist/hooks/guard-tty-spoof-rule.js.map +1 -0
- package/dist/hooks/hand-back-core.d.ts +39 -0
- package/dist/hooks/hand-back-core.d.ts.map +1 -0
- package/dist/hooks/hand-back-core.js +122 -0
- package/dist/hooks/hand-back-core.js.map +1 -0
- package/dist/hooks/hook-cli-command.d.ts +9 -0
- package/dist/hooks/hook-cli-command.d.ts.map +1 -0
- package/dist/hooks/hook-cli-command.js +46 -0
- package/dist/hooks/hook-cli-command.js.map +1 -0
- package/dist/hooks/hook-registry.d.ts +10 -0
- package/dist/hooks/hook-registry.d.ts.map +1 -0
- package/dist/hooks/hook-registry.js +24 -0
- package/dist/hooks/hook-registry.js.map +1 -0
- package/dist/hooks/hook-result-contract.d.ts +50 -0
- package/dist/hooks/hook-result-contract.d.ts.map +1 -0
- package/dist/hooks/hook-result-contract.js +80 -0
- package/dist/hooks/hook-result-contract.js.map +1 -0
- package/dist/hooks/hook-toggles.d.ts +10 -0
- package/dist/hooks/hook-toggles.d.ts.map +1 -0
- package/dist/hooks/hook-toggles.js +107 -0
- package/dist/hooks/hook-toggles.js.map +1 -0
- package/dist/hooks/pre-tool-guard-hook.d.ts +10 -0
- package/dist/hooks/pre-tool-guard-hook.d.ts.map +1 -0
- package/dist/hooks/pre-tool-guard-hook.js +105 -0
- package/dist/hooks/pre-tool-guard-hook.js.map +1 -0
- package/dist/hooks/session-start-unit-state-hook.d.ts +13 -0
- package/dist/hooks/session-start-unit-state-hook.d.ts.map +1 -0
- package/dist/hooks/session-start-unit-state-hook.js +57 -0
- package/dist/hooks/session-start-unit-state-hook.js.map +1 -0
- package/dist/hooks/stop-next-step-hook.d.ts +16 -0
- package/dist/hooks/stop-next-step-hook.d.ts.map +1 -0
- package/dist/hooks/stop-next-step-hook.js +42 -0
- package/dist/hooks/stop-next-step-hook.js.map +1 -0
- package/dist/hooks/sungen-project-root.d.ts +19 -0
- package/dist/hooks/sungen-project-root.d.ts.map +1 -0
- package/dist/hooks/sungen-project-root.js +130 -0
- package/dist/hooks/sungen-project-root.js.map +1 -0
- package/dist/orchestrator/ai-skills/command-partial-includes.d.ts +15 -0
- package/dist/orchestrator/ai-skills/command-partial-includes.d.ts.map +1 -0
- package/dist/orchestrator/ai-skills/command-partial-includes.js +106 -0
- package/dist/orchestrator/ai-skills/command-partial-includes.js.map +1 -0
- package/dist/orchestrator/ai-skills/commands.d.ts +5 -1
- package/dist/orchestrator/ai-skills/commands.d.ts.map +1 -1
- package/dist/orchestrator/ai-skills/commands.js +16 -6
- package/dist/orchestrator/ai-skills/commands.js.map +1 -1
- package/dist/orchestrator/ai-skills/golden-skills.test.js +29 -0
- package/dist/orchestrator/ai-skills/golden-skills.test.js.map +1 -1
- package/dist/orchestrator/assets-drift.d.ts +5 -0
- package/dist/orchestrator/assets-drift.d.ts.map +1 -1
- package/dist/orchestrator/assets-drift.js +12 -3
- package/dist/orchestrator/assets-drift.js.map +1 -1
- package/dist/orchestrator/claude-hooks-install.d.ts +27 -0
- package/dist/orchestrator/claude-hooks-install.d.ts.map +1 -0
- package/dist/orchestrator/claude-hooks-install.js +203 -0
- package/dist/orchestrator/claude-hooks-install.js.map +1 -0
- package/dist/orchestrator/lockstep-version.d.ts +15 -5
- package/dist/orchestrator/lockstep-version.d.ts.map +1 -1
- package/dist/orchestrator/lockstep-version.js +30 -17
- package/dist/orchestrator/lockstep-version.js.map +1 -1
- package/dist/orchestrator/project-initializer.d.ts +6 -0
- package/dist/orchestrator/project-initializer.d.ts.map +1 -1
- package/dist/orchestrator/project-initializer.js +24 -0
- package/dist/orchestrator/project-initializer.js.map +1 -1
- package/dist/orchestrator/screen-manager.d.ts.map +1 -1
- package/dist/orchestrator/screen-manager.js +4 -2
- package/dist/orchestrator/screen-manager.js.map +1 -1
- package/dist/orchestrator/templates/ai-src/commands/add-api.md +2 -0
- package/dist/orchestrator/templates/ai-src/commands/add-flow.md +2 -0
- package/dist/orchestrator/templates/ai-src/commands/add-screen.md +2 -0
- package/dist/orchestrator/templates/ai-src/commands/create-data-test.md +1 -0
- package/dist/orchestrator/templates/ai-src/commands/create-test.md +14 -16
- package/dist/orchestrator/templates/ai-src/commands/dashboard.md +2 -0
- package/dist/orchestrator/templates/ai-src/commands/delivery.md +2 -0
- package/dist/orchestrator/templates/ai-src/commands/design.md +2 -0
- package/dist/orchestrator/templates/ai-src/commands/feedback.md +2 -0
- package/dist/orchestrator/templates/ai-src/commands/locale.md +2 -0
- package/dist/orchestrator/templates/ai-src/commands/review.md +2 -0
- package/dist/orchestrator/templates/ai-src/commands/run-test.md +14 -10
- package/dist/orchestrator/templates/ai-src/config/claude.md +20 -7
- package/dist/orchestrator/templates/ai-src/config/copilot.md +20 -7
- package/dist/orchestrator/templates/ai-src/partials/hand-back-rule.claude.md +20 -0
- package/dist/orchestrator/templates/ai-src/partials/hand-back-rule.codex.md +20 -0
- package/dist/orchestrator/templates/ai-src/partials/hand-back-rule.copilot.md +20 -0
- package/dist/orchestrator/templates/ai-src/skills/sungen-api-coverage-model/SKILL.md +1 -1
- package/dist/orchestrator/templates/ai-src/skills/sungen-capture-mobile/SKILL.md +3 -3
- package/dist/orchestrator/templates/ai-src/skills/sungen-data-factory/SKILL.md +9 -0
- package/dist/orchestrator/templates/ai-src/skills/sungen-error-mapping/SKILL.md +2 -0
- package/dist/orchestrator/templates/ai-src/skills/sungen-gherkin-syntax/SKILL.md +1 -1
- package/dist/orchestrator/templates/ai-src/skills/sungen-harness-audit/SKILL.md +2 -0
- package/dist/orchestrator/templates/claude-hooks/sungen-hook.cjs +64 -0
- package/dist/utils/safe-yaml-parse.d.ts +8 -0
- package/dist/utils/safe-yaml-parse.d.ts.map +1 -0
- package/dist/utils/safe-yaml-parse.js +51 -0
- package/dist/utils/safe-yaml-parse.js.map +1 -0
- package/package.json +3 -3
- package/src/basis/anchor-grammar.ts +83 -0
- package/src/basis/anchor-hash.ts +27 -0
- package/src/basis/bm51-tables.ts +65 -0
- package/src/basis/extract-spec-anchors.ts +95 -0
- package/src/basis/migration-sentinel.ts +175 -0
- package/src/basis/spec-frontmatter.ts +68 -0
- package/src/basis/spec-sections.ts +35 -0
- package/src/basis/test-basis-schema.ts +81 -0
- package/src/capabilities/discover.ts +15 -2
- package/src/capabilities/registry.ts +16 -0
- package/src/cli/commands/audit.ts +10 -5
- package/src/cli/commands/capability.ts +4 -2
- package/src/cli/commands/contract-derive-command.ts +61 -0
- package/src/cli/commands/contract.ts +130 -0
- package/src/cli/commands/defect.ts +49 -0
- package/src/cli/commands/delivery.ts +92 -15
- package/src/cli/commands/generate.ts +3 -1
- package/src/cli/commands/journey.ts +41 -16
- package/src/cli/commands/next.ts +64 -10
- package/src/cli/commands/triage.ts +57 -0
- package/src/cli/commands/update.ts +35 -8
- package/src/cli/index.ts +24 -30
- package/src/cli/project-sungen-invocation.ts +39 -0
- package/src/contract/approve-contract.ts +94 -0
- package/src/contract/assertion-matcher-vocabulary.ts +93 -0
- package/src/contract/build-contract-verdict.ts +108 -0
- package/src/contract/build-trace-conformance.ts +100 -0
- package/src/contract/classify-contract-change.ts +92 -0
- package/src/contract/classify-contract-unit.ts +47 -0
- package/src/contract/classify-trace-conformance.ts +116 -0
- package/src/contract/contract-git.ts +106 -0
- package/src/contract/contract-hash.ts +34 -0
- package/src/contract/contract-report-file.ts +25 -0
- package/src/contract/contract-schema.ts +137 -0
- package/src/contract/contract-units.ts +52 -0
- package/src/contract/contracts-lock-file.ts +97 -0
- package/src/contract/data-normalize.ts +66 -0
- package/src/contract/derive-free-feature.ts +120 -0
- package/src/contract/field-semantic-redaction.ts +80 -0
- package/src/contract/free-feature-blocks.ts +56 -0
- package/src/contract/lock-secret-guard.ts +95 -0
- package/src/contract/lock-vs-git-head.ts +38 -0
- package/src/contract/normalize-contract-step.ts +74 -0
- package/src/contract/normalize-feature-steps.ts +93 -0
- package/src/contract/oracle-strength.ts +80 -0
- package/src/contract/project-contract-blockers.ts +36 -0
- package/src/contract/record-contract-approval.ts +70 -0
- package/src/contract/render-free-gherkin.ts +113 -0
- package/src/contract/render-trace-sentence.ts +98 -0
- package/src/contract/render-trace-target.ts +68 -0
- package/src/contract/resolve-unit-trace-identities.ts +35 -0
- package/src/contract/trace/collect-trace-evidence.ts +109 -0
- package/src/contract/trace/identity-cache.ts +61 -0
- package/src/contract/trace/select-final-attempt.ts +70 -0
- package/src/contract/trace/snapshot-html-renderer.ts +136 -0
- package/src/contract/trace/snapshot-resolver.ts +66 -0
- package/src/contract/trace/snapshot-store.ts +151 -0
- package/src/contract/trace/target-identity.ts +104 -0
- package/src/contract/trace/test-step-join.ts +104 -0
- package/src/contract/trace/trace-binding.ts +56 -0
- package/src/contract/trace/trace-file-reader.ts +133 -0
- package/src/contract/trace/trace-fingerprint.ts +68 -0
- package/src/contract/trace/trace-identity-resolution.ts +74 -0
- package/src/contract/trace/trace-run-record.ts +80 -0
- package/src/contract/trace/trace-steps.ts +88 -0
- package/src/contract/trace/trace-zip-limits.ts +77 -0
- package/src/contract/trace/trace-zip-reader.ts +127 -0
- package/src/contract/verify-approval-decisions.ts +60 -0
- package/src/decisions/append-decision.ts +35 -0
- package/src/decisions/confirmed-decision-commands.ts +149 -0
- package/src/decisions/decision-file.ts +93 -0
- package/src/decisions/decision-identity.ts +73 -0
- package/src/decisions/decision-integrity-check.ts +61 -0
- package/src/decisions/decision-schema.ts +76 -0
- package/src/decisions/decision-source.ts +14 -0
- package/src/decisions/exclusive-file-create.ts +42 -0
- package/src/decisions/git-head-trust.ts +32 -0
- package/src/decisions/human-confirmation.ts +119 -0
- package/src/decisions/legacy-waiver-migration.ts +135 -0
- package/src/decisions/read-decisions.ts +61 -0
- package/src/decisions/viewpoint-baseline-adapter.ts +74 -0
- package/src/decisions/waiver-decisions.ts +56 -0
- package/src/exporters/csv-exporter.ts +5 -1
- package/src/exporters/feature-parser.ts +12 -0
- package/src/exporters/matrix/build.ts +62 -18
- package/src/exporters/matrix/carry-manual-results.ts +70 -7
- package/src/exporters/matrix/export.ts +17 -2
- package/src/exporters/matrix/gates.ts +62 -129
- package/src/exporters/matrix/render-csv.ts +4 -1
- package/src/exporters/matrix/render-gate-overrides-sheet.ts +50 -0
- package/src/exporters/matrix/render-xlsx.ts +6 -0
- package/src/exporters/matrix/types.ts +9 -1
- package/src/exporters/matrix/xlsx-cells.ts +1 -1
- package/src/exporters/playwright-report-parser.ts +1 -0
- package/src/exporters/scenario-merger.ts +2 -1
- package/src/exporters/types.ts +5 -0
- package/src/generators/test-generator/code-generator.ts +13 -0
- package/src/graph/evaluate-graph.ts +66 -0
- package/src/graph/locate-finding.ts +93 -0
- package/src/graph/node-decl.ts +72 -0
- package/src/graph/nodes/core-node-catalog.ts +15 -0
- package/src/graph/nodes/test-case-contract-nodes.ts +74 -0
- package/src/graph/nodes/test-case-nodes.ts +68 -0
- package/src/graph/nodes/unit-audit-nodes.ts +95 -0
- package/src/graph/nodes/unit-contract-node.ts +89 -0
- package/src/graph/nodes/unit-design-nodes.ts +89 -0
- package/src/graph/nodes/unit-execution-nodes.ts +77 -0
- package/src/graph/test-case-identity.ts +75 -0
- package/src/graph/test-case-results.ts +90 -0
- package/src/graph/test-case-satisfied.ts +55 -0
- package/src/graph/test-case-state-shape.ts +62 -0
- package/src/graph/test-case-states.ts +84 -0
- package/src/graph/unit-artifact-probes.ts +77 -0
- package/src/graph/unit-state-facts.ts +112 -0
- package/src/harness/audit.ts +46 -11
- package/src/harness/catalog/gate-rules.yaml +647 -0
- package/src/harness/counted-assertions.ts +33 -0
- package/src/harness/defect-draft.ts +115 -0
- package/src/harness/defects.ts +94 -0
- package/src/harness/gates/audit-gate-terms.ts +80 -0
- package/src/harness/gates/audit-gate-verdict.ts +78 -0
- package/src/harness/gates/audit-input-hash.ts +55 -0
- package/src/harness/gates/audit-report-verification.ts +201 -0
- package/src/harness/gates/decisions-hash.ts +42 -0
- package/src/harness/gates/delivery-checklist.ts +120 -0
- package/src/harness/gates/delivery-decision-gate.ts +139 -0
- package/src/harness/gates/delivery-decision-step.ts +89 -0
- package/src/harness/gates/gate-override-report.ts +114 -0
- package/src/harness/gates/gate-override-scope.ts +66 -0
- package/src/harness/gates/gate-override-validation.ts +75 -0
- package/src/harness/gates/gate-rule-catalog.ts +82 -0
- package/src/harness/gates/gate-terms.ts +59 -0
- package/src/harness/gates/in-run-decision-check.ts +99 -0
- package/src/harness/gates/journey-gate-inputs.ts +89 -0
- package/src/harness/gates/migrated-waiver-review.ts +68 -0
- package/src/harness/gates/resolve-rule.ts +94 -0
- package/src/harness/gates/unit-gate-overrides.ts +99 -0
- package/src/harness/journey.ts +215 -38
- package/src/harness/manifest.ts +69 -45
- package/src/harness/next-step.ts +60 -170
- package/src/harness/parse.ts +40 -13
- package/src/harness/provenance.ts +99 -11
- package/src/harness/script-check.ts +27 -6
- package/src/harness/sensors.ts +73 -47
- package/src/harness/triage-signals.ts +46 -0
- package/src/harness/triage-unit.ts +113 -0
- package/src/harness/triage.ts +127 -0
- package/src/harness/viewpoint-ledger.ts +2 -2
- package/src/hooks/bash-write-targets.ts +109 -0
- package/src/hooks/claude-hook-entry.ts +85 -0
- package/src/hooks/claude-transcript-turn.ts +96 -0
- package/src/hooks/guard-delivery-contract-rule.ts +110 -0
- package/src/hooks/guard-protected-paths.ts +71 -0
- package/src/hooks/guard-rules.ts +98 -0
- package/src/hooks/guard-tty-spoof-rule.ts +31 -0
- package/src/hooks/hand-back-core.ts +112 -0
- package/src/hooks/hook-cli-command.ts +17 -0
- package/src/hooks/hook-registry.ts +24 -0
- package/src/hooks/hook-result-contract.ts +114 -0
- package/src/hooks/hook-toggles.ts +66 -0
- package/src/hooks/pre-tool-guard-hook.ts +67 -0
- package/src/hooks/session-start-unit-state-hook.ts +56 -0
- package/src/hooks/stop-next-step-hook.ts +37 -0
- package/src/hooks/sungen-project-root.ts +80 -0
- package/src/orchestrator/ai-skills/command-partial-includes.ts +72 -0
- package/src/orchestrator/ai-skills/commands.ts +22 -8
- package/src/orchestrator/ai-skills/golden-skills.test.ts +23 -0
- package/src/orchestrator/assets-drift.ts +12 -5
- package/src/orchestrator/claude-hooks-install.ts +168 -0
- package/src/orchestrator/lockstep-version.ts +28 -15
- package/src/orchestrator/project-initializer.ts +22 -0
- package/src/orchestrator/screen-manager.ts +3 -1
- package/src/orchestrator/templates/ai-src/commands/add-api.md +2 -0
- package/src/orchestrator/templates/ai-src/commands/add-flow.md +2 -0
- package/src/orchestrator/templates/ai-src/commands/add-screen.md +2 -0
- package/src/orchestrator/templates/ai-src/commands/create-data-test.md +1 -0
- package/src/orchestrator/templates/ai-src/commands/create-test.md +14 -16
- package/src/orchestrator/templates/ai-src/commands/dashboard.md +2 -0
- package/src/orchestrator/templates/ai-src/commands/delivery.md +2 -0
- package/src/orchestrator/templates/ai-src/commands/design.md +2 -0
- package/src/orchestrator/templates/ai-src/commands/feedback.md +2 -0
- package/src/orchestrator/templates/ai-src/commands/locale.md +2 -0
- package/src/orchestrator/templates/ai-src/commands/review.md +2 -0
- package/src/orchestrator/templates/ai-src/commands/run-test.md +14 -10
- package/src/orchestrator/templates/ai-src/config/claude.md +20 -7
- package/src/orchestrator/templates/ai-src/config/copilot.md +20 -7
- package/src/orchestrator/templates/ai-src/partials/hand-back-rule.claude.md +20 -0
- package/src/orchestrator/templates/ai-src/partials/hand-back-rule.codex.md +20 -0
- package/src/orchestrator/templates/ai-src/partials/hand-back-rule.copilot.md +20 -0
- package/src/orchestrator/templates/ai-src/skills/sungen-api-coverage-model/SKILL.md +1 -1
- package/src/orchestrator/templates/ai-src/skills/sungen-capture-mobile/SKILL.md +3 -3
- package/src/orchestrator/templates/ai-src/skills/sungen-data-factory/SKILL.md +9 -0
- package/src/orchestrator/templates/ai-src/skills/sungen-error-mapping/SKILL.md +2 -0
- package/src/orchestrator/templates/ai-src/skills/sungen-gherkin-syntax/SKILL.md +1 -1
- package/src/orchestrator/templates/ai-src/skills/sungen-harness-audit/SKILL.md +2 -0
- package/src/orchestrator/templates/claude-hooks/sungen-hook.cjs +64 -0
- package/src/utils/safe-yaml-parse.ts +44 -0
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A trace → its contract fingerprint: the rendered Gherkin steps normalized by the SAME parser and
|
|
3
|
+
* normalizer a hand-written `.feature` goes through (DOS-14.1), then hashed with the implementation
|
|
4
|
+
* path (DOS-14.2). So the fingerprint of a trace and the lock of the `.free.feature` derived from it
|
|
5
|
+
* are equal by construction — not by a second, parallel normalizer that could drift.
|
|
6
|
+
*
|
|
7
|
+
* The rendered steps are written to a throw-away `.feature` and read back with
|
|
8
|
+
* `scenariosOfFeatureFile`; nothing else in the pipeline parses Gherkin.
|
|
9
|
+
*/
|
|
10
|
+
import * as fs from 'fs';
|
|
11
|
+
import * as os from 'os';
|
|
12
|
+
import * as path from 'path';
|
|
13
|
+
import { contractHash } from '../contract-hash';
|
|
14
|
+
import type { ContractStep, Implementation } from '../contract-schema';
|
|
15
|
+
import { scenariosOfFeatureFile } from '../normalize-feature-steps';
|
|
16
|
+
import type { RenderedStep } from '../render-free-gherkin';
|
|
17
|
+
|
|
18
|
+
const KEYWORD = /^(Given|When|Then|And|But)\s/;
|
|
19
|
+
|
|
20
|
+
/** Normalize several step lists in one parse; `lines[i]` are full Gherkin lines (keyword first). */
|
|
21
|
+
export function normalizeGherkinLines(blocks: string[][]): ContractStep[][] {
|
|
22
|
+
if (!blocks.length) return [];
|
|
23
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'sungen-fingerprint-'));
|
|
24
|
+
try {
|
|
25
|
+
const file = path.join(dir, 'fingerprint.feature');
|
|
26
|
+
const text = ['Feature: fingerprint', '', ...blocks.flatMap((lines, i) => [
|
|
27
|
+
` Scenario: FP-${i + 1}0`, ...lines.map((l) => ` ${KEYWORD.test(l.trim()) ? l.trim() : `When ${l.trim()}`}`), '',
|
|
28
|
+
])].join('\n');
|
|
29
|
+
fs.writeFileSync(file, text);
|
|
30
|
+
const parsed = scenariosOfFeatureFile(file);
|
|
31
|
+
if (parsed.length !== blocks.length) throw new Error(`fingerprint: ${blocks.length} step lists normalized to ${parsed.length} scenarios`);
|
|
32
|
+
return parsed.map((s) => s.steps);
|
|
33
|
+
} finally {
|
|
34
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface TraceFingerprint {
|
|
39
|
+
steps: ContractStep[];
|
|
40
|
+
hash: string;
|
|
41
|
+
rendered: RenderedStep[];
|
|
42
|
+
/** impl:code: each `test.step` title (normalized) and the indexes of the steps it holds. */
|
|
43
|
+
groups: Array<{ title: string; titleStep: ContractStep; members: number[] }>;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Many fingerprints from ONE temp-file parse (a unit's scenarios × runs) — the guard path's cost. */
|
|
47
|
+
export function fingerprintsOf(items: Array<{ rendered: RenderedStep[]; implementation: Implementation }>): TraceFingerprint[] {
|
|
48
|
+
const blocks: string[][] = [];
|
|
49
|
+
const plan = items.map(({ rendered, implementation }) => {
|
|
50
|
+
const titles = implementation === 'impl:code' ? [...new Set(rendered.map((r) => r.group).filter((g): g is string => !!g))] : [];
|
|
51
|
+
const at = blocks.push(rendered.map((r) => `${r.keyword} ${r.sentence}`)) - 1;
|
|
52
|
+
const titlesAt = titles.length ? blocks.push(titles) - 1 : -1;
|
|
53
|
+
return { at, titlesAt, titles };
|
|
54
|
+
});
|
|
55
|
+
const normalized = normalizeGherkinLines(blocks);
|
|
56
|
+
return items.map(({ rendered, implementation }, i) => {
|
|
57
|
+
const { at, titlesAt, titles } = plan[i];
|
|
58
|
+
const steps = normalized[at];
|
|
59
|
+
const titleSteps = titlesAt >= 0 ? normalized[titlesAt] : [];
|
|
60
|
+
const groups = titles.map((title, k) => ({
|
|
61
|
+
title, titleStep: titleSteps[k], members: rendered.map((r, j) => (r.group === title ? j : -1)).filter((j) => j >= 0),
|
|
62
|
+
}));
|
|
63
|
+
return { steps, hash: contractHash(implementation, steps), rendered, groups };
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export const fingerprintOf = (rendered: RenderedStep[], implementation: Implementation): TraceFingerprint =>
|
|
68
|
+
fingerprintsOf([{ rendered, implementation }])[0];
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Every target of a trace → its identity (DOS-14.5): the driver's answer from the identity cache
|
|
3
|
+
* when one exists for the exact HTML core rendered, otherwise core's heuristic (always `low`).
|
|
4
|
+
*
|
|
5
|
+
* `resolveWithDriver` is the only asynchronous part of trace conformance: it renders the snapshots,
|
|
6
|
+
* asks the registered `traceIdentity` resolver (driver-ui: a real browser, JS off, every request
|
|
7
|
+
* blocked) for the ones not cached yet, and writes the cache. Everything else stays a plain
|
|
8
|
+
* function of the files on disk, so the CLI and the Claude Code guard build the same verdict.
|
|
9
|
+
*/
|
|
10
|
+
import { CachedIdentity, IdentityCache, readIdentityCache, writeIdentityCache } from './identity-cache';
|
|
11
|
+
import { resolveCallTarget, CallTarget } from './snapshot-resolver';
|
|
12
|
+
import { SnapshotStore } from './snapshot-store';
|
|
13
|
+
import { heuristicIdentity, TraceIdentityRequest, TraceIdentityResolver, TRUSTED_RESOLVER, validIdentityResult } from './target-identity';
|
|
14
|
+
import type { ParsedTrace } from './trace-file-reader';
|
|
15
|
+
import { traceIntentSteps, IntentStep } from './trace-steps';
|
|
16
|
+
import type { ResolvedTarget } from '../render-free-gherkin';
|
|
17
|
+
|
|
18
|
+
const NO_DRIVER = 'no driver identity yet — run `sungen contract check --trace` (needs @sungen/driver-ui and a browser)';
|
|
19
|
+
|
|
20
|
+
/** Steps aimed at the page itself carry no element identity. */
|
|
21
|
+
export const isPageLevel = (s: IntentStep): boolean =>
|
|
22
|
+
s.method === 'goto' || /^keyboard/.test(s.method) || /^to\.have\.(url|title)$/.test(String(s.params.expression ?? '')) || s.kind.startsWith('opaque');
|
|
23
|
+
|
|
24
|
+
function targetCalls(trace: ParsedTrace, steps: IntentStep[]): Array<{ callId: string; target: CallTarget | null }> {
|
|
25
|
+
const store = new SnapshotStore(trace.library);
|
|
26
|
+
return steps.filter((s) => s.callId && !isPageLevel(s)).map((s) => ({ callId: s.callId!, target: resolveCallTarget(store, trace, s.callId!) }));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function identitiesFor(trace: ParsedTrace, steps: IntentStep[], cache: IdentityCache): Map<string, ResolvedTarget> {
|
|
30
|
+
const out = new Map<string, ResolvedTarget>();
|
|
31
|
+
for (const { callId, target } of targetCalls(trace, steps)) {
|
|
32
|
+
if (!target) { out.set(callId, { identity: heuristicIdentity(null, 'no snapshot marks this target') }); continue; }
|
|
33
|
+
const hit = cache.entries.get(target.snapshotSha256);
|
|
34
|
+
if (!hit) { out.set(callId, { identity: heuristicIdentity(target.facts, NO_DRIVER), facts: target.facts }); continue; }
|
|
35
|
+
// What core already knows (missing CSS, broken refs) caps the driver's answer, whatever it says.
|
|
36
|
+
const reasons = [...target.capReasons, ...hit.reasons];
|
|
37
|
+
out.set(callId, {
|
|
38
|
+
identity: { ...hit, identityConfidence: reasons.length ? 'low' : hit.identityConfidence, reasons, source: 'driver' },
|
|
39
|
+
facts: target.facts,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
return out;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** One replay request per element target of a trace, keyed by the sha256 of its rendered HTML. */
|
|
46
|
+
export function identityRequestsFor(trace: ParsedTrace): TraceIdentityRequest[] {
|
|
47
|
+
return targetCalls(trace, traceIntentSteps(trace)).filter((t) => t.target).map(({ callId, target }) => ({
|
|
48
|
+
key: target!.snapshotSha256, html: target!.html, viewport: target!.viewport,
|
|
49
|
+
targetSelector: `[__playwright_target__="${callId}"]`, capReasons: target!.capReasons,
|
|
50
|
+
}));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Ask the driver about every not-yet-cached target of these traces; rewrite the cache. */
|
|
54
|
+
export async function resolveWithDriver(projectRoot: string, traces: ParsedTrace[], resolver: TraceIdentityResolver): Promise<{ asked: number; resolver: string; blockedRequests: number }> {
|
|
55
|
+
const { cache } = readIdentityCache(projectRoot);
|
|
56
|
+
// The cache is project-wide (every unit's traces): answers are only ever ADDED, never pruned here.
|
|
57
|
+
const keep = new Map<string, CachedIdentity>(cache.entries);
|
|
58
|
+
const requests = new Map<string, TraceIdentityRequest>();
|
|
59
|
+
for (const req of traces.flatMap(identityRequestsFor)) if (!keep.has(req.key)) requests.set(req.key, req);
|
|
60
|
+
if (!requests.size) return { asked: 0, resolver: cache.resolver, blockedRequests: 0 };
|
|
61
|
+
const answer = await resolver({ requests: [...requests.values()] });
|
|
62
|
+
if (!answer || !Array.isArray(answer.results)) throw new Error('the trace identity resolver returned no results');
|
|
63
|
+
if (!TRUSTED_RESOLVER.test(String(answer.resolver)) || typeof answer.browser !== 'string' || !answer.browser) {
|
|
64
|
+
throw new Error(`the trace identity resolver "${String(answer.resolver).slice(0, 40)}" is not driver-ui's, or named no browser — its answers are not kept`);
|
|
65
|
+
}
|
|
66
|
+
for (const raw of answer.results) {
|
|
67
|
+
const r = validIdentityResult(raw);
|
|
68
|
+
if (!r || !requests.has(r.key)) throw new Error(`the trace identity resolver returned a malformed or unasked-for result (${JSON.stringify(raw).slice(0, 120)})`);
|
|
69
|
+
const { key, ...rest } = r;
|
|
70
|
+
keep.set(key, { ...rest, browser: answer.browser.slice(0, 100) });
|
|
71
|
+
}
|
|
72
|
+
writeIdentityCache(projectRoot, answer.resolver, keep);
|
|
73
|
+
return { asked: requests.size, resolver: String(answer.resolver), blockedRequests: Number(answer.blockedRequests) || 0 };
|
|
74
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* sungen's own run record (G5, DOS-14.6): written by `@sungen/driver-ui`'s run-record reporter
|
|
3
|
+
* next to the traces, read here STRICTLY — it is the only place a trace's run id and the hash of
|
|
4
|
+
* the code that produced it live, so an unknown key, a wrong type or a malformed hash means the
|
|
5
|
+
* record is not trusted at all (every trace it would vouch for is unbound).
|
|
6
|
+
*
|
|
7
|
+
* Found by convention: `<project>/test-results-free/sungen-run-record*.json` — the free-test config's
|
|
8
|
+
* own outputDir (#941) — or `<project>/test-results/sungen-run-record*.json` (one per run or shard).
|
|
9
|
+
* Playwright wipes its outputDir at the start of every run, so a record and its traces share a
|
|
10
|
+
* lifetime; the free run has its own so the compiled suite's next run cannot delete its traces.
|
|
11
|
+
*/
|
|
12
|
+
import * as fs from 'fs';
|
|
13
|
+
import * as path from 'path';
|
|
14
|
+
|
|
15
|
+
export interface RunRecordAttempt {
|
|
16
|
+
testId: string; file: string; title: string; retry: number; status: string;
|
|
17
|
+
startTime: string; duration: number; trace: { sha256: string } | null;
|
|
18
|
+
}
|
|
19
|
+
export interface RunRecord {
|
|
20
|
+
file: string;
|
|
21
|
+
runId: string;
|
|
22
|
+
startedAt: string;
|
|
23
|
+
playwrightVersion: string;
|
|
24
|
+
shard: string | null;
|
|
25
|
+
report: string | null;
|
|
26
|
+
specs: Record<string, string>;
|
|
27
|
+
attempts: RunRecordAttempt[];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const TOP = new Set(['schema', 'tool', 'runId', 'startedAt', 'playwrightVersion', 'shard', 'report', 'specs', 'attempts']);
|
|
31
|
+
const ATTEMPT = new Set(['testId', 'file', 'title', 'retry', 'status', 'startTime', 'duration', 'trace']);
|
|
32
|
+
const SHA = /^[0-9a-f]{64}$/;
|
|
33
|
+
const isObj = (v: unknown): v is Record<string, unknown> => !!v && typeof v === 'object' && !Array.isArray(v);
|
|
34
|
+
|
|
35
|
+
/** The free / impl:code run's own outputDir (the documented free-test config); compiled runs clear `test-results/`. */
|
|
36
|
+
export const FREE_RUN_DIR = 'test-results-free';
|
|
37
|
+
/** Where run records are read, in this order: the free run's dir, then Playwright's default (older configs). */
|
|
38
|
+
export const RUN_RECORD_DIRS: readonly string[] = [FREE_RUN_DIR, 'test-results'];
|
|
39
|
+
|
|
40
|
+
export function findRunRecords(projectRoot: string): string[] {
|
|
41
|
+
return RUN_RECORD_DIRS.flatMap((rel) => {
|
|
42
|
+
const dir = path.join(projectRoot, rel);
|
|
43
|
+
let names: string[];
|
|
44
|
+
try { names = fs.readdirSync(dir); } catch { return []; }
|
|
45
|
+
return names.filter((n) => /^sungen-run-record(?:\.[A-Za-z0-9._-]+)?\.json$/.test(n)).sort().map((n) => path.join(dir, n));
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function attemptOf(raw: unknown, i: number): RunRecordAttempt {
|
|
50
|
+
const at = `attempt ${i + 1}`;
|
|
51
|
+
if (!isObj(raw)) throw new Error(`${at} is not an object`);
|
|
52
|
+
const extra = Object.keys(raw).find((k) => !ATTEMPT.has(k));
|
|
53
|
+
if (extra) throw new Error(`${at} has an unknown key "${extra}"`);
|
|
54
|
+
const { testId, file, title, retry, status, startTime, duration, trace } = raw;
|
|
55
|
+
if (typeof testId !== 'string' || typeof file !== 'string' || typeof title !== 'string' || typeof status !== 'string') throw new Error(`${at}: testId, file, title and status must be strings`);
|
|
56
|
+
if (!Number.isInteger(retry) || (retry as number) < 0 || typeof duration !== 'number' || typeof startTime !== 'string' || Number.isNaN(Date.parse(startTime))) {
|
|
57
|
+
throw new Error(`${at}: retry, duration and startTime are malformed`);
|
|
58
|
+
}
|
|
59
|
+
if (trace !== null && !(isObj(trace) && Object.keys(trace).length === 1 && typeof trace.sha256 === 'string' && SHA.test(trace.sha256))) throw new Error(`${at}: trace must be null or {sha256}`);
|
|
60
|
+
return { testId, file, title, retry: retry as number, status, startTime, duration, trace: trace as RunRecordAttempt['trace'] };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Read and check one record; throws an Error naming what is wrong. */
|
|
64
|
+
export function readRunRecord(file: string): RunRecord {
|
|
65
|
+
let raw: unknown;
|
|
66
|
+
try { raw = JSON.parse(fs.readFileSync(file, 'utf8')); } catch (e) { throw new Error(`not readable JSON (${(e as Error).message.split('\n')[0]})`); }
|
|
67
|
+
if (!isObj(raw)) throw new Error('not an object');
|
|
68
|
+
const extra = Object.keys(raw).find((k) => !TOP.has(k));
|
|
69
|
+
if (extra) throw new Error(`unknown key "${extra}"`);
|
|
70
|
+
if (raw.schema !== 1 || raw.tool !== 'sungen-trace-run-record') throw new Error('not a sungen run record (schema 1)');
|
|
71
|
+
if (typeof raw.runId !== 'string' || !raw.runId || typeof raw.startedAt !== 'string' || typeof raw.playwrightVersion !== 'string') throw new Error('runId, startedAt and playwrightVersion must be strings');
|
|
72
|
+
if (raw.shard !== null && typeof raw.shard !== 'string') throw new Error('shard must be a string or null');
|
|
73
|
+
if (raw.report !== null && typeof raw.report !== 'string') throw new Error('report must be a string or null');
|
|
74
|
+
if (!isObj(raw.specs) || !Object.values(raw.specs).every((v) => typeof v === 'string' && (v === '' || SHA.test(v)))) throw new Error('specs must map files to sha256');
|
|
75
|
+
if (!Array.isArray(raw.attempts)) throw new Error('attempts must be a list');
|
|
76
|
+
return {
|
|
77
|
+
file, runId: raw.runId, startedAt: raw.startedAt, playwrightVersion: raw.playwrightVersion, shard: raw.shard as string | null,
|
|
78
|
+
report: raw.report as string | null, specs: raw.specs as Record<string, string>, attempts: raw.attempts.map(attemptOf),
|
|
79
|
+
};
|
|
80
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A trace → the test's INTENT, in order (phase 08 requirement 1). What is kept:
|
|
3
|
+
*
|
|
4
|
+
* action Frame goto / click / dblclick / fill / check / uncheck / selectOption / press /
|
|
5
|
+
* hover / setInputFiles / type / tap, and page keyboard presses
|
|
6
|
+
* assert Frame.expect — a check on an element or the page (url, title)
|
|
7
|
+
* opaque-assert a runner `expect` with no browser call behind it: `expect(await res.json())…`,
|
|
8
|
+
* a check on a JS value — cannot be compared, always review (DOS-14.6)
|
|
9
|
+
* opaque-action an API request, an `evaluate` (canvas…) — behaviour the contract cannot see into
|
|
10
|
+
*
|
|
11
|
+
* Dropped as implementation detail: waits (`waitForTimeout`, `waitFor*`), reads (`textContent`,
|
|
12
|
+
* `isVisible`…), context/page plumbing, and everything in fixtures, runner hooks and teardown —
|
|
13
|
+
* only the body and user `beforeEach` hooks are the test's behaviour. A focus click right before a
|
|
14
|
+
* fill on the same element is dropped later, once identities say "same element" (render step).
|
|
15
|
+
*/
|
|
16
|
+
import { classifyStep, isBehaviour, joinCalls, RunnerStep } from './test-step-join';
|
|
17
|
+
import type { ParsedTrace, StackFrame } from './trace-file-reader';
|
|
18
|
+
|
|
19
|
+
export type IntentKind = 'action' | 'assert' | 'opaque-action' | 'opaque-assert';
|
|
20
|
+
export interface IntentStep {
|
|
21
|
+
kind: IntentKind;
|
|
22
|
+
method: string;
|
|
23
|
+
callId?: string;
|
|
24
|
+
params: Record<string, unknown>;
|
|
25
|
+
/** Enclosing `test.step` titles, outermost first (impl:code compares by the first). */
|
|
26
|
+
group: string[];
|
|
27
|
+
location?: { file: string; line: number };
|
|
28
|
+
/** The runner's own words for an opaque step (`Expect "toMatchObject"`, `GET /api/health`). */
|
|
29
|
+
verbatim?: string;
|
|
30
|
+
startTime: number;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const FRAME_ACTIONS = new Set(['goto', 'click', 'dblclick', 'fill', 'check', 'uncheck', 'selectOption', 'press', 'hover', 'setInputFiles', 'type', 'tap']);
|
|
34
|
+
const PAGE_KEYS = new Set(['keyboardPress', 'keyboardType', 'keyboardInsertText']);
|
|
35
|
+
const EVALUATE = /^evaluate/;
|
|
36
|
+
|
|
37
|
+
/** The first frame in the test's own code (not a library, not the runner's `project#` marker). */
|
|
38
|
+
function firstFrame(frames: StackFrame[] | undefined): { file: string; line: number } | undefined {
|
|
39
|
+
const f = frames?.find((x) => x.file && !x.file.includes('node_modules') && !x.file.endsWith('#'));
|
|
40
|
+
return f ? { file: f.file, line: f.line } : undefined;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function groupOf(steps: Map<string, RunnerStep>, stepId: string | undefined): string[] {
|
|
44
|
+
if (!stepId) return [];
|
|
45
|
+
const w = classifyStep(steps, stepId);
|
|
46
|
+
return w.phase === 'body' ? w.stepPath : [];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function traceIntentSteps(trace: ParsedTrace): IntentStep[] {
|
|
50
|
+
const { steps, calls } = joinCalls(trace);
|
|
51
|
+
const stacks = new Map(trace.library.map((l) => [l.name, l.stacks]));
|
|
52
|
+
const out: IntentStep[] = [];
|
|
53
|
+
const withCall = new Set(calls.map((c) => c.stepId).filter((s): s is string => !!s));
|
|
54
|
+
|
|
55
|
+
for (const c of calls) {
|
|
56
|
+
if (!isBehaviour(c.where)) continue;
|
|
57
|
+
let kind: IntentKind | null = null;
|
|
58
|
+
let verbatim: string | undefined;
|
|
59
|
+
if (c.class === 'Frame' && FRAME_ACTIONS.has(c.method)) kind = 'action';
|
|
60
|
+
else if (c.class === 'Frame' && c.method === 'expect') kind = 'assert';
|
|
61
|
+
else if (c.class === 'Page' && PAGE_KEYS.has(c.method)) kind = 'action';
|
|
62
|
+
else if (c.class === 'APIRequestContext' && c.method === 'fetch') {
|
|
63
|
+
kind = 'opaque-action';
|
|
64
|
+
verbatim = `${String(c.params.method ?? 'GET')} ${String(c.params.url ?? '')}`;
|
|
65
|
+
} else if (EVALUATE.test(c.method) && /^(Frame|Page|JSHandle|ElementHandle|Worker)$/.test(c.class)) {
|
|
66
|
+
kind = 'opaque-action';
|
|
67
|
+
verbatim = `${c.class}.${c.method}`;
|
|
68
|
+
}
|
|
69
|
+
if (!kind) continue;
|
|
70
|
+
const runner = c.stepId ? steps.get(c.stepId) : undefined;
|
|
71
|
+
out.push({
|
|
72
|
+
kind, method: c.method, callId: c.callId, params: c.params, group: groupOf(steps, c.stepId),
|
|
73
|
+
location: firstFrame(runner?.stack) ?? firstFrame(stacks.get(c.part)?.get(c.callId)),
|
|
74
|
+
...(verbatim ? { verbatim } : {}), startTime: c.startTime,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// A runner `expect` that drove no browser call checked a plain JS value: opaque.
|
|
79
|
+
const runnerStart = new Map(trace.runner.filter((e) => e.type === 'before').map((e) => [String(e.callId), Number(e.startTime) || 0]));
|
|
80
|
+
for (const s of steps.values()) {
|
|
81
|
+
if (s.method !== 'expect' || withCall.has(s.id) || !isBehaviour(classifyStep(steps, s.id))) continue;
|
|
82
|
+
out.push({
|
|
83
|
+
kind: 'opaque-assert', method: 'expect', params: s.params, group: groupOf(steps, s.id),
|
|
84
|
+
location: firstFrame(s.stack), verbatim: s.title, startTime: runnerStart.get(s.id) ?? 0,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
return out.sort((a, b) => a.startTime - b.startTime);
|
|
88
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Limits for reading an untrusted Playwright trace.zip (DOS-14.7), and the one error every trace
|
|
3
|
+
* refusal raises. A trace is attacker-shaped input: a report can point at any file, and a zip can
|
|
4
|
+
* claim small sizes and inflate to gigabytes, so every number below is checked BEFORE inflating and
|
|
5
|
+
* again WHILE inflating (the inflater is capped at the declared size).
|
|
6
|
+
*
|
|
7
|
+
* file size 20 MB a real trace is ~0.6 MB per attempt, max 1.3 MB measured (phase 07)
|
|
8
|
+
* entries 10 000
|
|
9
|
+
* one entry 16 MB uncompressed
|
|
10
|
+
* all entries 64 MB uncompressed (each raisable by env: traceZipLimits)
|
|
11
|
+
* ratio 100 : 1 per entry (a bomb is ~1000 : 1)
|
|
12
|
+
*
|
|
13
|
+
* An entry NAME is a key and nothing else: names with `..`, a leading `/`, a drive letter, a
|
|
14
|
+
* backslash or a NUL are refused outright rather than "sanitized", because nothing legitimate in a
|
|
15
|
+
* trace looks like that.
|
|
16
|
+
*/
|
|
17
|
+
export interface TraceZipLimits {
|
|
18
|
+
maxZipBytes: number;
|
|
19
|
+
maxEntries: number;
|
|
20
|
+
maxEntryBytes: number;
|
|
21
|
+
maxTotalBytes: number;
|
|
22
|
+
maxRatio: number;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const MB = 1024 * 1024;
|
|
26
|
+
|
|
27
|
+
export const TRACE_ZIP_LIMITS: Readonly<TraceZipLimits> = Object.freeze({
|
|
28
|
+
maxZipBytes: 20 * MB,
|
|
29
|
+
maxEntries: 10_000,
|
|
30
|
+
maxEntryBytes: 16 * MB,
|
|
31
|
+
maxTotalBytes: 64 * MB,
|
|
32
|
+
maxRatio: 100,
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* The limits in force: the defaults, each raisable by env for unusually large traces —
|
|
37
|
+
* `SUNGEN_TRACE_MAX_ZIP_MB`, `SUNGEN_TRACE_MAX_ENTRY_MB`, `SUNGEN_TRACE_MAX_TOTAL_MB` (positive numbers;
|
|
38
|
+
* anything else is ignored, so a typo can never turn a cap into zero or infinity).
|
|
39
|
+
*/
|
|
40
|
+
export function traceZipLimits(env: Record<string, string | undefined> = process.env): TraceZipLimits {
|
|
41
|
+
const mb = (name: string, fallback: number): number => {
|
|
42
|
+
const v = Number(env[name]);
|
|
43
|
+
return Number.isFinite(v) && v > 0 && v <= 4096 ? Math.floor(v * MB) : fallback;
|
|
44
|
+
};
|
|
45
|
+
return {
|
|
46
|
+
...TRACE_ZIP_LIMITS,
|
|
47
|
+
maxZipBytes: mb('SUNGEN_TRACE_MAX_ZIP_MB', TRACE_ZIP_LIMITS.maxZipBytes),
|
|
48
|
+
maxEntryBytes: mb('SUNGEN_TRACE_MAX_ENTRY_MB', TRACE_ZIP_LIMITS.maxEntryBytes),
|
|
49
|
+
maxTotalBytes: mb('SUNGEN_TRACE_MAX_TOTAL_MB', TRACE_ZIP_LIMITS.maxTotalBytes),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Trace format versions this reader was verified against: 8 = Playwright 1.57, 9 = 1.63, 10 = 1.64
|
|
55
|
+
* (one shared call id for a library call and its runner step; stacks keyed by that id). Anything
|
|
56
|
+
* else — a newer format included — is refused until it has been checked the same way.
|
|
57
|
+
*/
|
|
58
|
+
export const SUPPORTED_TRACE_VERSIONS: ReadonlySet<number> = new Set([8, 9, 10]);
|
|
59
|
+
|
|
60
|
+
export type TraceRejectCode =
|
|
61
|
+
| 'zip.too-large' | 'zip.too-many-entries' | 'zip.entry-too-large' | 'zip.total-too-large' | 'zip.ratio'
|
|
62
|
+
| 'zip.entry-name' | 'zip.malformed' | 'zip.unsupported' | 'trace.version-unsupported' | 'trace.malformed';
|
|
63
|
+
|
|
64
|
+
/** A trace that was refused before anything in it was trusted. `code` is stable; `message` names the cause. */
|
|
65
|
+
export class TraceRejected extends Error {
|
|
66
|
+
constructor(public readonly code: TraceRejectCode, message: string) {
|
|
67
|
+
super(`${code}: ${message}`);
|
|
68
|
+
this.name = 'TraceRejected';
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const BAD_NAME = /(^|[\\/])\.\.([\\/]|$)|^[\\/]|^[A-Za-z]:|\\|\0/;
|
|
73
|
+
|
|
74
|
+
/** Refuse an entry name that could ever be mistaken for a path outside the archive. */
|
|
75
|
+
export function assertSafeEntryName(name: string): void {
|
|
76
|
+
if (!name || BAD_NAME.test(name)) throw new TraceRejected('zip.entry-name', `refused entry name ${JSON.stringify(name.slice(0, 80))}`);
|
|
77
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A synchronous, limit-enforcing zip reader for trace.zip files (DOS-14.7) on Node's own zlib —
|
|
3
|
+
* no dependency, and synchronous so the contract verdict (and the Claude Code guard that rebuilds
|
|
4
|
+
* it) stays a plain function.
|
|
5
|
+
*
|
|
6
|
+
* Only what a Playwright trace needs is accepted: STORE or DEFLATE entries, no encryption, no
|
|
7
|
+
* ZIP64, no duplicate names. Sizes come from the central directory and are checked against
|
|
8
|
+
* TRACE_ZIP_LIMITS before anything is inflated; inflation itself is capped at the declared size and
|
|
9
|
+
* the CRC is verified, so a header that lies is caught while reading, not after. Entry names are
|
|
10
|
+
* map keys — nothing here ever touches the filesystem.
|
|
11
|
+
*/
|
|
12
|
+
import * as zlib from 'zlib';
|
|
13
|
+
import { assertSafeEntryName, TRACE_ZIP_LIMITS, TraceRejected, TraceZipLimits } from './trace-zip-limits';
|
|
14
|
+
|
|
15
|
+
interface Entry { name: string; method: number; compressed: number; size: number; crc: number; localOffset: number }
|
|
16
|
+
|
|
17
|
+
export interface ZipArchive {
|
|
18
|
+
names(): string[];
|
|
19
|
+
has(name: string): boolean;
|
|
20
|
+
readBuffer(name: string): Buffer;
|
|
21
|
+
readText(name: string): string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const EOCD = 0x06054b50;
|
|
25
|
+
const CENTRAL = 0x02014b50;
|
|
26
|
+
const LOCAL = 0x04034b50;
|
|
27
|
+
/** Below this, a high ratio is just a small repetitive file; the per-entry and total caps still apply. */
|
|
28
|
+
const RATIO_FLOOR_BYTES = 64 * 1024;
|
|
29
|
+
|
|
30
|
+
const malformed = (why: string): TraceRejected => new TraceRejected('zip.malformed', why);
|
|
31
|
+
|
|
32
|
+
const CRC_TABLE = (() => {
|
|
33
|
+
const t = new Int32Array(256);
|
|
34
|
+
for (let n = 0; n < 256; n++) { let c = n; for (let k = 0; k < 8; k++) c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1; t[n] = c; }
|
|
35
|
+
return t;
|
|
36
|
+
})();
|
|
37
|
+
function crc32(buf: Buffer): number {
|
|
38
|
+
let c = -1;
|
|
39
|
+
for (let i = 0; i < buf.length; i++) c = CRC_TABLE[(c ^ buf[i]) & 0xff] ^ (c >>> 8);
|
|
40
|
+
return (c ^ -1) >>> 0;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function findEocd(buf: Buffer): number {
|
|
44
|
+
const stop = Math.max(0, buf.length - 22 - 0xffff);
|
|
45
|
+
for (let i = buf.length - 22; i >= stop; i--) if (buf.readUInt32LE(i) === EOCD) return i;
|
|
46
|
+
throw malformed('no end-of-central-directory record (not a zip)');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function readEntries(buf: Buffer, limits: TraceZipLimits): Map<string, Entry> {
|
|
50
|
+
const eocd = findEocd(buf);
|
|
51
|
+
const count = buf.readUInt16LE(eocd + 10);
|
|
52
|
+
const cdOffset = buf.readUInt32LE(eocd + 16);
|
|
53
|
+
if (count === 0xffff || cdOffset === 0xffffffff) throw new TraceRejected('zip.unsupported', 'ZIP64 archives are not read');
|
|
54
|
+
if (count > limits.maxEntries) throw new TraceRejected('zip.too-many-entries', `${count} entries > ${limits.maxEntries}`);
|
|
55
|
+
const entries = new Map<string, Entry>();
|
|
56
|
+
let at = cdOffset;
|
|
57
|
+
let total = 0;
|
|
58
|
+
for (let i = 0; i < count; i++) {
|
|
59
|
+
if (buf.readUInt32LE(at) !== CENTRAL) throw malformed(`central directory entry ${i} has a bad signature`);
|
|
60
|
+
const flags = buf.readUInt16LE(at + 8);
|
|
61
|
+
const method = buf.readUInt16LE(at + 10);
|
|
62
|
+
const crc = buf.readUInt32LE(at + 16);
|
|
63
|
+
const compressed = buf.readUInt32LE(at + 20);
|
|
64
|
+
const size = buf.readUInt32LE(at + 24);
|
|
65
|
+
const nameLen = buf.readUInt16LE(at + 28);
|
|
66
|
+
const name = buf.toString('utf8', at + 46, at + 46 + nameLen);
|
|
67
|
+
const localOffset = buf.readUInt32LE(at + 42);
|
|
68
|
+
at += 46 + nameLen + buf.readUInt16LE(at + 30) + buf.readUInt16LE(at + 32);
|
|
69
|
+
assertSafeEntryName(name.replace(/\/$/, '') || name);
|
|
70
|
+
if (flags & 1) throw new TraceRejected('zip.unsupported', `${name} is encrypted`);
|
|
71
|
+
if (method !== 0 && method !== 8) throw new TraceRejected('zip.unsupported', `${name} uses compression method ${method}`);
|
|
72
|
+
if (compressed === 0xffffffff || size === 0xffffffff) throw new TraceRejected('zip.unsupported', `${name} needs ZIP64`);
|
|
73
|
+
if (name.endsWith('/')) continue;
|
|
74
|
+
if (entries.has(name)) throw malformed(`duplicate entry ${name}`);
|
|
75
|
+
if (size > limits.maxEntryBytes) throw new TraceRejected('zip.entry-too-large', `${name}: ${size} B > ${limits.maxEntryBytes}`);
|
|
76
|
+
if (size >= RATIO_FLOOR_BYTES && compressed > 0 && size / compressed > limits.maxRatio) {
|
|
77
|
+
throw new TraceRejected('zip.ratio', `${name}: ratio ${(size / compressed).toFixed(0)}:1 > ${limits.maxRatio}:1`);
|
|
78
|
+
}
|
|
79
|
+
if (size >= RATIO_FLOOR_BYTES && compressed === 0) throw new TraceRejected('zip.ratio', `${name}: ${size} B from 0 compressed bytes`);
|
|
80
|
+
total += size;
|
|
81
|
+
if (total > limits.maxTotalBytes) throw new TraceRejected('zip.total-too-large', `more than ${limits.maxTotalBytes} B uncompressed`);
|
|
82
|
+
entries.set(name, { name, method, compressed, size, crc, localOffset });
|
|
83
|
+
}
|
|
84
|
+
return entries;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function inflate(buf: Buffer, e: Entry): Buffer {
|
|
88
|
+
if (buf.readUInt32LE(e.localOffset) !== LOCAL) throw malformed(`${e.name}: bad local header`);
|
|
89
|
+
const start = e.localOffset + 30 + buf.readUInt16LE(e.localOffset + 26) + buf.readUInt16LE(e.localOffset + 28);
|
|
90
|
+
const data = buf.subarray(start, start + e.compressed);
|
|
91
|
+
if (data.length !== e.compressed) throw malformed(`${e.name}: truncated data`);
|
|
92
|
+
let out: Buffer;
|
|
93
|
+
if (e.method === 0) out = data;
|
|
94
|
+
else {
|
|
95
|
+
try { out = zlib.inflateRawSync(data, { maxOutputLength: Math.max(1, e.size) }); } catch (err) {
|
|
96
|
+
throw malformed(`${e.name}: inflates past its declared ${e.size} B or is corrupt (${(err as Error).message.split('\n')[0]})`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
if (out.length !== e.size) throw malformed(`${e.name}: ${out.length} B, header says ${e.size} B`);
|
|
100
|
+
if (crc32(out) !== e.crc) throw malformed(`${e.name}: CRC mismatch`);
|
|
101
|
+
return out;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** Open a zip held in memory. Throws TraceRejected for anything outside the limits or the format. */
|
|
105
|
+
export function openZip(buf: Buffer, limits: TraceZipLimits = TRACE_ZIP_LIMITS): ZipArchive {
|
|
106
|
+
if (buf.length > limits.maxZipBytes) throw new TraceRejected('zip.too-large', `${buf.length} B > ${limits.maxZipBytes}`);
|
|
107
|
+
if (buf.length < 22) throw malformed('too short to be a zip');
|
|
108
|
+
let entries: Map<string, Entry>;
|
|
109
|
+
try { entries = readEntries(buf, limits); } catch (e) {
|
|
110
|
+
if (e instanceof TraceRejected) throw e;
|
|
111
|
+
throw malformed(`unreadable zip structure (${(e as Error).message.split('\n')[0]})`);
|
|
112
|
+
}
|
|
113
|
+
const readBuffer = (name: string): Buffer => {
|
|
114
|
+
const e = entries.get(name);
|
|
115
|
+
if (!e) throw malformed(`no entry ${name}`);
|
|
116
|
+
try { return inflate(buf, e); } catch (err) {
|
|
117
|
+
if (err instanceof TraceRejected) throw err;
|
|
118
|
+
throw malformed(`${name}: ${(err as Error).message.split('\n')[0]}`);
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
return {
|
|
122
|
+
names: () => [...entries.keys()],
|
|
123
|
+
has: (name) => entries.has(name),
|
|
124
|
+
readBuffer,
|
|
125
|
+
readText: (name) => readBuffer(name).toString('utf8'),
|
|
126
|
+
};
|
|
127
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Every approved lock entry must be vouched for by its decision (DOS-13.2, DOS-14.4, review M1):
|
|
3
|
+
* `qa/<kind>/<unit>/decisions/<decisionId>.yaml`, COMMITTED in git HEAD (outside git: on disk),
|
|
4
|
+
* intact (schema, id = content, entry_hash = entry), of kind `contract-approval`, whose payload lists
|
|
5
|
+
* this scenario at this hash, and whose `valid_from` is the entry's `approvedAt`. A lock line copied
|
|
6
|
+
* from elsewhere, re-pointed at another decision, or backdated fails here — `unverified`, reported
|
|
7
|
+
* under contract.lock-uncommitted (hard).
|
|
8
|
+
*
|
|
9
|
+
* Only entries that are otherwise identical to HEAD are checked: an entry already added, changed or
|
|
10
|
+
* tampered is a hard finding on its own.
|
|
11
|
+
*/
|
|
12
|
+
import * as path from 'path';
|
|
13
|
+
import { computeDecisionId, computeEntryHash } from '../decisions/decision-identity';
|
|
14
|
+
import { DecisionEntry, storedEntryErrors } from '../decisions/decision-schema';
|
|
15
|
+
import { safeYamlParse } from '../utils/safe-yaml-parse';
|
|
16
|
+
import { HeadSnapshot, headFileText, posixRel, readTextOrNull } from './contract-git';
|
|
17
|
+
import { ContractLock, isHumanApproved, LockEntry } from './contract-schema';
|
|
18
|
+
import type { UncommittedEntry } from './lock-vs-git-head';
|
|
19
|
+
|
|
20
|
+
type Loaded = { entry: DecisionEntry } | { problem: string };
|
|
21
|
+
|
|
22
|
+
function loadDecision(projectRoot: string, unitDir: string, id: string, snap: HeadSnapshot): Loaded {
|
|
23
|
+
const file = path.join(unitDir, 'decisions', `${id}.yaml`);
|
|
24
|
+
const rel = posixRel(projectRoot, file);
|
|
25
|
+
const working = readTextOrNull(file);
|
|
26
|
+
const text = snap.blobs === null ? working : headFileText(snap, rel, working);
|
|
27
|
+
if (text === null) return { problem: snap.blobs === null ? `decision ${id} does not exist` : `decision ${id} is not in git HEAD (not committed through review)` };
|
|
28
|
+
let raw: unknown;
|
|
29
|
+
try { raw = safeYamlParse(text, rel); } catch (e) { return { problem: `decision ${id} is unreadable: ${e instanceof Error ? e.message : e}` }; }
|
|
30
|
+
const errors = storedEntryErrors(raw);
|
|
31
|
+
if (errors.length) return { problem: `decision ${id} is invalid: ${errors[0]}` };
|
|
32
|
+
const entry = raw as DecisionEntry;
|
|
33
|
+
const scope = posixRel(path.join(projectRoot, 'qa'), unitDir);
|
|
34
|
+
if (entry.id !== id || computeDecisionId(entry, scope) !== id) return { problem: `decision ${id} does not hash to its id in ${scope}` };
|
|
35
|
+
if (computeEntryHash(entry) !== entry.entry_hash) return { problem: `decision ${id} was altered (entry_hash)` };
|
|
36
|
+
return { entry };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function mismatch(scenario: string, lockEntry: LockEntry, d: DecisionEntry): string | null {
|
|
40
|
+
if (d.kind !== 'contract-approval') return `decision ${d.id} is a ${d.kind}, not a contract-approval`;
|
|
41
|
+
const listed = Array.isArray(d.payload?.scenarios) ? d.payload!.scenarios as Array<{ id?: unknown; hash?: unknown }> : [];
|
|
42
|
+
if (!listed.some((s) => s && s.id === scenario && s.hash === lockEntry.hash)) return `decision ${d.id} does not approve ${scenario} at hash ${lockEntry.hash}`;
|
|
43
|
+
if (Date.parse(lockEntry.approvedAt) !== Date.parse(d.valid_from)) return `approvedAt ${lockEntry.approvedAt} is not decision ${d.id}'s valid_from ${d.valid_from}`;
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function unverifiedApprovals(
|
|
48
|
+
projectRoot: string, unitDir: string, lock: ContractLock | null, skip: ReadonlySet<string>, snap: HeadSnapshot,
|
|
49
|
+
): UncommittedEntry[] {
|
|
50
|
+
const cache = new Map<string, Loaded>();
|
|
51
|
+
const out: UncommittedEntry[] = [];
|
|
52
|
+
for (const [id, entry] of Object.entries(lock?.scenarios ?? {})) {
|
|
53
|
+
if (!isHumanApproved(entry) || skip.has(id)) continue;
|
|
54
|
+
if (!cache.has(entry.decisionId)) cache.set(entry.decisionId, loadDecision(projectRoot, unitDir, entry.decisionId, snap));
|
|
55
|
+
const loaded = cache.get(entry.decisionId)!;
|
|
56
|
+
const problem = 'problem' in loaded ? loaded.problem : mismatch(id, entry, loaded.entry);
|
|
57
|
+
if (problem) out.push({ id, change: 'unverified', detail: problem });
|
|
58
|
+
}
|
|
59
|
+
return out;
|
|
60
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Append one decision: one file per entry, named by its content id, created exclusively
|
|
3
|
+
* (DOS-13.1, red-team #13). The file appears complete or not at all (createFileExclusive), so two
|
|
4
|
+
* writers can race freely: same content → same name → the second sees "already recorded"; different
|
|
5
|
+
* content → different names → both land. Nothing is sequential, so nothing can collide.
|
|
6
|
+
*/
|
|
7
|
+
import * as fs from 'fs';
|
|
8
|
+
import * as path from 'path';
|
|
9
|
+
import { createFileExclusive } from './exclusive-file-create';
|
|
10
|
+
import { DecisionDraft, DecisionEntry, draftErrors } from './decision-schema';
|
|
11
|
+
import { sealDecision } from './decision-identity';
|
|
12
|
+
import { decisionsDirFor, serializeDecision, verifyDecisionFile } from './decision-file';
|
|
13
|
+
|
|
14
|
+
export interface AppendResult { status: 'created' | 'already-recorded'; entry: DecisionEntry; path: string }
|
|
15
|
+
|
|
16
|
+
export class DecisionConflictError extends Error {
|
|
17
|
+
constructor(public readonly path: string, detail: string) {
|
|
18
|
+
super(`${path} already exists but does not verify (${detail}). Restore it from git; record a new entry instead.`);
|
|
19
|
+
this.name = 'DecisionConflictError';
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function appendDecision(projectRoot: string, scope: string, draft: DecisionDraft): AppendResult {
|
|
24
|
+
const errors = draftErrors(draft as unknown as Record<string, unknown>);
|
|
25
|
+
if (errors.length) throw new Error(`invalid decision: ${errors.join('; ')}`);
|
|
26
|
+
const dir = decisionsDirFor(projectRoot, scope);
|
|
27
|
+
const entry = sealDecision(draft, scope);
|
|
28
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
29
|
+
const file = path.join(dir, `${entry.id}.yaml`);
|
|
30
|
+
if (createFileExclusive(file, serializeDecision(entry))) return { status: 'created', entry, path: file };
|
|
31
|
+
|
|
32
|
+
const existing = verifyDecisionFile(file, scope);
|
|
33
|
+
if (existing.entry) return { status: 'already-recorded', entry: existing.entry, path: file };
|
|
34
|
+
throw new DecisionConflictError(file, `${existing.problem?.code}: ${existing.problem?.message}`);
|
|
35
|
+
}
|