project-tiny-context-harness 0.2.80 → 0.2.82

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.
Files changed (34) hide show
  1. package/README.md +10 -8
  2. package/assets/README.md +9 -7
  3. package/assets/README.zh-CN.md +10 -0
  4. package/assets/github/harness.yml +1 -1
  5. package/assets/skills/composite-long-task-workflow/SKILL.md +10 -2
  6. package/assets/skills/composite-long-task-workflow/assets/execution-binding.template.md +2 -0
  7. package/assets/skills/composite-long-task-workflow/assets/goal-objective.template.md +7 -3
  8. package/assets/skills/composite-long-task-workflow/references/composite-long-task-workflow-protocol.md +27 -11
  9. package/dist/lib/composite-long-task-renderer.js +18 -62
  10. package/dist/lib/superpowers-task-assertion-normalizers.d.ts +3 -0
  11. package/dist/lib/superpowers-task-assertion-normalizers.js +70 -0
  12. package/dist/lib/superpowers-task-assertions.d.ts +20 -0
  13. package/dist/lib/superpowers-task-assertions.js +243 -0
  14. package/dist/lib/superpowers-task-compile-diagnostics.d.ts +5 -0
  15. package/dist/lib/superpowers-task-compile-diagnostics.js +20 -0
  16. package/dist/lib/superpowers-task-compile-guards.d.ts +2 -0
  17. package/dist/lib/superpowers-task-compile-guards.js +66 -0
  18. package/dist/lib/superpowers-task-compile.js +25 -5
  19. package/dist/lib/superpowers-task-conformance.d.ts +2 -0
  20. package/dist/lib/superpowers-task-conformance.js +24 -0
  21. package/dist/lib/superpowers-task-delivery.js +30 -18
  22. package/dist/lib/superpowers-task-derive.d.ts +1 -0
  23. package/dist/lib/superpowers-task-derive.js +121 -4
  24. package/dist/lib/superpowers-task-fields.d.ts +22 -0
  25. package/dist/lib/superpowers-task-fields.js +276 -0
  26. package/dist/lib/superpowers-task-gates.js +57 -3
  27. package/dist/lib/superpowers-task-source-compile.js +128 -92
  28. package/dist/lib/superpowers-task-source-parser.js +12 -18
  29. package/dist/lib/superpowers-task-state-schema.d.ts +120 -1
  30. package/dist/lib/superpowers-task-state.js +33 -8
  31. package/dist/lib/superpowers-task-status.d.ts +2 -0
  32. package/dist/lib/superpowers-task-status.js +14 -0
  33. package/dist/lib/superpowers-task-validator.js +18 -9
  34. package/package.json +69 -69
@@ -1,6 +1,7 @@
1
1
  import path from "node:path";
2
2
  import { ensureDir, pathExists, readText, writeTextIfChanged } from "./fs.js";
3
3
  import { stableJson, loadSuperpowersState } from "./superpowers-task-state.js";
4
+ import { evaluateProofLayerAssertions } from "./superpowers-task-assertions.js";
4
5
  export async function deriveSuperpowersArtifacts(workdir) {
5
6
  const state = await loadSuperpowersState(workdir);
6
7
  const derived = deriveObjects(state);
@@ -10,6 +11,7 @@ export async function deriveSuperpowersArtifacts(workdir) {
10
11
  await writeDerived(files, path.join(derivedDir, "plan-conformance-matrix.json"), stableJson(derived.matrix));
11
12
  await writeDerived(files, path.join(derivedDir, "final-acceptance-verdict.json"), stableJson(derived.verdict));
12
13
  await writeDerived(files, path.join(derivedDir, "progress-ledger.json"), stableJson(derived.progress));
14
+ await writeDerived(files, path.join(derivedDir, "evidence-index.json"), stableJson(deriveEvidenceIndex(state)));
13
15
  await writeDerived(files, path.join(derivedDir, "plan-conformance-matrix.md"), matrixMarkdown(derived.matrix));
14
16
  await writeDerived(files, path.join(derivedDir, "final-acceptance-verdict.md"), verdictMarkdown(derived.verdict));
15
17
  await writeDerived(files, path.join(derivedDir, "local-audit.md"), localAuditMarkdown(state));
@@ -23,8 +25,10 @@ export function deriveObjects(state) {
23
25
  const matrixRows = Object.entries(state.graph.plan_items).map(([planItemId, item]) => {
24
26
  const relatedAcs = item.related_acs;
25
27
  const requiredLayers = item.required_proof_layers;
26
- const missingLayers = requiredLayers.filter((layerId) => state.graph.proof_layers[layerId]?.status !== "satisfied");
28
+ const assertionSummary = assertionSummaryForLayers(state, requiredLayers);
29
+ const missingLayers = requiredLayers.filter((layerId) => state.graph.proof_layers[layerId]?.status !== "satisfied" || evaluateProofLayerAssertions(state, layerId).assertion_status === "failed" || evaluateProofLayerAssertions(state, layerId).assertion_status === "missing" || evaluateProofLayerAssertions(state, layerId).assertion_status === "stale");
27
30
  const evidenceIds = evidenceForLayers(state, requiredLayers);
31
+ const invalidEvidence = invalidEvidenceSignals(assertionSummary);
28
32
  return {
29
33
  plan_item_id: planItemId,
30
34
  plan_requirement: item.requirement,
@@ -35,6 +39,15 @@ export function deriveObjects(state) {
35
39
  representative_samples: item.representative_samples,
36
40
  full_population_boundary: item.full_population_boundary,
37
41
  non_required_population: item.non_required_population,
42
+ owner_boundary: item.owner_boundary,
43
+ primary_capability_path: item.primary_capability_path,
44
+ trigger_contract: item.trigger_contract,
45
+ state_transition_contract: item.state_transition_contract,
46
+ observable_result_contract: item.observable_result_contract,
47
+ assertion_support: item.assertion_support,
48
+ required_assertion_commands: item.required_assertion_commands,
49
+ invalid_implementation_shortcuts: item.invalid_implementation_shortcuts,
50
+ blockers: item.blockers ?? [],
38
51
  conformance_type: item.owner_surfaces.length > 0 ? "product_surface" : "implementation",
39
52
  owner_surface: item.owner_surfaces[0] ?? "",
40
53
  forbidden_primary_surfaces: item.forbidden_surfaces,
@@ -51,16 +64,24 @@ export function deriveObjects(state) {
51
64
  required_proof_layers: requiredLayers,
52
65
  satisfied_proof_layers: requiredLayers.filter((layerId) => state.graph.proof_layers[layerId]?.status === "satisfied"),
53
66
  missing_required_layers: missingLayers,
67
+ assertion_status: assertionSummary.assertion_status,
68
+ blocking_assertion_failures: assertionSummary.blocking_assertion_failures,
69
+ negative_evidence_findings: assertionSummary.negative_evidence_findings,
70
+ invalid_evidence: invalidEvidence,
71
+ forbidden_shortcuts_hit: invalidEvidence.filter((item) => /forbidden shortcut|cannot satisfy/i.test(item)),
54
72
  evidence_ids: evidenceIds,
55
73
  scope_assessment: missingLayers.length === 0 ? "full" : "partial",
56
- drift: missingLayers.length === 0 ? "no drift detected" : "missing required proof layers"
74
+ drift: missingLayers.length === 0 ? "no drift detected" : "missing required proof layers",
75
+ decision: missingLayers.length === 0 ? "accept" : "continue"
57
76
  };
58
77
  });
59
78
  const verdictRows = Object.entries(state.graph.acceptance_criteria).map(([acId, ac]) => {
60
79
  const requiredLayers = ac.required_proof_layers.map((layer) => `${acId}.${layer}`);
61
- const missingLayers = requiredLayers.filter((layerId) => state.graph.proof_layers[layerId]?.status !== "satisfied");
80
+ const assertionSummary = assertionSummaryForLayers(state, requiredLayers);
81
+ const missingLayers = requiredLayers.filter((layerId) => state.graph.proof_layers[layerId]?.status !== "satisfied" || evaluateProofLayerAssertions(state, layerId).assertion_status === "failed" || evaluateProofLayerAssertions(state, layerId).assertion_status === "missing" || evaluateProofLayerAssertions(state, layerId).assertion_status === "stale");
62
82
  const evidenceIds = evidenceForLayers(state, requiredLayers);
63
83
  const status = missingLayers.length === 0 && requiredLayers.length > 0 ? "complete" : evidenceIds.length > 0 ? "partial" : ac.status;
84
+ const invalidCompletionSignals = invalidEvidenceSignals(assertionSummary);
64
85
  return {
65
86
  ac_id: acId,
66
87
  related_plan_item_ids: ac.related_plan_items,
@@ -71,11 +92,23 @@ export function deriveObjects(state) {
71
92
  sample_boundary: ac.sample_boundary,
72
93
  full_population_required: ac.full_population_required,
73
94
  full_population_status: ac.full_population_required === true || ac.acceptance_scope === "full_population_operation" ? status : "not_in_scope",
95
+ assertion_command: ac.assertion_command ?? "",
96
+ assertion_artifacts: ac.assertion_artifacts ?? [],
97
+ positive_assertions: ac.positive_assertions ?? [],
98
+ negative_assertions: ac.negative_assertions ?? [],
99
+ machine_blocking: ac.machine_blocking ?? false,
100
+ invalid_completion_signals: ac.invalid_completion_signals ?? [],
101
+ assertion_result_required: ac.assertion_result_required ?? false,
74
102
  required_evidence: requiredLayers,
75
103
  required_proof_chain: requiredLayers,
76
104
  fresh_evidence: evidenceText(state, evidenceIds),
77
105
  missing_evidence: [],
78
106
  missing_required_layers: missingLayers,
107
+ assertion_status: assertionSummary.assertion_status,
108
+ blocking_assertion_failures: assertionSummary.blocking_assertion_failures,
109
+ negative_evidence_findings: assertionSummary.negative_evidence_findings,
110
+ invalid_evidence_findings: invalidCompletionSignals,
111
+ required_next_evidence: requiredNextEvidence(missingLayers, assertionSummary),
79
112
  contradictions: [],
80
113
  context_fact_refs: [],
81
114
  evidence_ids: evidenceIds,
@@ -88,6 +121,16 @@ export function deriveObjects(state) {
88
121
  const allComplete = verdictRows.length > 0 && verdictRows.every((row) => row.status === "complete");
89
122
  const progress = {
90
123
  ...state.progress,
124
+ acceptance_progress: {
125
+ status: allComplete ? "complete" : "partial",
126
+ complete_count: verdictRows.filter((row) => row.status === "complete").length,
127
+ required_count: verdictRows.filter((row) => row.status !== "out_of_scope_NA").length
128
+ },
129
+ engineering_implementation_progress: progressStatus(Object.values(state.graph.plan_items).map((item) => item.status)),
130
+ runtime_proof_progress: progressStatus(Object.values(state.graph.proof_layers).map((layer) => layer.status)),
131
+ proof_layer_milestones: Object.entries(state.graph.proof_layers).map(([layerId, layer]) => ({ layer_id: layerId, status: layer.status, evidence_ids: layer.evidence_ids })),
132
+ artifact_budget: { evidence_records: state.evidence.length, artifact_count: state.evidence.reduce((sum, item) => sum + item.artifact_paths.length, 0) },
133
+ workflow_overhead: { slices: state.slices.length, blockers: state.blockers.length },
91
134
  complete_count: verdictRows.filter((row) => row.status === "complete").length,
92
135
  partial_count: verdictRows.filter((row) => row.status === "partial").length,
93
136
  acceptance_required_count: verdictRows.filter((row) => row.status !== "out_of_scope_NA").length,
@@ -103,13 +146,60 @@ export function deriveObjects(state) {
103
146
  progress
104
147
  };
105
148
  }
149
+ function invalidEvidenceSignals(summary) {
150
+ return [...summary.blocking_assertion_failures, ...summary.negative_evidence_findings].filter((item) => /invalid evidence|forbidden shortcut|cannot satisfy|negative evidence|forbidden text/i.test(item));
151
+ }
152
+ function requiredNextEvidence(missingLayers, summary) {
153
+ return [...missingLayers.map((layerId) => `fresh assertion-backed evidence for ${layerId}`), ...summary.blocking_assertion_failures, ...summary.negative_evidence_findings];
154
+ }
155
+ function progressStatus(statuses) {
156
+ const complete = statuses.filter((status) => status === "complete" || status === "satisfied").length;
157
+ return { status: statuses.length > 0 && complete === statuses.length ? "complete" : complete > 0 ? "partial" : "not_started", complete, total: statuses.length };
158
+ }
106
159
  export async function derivedMatchesState(workdir, state) {
107
160
  const errors = [];
108
161
  const expected = deriveObjects(state);
109
162
  await assertDerivedJson(workdir, "plan-conformance-matrix", expected.matrix, errors);
110
163
  await assertDerivedJson(workdir, "final-acceptance-verdict", expected.verdict, errors);
164
+ await assertDerivedJson(workdir, "evidence-index", deriveEvidenceIndex(state), errors);
111
165
  return errors;
112
166
  }
167
+ export function deriveEvidenceIndex(state) {
168
+ const evidenceById = Object.fromEntries((state.evidence ?? []).map((evidence) => [
169
+ evidence.evidence_id,
170
+ {
171
+ evidence_id: evidence.evidence_id,
172
+ type: evidence.type,
173
+ command: evidence.command ?? "",
174
+ command_exit_code: evidence.command_exit_code,
175
+ artifact_paths: evidence.artifact_paths,
176
+ proves: evidence.proves,
177
+ does_not_prove: evidence.does_not_prove,
178
+ assertion_result: evidence.assertion_result ?? null,
179
+ negative_evidence_scan: evidence.negative_evidence_scan ?? null,
180
+ freshness: evidence.freshness,
181
+ reviewability: evidence.reviewability,
182
+ redaction: evidence.redaction
183
+ }
184
+ ]));
185
+ const proofLayers = Object.fromEntries(Object.entries(state.graph.proof_layers ?? {}).map(([layerId, layer]) => {
186
+ const evaluation = evaluateProofLayerAssertions(state, layerId);
187
+ return [
188
+ layerId,
189
+ {
190
+ layer_id: layerId,
191
+ status: layer.status,
192
+ required: layer.required,
193
+ evidence_ids: layer.evidence_ids,
194
+ assertion_status: evaluation.assertion_status,
195
+ blocking_assertion_failures: evaluation.blocking_assertion_failures,
196
+ negative_evidence_findings: evaluation.negative_evidence_findings,
197
+ evidence: layer.evidence_ids.map((evidenceId) => evidenceById[evidenceId]).filter(Boolean)
198
+ }
199
+ ];
200
+ }));
201
+ return { proof_layers: proofLayers, evidence: evidenceById };
202
+ }
113
203
  async function assertDerivedJson(workdir, basename, expected, errors) {
114
204
  const file = path.join(workdir, "derived", `${basename}.json`);
115
205
  if (!(await pathExists(file))) {
@@ -123,15 +213,35 @@ async function assertDerivedJson(workdir, basename, expected, errors) {
123
213
  function evidenceForLayers(state, layerIds) {
124
214
  return [...new Set(layerIds.flatMap((layerId) => state.graph.proof_layers[layerId]?.evidence_ids ?? []))];
125
215
  }
216
+ function assertionSummaryForLayers(state, layerIds) {
217
+ const evaluations = layerIds.map((layerId) => evaluateProofLayerAssertions(state, layerId));
218
+ const applicable = evaluations.filter((item) => item.assertion_status !== "not_applicable");
219
+ const blocking_assertion_failures = applicable.flatMap((item) => item.blocking_assertion_failures);
220
+ const negative_evidence_findings = applicable.flatMap((item) => item.negative_evidence_findings);
221
+ const statuses = applicable.map((item) => item.assertion_status);
222
+ const assertion_status = applicable.length === 0
223
+ ? "not_applicable"
224
+ : statuses.includes("failed")
225
+ ? "failed"
226
+ : statuses.includes("stale")
227
+ ? "stale"
228
+ : statuses.includes("missing")
229
+ ? "missing"
230
+ : "passed";
231
+ return { assertion_status, blocking_assertion_failures, negative_evidence_findings };
232
+ }
126
233
  function evidenceText(state, evidenceIds, type) {
127
234
  return evidenceIds
128
235
  .map((evidenceId) => state.evidence.find((item) => item.evidence_id === evidenceId))
129
- .filter((item) => item && (!type || item.type === type || (type === "artifact" && item.artifact_paths.length > 0)))
236
+ .filter((item) => item && (!type || evidenceTypeMatches(item.type, type) || (type === "artifact" && item.artifact_paths.length > 0)))
130
237
  .map((item) => {
131
238
  const artifacts = item?.artifact_paths.join(", ");
132
239
  return `${item?.type} ${item?.command ?? ""} ${artifacts}`.trim();
133
240
  });
134
241
  }
242
+ function evidenceTypeMatches(actual, expected) {
243
+ return actual === expected || actual.includes(expected) || (expected === "browser" && /\b(playwright|ui_browser|browser)_assertion\b/.test(actual));
244
+ }
135
245
  function auditorStatus(state) {
136
246
  const auditor = state.gates.auditor;
137
247
  if (auditor && typeof auditor === "object" && !Array.isArray(auditor) && typeof auditor.auditor_status === "string") {
@@ -182,9 +292,16 @@ ${stableJson(progress)}
182
292
  `;
183
293
  }
184
294
  function evidenceMarkdown(state) {
295
+ const index = deriveEvidenceIndex(state);
296
+ const layers = Object.values(index.proof_layers);
185
297
  return [
186
298
  "# Evidence Index",
187
299
  "",
300
+ ...layers.map((item) => {
301
+ const evidenceIds = item.evidence_ids ?? [];
302
+ return `- ${item.layer_id}: ${item.assertion_status}; evidence ${evidenceIds.join(", ") || "(none)"}`;
303
+ }),
304
+ "",
188
305
  ...state.evidence.map((item) => `- ${item.evidence_id}: proves ${item.proves.join(", ")}; does_not_prove ${item.does_not_prove.join(", ")}`)
189
306
  ].join("\n");
190
307
  }
@@ -0,0 +1,22 @@
1
+ export declare const PRODUCT_FIELDS: readonly ["delivery_scope", "full_population_required", "representative_samples_validate", "representative_samples_do_not_validate", "out_of_scope_backlog", "scope_fit_decision", "selected_scope_fit_slice", "owner_boundary", "primary_capability_path", "non_completing_outcomes", "assertion_policy", "source_authority", "product_goal", "surface_ia_lock", "decision_lock", "context_delta", "source_to_context_coverage", "acceptance_semantics", "impact"];
2
+ export declare const PLAN_FIELDS: readonly ["delivery_scope", "capability_target", "representative_samples", "full_population_boundary", "non_required_population", "owner_surfaces", "forbidden_surfaces", "owner_boundary", "primary_capability_path", "trigger_contract", "state_transition_contract", "observable_result_contract", "assertion_support", "required_assertion_commands", "invalid_implementation_shortcuts", "implementation_paths", "required_tests", "related_acs", "requirement_ref", "decision_id", "proof_layer_ids", "api_schema_changes", "state_machine", "data_flow", "worker_runtime_behavior", "ui_ia_changes", "migration_plan", "evidence_artifacts", "explicit_no_test_scope", "non_completing_shortcuts", "substitution_policy", "drift_severity", "partial_conditions", "blockers", "context_fact_refs"];
3
+ export declare const ACCEPTANCE_FIELDS: readonly ["acceptance_scope", "ac_validates", "ac_does_not_validate", "sample_boundary", "full_population_required", "related_plan_items", "required_proof_layers", "assertion_command", "assertion_artifacts", "positive_assertions", "negative_assertions", "machine_blocking", "invalid_completion_signals", "assertion_result_required", "ac_type", "proof_chain", "verification_method", "fail_conditions", "invalid_evidence", "substitution_policy", "missing_layer_downgrade", "auditor_expectation", "out_of_scope_na_approval_source", "required_test_ids", "explicit_no_test_scope", "hard_blockers", "validates_explanation", "does_not_validate_explanation", "final_evidence_expected", "test_cases"];
4
+ export declare const PRODUCT_REQUIRED_FIELDS: readonly ["delivery_scope", "full_population_required", "representative_samples_validate", "representative_samples_do_not_validate", "out_of_scope_backlog", "scope_fit_decision", "selected_scope_fit_slice", "owner_boundary", "primary_capability_path", "non_completing_outcomes", "assertion_policy", "source_authority", "product_goal"];
5
+ export declare const PLAN_REQUIRED_FIELDS: readonly ["delivery_scope", "capability_target", "representative_samples", "full_population_boundary", "non_required_population", "owner_boundary", "primary_capability_path", "trigger_contract", "state_transition_contract", "observable_result_contract", "assertion_support", "required_assertion_commands", "invalid_implementation_shortcuts", "implementation_paths"];
6
+ export declare const ACCEPTANCE_REQUIRED_FIELDS: readonly ["acceptance_scope", "ac_validates", "ac_does_not_validate", "sample_boundary", "full_population_required", "related_plan_items", "required_proof_layers", "assertion_command", "assertion_artifacts", "positive_assertions", "negative_assertions", "machine_blocking", "invalid_completion_signals", "assertion_result_required"];
7
+ type FieldType = "array" | "boolean" | "enum" | "text";
8
+ export declare const PRODUCT_FIELD_TYPES: Record<(typeof PRODUCT_FIELDS)[number], FieldType>;
9
+ export declare const PLAN_FIELD_TYPES: Record<(typeof PLAN_FIELDS)[number], FieldType>;
10
+ export declare const ACCEPTANCE_FIELD_TYPES: Record<(typeof ACCEPTANCE_FIELDS)[number], FieldType>;
11
+ export declare const PRODUCT_DELIVERY_SCOPES: Set<string>;
12
+ export declare const PLAN_DELIVERY_SCOPES: Set<string>;
13
+ export declare const ACCEPTANCE_SCOPES: Set<string>;
14
+ export declare const SCOPE_FIT_DECISIONS: Set<string>;
15
+ export declare const CANONICAL_PROOF_LAYERS: readonly ["code", "api_schema", "worker_runtime", "data_artifact", "integration", "ui_browser", "security_redaction", "all_provider_all_runner", "cleanup_stale_scan", "test"];
16
+ export declare const LEGACY_PROOF_LAYER_ALIASES: Record<string, string>;
17
+ export declare const MACHINE_VERIFIABLE_LAYER_NAMES: Set<string>;
18
+ export declare function fieldSet(fields: readonly string[]): Set<string>;
19
+ export declare function normalizeProofLayerName(layer: string): string;
20
+ export declare function normalizeProofLayerId(layerId: string): string;
21
+ export declare function isSelectedScopeFitSlice(value: string): boolean;
22
+ export {};
@@ -0,0 +1,276 @@
1
+ export const PRODUCT_FIELDS = [
2
+ "delivery_scope",
3
+ "full_population_required",
4
+ "representative_samples_validate",
5
+ "representative_samples_do_not_validate",
6
+ "out_of_scope_backlog",
7
+ "scope_fit_decision",
8
+ "selected_scope_fit_slice",
9
+ "owner_boundary",
10
+ "primary_capability_path",
11
+ "non_completing_outcomes",
12
+ "assertion_policy",
13
+ "source_authority",
14
+ "product_goal",
15
+ "surface_ia_lock",
16
+ "decision_lock",
17
+ "context_delta",
18
+ "source_to_context_coverage",
19
+ "acceptance_semantics",
20
+ "impact"
21
+ ];
22
+ export const PLAN_FIELDS = [
23
+ "delivery_scope",
24
+ "capability_target",
25
+ "representative_samples",
26
+ "full_population_boundary",
27
+ "non_required_population",
28
+ "owner_surfaces",
29
+ "forbidden_surfaces",
30
+ "owner_boundary",
31
+ "primary_capability_path",
32
+ "trigger_contract",
33
+ "state_transition_contract",
34
+ "observable_result_contract",
35
+ "assertion_support",
36
+ "required_assertion_commands",
37
+ "invalid_implementation_shortcuts",
38
+ "implementation_paths",
39
+ "required_tests",
40
+ "related_acs",
41
+ "requirement_ref",
42
+ "decision_id",
43
+ "proof_layer_ids",
44
+ "api_schema_changes",
45
+ "state_machine",
46
+ "data_flow",
47
+ "worker_runtime_behavior",
48
+ "ui_ia_changes",
49
+ "migration_plan",
50
+ "evidence_artifacts",
51
+ "explicit_no_test_scope",
52
+ "non_completing_shortcuts",
53
+ "substitution_policy",
54
+ "drift_severity",
55
+ "partial_conditions",
56
+ "blockers",
57
+ "context_fact_refs"
58
+ ];
59
+ export const ACCEPTANCE_FIELDS = [
60
+ "acceptance_scope",
61
+ "ac_validates",
62
+ "ac_does_not_validate",
63
+ "sample_boundary",
64
+ "full_population_required",
65
+ "related_plan_items",
66
+ "required_proof_layers",
67
+ "assertion_command",
68
+ "assertion_artifacts",
69
+ "positive_assertions",
70
+ "negative_assertions",
71
+ "machine_blocking",
72
+ "invalid_completion_signals",
73
+ "assertion_result_required",
74
+ "ac_type",
75
+ "proof_chain",
76
+ "verification_method",
77
+ "fail_conditions",
78
+ "invalid_evidence",
79
+ "substitution_policy",
80
+ "missing_layer_downgrade",
81
+ "auditor_expectation",
82
+ "out_of_scope_na_approval_source",
83
+ "required_test_ids",
84
+ "explicit_no_test_scope",
85
+ "hard_blockers",
86
+ "validates_explanation",
87
+ "does_not_validate_explanation",
88
+ "final_evidence_expected",
89
+ "test_cases"
90
+ ];
91
+ export const PRODUCT_REQUIRED_FIELDS = [
92
+ "delivery_scope",
93
+ "full_population_required",
94
+ "representative_samples_validate",
95
+ "representative_samples_do_not_validate",
96
+ "out_of_scope_backlog",
97
+ "scope_fit_decision",
98
+ "selected_scope_fit_slice",
99
+ "owner_boundary",
100
+ "primary_capability_path",
101
+ "non_completing_outcomes",
102
+ "assertion_policy",
103
+ "source_authority",
104
+ "product_goal"
105
+ ];
106
+ export const PLAN_REQUIRED_FIELDS = [
107
+ "delivery_scope",
108
+ "capability_target",
109
+ "representative_samples",
110
+ "full_population_boundary",
111
+ "non_required_population",
112
+ "owner_boundary",
113
+ "primary_capability_path",
114
+ "trigger_contract",
115
+ "state_transition_contract",
116
+ "observable_result_contract",
117
+ "assertion_support",
118
+ "required_assertion_commands",
119
+ "invalid_implementation_shortcuts",
120
+ "implementation_paths"
121
+ ];
122
+ export const ACCEPTANCE_REQUIRED_FIELDS = [
123
+ "acceptance_scope",
124
+ "ac_validates",
125
+ "ac_does_not_validate",
126
+ "sample_boundary",
127
+ "full_population_required",
128
+ "related_plan_items",
129
+ "required_proof_layers",
130
+ "assertion_command",
131
+ "assertion_artifacts",
132
+ "positive_assertions",
133
+ "negative_assertions",
134
+ "machine_blocking",
135
+ "invalid_completion_signals",
136
+ "assertion_result_required"
137
+ ];
138
+ export const PRODUCT_FIELD_TYPES = {
139
+ delivery_scope: "enum",
140
+ full_population_required: "boolean",
141
+ representative_samples_validate: "array",
142
+ representative_samples_do_not_validate: "array",
143
+ out_of_scope_backlog: "array",
144
+ scope_fit_decision: "enum",
145
+ selected_scope_fit_slice: "text",
146
+ owner_boundary: "text",
147
+ primary_capability_path: "text",
148
+ non_completing_outcomes: "array",
149
+ assertion_policy: "text",
150
+ source_authority: "text",
151
+ product_goal: "text",
152
+ surface_ia_lock: "text",
153
+ decision_lock: "text",
154
+ context_delta: "text",
155
+ source_to_context_coverage: "text",
156
+ acceptance_semantics: "text",
157
+ impact: "text"
158
+ };
159
+ export const PLAN_FIELD_TYPES = {
160
+ delivery_scope: "enum",
161
+ capability_target: "text",
162
+ representative_samples: "array",
163
+ full_population_boundary: "text",
164
+ non_required_population: "array",
165
+ owner_surfaces: "array",
166
+ forbidden_surfaces: "array",
167
+ owner_boundary: "text",
168
+ primary_capability_path: "text",
169
+ trigger_contract: "text",
170
+ state_transition_contract: "text",
171
+ observable_result_contract: "text",
172
+ assertion_support: "text",
173
+ required_assertion_commands: "array",
174
+ invalid_implementation_shortcuts: "array",
175
+ implementation_paths: "array",
176
+ required_tests: "array",
177
+ related_acs: "array",
178
+ requirement_ref: "text",
179
+ decision_id: "text",
180
+ proof_layer_ids: "array",
181
+ api_schema_changes: "text",
182
+ state_machine: "text",
183
+ data_flow: "text",
184
+ worker_runtime_behavior: "text",
185
+ ui_ia_changes: "text",
186
+ migration_plan: "text",
187
+ evidence_artifacts: "array",
188
+ explicit_no_test_scope: "boolean",
189
+ non_completing_shortcuts: "array",
190
+ substitution_policy: "array",
191
+ drift_severity: "text",
192
+ partial_conditions: "array",
193
+ blockers: "array",
194
+ context_fact_refs: "array"
195
+ };
196
+ export const ACCEPTANCE_FIELD_TYPES = {
197
+ acceptance_scope: "enum",
198
+ ac_validates: "array",
199
+ ac_does_not_validate: "array",
200
+ sample_boundary: "text",
201
+ full_population_required: "boolean",
202
+ related_plan_items: "array",
203
+ required_proof_layers: "array",
204
+ assertion_command: "text",
205
+ assertion_artifacts: "array",
206
+ positive_assertions: "array",
207
+ negative_assertions: "array",
208
+ machine_blocking: "boolean",
209
+ invalid_completion_signals: "array",
210
+ assertion_result_required: "boolean",
211
+ ac_type: "text",
212
+ proof_chain: "array",
213
+ verification_method: "array",
214
+ fail_conditions: "array",
215
+ invalid_evidence: "array",
216
+ substitution_policy: "array",
217
+ missing_layer_downgrade: "text",
218
+ auditor_expectation: "text",
219
+ out_of_scope_na_approval_source: "text",
220
+ required_test_ids: "array",
221
+ explicit_no_test_scope: "boolean",
222
+ hard_blockers: "array",
223
+ validates_explanation: "text",
224
+ does_not_validate_explanation: "text",
225
+ final_evidence_expected: "array",
226
+ test_cases: "array"
227
+ };
228
+ export const PRODUCT_DELIVERY_SCOPES = new Set([
229
+ "system_capability_build", "representative_sample_validation", "full_population_operation", "mixed_scope_requires_boundary"
230
+ ]);
231
+ export const PLAN_DELIVERY_SCOPES = new Set([
232
+ "system_capability_build", "representative_sample_validation", "full_population_operation", "out_of_scope_backlog"
233
+ ]);
234
+ export const ACCEPTANCE_SCOPES = new Set([
235
+ "system_capability_build", "representative_sample_validation", "full_population_operation", "full_population_not_required"
236
+ ]);
237
+ export const SCOPE_FIT_DECISIONS = new Set(["fit_for_three_inputs", "selected_from_split", "blocked_for_decision"]);
238
+ export const CANONICAL_PROOF_LAYERS = [
239
+ "code",
240
+ "api_schema",
241
+ "worker_runtime",
242
+ "data_artifact",
243
+ "integration",
244
+ "ui_browser",
245
+ "security_redaction",
246
+ "all_provider_all_runner",
247
+ "cleanup_stale_scan",
248
+ "test"
249
+ ];
250
+ export const LEGACY_PROOF_LAYER_ALIASES = {
251
+ runtime: "worker_runtime",
252
+ browser: "ui_browser",
253
+ api: "api_schema",
254
+ data: "data_artifact",
255
+ security: "security_redaction"
256
+ };
257
+ export const MACHINE_VERIFIABLE_LAYER_NAMES = new Set(CANONICAL_PROOF_LAYERS.filter((layer) => layer !== "code"));
258
+ export function fieldSet(fields) {
259
+ return new Set(fields);
260
+ }
261
+ export function normalizeProofLayerName(layer) {
262
+ const normalized = layer.trim().toLowerCase().replace(/[- ]+/g, "_");
263
+ return LEGACY_PROOF_LAYER_ALIASES[normalized] ?? normalized;
264
+ }
265
+ export function normalizeProofLayerId(layerId) {
266
+ const value = layerId.trim();
267
+ if (!value.includes(".")) {
268
+ return normalizeProofLayerName(value);
269
+ }
270
+ const acId = value.slice(0, value.lastIndexOf("."));
271
+ const layer = value.slice(value.lastIndexOf(".") + 1);
272
+ return `${acId}.${normalizeProofLayerName(layer)}`;
273
+ }
274
+ export function isSelectedScopeFitSlice(value) {
275
+ return value === "none" || /^SFC-\d{3,}$/i.test(value);
276
+ }
@@ -1,5 +1,6 @@
1
1
  import { appendSuperpowersEvent } from "./superpowers-task-events.js";
2
2
  import { deriveSuperpowersArtifacts } from "./superpowers-task-derive.js";
3
+ import { validatePlanAcceptance } from "./plan-acceptance-validator.js";
3
4
  import { loadSuperpowersState, recomputeStatuses, saveSuperpowersState } from "./superpowers-task-state.js";
4
5
  import { completionConditionErrors, validateSuperpowersState } from "./superpowers-task-validator.js";
5
6
  export async function runSliceGate(workdir, sliceId) {
@@ -30,20 +31,73 @@ export async function runFinalGate(workdir) {
30
31
  await saveSuperpowersState(workdir, state);
31
32
  await deriveSuperpowersArtifacts(workdir);
32
33
  const report = await validateSuperpowersState(workdir, [workdir]);
34
+ const acceptanceReport = await validatePlanAcceptance(workdir, [workdir]);
33
35
  const latest = await loadSuperpowersState(workdir);
34
36
  const completionErrors = completionConditionErrors(latest);
35
- const errors = [...new Set([...report.errors, ...completionErrors])];
37
+ const errors = [...new Set([...report.errors, ...acceptanceReport.errors, ...completionErrors])];
36
38
  const complete = errors.length === 0;
39
+ const acceptanceStatus = complete ? "complete" : acceptanceStatusForErrors(errors);
40
+ const nextRequiredActions = complete ? [] : nextActionsForErrors(errors);
37
41
  latest.final.product_goal_complete = complete;
38
42
  latest.meta.product_goal_complete = complete;
39
- latest.final.acceptance_target_status = complete ? "complete" : "partial";
43
+ latest.final.acceptance_target_status = acceptanceStatus;
40
44
  latest.meta.acceptance_target_status = latest.final.acceptance_target_status;
41
45
  latest.final.audit_task_complete = true;
42
46
  latest.meta.audit_task_complete = true;
43
- latest.final.completion_basis = complete ? ["all_required_acs_complete", "validator_passed", "auditor_no_blocker"] : [];
47
+ latest.final.completion_basis = complete
48
+ ? ["all_required_acs_complete", "validator_passed", "assertion_evidence_passed", "negative_evidence_scan_passed", "auditor_no_blocker"]
49
+ : [];
50
+ latest.final.next_required_actions = nextRequiredActions;
44
51
  latest.gates.validator = { status: errors.length === 0 ? "pass" : "blocked", errors };
52
+ latest.gates.final_gate = {
53
+ status: complete ? "pass" : acceptanceStatus,
54
+ order: [
55
+ "derive",
56
+ "verification_before_completion_expected",
57
+ "validate_state",
58
+ "validate_derived",
59
+ "validate_plan_acceptance",
60
+ "auditor_blocker_scan",
61
+ "stale_overclaim_scope_scan",
62
+ "ac_evidence_assertion_gate",
63
+ "negative_evidence_scan_gate",
64
+ "compute_completion"
65
+ ],
66
+ errors,
67
+ next_required_actions: nextRequiredActions
68
+ };
45
69
  await saveSuperpowersState(workdir, latest);
46
70
  await deriveSuperpowersArtifacts(workdir);
47
71
  await appendSuperpowersEvent(workdir, "final_gate", { product_goal_complete: complete });
48
72
  return { product_goal_complete: complete, errors };
49
73
  }
74
+ function acceptanceStatusForErrors(errors) {
75
+ const text = errors.join("\n");
76
+ if (/source hash mismatch|source_changed_requires_recompile|scope_conflict_requires_decision|auditor blocker|Context Delta coverage is unresolved/i.test(text)) {
77
+ return "blocked";
78
+ }
79
+ if (/invalid evidence|forbidden shortcut|negative evidence|stale evidence|raw secret|contains_secret|sibling substitution|assertion_result\.status=failed|assertion exit_code=|command_exit_code=|forbidden text/i.test(text)) {
80
+ return "invalidated";
81
+ }
82
+ return "partial";
83
+ }
84
+ function nextActionsForErrors(errors) {
85
+ return errors.slice(0, 12).map((error) => {
86
+ if (/source hash mismatch/i.test(error)) {
87
+ return `${error}; rerun ty-context composite-long-task compile <workdir> after approving source changes`;
88
+ }
89
+ if (/scope_conflict_requires_decision/i.test(error)) {
90
+ return `${error}; clarify Product / Plan / Checklist delivery scope before more implementation`;
91
+ }
92
+ if (/missing assertion result|not machine-backed/i.test(error)) {
93
+ return `${error}; add assertion_result evidence for the target AC/proof layer`;
94
+ }
95
+ if (/negative evidence|forbidden/i.test(error)) {
96
+ return `${error}; rerun the negative evidence scan and fix the contradictory owner-surface state`;
97
+ }
98
+ if (/derived\/.*does not match/i.test(error)) {
99
+ return `${error}; rerun ty-context composite-long-task derive <workdir>`;
100
+ }
101
+ return error;
102
+ });
103
+ }