codex-workflow-v2 2.0.0-beta.13.9 → 2.0.0-beta.14.1
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 +98 -34
- package/dist/src/alpha6/adoption.d.ts +3 -1
- package/dist/src/alpha6/adoption.js +48 -8
- 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 +53 -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 +8 -21
- package/dist/src/alpha6/remediation.js +225 -474
- 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/alpha7/autonomy.d.ts +9 -0
- package/dist/src/alpha7/autonomy.js +40 -1
- package/dist/src/alpha7/autonomy.js.map +1 -1
- package/dist/src/beta1/project-transaction.d.ts +3 -1
- package/dist/src/beta1/project-transaction.js +20 -6
- 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 +3 -3
- package/dist/src/cli-actions.js +8 -15
- package/dist/src/cli-actions.js.map +1 -1
- package/dist/src/cli.js +43 -97
- package/dist/src/cli.js.map +1 -1
- package/dist/src/contracts.d.ts +54 -34
- package/dist/src/delegation.d.ts +5 -1
- package/dist/src/delegation.js +4 -0
- package/dist/src/delegation.js.map +1 -1
- 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/failed-checkpoint-update.d.ts +69 -0
- package/dist/src/failed-checkpoint-update.js +171 -0
- package/dist/src/failed-checkpoint-update.js.map +1 -0
- 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/lifecycle/corrective-replan-credential-core.d.ts +2 -0
- package/dist/src/lifecycle/corrective-replan-credential-core.js +45 -9
- package/dist/src/lifecycle/corrective-replan-credential-core.js.map +1 -1
- package/dist/src/lifecycle/corrective-replan.js +12 -8
- package/dist/src/lifecycle/corrective-replan.js.map +1 -1
- package/dist/src/lifecycle/semantic-registry.js +4 -2
- package/dist/src/lifecycle/semantic-registry.js.map +1 -1
- 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 +10 -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 +38 -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.d.ts +9 -1
- package/dist/src/state/corrective-replan-public.js +50 -8
- 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/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 +88 -64
- package/dist/src/workflow.js +832 -1456
- 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 +36 -237
- package/docs/project-memory.md +31 -50
- package/docs/release-app-evidence.md +190 -0
- package/docs/release.md +101 -388
- package/docs/updating-existing-project.md +65 -726
- 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 +88 -200
- package/plugins/codex-workflow-gateway/references/codebase-memory-routing.md +57 -0
- package/plugins/codex-workflow-gateway/references/protocol.md +51 -458
- package/plugins/codex-workflow-gateway/scripts/chat-dispatch.mjs +20 -4
- package/plugins/codex-workflow-gateway/scripts/chat-model-policy.mjs +21 -34
- package/plugins/codex-workflow-gateway/scripts/chat-registry.mjs +23 -5
- package/plugins/codex-workflow-gateway/skills/codex-workflow-gateway/SKILL.md +92 -778
- package/references/state-machine.md +7 -5
- package/roles/delivery-coordinator.md +31 -11
- package/roles/scope-lead.md +1 -1
- package/roles/technical-planner.md +5 -2
- package/schemas/adoption-posture-event.schema.json +11 -0
- package/schemas/authorization-event.schema.json +1 -1
- package/schemas/corrective-decision-event.schema.json +26 -4
- package/schemas/corrective-replan-credentials.private.schema.json +8 -5
- package/schemas/corrective-replan-execution-event.private.schema.json +9 -0
- package/schemas/corrective-replan-public.schema.json +2 -1
- package/schemas/delegation-grant.schema.json +1 -1
- package/schemas/project-knowledge-map.schema.json +3 -1
- package/schemas/remediation-event.schema.json +10 -1
- package/schemas/task.schema.json +64 -2
- package/schemas/transition-definition.schema.json +4 -0
- package/schemas/transition-payloads.schema.json +16 -1
- package/src/alpha6/adoption.ts +1146 -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 +45 -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 +2505 -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 +449 -0
- package/src/alpha7/corrective-recovery.ts +1332 -0
- package/src/artifacts.ts +130 -0
- package/src/beta1/project-transaction.ts +364 -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 +112 -0
- package/src/cli.ts +1490 -0
- package/src/contracts.ts +1512 -0
- package/src/credential-output.ts +89 -0
- package/src/credential-transport.ts +215 -0
- package/src/delegation.ts +194 -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/failed-checkpoint-update.ts +203 -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 +437 -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 +847 -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 +574 -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 +665 -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 +819 -0
- package/src/state/corrective-replan-public-schema.ts +83 -0
- package/src/state/corrective-replan-public.ts +952 -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 +10342 -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,136 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
HistoricalStepProvenanceRecoveryPreflight,
|
|
3
|
+
TaskState,
|
|
4
|
+
} from './contracts.js';
|
|
5
|
+
import { changedFiles, currentBranch, headCommit, runGit } from './repository.js';
|
|
6
|
+
|
|
7
|
+
export interface HistoricalStepProvenanceEnvironment {
|
|
8
|
+
activeLeaseCount: number;
|
|
9
|
+
staleLeaseCount: number;
|
|
10
|
+
pendingTransactionCount: number;
|
|
11
|
+
corruptTransactionCount: number;
|
|
12
|
+
coreOperationCount: number;
|
|
13
|
+
verifiedHistoricalStepBindings: Array<{
|
|
14
|
+
stepId: string;
|
|
15
|
+
commitSha: string;
|
|
16
|
+
planHash: string;
|
|
17
|
+
recordedAt: string;
|
|
18
|
+
}>;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface HistoricalStepProvenanceInspectionOptions {
|
|
22
|
+
historyBoundary?: string;
|
|
23
|
+
preservedHeadCommit?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function omittedStepEvidenceCommits(
|
|
27
|
+
task: TaskState,
|
|
28
|
+
nextSteps: ReadonlyArray<Pick<TaskState['steps'][number], 'evidence'>>,
|
|
29
|
+
): string[] {
|
|
30
|
+
const retainedByCurrentPlan = new Set(nextSteps.flatMap((step) =>
|
|
31
|
+
step.evidence ? [step.evidence.commitSha] : []));
|
|
32
|
+
return [...new Set(task.steps.flatMap((step) =>
|
|
33
|
+
step.evidence && !retainedByCurrentPlan.has(step.evidence.commitSha)
|
|
34
|
+
? [step.evidence.commitSha]
|
|
35
|
+
: []))];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function inspectHistoricalStepProvenanceRecovery(
|
|
39
|
+
repositoryRoot: string,
|
|
40
|
+
task: TaskState,
|
|
41
|
+
environment: HistoricalStepProvenanceEnvironment,
|
|
42
|
+
options: HistoricalStepProvenanceInspectionOptions = {},
|
|
43
|
+
): HistoricalStepProvenanceRecoveryPreflight {
|
|
44
|
+
const blockers: string[] = [];
|
|
45
|
+
const dirty = changedFiles(repositoryRoot);
|
|
46
|
+
const historyBoundary = options.historyBoundary ?? 'HEAD';
|
|
47
|
+
const head = options.preservedHeadCommit ?? headCommit(repositoryRoot);
|
|
48
|
+
const activeSteps = task.steps.filter((step) => step.status === 'in_progress');
|
|
49
|
+
const activeStep = activeSteps.length === 1 ? activeSteps[0]! : null;
|
|
50
|
+
|
|
51
|
+
if (task.status !== 'in_progress') blockers.push('The Task must be in progress.');
|
|
52
|
+
if (activeSteps.length !== 1) blockers.push('Exactly one Step must be in progress.');
|
|
53
|
+
if (activeStep && dirty.some((file) => !activeStep.allowedWrites.includes(file))) {
|
|
54
|
+
blockers.push('Dirty files must remain confined to the active Step allowedWrites.');
|
|
55
|
+
}
|
|
56
|
+
if (task.workspaceOwner !== 'local') blockers.push('Recovery is limited to a local Task workspace.');
|
|
57
|
+
if (!task.taskBranch || currentBranch(repositoryRoot) !== task.taskBranch) {
|
|
58
|
+
blockers.push('The current branch must be the recorded Task branch.');
|
|
59
|
+
}
|
|
60
|
+
if (!task.baseCommit) blockers.push('The Task must have a recorded base commit.');
|
|
61
|
+
if (!task.planHash) blockers.push('The Task must have a current Plan hash.');
|
|
62
|
+
if (environment.activeLeaseCount > 0) blockers.push('No active writer lease may exist.');
|
|
63
|
+
if (environment.staleLeaseCount > 0) blockers.push('No stale writer lease may exist.');
|
|
64
|
+
if (environment.pendingTransactionCount > 0) blockers.push('No pending Workflow transaction may exist.');
|
|
65
|
+
if (environment.corruptTransactionCount > 0) blockers.push('No corrupt Workflow transaction may exist.');
|
|
66
|
+
if (environment.coreOperationCount > 0) blockers.push('No Core Task operation may exist.');
|
|
67
|
+
|
|
68
|
+
let candidate: string | null = null;
|
|
69
|
+
let sourceFinalAcceptanceRecordedAt: string | null = null;
|
|
70
|
+
if (task.baseCommit) {
|
|
71
|
+
try {
|
|
72
|
+
const output = runGit(repositoryRoot, ['rev-list', '--reverse', `${task.baseCommit}..${historyBoundary}`]);
|
|
73
|
+
const actual = output ? output.split('\n') : [];
|
|
74
|
+
const expected = [...new Set([
|
|
75
|
+
...task.steps.flatMap((step) => (step.evidence ? [step.evidence.commitSha] : [])),
|
|
76
|
+
...task.systemCommits,
|
|
77
|
+
...(task.invalidatedStepCommits ?? []),
|
|
78
|
+
...(task.dependencyProvenanceRecoveries ?? []).flatMap((recovery) => recovery.retainedHistoricalCommits ?? []),
|
|
79
|
+
])];
|
|
80
|
+
const missing = expected.filter((commit) => !actual.includes(commit));
|
|
81
|
+
const unexpected = actual.filter((commit) => !expected.includes(commit));
|
|
82
|
+
if (missing.length > 0) blockers.push('Recorded Task history contains missing commits.');
|
|
83
|
+
if (unexpected.length !== 1) blockers.push('Recovery requires exactly one unregistered historical commit.');
|
|
84
|
+
candidate = unexpected.length === 1 ? unexpected[0]! : null;
|
|
85
|
+
const verifiedBindings = candidate
|
|
86
|
+
? environment.verifiedHistoricalStepBindings.filter((binding) => binding.commitSha === candidate)
|
|
87
|
+
: [];
|
|
88
|
+
if (verifiedBindings.length === 0) {
|
|
89
|
+
blockers.push('The historical commit lacks a complete passed strict Step Review chain.');
|
|
90
|
+
}
|
|
91
|
+
const finalAcceptance = candidate
|
|
92
|
+
? task.authorizations.find((authorization) =>
|
|
93
|
+
authorization.kind === 'final_acceptance'
|
|
94
|
+
&& ['approved', 'superseded'].includes(authorization.decision)
|
|
95
|
+
&& authorization.headCommit === candidate
|
|
96
|
+
&& authorization.planHash
|
|
97
|
+
&& authorization.resultHash
|
|
98
|
+
&& authorization.evidenceHash)
|
|
99
|
+
: null;
|
|
100
|
+
if (!finalAcceptance) {
|
|
101
|
+
blockers.push('The historical commit lacks a hash-bound prior final acceptance.');
|
|
102
|
+
} else {
|
|
103
|
+
sourceFinalAcceptanceRecordedAt = finalAcceptance.recordedAt;
|
|
104
|
+
if (!verifiedBindings.some((binding) =>
|
|
105
|
+
binding.planHash === finalAcceptance.planHash
|
|
106
|
+
&& binding.recordedAt <= finalAcceptance.recordedAt)) {
|
|
107
|
+
blockers.push('The prior final acceptance does not bind the passed historical Step Review Plan.');
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
if (candidate) {
|
|
111
|
+
const subject = runGit(repositoryRoot, ['show', '-s', '--format=%s', candidate]);
|
|
112
|
+
if (!subject.startsWith(`${task.id}/STEP-`)) {
|
|
113
|
+
blockers.push('The historical commit subject is not bound to this Task and a Step.');
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
} catch {
|
|
117
|
+
blockers.push('The Task history could not be inspected.');
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return {
|
|
122
|
+
eligible: blockers.length === 0,
|
|
123
|
+
action: blockers.length === 0 ? 'task historical-step-provenance-recover' : null,
|
|
124
|
+
projectId: task.projectId,
|
|
125
|
+
taskId: task.id,
|
|
126
|
+
taskRevision: task.revision,
|
|
127
|
+
briefHash: task.briefHash,
|
|
128
|
+
planHash: task.planHash,
|
|
129
|
+
headCommit: head,
|
|
130
|
+
activeStepId: activeStep?.id ?? null,
|
|
131
|
+
dirtyFiles: dirty,
|
|
132
|
+
commitSha: blockers.length === 0 ? candidate : null,
|
|
133
|
+
sourceFinalAcceptanceRecordedAt: blockers.length === 0 ? sourceFinalAcceptanceRecordedAt : null,
|
|
134
|
+
blockers,
|
|
135
|
+
};
|
|
136
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export * from './contracts.js';
|
|
2
|
+
export * from './errors.js';
|
|
3
|
+
export * from './diagnostics.js';
|
|
4
|
+
export * from './delegation.js';
|
|
5
|
+
export * from './graph.js';
|
|
6
|
+
export * from './memory.js';
|
|
7
|
+
export * from './migration.js';
|
|
8
|
+
export * from './repository.js';
|
|
9
|
+
export * from './reviewer.js';
|
|
10
|
+
export * from './observed-routes.js';
|
|
11
|
+
export * from './operational-contract.js';
|
|
12
|
+
export * from './state/store.js';
|
|
13
|
+
export * from './workflow.js';
|
|
14
|
+
export * from './version.js';
|
|
15
|
+
export * from './lifecycle/index.js';
|
|
16
|
+
export * from './alpha7/autonomy.js';
|
|
17
|
+
export * from './alpha6/mechanical-feasibility.js';
|
|
18
|
+
export { defaultTaskWorkerActor } from './alpha6/handoff.js';
|
|
19
|
+
export { defaultCorrectiveAuditorActor } from './alpha6/remediation.js';
|
|
20
|
+
export type { AdoptionPreparation } from './alpha6/adoption.js';
|
|
21
|
+
export type { RecordPlanRiskAuditInput } from './alpha6/plan-risk.js';
|
|
22
|
+
|
|
23
|
+
export type { CheckExecutionOptions } from './checks/runner.js';
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
|
|
3
|
+
type JsonPrimitive = boolean | null | number | string;
|
|
4
|
+
export type CanonicalJsonValue = JsonPrimitive | CanonicalJsonValue[] | { [key: string]: CanonicalJsonValue };
|
|
5
|
+
|
|
6
|
+
const EDITORIAL_KEYS = new Set(['description']);
|
|
7
|
+
|
|
8
|
+
export function hashCanonical(value: unknown): string {
|
|
9
|
+
return createHash('sha256').update(JSON.stringify(canonicalize(value))).digest('hex');
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function canonicalize(value: unknown): CanonicalJsonValue {
|
|
13
|
+
if (value === null || typeof value === 'boolean' || typeof value === 'string') return value;
|
|
14
|
+
if (typeof value === 'number') {
|
|
15
|
+
if (!Number.isFinite(value)) throw new TypeError('Canonical hashes do not support non-finite numbers.');
|
|
16
|
+
return value;
|
|
17
|
+
}
|
|
18
|
+
if (Array.isArray(value)) return value.map((entry) => canonicalize(entry));
|
|
19
|
+
if (typeof value === 'object') {
|
|
20
|
+
return Object.fromEntries(
|
|
21
|
+
Object.entries(value)
|
|
22
|
+
.filter(([key, entry]) => entry !== undefined && !EDITORIAL_KEYS.has(key))
|
|
23
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
24
|
+
.map(([key, entry]) => [key, canonicalize(entry)]),
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
throw new TypeError(`Unsupported canonical hash value: ${typeof value}`);
|
|
28
|
+
}
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import {
|
|
2
|
+
catalogDefinitionManifestHash, hashCanonical, runtimeCatalogHash, runtimeSemanticFingerprint,
|
|
3
|
+
transitionDefinitionManifestHash,
|
|
4
|
+
} from './fingerprint.js';
|
|
5
|
+
import { transitionSemanticRegistry } from './semantic-registry.js';
|
|
6
|
+
import {
|
|
7
|
+
TRANSITION_DEFINITION_FORMAT_VERSION,
|
|
8
|
+
type EffectCondition, type ExecutableTransitionDefinition, type GuardId, type RegisteredRef,
|
|
9
|
+
type SerializedTransitionDefinition, type TransitionDefinitionMetadata,
|
|
10
|
+
type TransitionEffectNode, type TransitionId, type VersionedRegistryEntry,
|
|
11
|
+
} from './types.js';
|
|
12
|
+
|
|
13
|
+
type AnyDefinition = ExecutableTransitionDefinition<unknown, unknown>;
|
|
14
|
+
type DefinitionLike = AnyDefinition | SerializedTransitionDefinition | TransitionDefinitionMetadata;
|
|
15
|
+
|
|
16
|
+
export type TransitionSemanticValidationRuleId =
|
|
17
|
+
| 'validation.atomicity.contract-invalid' | 'validation.definition.format-invalid'
|
|
18
|
+
| 'validation.definition.manifest-mismatch' | 'validation.effect.graph-invalid'
|
|
19
|
+
| 'validation.fingerprint.alias-mismatch' | 'validation.fingerprint.runtime-mismatch'
|
|
20
|
+
| 'validation.guard.evaluator-mismatch' | 'validation.guard.namespace-invalid'
|
|
21
|
+
| 'validation.namespace.transition-invalid' | 'validation.race.revalidation-invalid'
|
|
22
|
+
| 'validation.reference.unresolved' | 'validation.semantic-id.duplicate';
|
|
23
|
+
|
|
24
|
+
export interface TransitionSemanticValidationIssue {
|
|
25
|
+
readonly ruleId: TransitionSemanticValidationRuleId;
|
|
26
|
+
readonly path: string;
|
|
27
|
+
readonly ref?: string;
|
|
28
|
+
readonly message: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface TransitionSemanticValidationReport {
|
|
32
|
+
readonly valid: boolean;
|
|
33
|
+
readonly issues: readonly TransitionSemanticValidationIssue[];
|
|
34
|
+
readonly reportHash: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export class GuardedTransitionCatalog {
|
|
38
|
+
readonly #definitions: ReadonlyMap<TransitionId, AnyDefinition>;
|
|
39
|
+
readonly catalogHash: string;
|
|
40
|
+
readonly catalogDefinitionManifestHash: string;
|
|
41
|
+
|
|
42
|
+
constructor(definitions: readonly AnyDefinition[]) {
|
|
43
|
+
const indexed = new Map<TransitionId, AnyDefinition>();
|
|
44
|
+
for (const definition of definitions) {
|
|
45
|
+
const id = definition.metadata.transitionId;
|
|
46
|
+
if (indexed.has(id)) throw new TypeError(`validation.semantic-id.duplicate ${id}`);
|
|
47
|
+
const semantic = validateTransitionDefinitionSemantics(definition);
|
|
48
|
+
if (!semantic.valid) throw new TypeError(`${semantic.issues[0]!.ruleId} ${id}: ${semantic.issues[0]!.message}`);
|
|
49
|
+
if (definition.guards.length !== definition.metadata.guards.length
|
|
50
|
+
|| definition.guards.some((guard, index) => guard.metadata.guardId !== definition.metadata.guards[index]?.guardId)) {
|
|
51
|
+
throw new TypeError(`validation.guard.evaluator-mismatch ${id}`);
|
|
52
|
+
}
|
|
53
|
+
indexed.set(id, definition);
|
|
54
|
+
}
|
|
55
|
+
this.#definitions = indexed;
|
|
56
|
+
this.catalogHash = runtimeCatalogHash(this.serialize());
|
|
57
|
+
this.catalogDefinitionManifestHash = catalogDefinitionManifestHash(this.serialize());
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
has(id: TransitionId): boolean { return this.#definitions.has(id); }
|
|
61
|
+
get<TSnapshot, TBindings>(id: TransitionId): ExecutableTransitionDefinition<TSnapshot, TBindings> {
|
|
62
|
+
const definition = this.#definitions.get(id);
|
|
63
|
+
if (!definition) throw new TypeError(`Unknown transition definition: ${id}`);
|
|
64
|
+
return definition as ExecutableTransitionDefinition<TSnapshot, TBindings>;
|
|
65
|
+
}
|
|
66
|
+
serialize(): SerializedTransitionDefinition[] {
|
|
67
|
+
return structuredClone([...this.#definitions.values()].map((definition) => ({
|
|
68
|
+
...definition.metadata,
|
|
69
|
+
runtimeSemanticFingerprint: definition.runtimeSemanticFingerprint,
|
|
70
|
+
semanticFingerprint: definition.semanticFingerprint,
|
|
71
|
+
definitionManifestHash: definition.definitionManifestHash,
|
|
72
|
+
})).sort((left, right) => left.transitionId.localeCompare(right.transitionId)));
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function validateTransitionDefinitionSemantics(definition: DefinitionLike): TransitionSemanticValidationReport {
|
|
77
|
+
const issues: TransitionSemanticValidationIssue[] = [];
|
|
78
|
+
let unpacked: ReturnType<typeof unpack>;
|
|
79
|
+
try { unpacked = unpack(definition); }
|
|
80
|
+
catch (error) { issue(issues, 'validation.reference.unresolved', '/', message(error)); return report(issues); }
|
|
81
|
+
const metadata = unpacked.metadata;
|
|
82
|
+
if (metadata.definitionFormatVersion !== TRANSITION_DEFINITION_FORMAT_VERSION) issue(issues, 'validation.definition.format-invalid', '/definitionFormatVersion', 'Definition format must be 4.');
|
|
83
|
+
const namespace = metadata.entity === 'step' ? 'task' : metadata.entity === 'project-memory' ? 'project' : metadata.entity;
|
|
84
|
+
if (!metadata.transitionId.startsWith(`${namespace}.`)) issue(issues, 'validation.namespace.transition-invalid', '/transitionId', 'Transition namespace does not match entity.', metadata.transitionId);
|
|
85
|
+
if (!metadata.guardNamespace.startsWith(`${namespace}.`)) issue(issues, 'validation.guard.namespace-invalid', '/guardNamespace', 'Guard namespace does not match entity.');
|
|
86
|
+
let runtime = '';
|
|
87
|
+
try { runtime = runtimeSemanticFingerprint(metadata); }
|
|
88
|
+
catch (error) { issue(issues, 'validation.reference.unresolved', referencePath(error, metadata), message(error), referenceKey(error)); }
|
|
89
|
+
if (unpacked.runtime && runtime && unpacked.runtime !== runtime) issue(issues, 'validation.fingerprint.runtime-mismatch', '/runtimeSemanticFingerprint', 'Runtime fingerprint is stale.');
|
|
90
|
+
if (unpacked.semantic && unpacked.semantic !== unpacked.runtime) issue(issues, 'validation.fingerprint.alias-mismatch', '/semanticFingerprint', 'Deprecated alias is stale.');
|
|
91
|
+
try {
|
|
92
|
+
const manifest = transitionDefinitionManifestHash(metadata, runtime);
|
|
93
|
+
if (unpacked.manifest && unpacked.manifest !== manifest) issue(issues, 'validation.definition.manifest-mismatch', '/definitionManifestHash', 'Definition manifest is stale.');
|
|
94
|
+
} catch (error) { issue(issues, 'validation.reference.unresolved', '/readinessManifest', message(error), referenceKey(error)); }
|
|
95
|
+
const inputRefs = new Set(metadata.inputContract.map((entry) => refKey(entry.input)));
|
|
96
|
+
unique(metadata.inputContract.map((entry) => refKey(entry.input)), '/inputContract', issues);
|
|
97
|
+
const guards = new Map<GuardId, TransitionDefinitionMetadata['guards'][number]>();
|
|
98
|
+
metadata.guards.forEach((guard, index) => {
|
|
99
|
+
if (!guard.guardId.startsWith(`${metadata.guardNamespace}.`)) issue(issues, 'validation.guard.namespace-invalid', `/guards/${index}/guardId`, 'Guard is outside namespace.');
|
|
100
|
+
if (guards.has(guard.guardId)) issue(issues, 'validation.semantic-id.duplicate', `/guards/${index}/guardId`, 'Duplicate guard.', guard.guardId);
|
|
101
|
+
guards.set(guard.guardId, guard);
|
|
102
|
+
const evaluator = exactRef(transitionSemanticRegistry.guardEvaluators, guard.evaluator);
|
|
103
|
+
if (!evaluator) issue(issues, 'validation.reference.unresolved', `/guards/${index}/evaluator`, 'Guard evaluator is unresolved.', refKey(guard.evaluator));
|
|
104
|
+
else if (evaluator.transitionId !== metadata.transitionId || evaluator.guardId !== guard.guardId) issue(issues, 'validation.guard.evaluator-mismatch', `/guards/${index}/evaluator`, 'Evaluator does not bind this guard.', refKey(guard.evaluator));
|
|
105
|
+
guard.requiredInputs.forEach((input) => {
|
|
106
|
+
if (!inputRefs.has(refKey(input))) issue(issues, 'validation.guard.evaluator-mismatch', `/guards/${index}/requiredInputs`, 'Guard input is outside contract.', refKey(input));
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
metadata.inputContract.forEach((entry, index) => {
|
|
110
|
+
if (entry.required && !metadata.guards.some((guard) => guard.requiredInputs.some((input) => refKey(input) === refKey(entry.input)))) issue(issues, 'validation.guard.evaluator-mismatch', `/inputContract/${index}`, 'Required input has no guard.', refKey(entry.input));
|
|
111
|
+
});
|
|
112
|
+
validateEffects(metadata, guards, inputRefs, issues);
|
|
113
|
+
validateRaces(metadata, guards, issues);
|
|
114
|
+
validateAtomicity(metadata, issues);
|
|
115
|
+
return report(issues);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function validateEffects(metadata: TransitionDefinitionMetadata, guards: ReadonlyMap<GuardId, unknown>, inputs: ReadonlySet<string>, issues: TransitionSemanticValidationIssue[]): void {
|
|
119
|
+
const nodes = metadata.effects.orderedNodes;
|
|
120
|
+
unique(nodes.map((node) => node.effectId), '/effects/orderedNodes', issues);
|
|
121
|
+
const byId = new Map(nodes.map((node) => [node.effectId, node]));
|
|
122
|
+
nodes.forEach((node, index) => {
|
|
123
|
+
if (node.ordinal !== index) issue(issues, 'validation.effect.graph-invalid', `/effects/orderedNodes/${index}/ordinal`, 'Effect ordinals are not contiguous.');
|
|
124
|
+
node.dependsOn.forEach((dependency) => {
|
|
125
|
+
if (!byId.has(dependency) || dependency === node.effectId || depends(byId.get(dependency), node.effectId, byId)) issue(issues, 'validation.effect.graph-invalid', `/effects/orderedNodes/${index}/dependsOn`, 'Effect graph is cyclic or unresolved.', dependency);
|
|
126
|
+
});
|
|
127
|
+
node.conditions.forEach((condition, conditionIndex) => validateCondition(condition, `/effects/orderedNodes/${index}/conditions/${conditionIndex}`, guards, inputs, issues));
|
|
128
|
+
});
|
|
129
|
+
unique(metadata.effects.postconditions.map((entry) => entry.postconditionId), '/effects/postconditions', issues);
|
|
130
|
+
metadata.effects.postconditions.forEach((entry, index) => {
|
|
131
|
+
if (entry.effectNodeIds.length === 0 || entry.effectNodeIds.some((id) => !byId.has(id))) issue(issues, 'validation.effect.graph-invalid', `/effects/postconditions/${index}/effectNodeIds`, 'Postcondition does not bind effects.');
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function validateCondition(condition: EffectCondition, path: string, guards: ReadonlyMap<GuardId, unknown>, inputs: ReadonlySet<string>, issues: TransitionSemanticValidationIssue[]): void {
|
|
136
|
+
if (condition.kind === 'input-present' ? !inputs.has(refKey(condition.input)) : !guards.has(condition.guardId)) issue(issues, 'validation.effect.graph-invalid', path, 'Effect condition is unresolved.');
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function validateRaces(metadata: TransitionDefinitionMetadata, guards: ReadonlyMap<GuardId, TransitionDefinitionMetadata['guards'][number]>, issues: TransitionSemanticValidationIssue[]): void {
|
|
140
|
+
unique(metadata.admissionBoundary.orderedRevalidations.map((entry) => entry.revalidationId), '/admissionBoundary/orderedRevalidations', issues);
|
|
141
|
+
unique(metadata.racePreconditions.map((entry) => entry.raceId), '/racePreconditions', issues);
|
|
142
|
+
metadata.admissionBoundary.orderedRevalidations.forEach((entry, index) => {
|
|
143
|
+
const race = metadata.racePreconditions.filter((candidate) => candidate.raceId === entry.raceId && candidate.revalidationId === entry.revalidationId);
|
|
144
|
+
const guard = guards.get(entry.guardId);
|
|
145
|
+
if (entry.ordinal !== index || race.length !== 1 || race[0]!.guardId !== entry.guardId || !guard || refKey(guard.evaluator) !== refKey(entry.evaluator)) issue(issues, 'validation.race.revalidation-invalid', `/admissionBoundary/orderedRevalidations/${index}`, 'Race and revalidation are not exact.', entry.raceId);
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function validateAtomicity(metadata: TransitionDefinitionMetadata, issues: TransitionSemanticValidationIssue[]): void {
|
|
150
|
+
const atomicity = metadata.atomicity;
|
|
151
|
+
const writes = metadata.effects.orderedNodes.filter((entry) => entry.operation !== 'preserve').length;
|
|
152
|
+
if (atomicity.classification === 'single-write' && writes !== 1) issue(issues, 'validation.atomicity.contract-invalid', '/atomicity/classification', 'single-write requires exactly one write.');
|
|
153
|
+
if (atomicity.classification === 'read-only' && writes !== 0) issue(issues, 'validation.atomicity.contract-invalid', '/atomicity/classification', 'read-only cannot write.');
|
|
154
|
+
if (atomicity.classification.includes('multi-write') && writes < 2) issue(issues, 'validation.atomicity.contract-invalid', '/atomicity/classification', 'multi-write requires multiple writes.');
|
|
155
|
+
if (atomicity.classification === 'non-transactional-multi-write' && atomicity.journal.mode !== 'none') issue(issues, 'validation.atomicity.contract-invalid', '/atomicity/journal', 'Nontransactional write cannot claim journal.');
|
|
156
|
+
const postconditions = new Set(metadata.effects.postconditions.map((entry) => entry.postconditionId));
|
|
157
|
+
if (atomicity.readback.postconditionIds.length !== postconditions.size || atomicity.readback.postconditionIds.some((id) => !postconditions.has(id))) issue(issues, 'validation.atomicity.contract-invalid', '/atomicity/readback/postconditionIds', 'Readback does not cover postconditions.');
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function unpack(definition: DefinitionLike) {
|
|
161
|
+
if ('metadata' in definition) return { metadata: definition.metadata, runtime: definition.runtimeSemanticFingerprint, semantic: definition.semanticFingerprint, manifest: definition.definitionManifestHash };
|
|
162
|
+
if ('runtimeSemanticFingerprint' in definition) {
|
|
163
|
+
const { runtimeSemanticFingerprint: runtime, semanticFingerprint: semantic, definitionManifestHash: manifest, ...metadata } = definition;
|
|
164
|
+
return { metadata, runtime, semantic, manifest };
|
|
165
|
+
}
|
|
166
|
+
return { metadata: definition, runtime: null, semantic: null, manifest: null };
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function exactRef<T extends VersionedRegistryEntry<string>>(entries: readonly T[], ref: RegisteredRef<string>): T | undefined {
|
|
170
|
+
const matches = entries.filter((entry) => refKey(entry) === refKey(ref));
|
|
171
|
+
return matches.length === 1 ? matches[0] : undefined;
|
|
172
|
+
}
|
|
173
|
+
function referencePath(error: unknown, metadata: TransitionDefinitionMetadata): string {
|
|
174
|
+
const key = referenceKey(error); return key ? referencePaths(metadata).get(key) ?? '/' : '/';
|
|
175
|
+
}
|
|
176
|
+
function referencePaths(root: unknown): Map<string, string> {
|
|
177
|
+
const result = new Map<string, string>(); visit(root, ''); return result;
|
|
178
|
+
function visit(value: unknown, path: string): void {
|
|
179
|
+
if (Array.isArray(value)) return value.forEach((entry, index) => visit(entry, `${path}/${index}`));
|
|
180
|
+
if (value === null || typeof value !== 'object') return;
|
|
181
|
+
const candidate = value as Record<string, unknown>;
|
|
182
|
+
if (typeof candidate.id === 'string' && Number.isInteger(candidate.version)) result.set(`${candidate.id}@${candidate.version}`, path || '/');
|
|
183
|
+
Object.entries(candidate).forEach(([key, entry]) => visit(entry, `${path}/${key}`));
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
function referenceKey(error: unknown): string | undefined { return error instanceof Error ? (error as Error & { ref?: string }).ref : undefined; }
|
|
187
|
+
function refKey(ref: RegisteredRef<string>): string { return `${ref.id}@${ref.version}`; }
|
|
188
|
+
function message(error: unknown): string { return error instanceof Error ? error.message : String(error); }
|
|
189
|
+
function unique(values: readonly string[], path: string, issues: TransitionSemanticValidationIssue[]): void {
|
|
190
|
+
if (new Set(values).size !== values.length) issue(issues, 'validation.semantic-id.duplicate', path, 'Semantic IDs must be unique.');
|
|
191
|
+
}
|
|
192
|
+
function depends(node: TransitionEffectNode | undefined, target: string, nodes: ReadonlyMap<string, TransitionEffectNode>, seen = new Set<string>()): boolean {
|
|
193
|
+
if (!node) return false;
|
|
194
|
+
return node.dependsOn.some((id) => id === target || (!seen.has(id) && (seen.add(id), depends(nodes.get(id), target, nodes, seen))));
|
|
195
|
+
}
|
|
196
|
+
function issue(issues: TransitionSemanticValidationIssue[], ruleId: TransitionSemanticValidationRuleId, path: string, message: string, ref?: string): void {
|
|
197
|
+
issues.push(ref === undefined ? { ruleId, path, message } : { ruleId, path, message, ref });
|
|
198
|
+
}
|
|
199
|
+
function report(issues: readonly TransitionSemanticValidationIssue[]): TransitionSemanticValidationReport {
|
|
200
|
+
const ordered = [...issues].sort((left, right) => `${left.path}\0${left.ruleId}\0${left.ref ?? ''}`.localeCompare(`${right.path}\0${right.ruleId}\0${right.ref ?? ''}`));
|
|
201
|
+
const content = { valid: ordered.length === 0, issues: ordered }; return { ...content, reportHash: hashCanonical(content) };
|
|
202
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { diagnoseCoreIssuanceClosure, inspectCorePrivateIr, installedTransitionExecutableBindings } from '../transition-core.js';
|
|
2
|
+
import { validateTransitionDefinitionSemantics } from './catalog.js';
|
|
3
|
+
import { assessCorrectiveReplanAuthorityTuple, installedAuthoritySemanticBindings, projectAuthoritySemanticBundle } from './corrective-replan-authority.js';
|
|
4
|
+
import { correctiveReplanCredentialInspection } from './corrective-replan-credentials.js';
|
|
5
|
+
import { installedCoreRuleBindings } from './core-static-readiness.js';
|
|
6
|
+
import { runtimeSemanticFingerprint, transitionDefinitionManifestHash } from './fingerprint.js';
|
|
7
|
+
import { installedPayloadSchemaArtifacts, installedPayloadSchemaValidators } from './schema-artifact.js';
|
|
8
|
+
import { transitionSemanticRegistry } from './semantic-registry.js';
|
|
9
|
+
|
|
10
|
+
export const installedCoreRuleBindingsForTests = installedCoreRuleBindings;
|
|
11
|
+
export const installedAuthoritySemanticBindingsForTests = installedAuthoritySemanticBindings;
|
|
12
|
+
export const installedTransitionExecutableBindingsForTests = installedTransitionExecutableBindings;
|
|
13
|
+
export const installedPayloadSchemaArtifactsForTests = installedPayloadSchemaArtifacts;
|
|
14
|
+
export const installedPayloadSchemaValidatorsForTests = installedPayloadSchemaValidators;
|
|
15
|
+
export const installedSemanticRegistryForTests = transitionSemanticRegistry;
|
|
16
|
+
export const inspectCorePrivateIrForTests = inspectCorePrivateIr;
|
|
17
|
+
export const diagnoseCoreIssuanceClosureForTests = diagnoseCoreIssuanceClosure;
|
|
18
|
+
export const assessCorrectiveReplanAuthorityTupleForTests = assessCorrectiveReplanAuthorityTuple;
|
|
19
|
+
export const projectAuthoritySemanticBundleForTests = projectAuthoritySemanticBundle;
|
|
20
|
+
export const correctiveReplanCredentialInspectionForTests = correctiveReplanCredentialInspection;
|
|
21
|
+
export function runtimeSemanticFingerprintForInspection(metadata: unknown) {
|
|
22
|
+
return runtimeSemanticFingerprint(metadata as Parameters<typeof runtimeSemanticFingerprint>[0]);
|
|
23
|
+
}
|
|
24
|
+
export function definitionManifestHashForInspection(metadata: unknown, runtime?: string) {
|
|
25
|
+
return transitionDefinitionManifestHash(metadata as Parameters<typeof transitionDefinitionManifestHash>[0], runtime);
|
|
26
|
+
}
|
|
27
|
+
export function validateTransitionDefinitionForInspection(raw: unknown) {
|
|
28
|
+
return validateTransitionDefinitionSemantics(raw as never);
|
|
29
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { runtimeSemanticProjection } from './fingerprint.js';
|
|
2
|
+
import { deepFreeze } from './immutable.js';
|
|
3
|
+
import { coreReleaseRuleDescriptors } from './implementation-table.js';
|
|
4
|
+
import type { CoreReleaseRuleBinding, CoreReleaseRuleContext, RegisteredRef } from './types.js';
|
|
5
|
+
|
|
6
|
+
type RecordValue = Readonly<Record<string, unknown>>;
|
|
7
|
+
|
|
8
|
+
const ruleFunctions = [definitionIntegrity, exactAuthority, requiredInputs, admissionBoundary, reliableEffects] as const;
|
|
9
|
+
|
|
10
|
+
export const installedCoreRuleBindings: readonly CoreReleaseRuleBinding[] = deepFreeze(
|
|
11
|
+
coreReleaseRuleDescriptors.map(([id, suffix], index) => ({
|
|
12
|
+
id, version: 1,
|
|
13
|
+
implementationId: `implementation.release-rule.${suffix}`,
|
|
14
|
+
implementationVersion: 1,
|
|
15
|
+
callable: ruleFunctions[index]!,
|
|
16
|
+
})),
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
export function projectReleaseRuleContextForCore(raw: unknown, ruleId: string): CoreReleaseRuleContext {
|
|
20
|
+
const source = raw as Record<string, unknown>;
|
|
21
|
+
const metadata = Object.fromEntries(Object.entries(source)
|
|
22
|
+
.filter(([key]) => !['runtimeSemanticFingerprint', 'semanticFingerprint', 'definitionManifestHash'].includes(key)));
|
|
23
|
+
const closure = runtimeSemanticProjection(metadata as never).resolvedRegistryEntries as {
|
|
24
|
+
category: string; entry: Record<string, unknown>;
|
|
25
|
+
}[];
|
|
26
|
+
const entries = (category: string) => closure.filter((entry) => entry.category === category).map(({ entry }) => entry);
|
|
27
|
+
const guards = () => rows(metadata.guards).map((guard) => ({ guardId: guard.guardId }));
|
|
28
|
+
if (ruleId === 'semantic-rule.definition-integrity') return context({ guards: guards() });
|
|
29
|
+
if (ruleId === 'semantic-rule.exact-authority') {
|
|
30
|
+
const authority = record(metadata.authority); const actor = record(authority.actor); const principal = record(authority.principal);
|
|
31
|
+
return context({ authority: { actor: { required: actor.required, identity: ref(actor.identity) }, principal: { identity: ref(principal.identity) },
|
|
32
|
+
capabilities: presence(authority.capabilities), credentials: presence(authority.credentials), separationOfDuty: presence(authority.separationOfDuty),
|
|
33
|
+
forbiddenCombinations: presence(authority.forbiddenCombinations),
|
|
34
|
+
ownership: ref(authority.ownership), eligibleRoles: presence(authority.eligibleRoles) }, guards: guards() }, {
|
|
35
|
+
capabilities: presence(entries('capabilities')), credentialPolicies: presence(entries('credentialPolicies')),
|
|
36
|
+
identityComparators: presence(entries('identityComparators')), ownershipPolicies: entries('ownershipPolicies').map((entry) => ({ id: entry.id, version: entry.version, c1Ownership: entry.c1Ownership, writerLease: entry.writerLease })),
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
if (ruleId === 'semantic-rule.required-inputs') return context({ inputContract: rows(metadata.inputContract).map((entry) => ({ required: entry.required, input: ref(entry.input) })), guards: rows(metadata.guards).map((guard) => ({ requiredInputs: rows(guard.requiredInputs).map(ref) })) });
|
|
40
|
+
if (ruleId === 'semantic-rule.admission-boundary') { const boundary = record(metadata.admissionBoundary); return context({ admissionBoundary: {
|
|
41
|
+
lock: { policy: ref(record(boundary.lock).policy) },
|
|
42
|
+
orderedRevalidations: rows(boundary.orderedRevalidations).map((entry) => ({ underLock: entry.underLock })),
|
|
43
|
+
firstEffectAfterRevalidation: boundary.firstEffectAfterRevalidation, release: boundary.release,
|
|
44
|
+
} }, { lockPolicies: entries('lockPolicies').map((entry) => ({ id: entry.id, version: entry.version, lockKind: entry.lockKind })) }); }
|
|
45
|
+
if (ruleId === 'semantic-rule.reliable-effects') { const atomicity = record(metadata.atomicity); const readback = record(atomicity.readback); return context({ atomicity: {
|
|
46
|
+
classification: atomicity.classification, nonMutatingFailureGuarantee: atomicity.nonMutatingFailureGuarantee,
|
|
47
|
+
readback: { adapter: ref(readback.adapter), wiring: readback.wiring, checkpoints: record(readback.checkpoints) },
|
|
48
|
+
journal: { mode: record(atomicity.journal).mode }, recovery: { mode: record(atomicity.recovery).mode, trigger: record(atomicity.recovery).trigger },
|
|
49
|
+
} }, { readbackAdapters: entries('readbackAdapters').map((entry) => ({ id: entry.id, version: entry.version, wiring: entry.wiring })) }); }
|
|
50
|
+
throw new TypeError(`No installed Core context projection for ${ruleId}.`);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function evaluateInstalledReleaseRulesForCore(
|
|
54
|
+
bindings: readonly (CoreReleaseRuleBinding & { readonly context: CoreReleaseRuleContext })[],
|
|
55
|
+
): readonly string[] {
|
|
56
|
+
return [...new Set(bindings.flatMap((binding) => binding.callable(binding.context)))].sort();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function definitionIntegrity({ transition }: CoreReleaseRuleContext): readonly string[] {
|
|
60
|
+
const guards = new Set(array(transition.guards).map((guard) => String(guard.guardId)));
|
|
61
|
+
const required = ['actor-role-eligible', 'no-c1-claim', 'no-writer-lease', 'plan-validation-passed', 'receipt-valid', 'risk-audit-passed']
|
|
62
|
+
.map((suffix) => `task.corrective-replan.${suffix}`);
|
|
63
|
+
return required.every((guard) => guards.has(guard)) ? [] : ['static.definition.mandatory-guard-incomplete'];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function exactAuthority({ transition, registry }: CoreReleaseRuleContext): readonly string[] {
|
|
67
|
+
const authority = transition.authority as RecordValue;
|
|
68
|
+
const actor = authority.actor as RecordValue;
|
|
69
|
+
const principal = authority.principal as RecordValue;
|
|
70
|
+
const blockers: string[] = [];
|
|
71
|
+
if (actor.required !== true || refText(actor.identity) !== 'identity.actor@1'
|
|
72
|
+
|| refText(principal.identity) !== 'identity.human-principal@1') blockers.push('static.authority.actor-unbound');
|
|
73
|
+
if (array(authority.capabilities).length === 0 || array(registry.capabilities).length === 0) blockers.push('static.authority.exact-capability-missing');
|
|
74
|
+
if (array(authority.credentials).length === 0 || array(registry.credentialPolicies).length === 0) blockers.push('static.authority.exact-credential-missing');
|
|
75
|
+
if (array(authority.separationOfDuty).length !== 2 || array(authority.forbiddenCombinations).length !== 1
|
|
76
|
+
|| array(registry.identityComparators).length !== 3) blockers.push('static.authority.sod-unenforced');
|
|
77
|
+
const ownership = array(registry.ownershipPolicies).find((entry) => refText(entry) === refText(authority.ownership));
|
|
78
|
+
if (!ownership || ownership.c1Ownership === 'unchecked' || ownership.writerLease === 'unchecked') blockers.push('static.authority.ownership-unchecked');
|
|
79
|
+
if (array(authority.eligibleRoles).length === 0 || !array(transition.guards)
|
|
80
|
+
.some((guard) => guard.guardId === 'task.corrective-replan.actor-role-eligible')) blockers.push('static.authority.role-eligibility-unenforced');
|
|
81
|
+
return blockers;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function requiredInputs({ transition }: CoreReleaseRuleContext): readonly string[] {
|
|
85
|
+
const inputs = new Set(array(transition.inputContract).filter((entry) => entry.required === true).map((entry) => refText(entry.input)));
|
|
86
|
+
const guarded = new Set(array(transition.guards).flatMap((guard) => array(guard.requiredInputs).map(refText)));
|
|
87
|
+
const exact = ['corrective-execution-receipt', 'corrective-plan-risk-audit', 'corrective-plan-validation', 'corrective-replacement-plan', 'expected-revision', 'task-id']
|
|
88
|
+
.map((id) => `input.${id}@1`);
|
|
89
|
+
return exact.every((id) => inputs.has(id) && guarded.has(id)) ? [] : ['static.input.exact-contract-incomplete'];
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function admissionBoundary({ transition, registry }: CoreReleaseRuleContext): readonly string[] {
|
|
93
|
+
const boundary = transition.admissionBoundary as RecordValue;
|
|
94
|
+
const lock = (boundary.lock as RecordValue).policy;
|
|
95
|
+
const installed = array(registry.lockPolicies).find((entry) => refText(entry) === refText(lock));
|
|
96
|
+
const revalidations = array(boundary.orderedRevalidations);
|
|
97
|
+
return installed?.lockKind === 'exclusive-task'
|
|
98
|
+
&& revalidations.length === 3 && revalidations.every((entry) => entry.underLock === true)
|
|
99
|
+
&& boundary.firstEffectAfterRevalidation === true && boundary.release === 'finally-after-readback'
|
|
100
|
+
? [] : ['static.admission.exclusive-lock-incomplete'];
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function reliableEffects({ transition, registry }: CoreReleaseRuleContext): readonly string[] {
|
|
104
|
+
const atomicity = transition.atomicity as RecordValue;
|
|
105
|
+
const readback = atomicity.readback as RecordValue;
|
|
106
|
+
const installed = array(registry.readbackAdapters).find((entry) => refText(entry) === refText(readback.adapter));
|
|
107
|
+
const blockers: string[] = [];
|
|
108
|
+
const checkpoints = readback.checkpoints as RecordValue;
|
|
109
|
+
if (readback.wiring !== 'invoked-post-effect' || installed?.wiring !== 'invoked-post-effect'
|
|
110
|
+
|| checkpoints.preCompletion !== 'domain-readback' || checkpoints.postTerminal !== 'terminal-readback') blockers.push('static.reliability.readback-unwired');
|
|
111
|
+
if (atomicity.classification !== 'journaled-multi-write' || atomicity.nonMutatingFailureGuarantee !== 'all-guards-before-effects'
|
|
112
|
+
|| (atomicity.journal as RecordValue).mode !== 'required' || (atomicity.recovery as RecordValue).mode !== 'automatic-roll-forward'
|
|
113
|
+
|| (atomicity.recovery as RecordValue).trigger !== 'explicit-repair') {
|
|
114
|
+
blockers.push('static.reliability.contract-incomplete');
|
|
115
|
+
}
|
|
116
|
+
return blockers;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function context(transition: Record<string, unknown>, registry: Record<string, readonly Record<string, unknown>[]> = {}): CoreReleaseRuleContext { return deepFreeze({ transition, registry, policy: {} }); }
|
|
120
|
+
function record(value: unknown): Record<string, unknown> { return value !== null && typeof value === 'object' && !Array.isArray(value) ? value as Record<string, unknown> : {}; }
|
|
121
|
+
function rows(value: unknown): Record<string, unknown>[] { return Array.isArray(value) ? value.map(record) : []; }
|
|
122
|
+
function presence(value: unknown): Record<string, never>[] { return Array.isArray(value) ? value.map(() => ({})) : []; }
|
|
123
|
+
function ref(value: unknown): Record<string, unknown> { const entry = record(value); return { id: entry.id, version: entry.version }; }
|
|
124
|
+
|
|
125
|
+
function array(value: unknown): Record<string, unknown>[] {
|
|
126
|
+
return Array.isArray(value) ? value as Record<string, unknown>[] : [];
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function refText(value: unknown): string {
|
|
130
|
+
const ref = value as Partial<RegisteredRef<string>> | undefined;
|
|
131
|
+
return ref && typeof ref.id === 'string' && Number.isInteger(ref.version) ? `${ref.id}@${ref.version}` : '';
|
|
132
|
+
}
|