codex-workflow-v2 2.0.0-beta.13.9 → 2.0.0-beta.14
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 +27 -388
- package/dist/reviewer-runtime-build.json +89 -29
- package/dist/src/alpha6/adoption.d.ts +2 -0
- package/dist/src/alpha6/adoption.js +22 -0
- package/dist/src/alpha6/adoption.js.map +1 -1
- package/dist/src/alpha6/captured-check-evidence.d.ts +51 -0
- package/dist/src/alpha6/captured-check-evidence.js +152 -0
- package/dist/src/alpha6/captured-check-evidence.js.map +1 -0
- package/dist/src/alpha6/component-owner.d.ts +15 -1
- package/dist/src/alpha6/component-owner.js +44 -2
- package/dist/src/alpha6/component-owner.js.map +1 -1
- package/dist/src/alpha6/corrective-decision-boundary.d.ts +4 -0
- package/dist/src/alpha6/corrective-decision-boundary.js +47 -0
- package/dist/src/alpha6/corrective-decision-boundary.js.map +1 -0
- package/dist/src/alpha6/downstream-proof.d.ts +1 -25
- package/dist/src/alpha6/downstream-proof.js +0 -171
- package/dist/src/alpha6/downstream-proof.js.map +1 -1
- package/dist/src/alpha6/literal-test-invocation.d.ts +2 -0
- package/dist/src/alpha6/literal-test-invocation.js +106 -0
- package/dist/src/alpha6/literal-test-invocation.js.map +1 -0
- package/dist/src/alpha6/milestone.d.ts +2 -0
- package/dist/src/alpha6/milestone.js +10 -1
- package/dist/src/alpha6/milestone.js.map +1 -1
- package/dist/src/alpha6/navigation-dirty-carryover.d.ts +11 -0
- package/dist/src/alpha6/navigation-dirty-carryover.js +130 -0
- package/dist/src/alpha6/navigation-dirty-carryover.js.map +1 -0
- package/dist/src/alpha6/plan-integrity.js +7 -5
- package/dist/src/alpha6/plan-integrity.js.map +1 -1
- package/dist/src/alpha6/remediation.d.ts +6 -21
- package/dist/src/alpha6/remediation.js +161 -476
- package/dist/src/alpha6/remediation.js.map +1 -1
- package/dist/src/alpha6/root-cause-replan-carryover.d.ts +4 -1
- package/dist/src/alpha6/root-cause-replan-carryover.js +29 -5
- package/dist/src/alpha6/root-cause-replan-carryover.js.map +1 -1
- package/dist/src/beta1/project-transaction.d.ts +3 -1
- package/dist/src/beta1/project-transaction.js +6 -2
- package/dist/src/beta1/project-transaction.js.map +1 -1
- package/dist/src/checks/runner.d.ts +26 -0
- package/dist/src/checks/runner.js +223 -0
- package/dist/src/checks/runner.js.map +1 -0
- package/dist/src/checks/task-sync.d.ts +10 -0
- package/dist/src/checks/task-sync.js +41 -0
- package/dist/src/checks/task-sync.js.map +1 -0
- package/dist/src/checks/worker.d.ts +1 -0
- package/dist/src/checks/worker.js +154 -0
- package/dist/src/checks/worker.js.map +1 -0
- package/dist/src/cli-actions.d.ts +2 -2
- package/dist/src/cli-actions.js +3 -15
- package/dist/src/cli-actions.js.map +1 -1
- package/dist/src/cli.js +14 -98
- package/dist/src/cli.js.map +1 -1
- package/dist/src/contracts.d.ts +35 -32
- package/dist/src/dependency-provenance.d.ts +2 -2
- package/dist/src/dependency-provenance.js +12 -69
- package/dist/src/dependency-provenance.js.map +1 -1
- package/dist/src/domain/base-sync-conflict.d.ts +13 -0
- package/dist/src/domain/base-sync-conflict.js +32 -0
- package/dist/src/domain/base-sync-conflict.js.map +1 -0
- package/dist/src/domain/step-start-admission.d.ts +4 -0
- package/dist/src/domain/step-start-admission.js +9 -0
- package/dist/src/domain/step-start-admission.js.map +1 -0
- package/dist/src/domain/virgin-registration.d.ts +3 -0
- package/dist/src/domain/virgin-registration.js +65 -0
- package/dist/src/domain/virgin-registration.js.map +1 -0
- package/dist/src/errors.d.ts +1 -1
- package/dist/src/errors.js.map +1 -1
- package/dist/src/gateway-handshake.js +0 -8
- package/dist/src/gateway-handshake.js.map +1 -1
- package/dist/src/git.d.ts +3 -1
- package/dist/src/git.js +41 -14
- package/dist/src/git.js.map +1 -1
- package/dist/src/graph.js +25 -3
- package/dist/src/graph.js.map +1 -1
- package/dist/src/index.d.ts +1 -0
- package/dist/src/navigation-actions.d.ts +9 -0
- package/dist/src/navigation-actions.js +72 -0
- package/dist/src/navigation-actions.js.map +1 -0
- package/dist/src/navigation-update-artifact.d.ts +5 -0
- package/dist/src/navigation-update-artifact.js +227 -0
- package/dist/src/navigation-update-artifact.js.map +1 -0
- package/dist/src/navigation-update.d.ts +39 -0
- package/dist/src/navigation-update.js +82 -0
- package/dist/src/navigation-update.js.map +1 -0
- package/dist/src/observation.js +52 -29
- package/dist/src/observation.js.map +1 -1
- package/dist/src/observed-routes.js +5 -6
- package/dist/src/observed-routes.js.map +1 -1
- package/dist/src/pending-review-update.d.ts +0 -13
- package/dist/src/pending-review-update.js +1 -6
- package/dist/src/pending-review-update.js.map +1 -1
- package/dist/src/reviewer.d.ts +1 -1
- package/dist/src/reviewer.js +20 -17
- package/dist/src/reviewer.js.map +1 -1
- package/dist/src/runtime.d.ts +3 -0
- package/dist/src/runtime.js +6 -0
- package/dist/src/runtime.js.map +1 -0
- package/dist/src/state/corrective-replan-executor.d.ts +10 -0
- package/dist/src/state/corrective-replan-executor.js +37 -1
- package/dist/src/state/corrective-replan-executor.js.map +1 -1
- package/dist/src/state/corrective-replan-public-schema.js +15 -2
- package/dist/src/state/corrective-replan-public-schema.js.map +1 -1
- package/dist/src/state/corrective-replan-public.js +1 -1
- package/dist/src/state/corrective-replan-public.js.map +1 -1
- package/dist/src/state/corrective-replan-transaction.d.ts +1 -0
- package/dist/src/state/corrective-replan-transaction.js +12 -11
- package/dist/src/state/corrective-replan-transaction.js.map +1 -1
- package/dist/src/state/corrective-yield-transaction.d.ts +1 -0
- package/dist/src/state/corrective-yield-transaction.js +11 -10
- package/dist/src/state/corrective-yield-transaction.js.map +1 -1
- package/dist/src/state/lock.d.ts +4 -0
- package/dist/src/state/lock.js +21 -0
- package/dist/src/state/lock.js.map +1 -1
- package/dist/src/version.d.ts +1 -1
- package/dist/src/version.js +1 -1
- package/dist/src/version.js.map +1 -1
- package/dist/src/workflow-blocker-route.d.ts +8 -0
- package/dist/src/workflow-blocker-route.js +106 -0
- package/dist/src/workflow-blocker-route.js.map +1 -0
- package/dist/src/workflow.d.ts +48 -63
- package/dist/src/workflow.js +666 -1460
- package/dist/src/workflow.js.map +1 -1
- package/docs/autonomy-guardrails.md +26 -304
- package/docs/decisions.md +14 -112
- package/docs/development-flow.md +35 -238
- package/docs/project-memory.md +31 -50
- package/docs/release-app-evidence.md +190 -0
- package/docs/release.md +95 -388
- package/docs/updating-existing-project.md +19 -717
- package/package.json +11 -13
- package/plugins/codex-workflow-gateway/.codex-plugin/plugin.json +2 -2
- package/plugins/codex-workflow-gateway/references/chat-dispatch.md +73 -197
- package/plugins/codex-workflow-gateway/references/codebase-memory-routing.md +57 -0
- package/plugins/codex-workflow-gateway/references/protocol.md +42 -445
- package/plugins/codex-workflow-gateway/scripts/chat-dispatch.mjs +7 -1
- package/plugins/codex-workflow-gateway/scripts/chat-model-policy.mjs +10 -8
- package/plugins/codex-workflow-gateway/scripts/chat-registry.mjs +23 -5
- package/plugins/codex-workflow-gateway/skills/codex-workflow-gateway/SKILL.md +66 -757
- package/references/state-machine.md +4 -4
- package/roles/technical-planner.md +1 -1
- package/schemas/corrective-decision-event.schema.json +3 -1
- package/schemas/project-knowledge-map.schema.json +3 -1
- package/schemas/remediation-event.schema.json +10 -1
- package/schemas/task.schema.json +46 -2
- package/schemas/transition-payloads.schema.json +16 -1
- package/src/alpha6/adoption.ts +1123 -0
- package/src/alpha6/captured-check-evidence.ts +132 -0
- package/src/alpha6/check-support-anchor.ts +128 -0
- package/src/alpha6/component-owner.ts +168 -0
- package/src/alpha6/corrective-decision-boundary.ts +39 -0
- package/src/alpha6/downstream-proof.ts +520 -0
- package/src/alpha6/failed-step-planning-recovery.ts +88 -0
- package/src/alpha6/handoff.ts +1443 -0
- package/src/alpha6/journal.ts +473 -0
- package/src/alpha6/literal-test-invocation.ts +84 -0
- package/src/alpha6/mechanical-feasibility.ts +488 -0
- package/src/alpha6/milestone.ts +2192 -0
- package/src/alpha6/navigation-dirty-carryover.ts +154 -0
- package/src/alpha6/npm-check-contract.ts +47 -0
- package/src/alpha6/plan-integrity.ts +298 -0
- package/src/alpha6/plan-risk.ts +1480 -0
- package/src/alpha6/preexecution-replan.ts +187 -0
- package/src/alpha6/remediation-cause.ts +98 -0
- package/src/alpha6/remediation.ts +2438 -0
- package/src/alpha6/review.ts +1198 -0
- package/src/alpha6/root-cause-replan-carryover.ts +491 -0
- package/src/alpha6/store-sidecars.ts +335 -0
- package/src/alpha7/autonomy.ts +411 -0
- package/src/alpha7/corrective-recovery.ts +1332 -0
- package/src/artifacts.ts +130 -0
- package/src/beta1/project-transaction.ts +355 -0
- package/src/change-explanation.ts +153 -0
- package/src/checks/runner.ts +245 -0
- package/src/checks/task-sync.ts +41 -0
- package/src/checks/worker.ts +149 -0
- package/src/cli-actions.ts +107 -0
- package/src/cli.ts +1457 -0
- package/src/contracts.ts +1494 -0
- package/src/credential-output.ts +89 -0
- package/src/credential-transport.ts +215 -0
- package/src/delegation.ts +190 -0
- package/src/dependency-provenance.ts +472 -0
- package/src/diagnostics.ts +93 -0
- package/src/domain/base-sync-conflict.ts +35 -0
- package/src/domain/completed-step-carryover.ts +76 -0
- package/src/domain/discovery.ts +27 -0
- package/src/domain/plan-semantics.ts +58 -0
- package/src/domain/step-start-admission.ts +10 -0
- package/src/domain/validation.ts +177 -0
- package/src/domain/virgin-registration.ts +46 -0
- package/src/errors.ts +24 -0
- package/src/fs-utils.ts +61 -0
- package/src/gateway-handshake.ts +95 -0
- package/src/git.ts +183 -0
- package/src/graph.ts +342 -0
- package/src/historical-step-provenance.ts +136 -0
- package/src/index.ts +23 -0
- package/src/lifecycle/canonical-hash.ts +28 -0
- package/src/lifecycle/catalog.ts +202 -0
- package/src/lifecycle/compiler-inspection.ts +29 -0
- package/src/lifecycle/core-static-readiness.ts +132 -0
- package/src/lifecycle/corrective-replan-authority.ts +136 -0
- package/src/lifecycle/corrective-replan-binding-manifest.ts +51 -0
- package/src/lifecycle/corrective-replan-credential-core.ts +408 -0
- package/src/lifecycle/corrective-replan-credential-schema.ts +54 -0
- package/src/lifecycle/corrective-replan-credentials.ts +48 -0
- package/src/lifecycle/corrective-replan.ts +843 -0
- package/src/lifecycle/evaluator.ts +48 -0
- package/src/lifecycle/fingerprint.ts +488 -0
- package/src/lifecycle/immutable.ts +8 -0
- package/src/lifecycle/implementation-table.ts +118 -0
- package/src/lifecycle/index.ts +8 -0
- package/src/lifecycle/schema-artifact.ts +263 -0
- package/src/lifecycle/semantic-registry.ts +572 -0
- package/src/lifecycle/types.ts +838 -0
- package/src/memory.ts +273 -0
- package/src/migration.ts +161 -0
- package/src/navigation-actions.ts +70 -0
- package/src/navigation-update-artifact.ts +198 -0
- package/src/navigation-update.ts +121 -0
- package/src/observation.ts +225 -0
- package/src/observed-routes.ts +660 -0
- package/src/operational-contract.ts +125 -0
- package/src/pending-review-update.ts +175 -0
- package/src/repository.ts +99 -0
- package/src/reviewer.ts +1879 -0
- package/src/runtime.ts +6 -0
- package/src/state/corrective-replan-executor.ts +818 -0
- package/src/state/corrective-replan-public-schema.ts +83 -0
- package/src/state/corrective-replan-public.ts +908 -0
- package/src/state/corrective-replan-transaction.ts +949 -0
- package/src/state/corrective-yield-executor.ts +327 -0
- package/src/state/corrective-yield-transaction.ts +730 -0
- package/src/state/lock.ts +902 -0
- package/src/state/store.ts +567 -0
- package/src/transition-core.ts +330 -0
- package/src/ulid.ts +24 -0
- package/src/version.ts +2 -0
- package/src/workflow-blocker-route.ts +109 -0
- package/src/workflow.ts +10172 -0
- package/docs/alpha7.1-implementation-brief.md +0 -268
- package/docs/alpha7.2-corrective-context-refresh-brief.md +0 -484
- package/docs/alpha7.2.1-remediation-recovery-brief.md +0 -86
- package/docs/beta1-stabilization-brief.md +0 -165
- package/docs/beta11-plan-integrity-recovery-brief.md +0 -38
- package/docs/beta13.2-signal-review-recovery.md +0 -38
- package/docs/beta2-initial-assembly-navigation-brief.md +0 -616
- package/docs/change-model.md +0 -118
- package/docs/delegated-approval.md +0 -254
- package/docs/lifecycle/state-machine-stabilization.md +0 -641
- package/docs/pdf/README.md +0 -24
- package/docs/pdf/codex-workflow-v2-architecture-ru.pdf +0 -0
- package/docs/pdf/codex-workflow-v2-chat-only-guide-ru.pdf +0 -0
- package/docs/pdf/codex-workflow-v2-technical-reference-ru.pdf +0 -0
- package/docs/pdf/requirements.txt +0 -1
- package/docs/pdf/sources/codex-workflow-v2-architecture-ru.md +0 -478
- package/docs/pdf/sources/codex-workflow-v2-chat-only-guide-ru.md +0 -506
- package/docs/pdf/sources/codex-workflow-v2-technical-reference-ru.md +0 -778
- package/docs/pending-review-update.md +0 -15
- package/docs/problem-briefs/01-pre-implementation-integrity.md +0 -482
- package/docs/problem-briefs/02-minimal-step-integrity.md +0 -411
- package/docs/problem-briefs/03-minimal-agent-context-integrity.md +0 -358
- package/docs/problem-briefs/04-task-dependency-and-structural-replacement-integrity.md +0 -573
- package/docs/problem-briefs/BRIEF-TEMPLATE.md +0 -56
- package/docs/problem-briefs/README.md +0 -120
- package/docs/problem-briefs/evidence/p01-mechanical-feasibility-corpus.md +0 -90
- package/docs/problem-briefs/evidence/signal-v4-pre-m3-replay.md +0 -246
- package/docs/split-required-recovery.md +0 -47
- package/docs/stable-release-defect-register.md +0 -730
- package/docs/validation-report.md +0 -182
- package/scripts/generate-pdf-docs.py +0 -524
- package/scripts/run-pdf-docs.mjs +0 -62
|
@@ -0,0 +1,488 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import type {
|
|
3
|
+
MechanicalFeasibilityAnalyzer,
|
|
4
|
+
MechanicalFeasibilityEvidence,
|
|
5
|
+
MechanicalFeasibilityFinding,
|
|
6
|
+
TaskState,
|
|
7
|
+
} from '../contracts.js';
|
|
8
|
+
import { pathAllowed } from '../domain/validation.js';
|
|
9
|
+
import { WorkflowError } from '../errors.js';
|
|
10
|
+
import { currentBranch, gitIsAncestor, headCommit, runGit } from '../repository.js';
|
|
11
|
+
import {
|
|
12
|
+
hasRequiredSelector,
|
|
13
|
+
inspectRequiredSelectorContract,
|
|
14
|
+
parseNpmRunCheck,
|
|
15
|
+
} from './npm-check-contract.js';
|
|
16
|
+
import { inspectCheckSupportAnchors } from './check-support-anchor.js';
|
|
17
|
+
import { canonicalJsonStringify, sha256Hex } from './store-sidecars.js';
|
|
18
|
+
|
|
19
|
+
export const MECHANICAL_FEASIBILITY_ANALYZERS: readonly MechanicalFeasibilityAnalyzer[] = [
|
|
20
|
+
{ analyzerId: 'wf2.root-npm-script', analyzerVersion: 1 },
|
|
21
|
+
{ analyzerId: 'wf2.root-npm-script-arguments', analyzerVersion: 1 },
|
|
22
|
+
{ analyzerId: 'wf2.check-support-anchor', analyzerVersion: 1 },
|
|
23
|
+
{ analyzerId: 'wf2.exact-output-path', analyzerVersion: 1 },
|
|
24
|
+
{ analyzerId: 'wf2.exact-predecessor-path', analyzerVersion: 1 },
|
|
25
|
+
] as const;
|
|
26
|
+
|
|
27
|
+
const EXACT_PATH = /^path:([A-Za-z0-9._-]+(?:\/[A-Za-z0-9._-]+)*)$/;
|
|
28
|
+
|
|
29
|
+
interface EvaluationSource {
|
|
30
|
+
sourceKind: MechanicalFeasibilityEvidence['sourceKind'];
|
|
31
|
+
sourceBranch: string;
|
|
32
|
+
evaluatedHead: string;
|
|
33
|
+
baseCommit: string | null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function mechanicalFeasibilityAnalyzerSetHash(): string {
|
|
37
|
+
return sha256Hex(canonicalJsonStringify({ analyzers: MECHANICAL_FEASIBILITY_ANALYZERS }));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function mechanicalFeasibilityAnalyzerInputHash(task: TaskState): string {
|
|
41
|
+
return sha256Hex(canonicalJsonStringify({
|
|
42
|
+
steps: task.steps.map((step) => ({
|
|
43
|
+
id: step.id,
|
|
44
|
+
dependencies: step.dependencies,
|
|
45
|
+
expectedOutputs: step.expectedOutputs,
|
|
46
|
+
allowedWrites: step.allowedWrites,
|
|
47
|
+
checks: step.checks,
|
|
48
|
+
})),
|
|
49
|
+
}));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function hashMechanicalFeasibilityEvidence(evidence: MechanicalFeasibilityEvidence): string {
|
|
53
|
+
return sha256Hex(canonicalJsonStringify(evidence));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function evaluateMechanicalFeasibility(
|
|
57
|
+
repositoryRoot: string,
|
|
58
|
+
task: TaskState,
|
|
59
|
+
briefHash: string,
|
|
60
|
+
planHash: string,
|
|
61
|
+
): MechanicalFeasibilityEvidence {
|
|
62
|
+
const source = selectEvaluationSource(repositoryRoot, task);
|
|
63
|
+
const packageJson = readGitPath(repositoryRoot, source.evaluatedHead, 'package.json');
|
|
64
|
+
const manifest = parseRootManifest(packageJson);
|
|
65
|
+
const byId = new Map(task.steps.map((step) => [step.id, step]));
|
|
66
|
+
const findings: MechanicalFeasibilityFinding[] = [];
|
|
67
|
+
|
|
68
|
+
for (const step of task.steps) {
|
|
69
|
+
const predecessorIds = transitivePredecessorIds(step.id, byId);
|
|
70
|
+
const manifestWriters = [step.id, ...predecessorIds]
|
|
71
|
+
.filter((stepId) => pathAllowed('package.json', requireStep(byId, stepId).allowedWrites))
|
|
72
|
+
.sort();
|
|
73
|
+
|
|
74
|
+
for (const check of step.checks) {
|
|
75
|
+
const invocation = parseNpmRunCheck(check);
|
|
76
|
+
if (!invocation) {
|
|
77
|
+
findings.push(finding(
|
|
78
|
+
'wf2.root-npm-script',
|
|
79
|
+
'unverified',
|
|
80
|
+
step.id,
|
|
81
|
+
check,
|
|
82
|
+
'Check is outside the exact root npm grammar.',
|
|
83
|
+
'UNSUPPORTED_CHECK_SYNTAX',
|
|
84
|
+
));
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
const script = invocation.script;
|
|
88
|
+
const scriptCommand = manifest.scripts.get(script) ?? null;
|
|
89
|
+
if (scriptCommand !== null) {
|
|
90
|
+
findings.push(finding(
|
|
91
|
+
'wf2.root-npm-script',
|
|
92
|
+
'pass',
|
|
93
|
+
step.id,
|
|
94
|
+
check,
|
|
95
|
+
`Root package.json at ${source.evaluatedHead} declares script ${script}.`,
|
|
96
|
+
'ROOT_NPM_SCRIPT_PRESENT',
|
|
97
|
+
));
|
|
98
|
+
const selectorContract = inspectRequiredSelectorContract(
|
|
99
|
+
scriptCommand,
|
|
100
|
+
(runnerPath) => readGitPath(repositoryRoot, source.evaluatedHead, runnerPath),
|
|
101
|
+
);
|
|
102
|
+
if (selectorContract) {
|
|
103
|
+
const runnerWriters = [step.id, ...predecessorIds]
|
|
104
|
+
.filter((stepId) => pathAllowed(selectorContract.runnerPath, requireStep(byId, stepId).allowedWrites))
|
|
105
|
+
.sort();
|
|
106
|
+
if (hasRequiredSelector(invocation.forwardedArguments)) {
|
|
107
|
+
findings.push(finding(
|
|
108
|
+
'wf2.root-npm-script-arguments',
|
|
109
|
+
'pass',
|
|
110
|
+
step.id,
|
|
111
|
+
check,
|
|
112
|
+
`Repository-local affected runner ${selectorContract.runnerPath} receives an explicit base, head, or files selector.`,
|
|
113
|
+
'ROOT_NPM_SCRIPT_REQUIRED_SELECTOR_PRESENT',
|
|
114
|
+
));
|
|
115
|
+
} else if (runnerWriters.length > 0) {
|
|
116
|
+
findings.push(finding(
|
|
117
|
+
'wf2.root-npm-script-arguments',
|
|
118
|
+
'unverified',
|
|
119
|
+
step.id,
|
|
120
|
+
check,
|
|
121
|
+
`Repository-local affected runner ${selectorContract.runnerPath} currently requires a selector but is writable by ${runnerWriters.join(', ')}.`,
|
|
122
|
+
'ROOT_NPM_SCRIPT_REQUIRED_SELECTOR_MAY_CHANGE',
|
|
123
|
+
));
|
|
124
|
+
} else {
|
|
125
|
+
findings.push(finding(
|
|
126
|
+
'wf2.root-npm-script-arguments',
|
|
127
|
+
'blocked',
|
|
128
|
+
step.id,
|
|
129
|
+
check,
|
|
130
|
+
`Repository-local affected runner ${selectorContract.runnerPath} requires --base, --head, or --files, but the check forwards no selector and no responsible Step may change the runner.`,
|
|
131
|
+
'ROOT_NPM_SCRIPT_REQUIRED_SELECTOR_MISSING',
|
|
132
|
+
));
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
const supportConflicts = inspectCheckSupportAnchors({
|
|
136
|
+
step,
|
|
137
|
+
scripts: manifest.scripts,
|
|
138
|
+
readFile: (relativePath) => {
|
|
139
|
+
const content = readGitPath(repositoryRoot, source.evaluatedHead, relativePath);
|
|
140
|
+
return { content, hash: content === null ? null : sha256Hex(content) };
|
|
141
|
+
},
|
|
142
|
+
pathExists: (relativePath) => gitPathExists(repositoryRoot, source.evaluatedHead, relativePath),
|
|
143
|
+
});
|
|
144
|
+
for (const conflict of supportConflicts) {
|
|
145
|
+
findings.push(finding(
|
|
146
|
+
'wf2.check-support-anchor',
|
|
147
|
+
'blocked',
|
|
148
|
+
step.id,
|
|
149
|
+
check,
|
|
150
|
+
`${conflict.runnerPath} directly imports checksum/catalog authority ${conflict.supportPath}; absent output ${conflict.expectedOutputPaths.join(', ')} requires that support authority to change, but ${step.id} cannot write it.`,
|
|
151
|
+
conflict.reasonCode,
|
|
152
|
+
));
|
|
153
|
+
}
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
if (manifestWriters.length > 0) {
|
|
157
|
+
findings.push(finding(
|
|
158
|
+
'wf2.root-npm-script',
|
|
159
|
+
'unverified',
|
|
160
|
+
step.id,
|
|
161
|
+
check,
|
|
162
|
+
`Script ${script} is absent at ${source.evaluatedHead}; package.json is writable by ${manifestWriters.join(', ')}.`,
|
|
163
|
+
'ROOT_NPM_SCRIPT_MAY_BE_CREATED',
|
|
164
|
+
));
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
findings.push(finding(
|
|
168
|
+
'wf2.root-npm-script',
|
|
169
|
+
'blocked',
|
|
170
|
+
step.id,
|
|
171
|
+
check,
|
|
172
|
+
manifest.state === 'valid'
|
|
173
|
+
? `Script ${script} is absent from root package.json at ${source.evaluatedHead}, and no responsible Step may write package.json.`
|
|
174
|
+
: `Root package.json is ${manifest.state} at ${source.evaluatedHead}, and no responsible Step may write package.json.`,
|
|
175
|
+
manifest.state === 'valid' ? 'ROOT_NPM_SCRIPT_MISSING' : `ROOT_PACKAGE_JSON_${manifest.state.toUpperCase()}`,
|
|
176
|
+
));
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
for (const output of step.expectedOutputs) {
|
|
180
|
+
const relativePath = decodeExactPath(output);
|
|
181
|
+
if (relativePath === null) {
|
|
182
|
+
findings.push(finding(
|
|
183
|
+
'wf2.exact-output-path',
|
|
184
|
+
'unverified',
|
|
185
|
+
step.id,
|
|
186
|
+
output,
|
|
187
|
+
'Expected output is outside the exact path:<repository-relative POSIX path> grammar.',
|
|
188
|
+
'UNSUPPORTED_OUTPUT_SYNTAX',
|
|
189
|
+
));
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
if (gitPathExists(repositoryRoot, source.evaluatedHead, relativePath)) {
|
|
193
|
+
findings.push(finding(
|
|
194
|
+
'wf2.exact-output-path',
|
|
195
|
+
'pass',
|
|
196
|
+
step.id,
|
|
197
|
+
output,
|
|
198
|
+
`${relativePath} exists at ${source.evaluatedHead}.`,
|
|
199
|
+
'EXACT_OUTPUT_PRESENT',
|
|
200
|
+
));
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
if (pathAllowed(relativePath, step.allowedWrites)) {
|
|
204
|
+
findings.push(finding(
|
|
205
|
+
'wf2.exact-output-path',
|
|
206
|
+
'pass',
|
|
207
|
+
step.id,
|
|
208
|
+
output,
|
|
209
|
+
`${step.id} may write absent exact output ${relativePath}.`,
|
|
210
|
+
'EXACT_OUTPUT_WRITABLE',
|
|
211
|
+
));
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
const providers = predecessorIds
|
|
215
|
+
.filter((predecessorId) => {
|
|
216
|
+
const predecessor = requireStep(byId, predecessorId);
|
|
217
|
+
return predecessor.expectedOutputs.some((candidate) => decodeExactPath(candidate) === relativePath)
|
|
218
|
+
&& pathAllowed(relativePath, predecessor.allowedWrites);
|
|
219
|
+
})
|
|
220
|
+
.sort();
|
|
221
|
+
if (providers.length > 0) {
|
|
222
|
+
findings.push(finding(
|
|
223
|
+
'wf2.exact-predecessor-path',
|
|
224
|
+
'pass',
|
|
225
|
+
step.id,
|
|
226
|
+
output,
|
|
227
|
+
`Transitive declared predecessor ${providers.join(', ')} owns and may write ${relativePath}.`,
|
|
228
|
+
'EXACT_PREDECESSOR_OUTPUT_AVAILABLE',
|
|
229
|
+
));
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
findings.push(finding(
|
|
233
|
+
'wf2.exact-output-path',
|
|
234
|
+
'blocked',
|
|
235
|
+
step.id,
|
|
236
|
+
output,
|
|
237
|
+
`${relativePath} is absent at ${source.evaluatedHead}, outside ${step.id} write bounds, and has no exact writable declared predecessor.`,
|
|
238
|
+
'EXACT_OUTPUT_UNSATISFIABLE',
|
|
239
|
+
));
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
findings.sort(compareFindings);
|
|
244
|
+
return {
|
|
245
|
+
schemaVersion: 1,
|
|
246
|
+
taskId: task.id,
|
|
247
|
+
evaluatedTaskRevision: task.revision,
|
|
248
|
+
briefHash,
|
|
249
|
+
planHash,
|
|
250
|
+
...source,
|
|
251
|
+
analyzerSetHash: mechanicalFeasibilityAnalyzerSetHash(),
|
|
252
|
+
analyzerInputHash: mechanicalFeasibilityAnalyzerInputHash(task),
|
|
253
|
+
analyzers: MECHANICAL_FEASIBILITY_ANALYZERS.map((analyzer) => ({ ...analyzer })),
|
|
254
|
+
findings,
|
|
255
|
+
decision: findings.some((candidate) => candidate.result === 'blocked') ? 'blocked' : 'clear',
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
export function assertMechanicalFeasibilityFresh(
|
|
260
|
+
repositoryRoot: string,
|
|
261
|
+
task: TaskState,
|
|
262
|
+
evidence: MechanicalFeasibilityEvidence,
|
|
263
|
+
briefHash: string,
|
|
264
|
+
planHash: string,
|
|
265
|
+
): void {
|
|
266
|
+
assertEvidenceShape(evidence, task.id);
|
|
267
|
+
const currentAnalyzerSetHash = mechanicalFeasibilityAnalyzerSetHash();
|
|
268
|
+
const currentAnalyzerInputHash = mechanicalFeasibilityAnalyzerInputHash(task);
|
|
269
|
+
if (
|
|
270
|
+
evidence.taskId !== task.id
|
|
271
|
+
|| evidence.briefHash !== briefHash
|
|
272
|
+
|| evidence.planHash !== planHash
|
|
273
|
+
|| evidence.analyzerSetHash !== currentAnalyzerSetHash
|
|
274
|
+
|| evidence.analyzerInputHash !== currentAnalyzerInputHash
|
|
275
|
+
) {
|
|
276
|
+
throw new WorkflowError('TRANSITION_BLOCKED', 'Mechanical-feasibility authorization binding is stale.', {
|
|
277
|
+
taskId: task.id,
|
|
278
|
+
authorizedBriefHash: evidence.briefHash,
|
|
279
|
+
currentBriefHash: briefHash,
|
|
280
|
+
authorizedPlanHash: evidence.planHash,
|
|
281
|
+
currentPlanHash: planHash,
|
|
282
|
+
authorizedAnalyzerSetHash: evidence.analyzerSetHash,
|
|
283
|
+
currentAnalyzerSetHash,
|
|
284
|
+
authorizedAnalyzerInputHash: evidence.analyzerInputHash,
|
|
285
|
+
currentAnalyzerInputHash,
|
|
286
|
+
requiredAction: 'Record a fresh execution authorization.',
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
if (evidence.decision !== 'clear' || evidence.findings.some((findingValue) => findingValue.result === 'blocked')) {
|
|
290
|
+
throw new WorkflowError('STATE_CORRUPT', 'Approved mechanical-feasibility evidence is not clear.', {
|
|
291
|
+
taskId: task.id,
|
|
292
|
+
decision: evidence.decision,
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
if (task.baseCommit === null) {
|
|
297
|
+
const currentBaseHead = runGit(repositoryRoot, ['rev-parse', task.baseBranch]);
|
|
298
|
+
if (
|
|
299
|
+
evidence.sourceKind !== 'pre-start-base'
|
|
300
|
+
|| evidence.sourceBranch !== task.baseBranch
|
|
301
|
+
|| evidence.baseCommit !== null
|
|
302
|
+
|| evidence.evaluatedHead !== currentBaseHead
|
|
303
|
+
) {
|
|
304
|
+
throw staleSourceError(task, evidence, currentBaseHead);
|
|
305
|
+
}
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
if (!task.taskBranch) {
|
|
310
|
+
throw new WorkflowError('STATE_CORRUPT', `Started Task ${task.id} has no Task branch.`);
|
|
311
|
+
}
|
|
312
|
+
const branch = currentBranch(repositoryRoot);
|
|
313
|
+
if (branch !== task.taskBranch) {
|
|
314
|
+
throw new WorkflowError('GIT_PRECONDITION_FAILED', 'Mechanical-feasibility freshness requires the owned Task branch.', {
|
|
315
|
+
taskId: task.id,
|
|
316
|
+
expectedBranch: task.taskBranch,
|
|
317
|
+
currentBranch: branch,
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
const currentTaskHead = headCommit(repositoryRoot);
|
|
321
|
+
let sourceMatches = false;
|
|
322
|
+
if (evidence.sourceKind === 'pre-start-base') {
|
|
323
|
+
sourceMatches = evidence.sourceBranch === task.baseBranch
|
|
324
|
+
&& evidence.baseCommit === null
|
|
325
|
+
&& task.baseCommit === evidence.evaluatedHead;
|
|
326
|
+
} else if (evidence.sourceKind === 'corrective-task-workspace') {
|
|
327
|
+
sourceMatches = evidence.sourceBranch === task.taskBranch
|
|
328
|
+
&& evidence.baseCommit === task.baseCommit;
|
|
329
|
+
}
|
|
330
|
+
if (!sourceMatches || !gitIsAncestor(repositoryRoot, evidence.evaluatedHead, currentTaskHead)) {
|
|
331
|
+
throw staleSourceError(task, evidence, currentTaskHead);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function selectEvaluationSource(repositoryRoot: string, task: TaskState): EvaluationSource {
|
|
336
|
+
if (task.baseCommit === null) {
|
|
337
|
+
return {
|
|
338
|
+
sourceKind: 'pre-start-base',
|
|
339
|
+
sourceBranch: task.baseBranch,
|
|
340
|
+
evaluatedHead: runGit(repositoryRoot, ['rev-parse', task.baseBranch]),
|
|
341
|
+
baseCommit: null,
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
if (!task.taskBranch) {
|
|
345
|
+
throw new WorkflowError('STATE_CORRUPT', `Started Task ${task.id} has no Task branch.`);
|
|
346
|
+
}
|
|
347
|
+
const branch = currentBranch(repositoryRoot);
|
|
348
|
+
if (branch !== task.taskBranch) {
|
|
349
|
+
throw new WorkflowError('GIT_PRECONDITION_FAILED', 'Mechanical feasibility must be evaluated in the owned Task workspace.', {
|
|
350
|
+
taskId: task.id,
|
|
351
|
+
expectedBranch: task.taskBranch,
|
|
352
|
+
currentBranch: branch,
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
return {
|
|
356
|
+
sourceKind: 'corrective-task-workspace',
|
|
357
|
+
sourceBranch: task.taskBranch,
|
|
358
|
+
evaluatedHead: headCommit(repositoryRoot),
|
|
359
|
+
baseCommit: task.baseCommit,
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
function parseRootManifest(content: string | null): { state: 'valid' | 'missing' | 'invalid'; scripts: Map<string, string> } {
|
|
364
|
+
if (content === null) return { state: 'missing', scripts: new Map() };
|
|
365
|
+
try {
|
|
366
|
+
const parsed = JSON.parse(content) as unknown;
|
|
367
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
368
|
+
return { state: 'invalid', scripts: new Map() };
|
|
369
|
+
}
|
|
370
|
+
const scriptsValue = (parsed as Record<string, unknown>).scripts;
|
|
371
|
+
if (!scriptsValue || typeof scriptsValue !== 'object' || Array.isArray(scriptsValue)) {
|
|
372
|
+
return { state: 'valid', scripts: new Map() };
|
|
373
|
+
}
|
|
374
|
+
return {
|
|
375
|
+
state: 'valid',
|
|
376
|
+
scripts: new Map(Object.entries(scriptsValue)
|
|
377
|
+
.filter((entry): entry is [string, string] => typeof entry[1] === 'string')),
|
|
378
|
+
};
|
|
379
|
+
} catch {
|
|
380
|
+
return { state: 'invalid', scripts: new Map() };
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
function decodeExactPath(subject: string): string | null {
|
|
385
|
+
const match = EXACT_PATH.exec(subject);
|
|
386
|
+
if (!match) return null;
|
|
387
|
+
const relativePath = match[1]!;
|
|
388
|
+
const segments = relativePath.split('/');
|
|
389
|
+
if (segments.some((segment) => segment === '.' || segment === '..')) return null;
|
|
390
|
+
return relativePath;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
function transitivePredecessorIds(
|
|
394
|
+
stepId: string,
|
|
395
|
+
byId: ReadonlyMap<string, TaskState['steps'][number]>,
|
|
396
|
+
): string[] {
|
|
397
|
+
const found = new Set<string>();
|
|
398
|
+
const visit = (candidateId: string): void => {
|
|
399
|
+
const candidate = requireStep(byId, candidateId);
|
|
400
|
+
for (const dependencyId of candidate.dependencies) {
|
|
401
|
+
if (found.has(dependencyId)) continue;
|
|
402
|
+
requireStep(byId, dependencyId);
|
|
403
|
+
found.add(dependencyId);
|
|
404
|
+
visit(dependencyId);
|
|
405
|
+
}
|
|
406
|
+
};
|
|
407
|
+
visit(stepId);
|
|
408
|
+
return [...found].sort();
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
function requireStep(
|
|
412
|
+
byId: ReadonlyMap<string, TaskState['steps'][number]>,
|
|
413
|
+
stepId: string,
|
|
414
|
+
): TaskState['steps'][number] {
|
|
415
|
+
const step = byId.get(stepId);
|
|
416
|
+
if (!step) throw new WorkflowError('STATE_CORRUPT', `Mechanical-feasibility input references missing Step ${stepId}.`);
|
|
417
|
+
return step;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
function finding(
|
|
421
|
+
analyzerId: MechanicalFeasibilityAnalyzer['analyzerId'],
|
|
422
|
+
result: MechanicalFeasibilityFinding['result'],
|
|
423
|
+
stepId: string,
|
|
424
|
+
subject: string,
|
|
425
|
+
evidence: string,
|
|
426
|
+
reasonCode: string,
|
|
427
|
+
): MechanicalFeasibilityFinding {
|
|
428
|
+
return { analyzerId, analyzerVersion: 1, result, stepId, subject, evidence, reasonCode };
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
function compareFindings(left: MechanicalFeasibilityFinding, right: MechanicalFeasibilityFinding): number {
|
|
432
|
+
return left.stepId.localeCompare(right.stepId)
|
|
433
|
+
|| left.analyzerId.localeCompare(right.analyzerId)
|
|
434
|
+
|| left.subject.localeCompare(right.subject)
|
|
435
|
+
|| left.result.localeCompare(right.result);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
function readGitPath(repositoryRoot: string, commit: string, relativePath: string): string | null {
|
|
439
|
+
const result = spawnSync('git', ['-C', repositoryRoot, 'show', `${commit}:${relativePath}`], { encoding: 'utf8' });
|
|
440
|
+
if (result.status === 0) return result.stdout;
|
|
441
|
+
if (result.status === 128) return null;
|
|
442
|
+
throw new WorkflowError('COMMAND_FAILED', `Unable to read ${relativePath} at ${commit}.`, {
|
|
443
|
+
stderr: result.stderr.trim(),
|
|
444
|
+
exitCode: result.status,
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
function gitPathExists(repositoryRoot: string, commit: string, relativePath: string): boolean {
|
|
449
|
+
const result = spawnSync('git', ['-C', repositoryRoot, 'cat-file', '-e', `${commit}:${relativePath}`], { encoding: 'utf8' });
|
|
450
|
+
if (result.status === 0) return true;
|
|
451
|
+
if (result.status === 128) return false;
|
|
452
|
+
throw new WorkflowError('COMMAND_FAILED', `Unable to inspect ${relativePath} at ${commit}.`, {
|
|
453
|
+
stderr: result.stderr.trim(),
|
|
454
|
+
exitCode: result.status,
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
function assertEvidenceShape(evidence: MechanicalFeasibilityEvidence, taskId: string): void {
|
|
459
|
+
const analyzersCanonical = canonicalJsonStringify(evidence.analyzers);
|
|
460
|
+
const currentAnalyzersCanonical = canonicalJsonStringify(MECHANICAL_FEASIBILITY_ANALYZERS);
|
|
461
|
+
const sortedFindings = [...evidence.findings].sort(compareFindings);
|
|
462
|
+
if (
|
|
463
|
+
evidence.schemaVersion !== 1
|
|
464
|
+
|| analyzersCanonical !== currentAnalyzersCanonical
|
|
465
|
+
|| canonicalJsonStringify(evidence.findings) !== canonicalJsonStringify(sortedFindings)
|
|
466
|
+
) {
|
|
467
|
+
throw new WorkflowError('STATE_CORRUPT', 'Mechanical-feasibility evidence shape or canonical order is invalid.', {
|
|
468
|
+
taskId,
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
function staleSourceError(
|
|
474
|
+
task: TaskState,
|
|
475
|
+
evidence: MechanicalFeasibilityEvidence,
|
|
476
|
+
currentHead: string,
|
|
477
|
+
): WorkflowError {
|
|
478
|
+
return new WorkflowError('TRANSITION_BLOCKED', 'Mechanical-feasibility evaluation source is stale.', {
|
|
479
|
+
taskId: task.id,
|
|
480
|
+
sourceKind: evidence.sourceKind,
|
|
481
|
+
sourceBranch: evidence.sourceBranch,
|
|
482
|
+
evaluatedHead: evidence.evaluatedHead,
|
|
483
|
+
currentHead,
|
|
484
|
+
authorizedBaseCommit: evidence.baseCommit,
|
|
485
|
+
currentBaseCommit: task.baseCommit,
|
|
486
|
+
requiredAction: 'Record a fresh execution authorization.',
|
|
487
|
+
});
|
|
488
|
+
}
|