project-tiny-context-harness 0.2.82 → 0.2.84
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -4
- package/assets/README.md +13 -7
- package/assets/README.zh-CN.md +8 -2
- package/assets/protected-harness-baseline.json +20 -0
- package/assets/skills/composite-long-task-workflow/SKILL.md +27 -3
- package/assets/skills/composite-long-task-workflow/assets/execution-binding.template.md +24 -0
- package/assets/skills/composite-long-task-workflow/assets/goal-objective.template.md +6 -15
- package/assets/skills/composite-long-task-workflow/references/composite-long-task-workflow-protocol.md +51 -25
- package/dist/commands/composite-long-task.js +60 -5
- package/dist/lib/superpowers-task-ac010.d.ts +6 -0
- package/dist/lib/superpowers-task-ac010.js +26 -0
- package/dist/lib/superpowers-task-assertion-normalizers.js +4 -0
- package/dist/lib/superpowers-task-assertions.js +18 -4
- package/dist/lib/superpowers-task-attempt.d.ts +4 -0
- package/dist/lib/superpowers-task-attempt.js +102 -0
- package/dist/lib/superpowers-task-command-run-correlation.d.ts +8 -0
- package/dist/lib/superpowers-task-command-run-correlation.js +103 -0
- package/dist/lib/superpowers-task-command-specs.d.ts +3 -0
- package/dist/lib/superpowers-task-command-specs.js +52 -0
- package/dist/lib/superpowers-task-compile.d.ts +4 -1
- package/dist/lib/superpowers-task-compile.js +7 -1
- package/dist/lib/superpowers-task-completion-output.d.ts +52 -0
- package/dist/lib/superpowers-task-completion-output.js +228 -0
- package/dist/lib/superpowers-task-contradictions.d.ts +6 -0
- package/dist/lib/superpowers-task-contradictions.js +126 -0
- package/dist/lib/superpowers-task-current-evidence.d.ts +3 -0
- package/dist/lib/superpowers-task-current-evidence.js +176 -0
- package/dist/lib/superpowers-task-derive.js +69 -8
- package/dist/lib/superpowers-task-evidence-kernel.d.ts +19 -0
- package/dist/lib/superpowers-task-evidence-kernel.js +347 -0
- package/dist/lib/superpowers-task-evidence-records.d.ts +2 -0
- package/dist/lib/superpowers-task-evidence-records.js +55 -0
- package/dist/lib/superpowers-task-evidence.d.ts +10 -0
- package/dist/lib/superpowers-task-evidence.js +147 -0
- package/dist/lib/superpowers-task-final-card.d.ts +3 -0
- package/dist/lib/superpowers-task-final-card.js +24 -0
- package/dist/lib/superpowers-task-gates.d.ts +2 -2
- package/dist/lib/superpowers-task-gates.js +87 -35
- package/dist/lib/superpowers-task-harness-drift.d.ts +11 -0
- package/dist/lib/superpowers-task-harness-drift.js +90 -0
- package/dist/lib/superpowers-task-protected-baseline.d.ts +10 -0
- package/dist/lib/superpowers-task-protected-baseline.js +66 -0
- package/dist/lib/superpowers-task-state-schema.d.ts +116 -3
- package/dist/lib/superpowers-task-state-schema.js +23 -1
- package/dist/lib/superpowers-task-state-shape.d.ts +3 -0
- package/dist/lib/superpowers-task-state-shape.js +50 -0
- package/dist/lib/superpowers-task-state.js +17 -37
- package/dist/lib/superpowers-task-status.js +11 -1
- package/dist/lib/superpowers-task-under-specified.d.ts +7 -0
- package/dist/lib/superpowers-task-under-specified.js +61 -0
- package/dist/lib/superpowers-task-unregistered-evidence.d.ts +11 -0
- package/dist/lib/superpowers-task-unregistered-evidence.js +72 -0
- package/dist/lib/superpowers-task-validator.js +43 -27
- package/package.json +69 -69
- package/source-mappings.yaml +3 -0
|
@@ -35,9 +35,11 @@ Implementation advances through coherent slices. Each slice selects related PI /
|
|
|
35
35
|
|
|
36
36
|
## Evidence Protocol
|
|
37
37
|
|
|
38
|
-
Evidence is canonical state, not prose. Every proof record enters `task-state.evidence[]` with evidence id, slice id, type, command or artifact paths, command exit code when applicable, `proves`, `does_not_prove`, freshness, redaction, reviewability / reproduction data and, for machine-verifiable layers, an `assertion_result`. Evidence must be fresh, reviewable and free of secrets, raw credentials, tokens, cookies and long raw payloads.
|
|
38
|
+
Evidence is canonical state, not prose. Every proof record enters `task-state.evidence[]` with evidence id, slice id, type, command or artifact paths, command exit code when applicable, `proves`, `does_not_prove`, freshness, redaction, reviewability / reproduction data and, for machine-verifiable layers, an `assertion_result`. Strict final completion uses EvidenceRecordV2 records bound to the current attempt, source bundle, product source hash, technical plan hash, acceptance checklist hash, git head, worktree fingerprint, command spec/run id, command line/exit code, artifact path/SHA/mtime, target AC ids, target PI ids, target proof layers, assertion status/exit code, positive assertions, negative assertions, invalid completion signals, negative evidence scan and required test ids. Evidence must be fresh, reviewable and free of secrets, raw credentials, tokens, cookies and long raw payloads.
|
|
39
39
|
|
|
40
|
-
Canonical proof layers are `code`, `api_schema`, `worker_runtime`, `data_artifact`, `integration`, `ui_browser`, `security_redaction`, `all_provider_all_runner`, `cleanup_stale_scan` and `test`; legacy aliases map `runtime -> worker_runtime`, `browser -> ui_browser`, `api -> api_schema`, `data -> data_artifact` and `security -> security_redaction`. `code` cannot complete a machine-backed AC by itself. Machine-verifiable layers are not complete from descriptions, screenshots, final cards, validator passes, matrix rows
|
|
40
|
+
Canonical proof layers are `code`, `api_schema`, `worker_runtime`, `data_artifact`, `integration`, `ui_browser`, `security_redaction`, `all_provider_all_runner`, `cleanup_stale_scan` and `test`; legacy aliases map `runtime -> worker_runtime`, `browser -> ui_browser`, `api -> api_schema`, `data -> data_artifact` and `security -> security_redaction`. `code` cannot complete a machine-backed AC by itself. Machine-verifiable layers are not complete from descriptions, screenshots, final cards, validator passes, matrix rows, verdict rows, evidence-index rows, final-summary text, historical `events.ndjson` complete events, auditor prose, summary-only AC proof or unregistered temporary JSON. They require current-attempt EvidenceRecordV2 plus `assertion_result.schema_version=assertion-result-v2`, `assertion_result.status=passed`, assertion exit code `0`, command exit code `0`, target AC/PI/layer coverage, passed positive and negative assertions, no invalid completion signal and reviewable artifacts. UI/browser layers also require owner surface, route/path, user action, browser/playwright/UI assertion evidence and a passed `negative_evidence_scan` with matching target proof layers and checked invalid completion signals.
|
|
41
|
+
|
|
42
|
+
The Trusted Evidence Kernel marks a machine-blocking AC `under_specified` when it lacks an assertion command, assertion artifacts, positive assertions, negative assertions, invalid completion signals, required UI/browser proof, concrete assertion result production or any non-generated final evidence path. Under-specified ACs block related PIs and force `product_goal_complete=false`. AC-010 / final-gate summary proof only summarizes fresh current EvidenceRecordV2 proof for the other ACs; it cannot bootstrap missing, failed, stale or under-specified ACs and is invalidated with `final_gate_cannot_bootstrap_from_summary_only` when it tries.
|
|
41
43
|
|
|
42
44
|
Strict V2 source fields are canonical. Product Source must carry Scope Fit, owner, primary capability and assertion policy fields. PI items must carry owner boundary, primary capability path, trigger/state/observable contracts, assertion support, required assertion commands and invalid implementation shortcuts. ACs must carry assertion command, artifacts, positive/negative assertions, machine-blocking flag, invalid completion signals and assertion-result requirement. Unknown, duplicate, table or missing canonical fields block compile; the workflow must not generate, infer, rewrite or repair the Technical Realization Plan or Acceptance Checklist.
|
|
43
45
|
|
|
@@ -63,19 +65,23 @@ Superpowers remains the execution layer: prefer `superpowers:subagent-driven-dev
|
|
|
63
65
|
|
|
64
66
|
## Final Gate Protocol
|
|
65
67
|
|
|
66
|
-
Final completion always runs
|
|
68
|
+
Final completion always runs through the Trusted Evidence Kernel, then through the completion-output resolver. Final gate, `validate-superpowers-state`, state-backed `validate-plan-acceptance` and derived completion views use the same kernel and resolver result. Superpowers verification, validators, auditor checks and generated views are useful execution checks, but they are not proof authority or completion-output authority. The AC Evidence Assertion Gate and Negative Evidence Scan Gate are enforced inside the kernel, not by trusting generated matrix or verdict text. The final gate itself runs in fixed order: load the three inputs, recompute source hashes, load task state, resolve the current attempt, load required command specs, load command-run records, load registered EvidenceRecords, discard stale evidence, scan unregistered assertion JSON, run contradiction scan, run AC-010 bootstrap prevention, run under-specified AC checks, run Harness Drift Lock, run protected baseline guard, validate scope conflicts, recompute every AC, recompute every PI, recompute `acceptance_target_status`, recompute `product_goal_complete`, resolve `completion_output_status`, regenerate `derived/**` and append an event.
|
|
69
|
+
|
|
70
|
+
The final gate recomputes from current source hashes, current attempt, required command specs/runs, registered EvidenceRecordV2 records, contradiction scan, Harness Drift Lock and protected baseline state. It ignores stale passed artifacts, historical complete events, stale derived complete views, matrix/verdict/evidence-index/final-summary rows, validator passes, final cards, auditor prose, AC summary-only proof, unregistered temporary JSON and hand-written status files as proof. The completion-output resolver ignores those same surfaces as authority and emits only `accept`, `reject` or `blocked`. Newer failed commands, Playwright `.last-run.json`, `test-results/**/error-context.md`, JUnit/JSON reporter failures, negative evidence artifacts, owner DOM forbidden states, task-state false/partial values and derived/state mismatch block completion for the affected AC/layer. If historical completion conflicts with current recompute, report `Historical stale completion event detected and ignored.` and `Current recomputed product_goal_complete=false.`
|
|
71
|
+
|
|
72
|
+
Harness Drift Lock: `product_task` is blocked when the current attempt changed Playwright specs, tests, assertion generators, AC010 helpers, evidence writers, final-gate, validator, derive, task-state reducer, this workflow Skill/protocol or related Makefile/package test targets. The output must include `harness_drift_detected`, `acceptance_target_status=blocked`, `product_goal_complete=false` and `本轮修改了验收工具链或测试本身,不能用被修改后的验收证明同一轮产品完成。请拆成独立 harness_task。` A `harness_task` must include adversarial fixtures with expected final-gate outcomes for stale evidence, historical complete, derived contradiction, AC010 summary-only, target mismatch, API-only-for-UI, negative evidence after pass, source hash mismatch, dirty worktree mismatch, missing assertion_result, test weakening, scope leakage, missing UI/browser owner-surface proof, missing negative semantic proof and one happy path; it never proves product completion. HFC-003 is the durable false-completion regression suite: 35 committed mini workdirs plus one runner cover the Trusted Evidence Kernel, completion-output resolver, generated-output scanner, selected CLI smoke paths and one happy path. `protected-harness-baseline.json` blocks product-task changes to protected harness surfaces and requires a baseline reason plus fixtures for harness-task changes.
|
|
67
73
|
|
|
68
74
|
## Completion State Machine
|
|
69
75
|
|
|
70
|
-
`audit_task_complete` means the workflow or audit pass finished. `acceptance_target_status` is the acceptance verdict. `product_goal_complete` is computed only by final-gate and is the only
|
|
76
|
+
`audit_task_complete` means the workflow or audit pass finished. `acceptance_target_status` is the acceptance verdict. `product_goal_complete` is computed only by final-gate. `completion_output_status` is computed only by the final-gate completion-output resolver and is the only official generated final-answer status. Implementation/execution Goal completion requires both `product_goal_complete=true` and `completion_output_status=accept`. A read-only audit goal may finish at audit completion only while saying `Audit workflow completed; acceptance target not complete.` for non-accepted targets.
|
|
71
77
|
|
|
72
78
|
## Forbidden Shortcuts
|
|
73
79
|
|
|
74
|
-
Tests alone do not prove plan conformance. Superpowers review does not prove AC acceptance. Sample evidence does not prove full population unless the AC allows it. Browser screenshots, final cards, validator passes, matrix/verdict rows and prose evidence are auxiliary only for machine-verifiable ACs unless a passed assertion report is bound to the target AC/layer. Derived files, local audit, validator output and auditor reports cannot rewrite Product / Plan / Checklist. Local audit cannot mark product completion. Agents must not handwrite `product_goal_complete`.
|
|
80
|
+
Tests alone do not prove plan conformance. Superpowers review does not prove AC acceptance. Sample evidence does not prove full population unless the AC allows it. Browser screenshots, final cards, validator passes, matrix/verdict rows, evidence-index/final-summary rows, unregistered JSON and prose evidence are auxiliary only for machine-verifiable ACs unless a passed assertion report is bound to the target AC/layer. Derived files, local audit, validator output and auditor reports cannot rewrite Product / Plan / Checklist. Local audit cannot mark product completion. Agents must not handwrite `product_goal_complete`.
|
|
75
81
|
|
|
76
82
|
## Hallucination Guard
|
|
77
83
|
|
|
78
|
-
The protocol must prevent false fusion: do not interpret the composite workflow as the Tiny Context Workflow Contract itself; do not register `workflow-protocol.md` in `project_context/context.toml`; do not treat it as a business fact source; do not use local audit, tests, Superpowers review, sampled evidence, screenshots, final cards, matrix/verdict rows, validator passes or final-gate failure as product completion; do not claim full alignment with unresolved Source-to-Context Coverage or Context-to-Implementation Binding gaps; and do not call a Codex implementation Goal complete before final-gate passes
|
|
84
|
+
The protocol must prevent false fusion: do not interpret the composite workflow as the Tiny Context Workflow Contract itself; do not register `workflow-protocol.md` in `project_context/context.toml`; do not treat it as a business fact source; do not use local audit, tests, Superpowers review, sampled evidence, screenshots, final cards, matrix/verdict rows, validator passes, stale generated output or final-gate failure as product completion; do not let those surfaces promote `completion_output_status` to `accept`; do not claim full alignment with unresolved Source-to-Context Coverage or Context-to-Implementation Binding gaps; and do not call a Codex implementation Goal complete before final-gate passes and resolver status is `accept`.
|
|
79
85
|
|
|
80
86
|
## Blocker Protocol
|
|
81
87
|
|
|
@@ -498,17 +504,30 @@ Completion is blocked until the source conflict is resolved. Validators inspect
|
|
|
498
504
|
|
|
499
505
|
## 13. Phase Twelve: Fixed Final Gate Order
|
|
500
506
|
|
|
501
|
-
Before final completion, the order is fixed:
|
|
502
|
-
|
|
503
|
-
```text
|
|
504
|
-
1.
|
|
505
|
-
2.
|
|
506
|
-
3.
|
|
507
|
-
4.
|
|
508
|
-
5.
|
|
509
|
-
6.
|
|
510
|
-
7.
|
|
511
|
-
8.
|
|
507
|
+
Before final completion, the kernel order is fixed:
|
|
508
|
+
|
|
509
|
+
```text
|
|
510
|
+
1. load product-architecture-source.md, technical-realization-plan.md, acceptance-checklist.md
|
|
511
|
+
2. recompute source hashes
|
|
512
|
+
3. load task-state.json
|
|
513
|
+
4. resolve current_attempt
|
|
514
|
+
5. load required command specs
|
|
515
|
+
6. load command-run records
|
|
516
|
+
7. load registered EvidenceRecordV2 records
|
|
517
|
+
8. discard stale evidence
|
|
518
|
+
9. scan unregistered assertion JSON
|
|
519
|
+
10. contradiction scan
|
|
520
|
+
11. run AC-010 bootstrap prevention
|
|
521
|
+
12. run under-specified AC checks
|
|
522
|
+
13. run Harness Drift Lock
|
|
523
|
+
14. run protected baseline guard
|
|
524
|
+
15. recompute every AC
|
|
525
|
+
16. recompute every PI
|
|
526
|
+
17. recompute acceptance_target_status
|
|
527
|
+
18. recompute product_goal_complete
|
|
528
|
+
19. resolve completion_output_status
|
|
529
|
+
20. regenerate derived/**
|
|
530
|
+
21. append event
|
|
512
531
|
```
|
|
513
532
|
|
|
514
533
|
Legacy/internal compatibility may exist as:
|
|
@@ -521,9 +540,10 @@ Only final-gate computes:
|
|
|
521
540
|
|
|
522
541
|
```text
|
|
523
542
|
product_goal_complete=true
|
|
543
|
+
completion_output_status=accept
|
|
524
544
|
```
|
|
525
545
|
|
|
526
|
-
Implementation / execution Goals complete only after
|
|
546
|
+
Implementation / execution Goals complete only after both computed values are present.
|
|
527
547
|
|
|
528
548
|
## 14. Completion Semantics
|
|
529
549
|
|
|
@@ -538,6 +558,9 @@ acceptance_target_status
|
|
|
538
558
|
|
|
539
559
|
product_goal_complete
|
|
540
560
|
= whether implementation/execution is truly complete.
|
|
561
|
+
|
|
562
|
+
completion_output_status
|
|
563
|
+
= official generated final-answer status: accept, reject or blocked.
|
|
541
564
|
```
|
|
542
565
|
|
|
543
566
|
Rules:
|
|
@@ -545,11 +568,13 @@ Rules:
|
|
|
545
568
|
```text
|
|
546
569
|
1. Do not handwrite product_goal_complete.
|
|
547
570
|
2. product_goal_complete is computed only by final-gate.
|
|
548
|
-
3.
|
|
549
|
-
4.
|
|
550
|
-
5.
|
|
571
|
+
3. Do not handwrite completion_output_status.
|
|
572
|
+
4. completion_output_status=accept requires product_goal_complete=true and accepted target status.
|
|
573
|
+
5. audit_task_complete=true is not product completion.
|
|
574
|
+
6. If completion_output_status is reject or blocked, do not say Goal achieved, completed, accepted or done.
|
|
575
|
+
7. A read-only audit task may end, but must say:
|
|
551
576
|
Audit workflow completed; acceptance target not complete.
|
|
552
|
-
|
|
577
|
+
8. Implementation / execution Goal mode can call update_goal complete only when product_goal_complete=true and completion_output_status=accept.
|
|
553
578
|
```
|
|
554
579
|
|
|
555
580
|
This must stay visible in Skill and Goal objective because Codex Goal mode can otherwise confuse "audit workflow ended" with "product target accepted."
|
|
@@ -621,13 +646,13 @@ Final
|
|
|
621
646
|
-> Acceptance Evidence Gate
|
|
622
647
|
-> External Reviewer / stale-overclaim scan
|
|
623
648
|
-> final-gate
|
|
624
|
-
-> product_goal_complete=true before Codex Goal completion
|
|
649
|
+
-> product_goal_complete=true and completion_output_status=accept before Codex Goal completion
|
|
625
650
|
```
|
|
626
651
|
|
|
627
652
|
One-sentence definition:
|
|
628
653
|
|
|
629
654
|
```text
|
|
630
|
-
The expected runtime effect of the composite long-task workflow is to make a Codex agent fuse Tiny Context fact/process constraints, three upstream task authorities, Superpowers long-task execution and a task-state evidence state machine in one context; execution goes Context first, then Plan, then Superpowers slices, and completion is judged by state-backed gates for plan conformance, AC evidence and
|
|
655
|
+
The expected runtime effect of the composite long-task workflow is to make a Codex agent fuse Tiny Context fact/process constraints, three upstream task authorities, Superpowers long-task execution and a task-state evidence state machine in one context; execution goes Context first, then Plan, then Superpowers slices, and completion is judged by state-backed gates for plan conformance, AC evidence, product_goal_complete and completion_output_status so long tasks do not drift in implementation, acceptance or completion claims.
|
|
631
656
|
```
|
|
632
657
|
|
|
633
658
|
## 17. Forbidden Wrong Fusion / 不允许的错误融合
|
|
@@ -645,5 +670,6 @@ Do not treat sample evidence as full-population proof.
|
|
|
645
670
|
Do not claim full implementation when Context Delta is required but Context is not updated.
|
|
646
671
|
Do not claim full alignment while Source-to-Context Coverage / Context-to-Implementation Binding has unresolved gaps.
|
|
647
672
|
Do not handwrite product_goal_complete.
|
|
648
|
-
Do not
|
|
673
|
+
Do not handwrite completion_output_status.
|
|
674
|
+
Do not call update_goal complete before final-gate passes and completion_output_status=accept.
|
|
649
675
|
```
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { applySliceDelta, initializeSuperpowersTask } from "../lib/superpowers-task-state.js";
|
|
3
3
|
import { compileSuperpowersTask } from "../lib/superpowers-task-compile.js";
|
|
4
|
+
import { startAndSaveSuperpowersAttempt } from "../lib/superpowers-task-attempt.js";
|
|
5
|
+
import { recordSuperpowersEvidence, runSuperpowersAssertion } from "../lib/superpowers-task-evidence.js";
|
|
4
6
|
import { deriveSuperpowersArtifacts } from "../lib/superpowers-task-derive.js";
|
|
5
7
|
import { runEpochGate, runFinalGate, runSliceGate } from "../lib/superpowers-task-gates.js";
|
|
6
8
|
import { nextSuperpowersSlices } from "../lib/superpowers-task-next-slices.js";
|
|
@@ -26,10 +28,37 @@ export async function runCompositeLongTaskCommand(args, options) {
|
|
|
26
28
|
return;
|
|
27
29
|
}
|
|
28
30
|
if (subcommand === "compile") {
|
|
29
|
-
const state = await compileSuperpowersTask(workdir);
|
|
31
|
+
const state = await compileSuperpowersTask(workdir, { mode: attemptMode(args) });
|
|
30
32
|
console.log(`compiled ${options.label} graph plan_items=${Object.keys(state.graph.plan_items).length} acs=${Object.keys(state.graph.acceptance_criteria).length}`);
|
|
31
33
|
return;
|
|
32
34
|
}
|
|
35
|
+
if (subcommand === "start-attempt") {
|
|
36
|
+
const attempt = await startAndSaveSuperpowersAttempt(workdir, attemptMode(args));
|
|
37
|
+
console.log(`started attempt ${attempt.task_attempt_id}`);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
if (subcommand === "run-assertion") {
|
|
41
|
+
const separator = args.indexOf("--");
|
|
42
|
+
const commandArgs = separator >= 0 ? args.slice(separator + 1) : [];
|
|
43
|
+
const run = await runSuperpowersAssertion(workdir, {
|
|
44
|
+
acId: optionValue(args, "--ac") ?? "",
|
|
45
|
+
proofLayer: optionValue(args, "--proof-layer") ?? "",
|
|
46
|
+
commandArgs
|
|
47
|
+
});
|
|
48
|
+
console.log(`recorded assertion command_run_id=${run.command_run_id} exit_code=${run.exit_code}`);
|
|
49
|
+
if (run.exit_code !== 0) {
|
|
50
|
+
process.exitCode = 1;
|
|
51
|
+
}
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
if (subcommand === "record-evidence") {
|
|
55
|
+
const evidence = await recordSuperpowersEvidence(workdir, {
|
|
56
|
+
artifactPath: path.resolve(process.cwd(), optionValue(args, "--from") ?? ""),
|
|
57
|
+
commandRunId: optionValue(args, "--command-run-id") ?? ""
|
|
58
|
+
});
|
|
59
|
+
console.log(`registered evidence ${evidence.evidence_id}`);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
33
62
|
if (subcommand === "apply-slice-delta") {
|
|
34
63
|
const delta = args[2];
|
|
35
64
|
if (!delta) {
|
|
@@ -63,8 +92,20 @@ export async function runCompositeLongTaskCommand(args, options) {
|
|
|
63
92
|
if (subcommand === "final-gate") {
|
|
64
93
|
const result = await runFinalGate(workdir);
|
|
65
94
|
console.log(`final gate product_goal_complete=${result.product_goal_complete}`);
|
|
66
|
-
|
|
67
|
-
|
|
95
|
+
console.log(`acceptance_target_status=${result.acceptance_target_status}`);
|
|
96
|
+
console.log(`completion_output_status=${result.completion_output_status}`);
|
|
97
|
+
console.log(`final_answer_allowed=${result.final_answer_allowed}`);
|
|
98
|
+
console.log(`required_user_visible_status=${result.required_user_visible_status}`);
|
|
99
|
+
console.log(`exit_code=${result.exit_code}`);
|
|
100
|
+
console.log(`audit_task_complete=${result.audit_task_complete}`);
|
|
101
|
+
if (result.blocked_reasons.length > 0) {
|
|
102
|
+
console.log(`blocked_reasons=${result.blocked_reasons.join("; ")}`);
|
|
103
|
+
}
|
|
104
|
+
if (result.rejection_reasons.length > 0) {
|
|
105
|
+
console.log(`rejection_reasons=${result.rejection_reasons.join("; ")}`);
|
|
106
|
+
}
|
|
107
|
+
if (!result.final_answer_allowed) {
|
|
108
|
+
process.exitCode = result.exit_code;
|
|
68
109
|
for (const error of result.errors) {
|
|
69
110
|
console.error(`error: ${error}`);
|
|
70
111
|
}
|
|
@@ -89,15 +130,29 @@ function help(commandName, showRenderGoal) {
|
|
|
89
130
|
const renderGoal = showRenderGoal ? "\n render-goal <workdir> Render workflow-protocol.md, execution-binding.md and goal-objective.txt" : "";
|
|
90
131
|
console.log(`${commandName} commands:
|
|
91
132
|
init <workdir> Initialize task-state.json and events.ndjson
|
|
92
|
-
compile <workdir>
|
|
133
|
+
compile <workdir> [--mode product_task|harness_task]
|
|
134
|
+
Compile sources into task graph
|
|
135
|
+
start-attempt <workdir> [--mode product_task|harness_task]
|
|
136
|
+
Start a fresh current attempt
|
|
137
|
+
run-assertion <workdir> --ac <id> --proof-layer <layer> -- <command>
|
|
138
|
+
Run and record an assertion command
|
|
139
|
+
record-evidence <workdir> --from <artifact> --command-run-id <id>
|
|
140
|
+
Register current-attempt EvidenceRecordV2
|
|
93
141
|
apply-slice-delta <workdir> <delta> Apply structured slice delta, evidence and derived views
|
|
94
142
|
derive <workdir> Generate derived/** views
|
|
95
143
|
slice-gate <workdir> --slice <id> Validate one slice has real progress
|
|
96
144
|
epoch-gate <workdir> --epoch <id> Refresh shared epoch evidence views
|
|
97
|
-
final-gate <workdir> Compute product_goal_complete
|
|
145
|
+
final-gate <workdir> Compute product_goal_complete and completion_output_status
|
|
98
146
|
next-slices <workdir> --limit 5 Recommend next proof clusters${renderGoal}`);
|
|
99
147
|
}
|
|
100
148
|
function optionValue(args, name) {
|
|
101
149
|
const index = args.indexOf(name);
|
|
102
150
|
return index >= 0 ? args[index + 1] : undefined;
|
|
103
151
|
}
|
|
152
|
+
function attemptMode(args) {
|
|
153
|
+
const value = optionValue(args, "--mode");
|
|
154
|
+
if (value === "harness_task") {
|
|
155
|
+
return "harness_task";
|
|
156
|
+
}
|
|
157
|
+
return "product_task";
|
|
158
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { SuperpowersTaskState } from "./superpowers-task-state-schema.js";
|
|
2
|
+
export interface Ac010BootstrapResult {
|
|
3
|
+
invalidated_ac_ids: string[];
|
|
4
|
+
errors: string[];
|
|
5
|
+
}
|
|
6
|
+
export declare function evaluateAc010Bootstrap(state: SuperpowersTaskState, acStatuses: Record<string, string>): Ac010BootstrapResult;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
const SUMMARY_ONLY = /\b(AC[-_ ]?010|final[-_ ]?gate|summary|final[-_ ]?acceptance[-_ ]?verdict|final[-_ ]?summary|matrix|validator)\b/i;
|
|
2
|
+
export function evaluateAc010Bootstrap(state, acStatuses) {
|
|
3
|
+
const invalidated = [];
|
|
4
|
+
const errors = [];
|
|
5
|
+
for (const [acId, ac] of Object.entries(state.graph?.acceptance_criteria ?? {})) {
|
|
6
|
+
if (!isSummaryAc(acId, ac)) {
|
|
7
|
+
continue;
|
|
8
|
+
}
|
|
9
|
+
if (acStatuses[acId] !== "complete" && ac.status !== "complete") {
|
|
10
|
+
continue;
|
|
11
|
+
}
|
|
12
|
+
const incomplete = Object.keys(state.graph.acceptance_criteria).filter((otherId) => otherId !== acId && acIsRequired(otherId) && acStatuses[otherId] !== "complete" && acStatuses[otherId] !== "out_of_scope_NA");
|
|
13
|
+
if (incomplete.length === 0) {
|
|
14
|
+
continue;
|
|
15
|
+
}
|
|
16
|
+
invalidated.push(acId);
|
|
17
|
+
errors.push(`${acId} final_gate_cannot_bootstrap_from_summary_only: summary AC cannot prove incomplete ACs ${incomplete.join(", ")}`);
|
|
18
|
+
}
|
|
19
|
+
return { invalidated_ac_ids: invalidated, errors };
|
|
20
|
+
}
|
|
21
|
+
function isSummaryAc(acId, ac) {
|
|
22
|
+
return /^AC-?0?10$/i.test(acId) || SUMMARY_ONLY.test([ac.scope ?? "", ac.assertion_command ?? "", ...(ac.final_evidence_expected ?? [])].join("\n"));
|
|
23
|
+
}
|
|
24
|
+
function acIsRequired(acId) {
|
|
25
|
+
return !/^AC-?0?10$/i.test(acId);
|
|
26
|
+
}
|
|
@@ -10,12 +10,16 @@ export function normalizeAssertionResult(value) {
|
|
|
10
10
|
runner: String(value.runner ?? ""),
|
|
11
11
|
exit_code: numberValue(value.exit_code),
|
|
12
12
|
target_ac_ids: stringArray(value.target_ac_ids),
|
|
13
|
+
target_pi_ids: stringArray(value.target_pi_ids),
|
|
13
14
|
target_proof_layers: stringArray(value.target_proof_layers).map(normalizeProofLayerId),
|
|
14
15
|
owner_surface: value.owner_surface === undefined ? undefined : String(value.owner_surface),
|
|
15
16
|
route: value.route === undefined ? undefined : String(value.route),
|
|
16
17
|
action: value.action === undefined ? undefined : String(value.action),
|
|
17
18
|
positive_assertions: checkArray(value.positive_assertions),
|
|
18
19
|
negative_assertions: checkArray(value.negative_assertions),
|
|
20
|
+
invalid_completion_signals: checkArray(value.invalid_completion_signals),
|
|
21
|
+
negative_evidence_scan: normalizeNegativeEvidenceScan(value.negative_evidence_scan),
|
|
22
|
+
required_test_ids: stringArray(value.required_test_ids),
|
|
19
23
|
artifacts: stringArray(value.artifacts)
|
|
20
24
|
};
|
|
21
25
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { evaluateCurrentAttemptEvidence } from "./superpowers-task-current-evidence.js";
|
|
1
2
|
import { MACHINE_VERIFIABLE_LAYER_NAMES, normalizeProofLayerId, normalizeProofLayerName } from "./superpowers-task-fields.js";
|
|
2
3
|
export { normalizeAssertionResult, normalizeNegativeEvidenceScan } from "./superpowers-task-assertion-normalizers.js";
|
|
3
4
|
export const MACHINE_VERIFIABLE_PROOF_LAYERS = new Set(MACHINE_VERIFIABLE_LAYER_NAMES);
|
|
@@ -67,6 +68,13 @@ export function evaluateProofLayerAssertions(state, layerId) {
|
|
|
67
68
|
if (!isMachineVerifiableLayer(layerId)) {
|
|
68
69
|
return { assertion_status: "not_applicable", blocking_assertion_failures: [], negative_evidence_findings: [] };
|
|
69
70
|
}
|
|
71
|
+
if (!state.current_attempt_id) {
|
|
72
|
+
return {
|
|
73
|
+
assertion_status: "missing",
|
|
74
|
+
blocking_assertion_failures: [`proof layer ${layerId} missing current attempt; current attempt is required for machine-backed completion`],
|
|
75
|
+
negative_evidence_findings: []
|
|
76
|
+
};
|
|
77
|
+
}
|
|
70
78
|
const layer = state.graph.proof_layers[layerId];
|
|
71
79
|
const evidenceById = new Map((state.evidence ?? []).map((item) => [item.evidence_id, item]));
|
|
72
80
|
const evidenceRecords = (layer?.evidence_ids ?? []).map((id) => evidenceById.get(id)).filter((item) => Boolean(item));
|
|
@@ -80,6 +88,7 @@ export function evaluateProofLayerAssertions(state, layerId) {
|
|
|
80
88
|
const blocking = [];
|
|
81
89
|
const negative = [];
|
|
82
90
|
for (const evidence of evidenceRecords) {
|
|
91
|
+
blocking.push(...evaluateCurrentAttemptEvidence(state, evidence, layerId));
|
|
83
92
|
blocking.push(...evaluateAssertionEvidence(evidence, layerId));
|
|
84
93
|
negative.push(...evaluateNegativeEvidence(evidence, layerId));
|
|
85
94
|
}
|
|
@@ -115,8 +124,8 @@ export function evaluateAssertionEvidence(evidence, layerId) {
|
|
|
115
124
|
failures.push(`${label} missing assertion result; ${layerName} proof not machine-backed`);
|
|
116
125
|
return failures;
|
|
117
126
|
}
|
|
118
|
-
if (assertion.schema_version !== "assertion-result-v1") {
|
|
119
|
-
failures.push(`${label} assertion_result.schema_version must be assertion-result-v1`);
|
|
127
|
+
if (assertion.schema_version !== "assertion-result-v1" && assertion.schema_version !== "assertion-result-v2") {
|
|
128
|
+
failures.push(`${label} assertion_result.schema_version must be assertion-result-v1 or assertion-result-v2`);
|
|
120
129
|
}
|
|
121
130
|
if (assertion.status !== "passed") {
|
|
122
131
|
failures.push(`${label} assertion_result.status=${assertion.status}; expected passed`);
|
|
@@ -137,6 +146,11 @@ export function evaluateAssertionEvidence(evidence, layerId) {
|
|
|
137
146
|
}
|
|
138
147
|
failures.push(...checkAssertions(`${label} positive assertion`, assertion.positive_assertions));
|
|
139
148
|
failures.push(...checkAssertions(`${label} negative assertion`, assertion.negative_assertions));
|
|
149
|
+
failures.push(...checkAssertions(`${label} invalid completion signal`, assertion.invalid_completion_signals ?? []));
|
|
150
|
+
const missingRequiredTests = (assertion.required_test_ids ?? []).filter((testId) => !testId);
|
|
151
|
+
if (missingRequiredTests.length > 0) {
|
|
152
|
+
failures.push(`${label} assertion_result.required_test_ids contains empty test ids`);
|
|
153
|
+
}
|
|
140
154
|
if ((assertion.artifacts?.length ?? 0) === 0 && evidence.artifact_paths.length === 0) {
|
|
141
155
|
failures.push(`${label} assertion-backed evidence must include artifacts`);
|
|
142
156
|
}
|
|
@@ -182,9 +196,9 @@ function checkAssertions(prefix, checks) {
|
|
|
182
196
|
.map((check) => `${prefix} ${check.id || "(unnamed)"} status=${check.status}; expected passed${check.forbidden_text ? ` forbidden_text=${check.forbidden_text}` : ""}`);
|
|
183
197
|
}
|
|
184
198
|
export function evaluateNegativeEvidence(evidence, layerId) {
|
|
185
|
-
const scan = evidence.negative_evidence_scan;
|
|
199
|
+
const scan = evidence.assertion_result?.negative_evidence_scan ?? evidence.negative_evidence_scan;
|
|
186
200
|
if (!scan) {
|
|
187
|
-
return [];
|
|
201
|
+
return [`proof layer ${layerId} evidence ${evidence.evidence_id} missing negative_evidence_scan`];
|
|
188
202
|
}
|
|
189
203
|
const acId = proofLayerAcId(layerId);
|
|
190
204
|
const label = `proof layer ${layerId} evidence ${evidence.evidence_id}`;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { ExecutionAttempt, SuperpowersAttemptMode, SuperpowersTaskState } from "./superpowers-task-state-schema.js";
|
|
2
|
+
export declare function startSuperpowersAttempt(workdir: string, state: SuperpowersTaskState, mode?: SuperpowersAttemptMode): Promise<ExecutionAttempt>;
|
|
3
|
+
export declare function startAndSaveSuperpowersAttempt(workdir: string, mode?: SuperpowersAttemptMode): Promise<ExecutionAttempt>;
|
|
4
|
+
export declare function computeSourceBundleHash(state: SuperpowersTaskState, specsHash?: string): string;
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { promisify } from "node:util";
|
|
3
|
+
import { appendSuperpowersEvent } from "./superpowers-task-events.js";
|
|
4
|
+
import { requiredCommandSpecsHash } from "./superpowers-task-command-specs.js";
|
|
5
|
+
import { loadSuperpowersState, saveSuperpowersState, sha256, stableJson } from "./superpowers-task-state.js";
|
|
6
|
+
const execFileAsync = promisify(execFile);
|
|
7
|
+
export async function startSuperpowersAttempt(workdir, state, mode = "product_task") {
|
|
8
|
+
const now = new Date().toISOString();
|
|
9
|
+
const specsHash = requiredCommandSpecsHash(state.required_command_specs ?? []);
|
|
10
|
+
const sourceBundleHash = computeSourceBundleHash(state, specsHash);
|
|
11
|
+
const git = await readGitIdentity(workdir);
|
|
12
|
+
const worktreeFingerprint = sha256(stableJson(git));
|
|
13
|
+
const attemptOrdinal = (state.attempts ?? []).length + 1;
|
|
14
|
+
const attempt = {
|
|
15
|
+
task_attempt_id: `ATT-${compactDate(now)}-${attemptOrdinal}-${sourceBundleHash.slice(0, 8)}`,
|
|
16
|
+
source_bundle_hash: sourceBundleHash,
|
|
17
|
+
product_source_hash: state.sources.product_architecture_source?.sha256 ?? "",
|
|
18
|
+
technical_plan_hash: state.sources.technical_realization_plan?.sha256 ?? "",
|
|
19
|
+
acceptance_checklist_hash: state.sources.acceptance_checklist?.sha256 ?? "",
|
|
20
|
+
git_head: git.git_head,
|
|
21
|
+
git_status_short: git.git_status_short,
|
|
22
|
+
tracked_diff_hash: git.tracked_diff_hash,
|
|
23
|
+
relevant_untracked_hash: git.relevant_untracked_hash,
|
|
24
|
+
untracked_relevant_hash: git.relevant_untracked_hash,
|
|
25
|
+
worktree_fingerprint: worktreeFingerprint,
|
|
26
|
+
started_at: now,
|
|
27
|
+
ended_at: null,
|
|
28
|
+
finalized_at: null,
|
|
29
|
+
required_command_specs_hash: specsHash,
|
|
30
|
+
mode,
|
|
31
|
+
changed_files: git.changed_files
|
|
32
|
+
};
|
|
33
|
+
state.attempts = [...(state.attempts ?? []).filter((item) => item.task_attempt_id !== attempt.task_attempt_id), attempt];
|
|
34
|
+
state.current_attempt_id = attempt.task_attempt_id;
|
|
35
|
+
return attempt;
|
|
36
|
+
}
|
|
37
|
+
export async function startAndSaveSuperpowersAttempt(workdir, mode = "product_task") {
|
|
38
|
+
const state = await loadSuperpowersState(workdir);
|
|
39
|
+
const attempt = await startSuperpowersAttempt(workdir, state, mode);
|
|
40
|
+
await saveSuperpowersState(workdir, state);
|
|
41
|
+
await appendSuperpowersEvent(workdir, "attempt_started", { task_attempt_id: attempt.task_attempt_id });
|
|
42
|
+
return attempt;
|
|
43
|
+
}
|
|
44
|
+
export function computeSourceBundleHash(state, specsHash = requiredCommandSpecsHash(state.required_command_specs ?? [])) {
|
|
45
|
+
return sha256(stableJson({
|
|
46
|
+
product_source_hash: state.sources.product_architecture_source?.sha256 ?? "",
|
|
47
|
+
technical_plan_hash: state.sources.technical_realization_plan?.sha256 ?? "",
|
|
48
|
+
acceptance_checklist_hash: state.sources.acceptance_checklist?.sha256 ?? "",
|
|
49
|
+
required_command_specs_hash: specsHash
|
|
50
|
+
}));
|
|
51
|
+
}
|
|
52
|
+
async function readGitIdentity(workdir) {
|
|
53
|
+
const head = await git(workdir, ["rev-parse", "HEAD"]);
|
|
54
|
+
if (!head.ok) {
|
|
55
|
+
return {
|
|
56
|
+
git_head: "no-git",
|
|
57
|
+
git_status_short: "no-git",
|
|
58
|
+
tracked_diff_hash: sha256("no-git-tracked-diff"),
|
|
59
|
+
relevant_untracked_hash: "ignored:no-git",
|
|
60
|
+
changed_files: []
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
const status = await git(workdir, ["status", "--short"]);
|
|
64
|
+
const diff = await git(workdir, ["diff", "--binary"]);
|
|
65
|
+
const stagedDiff = await git(workdir, ["diff", "--cached", "--binary"]);
|
|
66
|
+
const untracked = await git(workdir, ["ls-files", "--others", "--exclude-standard"]);
|
|
67
|
+
const trackedNames = await git(workdir, ["diff", "--name-only"]);
|
|
68
|
+
const stagedNames = await git(workdir, ["diff", "--cached", "--name-only"]);
|
|
69
|
+
const changedFiles = unique([
|
|
70
|
+
...lines(trackedNames.ok ? trackedNames.stdout : ""),
|
|
71
|
+
...lines(stagedNames.ok ? stagedNames.stdout : ""),
|
|
72
|
+
...lines(untracked.ok ? untracked.stdout : "")
|
|
73
|
+
]);
|
|
74
|
+
return {
|
|
75
|
+
git_head: head.stdout.trim(),
|
|
76
|
+
git_status_short: status.ok ? status.stdout.trim() : "git-status-unavailable",
|
|
77
|
+
tracked_diff_hash: sha256(`${diff.ok ? diff.stdout : "git-diff-unavailable"}\n${stagedDiff.ok ? stagedDiff.stdout : ""}`),
|
|
78
|
+
relevant_untracked_hash: untracked.ok && untracked.stdout.trim() ? sha256(untracked.stdout.trim()) : "none",
|
|
79
|
+
changed_files: changedFiles
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
async function git(workdir, args) {
|
|
83
|
+
try {
|
|
84
|
+
const result = await execFileAsync("git", args, { cwd: workdir, windowsHide: true, timeout: 10000 });
|
|
85
|
+
return { ok: true, stdout: result.stdout };
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
return { ok: false, stdout: "" };
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
function compactDate(value) {
|
|
92
|
+
return value.replace(/[-:.TZ]/g, "").slice(0, 14);
|
|
93
|
+
}
|
|
94
|
+
function lines(value) {
|
|
95
|
+
return value
|
|
96
|
+
.split(/\r?\n/)
|
|
97
|
+
.map((item) => item.trim().replace(/\\/g, "/"))
|
|
98
|
+
.filter(Boolean);
|
|
99
|
+
}
|
|
100
|
+
function unique(values) {
|
|
101
|
+
return [...new Set(values.filter(Boolean))];
|
|
102
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { CommandRunRecord, ExecutionAttempt, RequiredCommandSpec, SuperpowersTaskState } from "./superpowers-task-state-schema.js";
|
|
2
|
+
export interface CommandRunCorrelationResult {
|
|
3
|
+
errors: string[];
|
|
4
|
+
invalidated_evidence_ids: string[];
|
|
5
|
+
}
|
|
6
|
+
export declare function validateRequiredCommandCorrelation(state: SuperpowersTaskState, attempt: ExecutionAttempt | undefined, expectedSpecs: RequiredCommandSpec[]): CommandRunCorrelationResult;
|
|
7
|
+
export declare function validateCommandRunsForSpec(state: SuperpowersTaskState, attempt: ExecutionAttempt | undefined, spec: RequiredCommandSpec): string[];
|
|
8
|
+
export declare function validateCommandRun(run: CommandRunRecord, attempt: ExecutionAttempt | undefined): string[];
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { isMachineVerifiableLayer } from "./superpowers-task-assertions.js";
|
|
2
|
+
import { normalizeProofLayerName } from "./superpowers-task-fields.js";
|
|
3
|
+
import { requiredCommandSpecsHash } from "./superpowers-task-command-specs.js";
|
|
4
|
+
export function validateRequiredCommandCorrelation(state, attempt, expectedSpecs) {
|
|
5
|
+
const errors = [];
|
|
6
|
+
const invalidated = new Set();
|
|
7
|
+
const expectedByAc = new Map(expectedSpecs.map((spec) => [spec.ac_id, spec]));
|
|
8
|
+
for (const [acId, ac] of Object.entries(state.graph?.acceptance_criteria ?? {})) {
|
|
9
|
+
if (ac.machine_blocking !== true && ac.assertion_result_required !== true) {
|
|
10
|
+
continue;
|
|
11
|
+
}
|
|
12
|
+
const expected = expectedByAc.get(acId);
|
|
13
|
+
const actual = (state.required_command_specs ?? []).find((spec) => spec.ac_id === acId);
|
|
14
|
+
if (!expected || !actual) {
|
|
15
|
+
errors.push(`${acId} missing required_command_spec`);
|
|
16
|
+
continue;
|
|
17
|
+
}
|
|
18
|
+
if (actual.command_spec_id !== expected.command_spec_id) {
|
|
19
|
+
errors.push(`${acId} command_spec_id mismatch; required command specs must be recompiled from Acceptance Checklist`);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
if (attempt) {
|
|
23
|
+
const specsHash = requiredCommandSpecsHash(state.required_command_specs ?? []);
|
|
24
|
+
if (attempt.required_command_specs_hash !== specsHash) {
|
|
25
|
+
errors.push("required_command_specs_hash mismatch for current attempt");
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
for (const spec of expectedSpecs) {
|
|
29
|
+
errors.push(...validateCommandRunsForSpec(state, attempt, spec));
|
|
30
|
+
}
|
|
31
|
+
for (const finding of findFailedCommandInvalidations(state)) {
|
|
32
|
+
invalidated.add(finding.evidence_id);
|
|
33
|
+
errors.push(`${finding.evidence_id} newer failed command invalidates older passed evidence for ${finding.ac_id}.${finding.proof_layer}: ${finding.command_run_id}`);
|
|
34
|
+
}
|
|
35
|
+
return { errors: unique(errors), invalidated_evidence_ids: [...invalidated] };
|
|
36
|
+
}
|
|
37
|
+
export function validateCommandRunsForSpec(state, attempt, spec) {
|
|
38
|
+
const errors = [];
|
|
39
|
+
for (const proofLayer of spec.proof_layers.filter((layer) => isMachineVerifiableLayer(`${spec.ac_id}.${layer}`))) {
|
|
40
|
+
const run = (state.command_runs ?? []).find((item) => item.task_attempt_id === state.current_attempt_id &&
|
|
41
|
+
item.command_spec_id === spec.command_spec_id &&
|
|
42
|
+
item.ac_id === spec.ac_id &&
|
|
43
|
+
normalizeProofLayerName(item.proof_layer) === normalizeProofLayerName(proofLayer));
|
|
44
|
+
if (!run) {
|
|
45
|
+
errors.push(`${spec.ac_id}.${proofLayer} missing current attempt command-run record for command_spec_id ${spec.command_spec_id}`);
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
errors.push(...validateCommandRun(run, attempt));
|
|
49
|
+
}
|
|
50
|
+
return errors;
|
|
51
|
+
}
|
|
52
|
+
export function validateCommandRun(run, attempt) {
|
|
53
|
+
const errors = [];
|
|
54
|
+
if (attempt && run.task_attempt_id !== attempt.task_attempt_id) {
|
|
55
|
+
errors.push(`${run.command_run_id} stale command run from ${run.task_attempt_id}; expected ${attempt.task_attempt_id}`);
|
|
56
|
+
}
|
|
57
|
+
if (run.exit_code !== 0) {
|
|
58
|
+
errors.push(`${run.command_run_id} command_exit_code=${run.exit_code}; expected 0`);
|
|
59
|
+
}
|
|
60
|
+
if (!run.command_line.trim()) {
|
|
61
|
+
errors.push(`${run.command_run_id} missing command_line`);
|
|
62
|
+
}
|
|
63
|
+
if (!run.started_at) {
|
|
64
|
+
errors.push(`${run.command_run_id} missing started_at`);
|
|
65
|
+
}
|
|
66
|
+
if (!run.completed_at) {
|
|
67
|
+
errors.push(`${run.command_run_id} missing completed_at`);
|
|
68
|
+
}
|
|
69
|
+
if (!run.task_attempt_id) {
|
|
70
|
+
errors.push(`${run.command_run_id} missing attempt_id`);
|
|
71
|
+
}
|
|
72
|
+
return errors;
|
|
73
|
+
}
|
|
74
|
+
function findFailedCommandInvalidations(state) {
|
|
75
|
+
const evidence = state.evidence ?? [];
|
|
76
|
+
const failedRuns = (state.command_runs ?? []).filter((run) => run.task_attempt_id === state.current_attempt_id && Number(run.exit_code) !== 0);
|
|
77
|
+
return failedRuns.flatMap((run) => evidence
|
|
78
|
+
.filter((item) => evidenceTargetsRunLayer(item, run) && evidencePredatesRun(item, run))
|
|
79
|
+
.map((item) => ({
|
|
80
|
+
evidence_id: item.evidence_id,
|
|
81
|
+
command_run_id: run.command_run_id,
|
|
82
|
+
ac_id: run.ac_id,
|
|
83
|
+
proof_layer: run.proof_layer
|
|
84
|
+
})));
|
|
85
|
+
}
|
|
86
|
+
function evidenceTargetsRunLayer(evidence, run) {
|
|
87
|
+
const layerId = `${run.ac_id}.${normalizeProofLayerName(run.proof_layer)}`;
|
|
88
|
+
const targetLayers = (evidence.target_proof_layers ?? evidence.assertion_result?.target_proof_layers ?? []).map((item) => item.includes(".") ? item : `${run.ac_id}.${normalizeProofLayerName(item)}`);
|
|
89
|
+
return (evidence.task_attempt_id === run.task_attempt_id &&
|
|
90
|
+
(evidence.target_ac_ids ?? evidence.assertion_result?.target_ac_ids ?? []).includes(run.ac_id) &&
|
|
91
|
+
(targetLayers.includes(layerId) || evidence.proves.includes(layerId)));
|
|
92
|
+
}
|
|
93
|
+
function evidencePredatesRun(evidence, run) {
|
|
94
|
+
const evidenceTime = Date.parse(evidence.generated_at ?? evidence.freshness?.created_at ?? evidence.artifact_mtime ?? "");
|
|
95
|
+
const runTime = Date.parse(run.started_at || run.completed_at || run.ended_at || "");
|
|
96
|
+
if (Number.isNaN(evidenceTime) || Number.isNaN(runTime)) {
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
return evidenceTime <= runTime;
|
|
100
|
+
}
|
|
101
|
+
function unique(values) {
|
|
102
|
+
return [...new Set(values.filter(Boolean))];
|
|
103
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { RequiredCommandSpec, SuperpowersTaskState } from "./superpowers-task-state-schema.js";
|
|
2
|
+
export declare function deriveRequiredCommandSpecs(state: SuperpowersTaskState): RequiredCommandSpec[];
|
|
3
|
+
export declare function requiredCommandSpecsHash(specs: RequiredCommandSpec[]): string;
|