codex-workflow-v2 2.0.0-beta.13.8 → 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 +96 -32
- 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/failed-step-planning-recovery.d.ts +10 -0
- package/dist/src/alpha6/failed-step-planning-recovery.js +93 -0
- package/dist/src/alpha6/failed-step-planning-recovery.js.map +1 -0
- 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/plan-risk.d.ts +2 -0
- package/dist/src/alpha6/plan-risk.js +10 -3
- package/dist/src/alpha6/plan-risk.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/lifecycle/corrective-replan.js +3 -3
- package/dist/src/lifecycle/corrective-replan.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 +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 +6 -5
- 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-executor.js +2 -2
- package/dist/src/state/corrective-yield-executor.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 +698 -1470
- 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 -363
- 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 -740
- 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 -508
- 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,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
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { correctiveReplanBindingManifest } from './corrective-replan-binding-manifest.js';
|
|
3
|
+
import { hashCanonical } from './fingerprint.js';
|
|
4
|
+
import { deepFreeze } from './immutable.js';
|
|
5
|
+
|
|
6
|
+
type JsonRecord = Record<string, unknown>;
|
|
7
|
+
type AuthorityCallable = (...arguments_: readonly unknown[]) => unknown;
|
|
8
|
+
|
|
9
|
+
export interface AuthoritySemanticBinding {
|
|
10
|
+
readonly id: string;
|
|
11
|
+
readonly version: 1;
|
|
12
|
+
readonly kind: 'role-evaluator' | 'identity-comparator';
|
|
13
|
+
readonly implementationId: `implementation.${string}`;
|
|
14
|
+
readonly implementationVersion: number;
|
|
15
|
+
readonly wiring: 'invoked';
|
|
16
|
+
readonly callable: AuthorityCallable;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface AuthorityIssue { readonly issueId: string; readonly path: string; readonly message: string }
|
|
20
|
+
|
|
21
|
+
const eligibleRole: AuthorityCallable = (input) => roleVerdict(input);
|
|
22
|
+
const authorValidator: AuthorityCallable = (input) => identityVerdict(input);
|
|
23
|
+
const authorRiskAuditor: AuthorityCallable = (input) => identityVerdict(input);
|
|
24
|
+
const authorHumanPrincipal: AuthorityCallable = (input) => identityVerdict(input);
|
|
25
|
+
|
|
26
|
+
export const installedAuthoritySemanticBindings: readonly AuthoritySemanticBinding[] = deepFreeze([
|
|
27
|
+
binding('authority-role-evaluator.corrective-replan.eligible-role', 'authority-role-evaluator.corrective-replan.eligible-role', 'role-evaluator', eligibleRole),
|
|
28
|
+
binding('identity-comparator.corrective-replan.author-validator', 'identity-comparator.corrective-replan.author-validator', 'identity-comparator', authorValidator),
|
|
29
|
+
binding('identity-comparator.corrective-replan.author-risk-auditor', 'identity-comparator.corrective-replan.author-risk-auditor', 'identity-comparator', authorRiskAuditor),
|
|
30
|
+
binding('identity-comparator.corrective-replan.author-human-principal', 'identity-comparator.corrective-replan.author-human-principal', 'identity-comparator', authorHumanPrincipal),
|
|
31
|
+
]);
|
|
32
|
+
|
|
33
|
+
const ref = (id: string) => ({ id, version: 1 });
|
|
34
|
+
const constraints = [
|
|
35
|
+
constraint('constraint.corrective-replan.author-not-validator', 'author-validator', 'actor.validator'),
|
|
36
|
+
constraint('constraint.corrective-replan.author-not-risk-auditor', 'author-risk-auditor', 'actor.risk-auditor'),
|
|
37
|
+
constraint('constraint.corrective-replan.author-not-human-principal', 'author-human-principal', 'principal.human'),
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
/** Diagnostic-only assessment of the installed static tuple. It cannot issue a Core handle. */
|
|
41
|
+
export function assessCorrectiveReplanAuthorityTuple(tuple: unknown) {
|
|
42
|
+
const value = record(tuple); const issues: AuthorityIssue[] = [];
|
|
43
|
+
extraFields(value, ['transitionId', 'actor', 'eligibleRoles', 'roleEvaluator', 'humanPrincipal', 'capability', 'delegation', 'ownership', 'constraints', 'priorPlanAuthorization'],
|
|
44
|
+
'/','authority.tuple.extra-field', issues);
|
|
45
|
+
extraFields(record(value.actor), ['identity', 'basis'], '/actor/', 'authority.tuple.extra-field', issues);
|
|
46
|
+
extraFields(record(value.humanPrincipal), ['identity', 'basis', 'kind'], '/humanPrincipal/', 'authority.tuple.extra-field', issues);
|
|
47
|
+
extraFields(record(value.capability), ['id', 'version', 'source', 'issuer', 'transitionId', 'scope', 'bindingManifest'], '/capability/', 'authority.tuple.extra-field', issues);
|
|
48
|
+
extraFields(record(value.priorPlanAuthorization), ['disposition', 'authority'], '/priorPlanAuthorization/', 'authority.tuple.extra-field', issues);
|
|
49
|
+
check(value, '/transitionId', 'authority.transition.mismatch', 'task.plan.corrective-replan', issues);
|
|
50
|
+
check(value, '/actor/identity', 'authority.actor.identity-mismatch', ref('identity.actor'), issues);
|
|
51
|
+
check(value, '/actor/basis', 'authority.actor.basis-mismatch', ref('principal-basis.human-confirmation'), issues);
|
|
52
|
+
check(value, '/eligibleRoles', 'authority.role-evaluator.mismatch', ['delivery-coordinator', 'technical-planner'], issues);
|
|
53
|
+
check(value, '/roleEvaluator', 'authority.role-evaluator.mismatch', ref('authority-role-evaluator.corrective-replan.eligible-role'), issues);
|
|
54
|
+
check(value, '/humanPrincipal/identity', 'authority.principal.identity-mismatch', ref('identity.human-principal'), issues);
|
|
55
|
+
check(value, '/humanPrincipal/basis', 'authority.principal.basis-mismatch', ref('principal-basis.human-confirmation'), issues);
|
|
56
|
+
check(value, '/humanPrincipal/kind', 'authority.principal.kind-mismatch', 'human', issues);
|
|
57
|
+
const capability = get(value, '/capability');
|
|
58
|
+
if (!isRecord(capability)) issue(issues, 'authority.capability.missing', '/capability');
|
|
59
|
+
else {
|
|
60
|
+
check(value, '/capability/id', 'authority.capability.id-mismatch', 'capability.task.corrective-replan', issues);
|
|
61
|
+
check(value, '/capability/version', 'authority.capability.version-mismatch', 1, issues);
|
|
62
|
+
check(value, '/capability/source', 'authority.capability.source-mismatch', ref('authority-source.human-confirmation'), issues);
|
|
63
|
+
check(value, '/capability/issuer', 'authority.capability.issuer-mismatch', ref('identity.human-principal'), issues);
|
|
64
|
+
check(value, '/capability/transitionId', 'authority.capability.transition-mismatch', 'task.plan.corrective-replan', issues);
|
|
65
|
+
check(value, '/capability/scope', 'authority.capability.scope-mismatch', { entity: 'task', task: 'taskId', guardedStep: 'guardedStepId' }, issues);
|
|
66
|
+
if (capability.bindingManifest !== correctiveReplanBindingManifest)
|
|
67
|
+
issue(issues, 'authority.capability.binding-manifest-mismatch', '/capability/bindingManifest');
|
|
68
|
+
}
|
|
69
|
+
check(value, '/delegation', 'authority.delegation.not-forbidden', ref('delegation.forbidden'), issues);
|
|
70
|
+
check(value, '/ownership', 'authority.ownership.not-clear', { c1Claim: 'none', writerLease: 'none' }, issues);
|
|
71
|
+
const rows = Array.isArray(value.constraints) ? value.constraints : [];
|
|
72
|
+
const seen = new Set<string>();
|
|
73
|
+
rows.forEach((entry, index) => {
|
|
74
|
+
const id = isRecord(entry) && typeof entry.constraintId === 'string' ? entry.constraintId : '';
|
|
75
|
+
if (seen.has(id)) issue(issues, 'authority.constraint.duplicate', `/constraints/${index}/constraintId`);
|
|
76
|
+
else if (id === 'constraint.corrective-replan.validator-not-risk-auditor') issue(issues, 'authority.constraint.forbidden-validator-risk', `/constraints/${index}`);
|
|
77
|
+
else if (!constraints.some((expected) => expected.constraintId === id)) issue(issues, 'authority.constraint.unknown', `/constraints/${index}/constraintId`);
|
|
78
|
+
seen.add(id);
|
|
79
|
+
if (isRecord(entry)) extraFields(entry, ['constraintId', 'comparator', 'operands', 'relation', 'missing', 'enforcement'],
|
|
80
|
+
`/constraints/${index}/`, 'authority.constraint.extra-field', issues);
|
|
81
|
+
});
|
|
82
|
+
constraints.forEach((expected, index) => {
|
|
83
|
+
const actualIndex = rows.findIndex((entry) => isRecord(entry) && entry.constraintId === expected.constraintId);
|
|
84
|
+
if (actualIndex < 0) return issue(issues, 'authority.constraint.missing', `/constraints/${index}`);
|
|
85
|
+
for (const field of ['comparator', 'operands', 'relation', 'missing', 'enforcement'] as const) {
|
|
86
|
+
check(value, `/constraints/${actualIndex}/${field}`, `authority.constraint.${field === 'comparator' ? 'comparator-id' : field}-mismatch`, expected[field], issues,
|
|
87
|
+
field === 'comparator' ? `/constraints/${index}/${field}/id` : `/constraints/${index}/${field}`);
|
|
88
|
+
if (field === 'comparator') check(value, `/constraints/${actualIndex}/comparator/version`, 'authority.constraint.comparator-version-mismatch', 1, issues, `/constraints/${index}/comparator/version`);
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
check(value, '/priorPlanAuthorization/disposition', 'authority.plan-bound-grant-active', 'superseded', issues);
|
|
92
|
+
check(value, '/priorPlanAuthorization/authority', 'authority.plan-bound-grant-active', 'none', issues);
|
|
93
|
+
const ordered = [...issues].sort((a, b) => `${a.path}\0${a.issueId}`.localeCompare(`${b.path}\0${b.issueId}`));
|
|
94
|
+
const blockers: string[] = [];
|
|
95
|
+
if (ordered.some((entry) => entry.issueId === 'authority.capability.missing')) blockers.push('static.authority.exact-capability-missing');
|
|
96
|
+
if (ordered.some((entry) => entry.issueId === 'authority.plan-bound-grant-active')) blockers.push('static.authority.plan-bound-grant-not-superseded');
|
|
97
|
+
if (ordered.length > 0) blockers.push('static.authority.exact-tuple-invalid');
|
|
98
|
+
const content = { staticTupleValid: ordered.length === 0, blockers: [...new Set(blockers)], issues: ordered,
|
|
99
|
+
bindings: installedAuthoritySemanticBindings };
|
|
100
|
+
return deepFreeze({ ...content, reportHash: hashCanonical({ ...content, bindings: content.bindings.map(projectBinding) }) });
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function binding(id: string, suffix: string, kind: AuthoritySemanticBinding['kind'], callable: AuthorityCallable): AuthoritySemanticBinding {
|
|
104
|
+
return { id, version: 1, kind, implementationId: `implementation.${suffix}`, implementationVersion: 1, wiring: 'invoked', callable };
|
|
105
|
+
}
|
|
106
|
+
export function projectAuthoritySemanticBundle(bindings: readonly AuthoritySemanticBinding[]) {
|
|
107
|
+
const projection = { bundleVersion: 2, bindingManifest: correctiveReplanBindingManifest, bindings: bindings.map(projectBinding) };
|
|
108
|
+
return deepFreeze({ ...projection, bundleSha256: createHash('sha256').update(JSON.stringify(projection)).digest('hex') });
|
|
109
|
+
}
|
|
110
|
+
function constraint(constraintId: string, suffix: string, right: string) { return { constraintId,
|
|
111
|
+
comparator: ref(`identity-comparator.corrective-replan.${suffix}`), operands: ['actor.author', right],
|
|
112
|
+
relation: 'not-equal', missing: 'block', enforcement: 'static-and-runtime' }; }
|
|
113
|
+
function roleVerdict(input: unknown) { const value = record(input); const roles = ['delivery-coordinator', 'technical-planner'];
|
|
114
|
+
if (!/^[a-f0-9]{64}$/.test(String(value.actorIdentityHash ?? '')) || !sameSet(value.eligibleRoles, roles)
|
|
115
|
+
|| typeof value.actorRole !== 'string' || value.actorRole.length === 0) return verdict(false, 'ROLE_INPUT_INVALID');
|
|
116
|
+
return roles.includes(value.actorRole) ? verdict(true, null) : verdict(false, 'ROLE_NOT_ELIGIBLE'); }
|
|
117
|
+
function identityVerdict(input: unknown) { const value = record(input); const valid = /^[a-f0-9]{64}$/;
|
|
118
|
+
if (typeof value.leftIdentityHash !== 'string' || typeof value.rightIdentityHash !== 'string'
|
|
119
|
+
|| !valid.test(value.leftIdentityHash) || !valid.test(value.rightIdentityHash)) return verdict(false, 'IDENTITY_INPUT_INVALID');
|
|
120
|
+
return value.leftIdentityHash !== value.rightIdentityHash ? verdict(true, null) : verdict(false, 'IDENTITY_CONSTRAINT_VIOLATED'); }
|
|
121
|
+
function verdict(passed: boolean, reasonCode: string | null) { return deepFreeze({ passed, blocked: !passed, reasonCode }); }
|
|
122
|
+
function projectBinding({ callable: _callable, wiring: _wiring, ...binding }: AuthoritySemanticBinding) { return binding; }
|
|
123
|
+
function record(value: unknown): JsonRecord { return isRecord(value) ? value : {}; }
|
|
124
|
+
function isRecord(value: unknown): value is JsonRecord { return value !== null && typeof value === 'object' && !Array.isArray(value); }
|
|
125
|
+
function get(value: JsonRecord, path: string): unknown { return path.split('/').filter(Boolean).reduce<unknown>((cursor, key) =>
|
|
126
|
+
Array.isArray(cursor) && /^\d+$/.test(key) ? cursor[Number(key)] : isRecord(cursor) ? cursor[key] : undefined, value); }
|
|
127
|
+
function check(value: JsonRecord, path: string, issueId: string, expected: unknown, issues: AuthorityIssue[], reportedPath = path): void {
|
|
128
|
+
const actual = get(value, path);
|
|
129
|
+
if (actual === undefined || hashCanonical(actual) !== hashCanonical(expected)) issue(issues, issueId, reportedPath);
|
|
130
|
+
}
|
|
131
|
+
function sameSet(value: unknown, expected: readonly string[]): boolean { return Array.isArray(value) && value.length === expected.length
|
|
132
|
+
&& new Set(value).size === expected.length && value.every((entry) => typeof entry === 'string' && expected.includes(entry)); }
|
|
133
|
+
function extraFields(value: JsonRecord, allowed: readonly string[], prefix: string, issueId: string, issues: AuthorityIssue[]): void {
|
|
134
|
+
Object.keys(value).filter((key) => !allowed.includes(key)).forEach((key) => issue(issues, issueId, `${prefix}${key}`));
|
|
135
|
+
}
|
|
136
|
+
function issue(issues: AuthorityIssue[], issueId: string, path: string): void { issues.push({ issueId, path, message: `${issueId} at ${path}.` }); }
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { hashCanonical } from './canonical-hash.js';
|
|
2
|
+
import { deepFreeze } from './immutable.js';
|
|
3
|
+
import type { ProjectionFieldId, ProjectionId } from './types.js';
|
|
4
|
+
|
|
5
|
+
const ref = <TId extends string>(id: TId) => ({ id, version: 1 as const });
|
|
6
|
+
type ValueKind = 'literal' | 'sha256' | 'safe-integer' | 'entity-id' | 'git-oid' | 'actor-role';
|
|
7
|
+
type Canonicalization = 'exact' | 'lowercase-hex' | 'safe-nonnegative-integer' | 'nfc-trim' | 'lowercase-git-oid' | 'registered-enum';
|
|
8
|
+
const row = (bindingId: string, projection: ProjectionId, fieldId: ProjectionFieldId, valueKind: ValueKind, canonicalization: Canonicalization) => ({
|
|
9
|
+
ordinal: rowsOrdinal++, bindingId, projection: ref(projection), fieldId, valueKind, canonicalization,
|
|
10
|
+
receiptPath: `/binding/values/${bindingId}` as `/binding/values/${string}`,
|
|
11
|
+
});
|
|
12
|
+
let rowsOrdinal = 0;
|
|
13
|
+
const protocol = (id: string, field: ProjectionFieldId, kind: ValueKind, canonical: Canonicalization) => row(id, 'projection.corrective-replan.protocol', field, kind, canonical);
|
|
14
|
+
const state = (id: string, field: ProjectionFieldId, kind: ValueKind, canonical: Canonicalization) => row(id, 'projection.corrective-replan.state', field, kind, canonical);
|
|
15
|
+
const candidate = (id: string, field: ProjectionFieldId) => row(id, 'projection.corrective-replan.candidate', field, 'sha256', 'lowercase-hex');
|
|
16
|
+
const candidateState = (id: string, field: ProjectionFieldId, kind: ValueKind, canonical: Canonicalization) => row(id, 'projection.corrective-replan.candidate', field, kind, canonical);
|
|
17
|
+
const authority = (id: string, field: ProjectionFieldId, kind: ValueKind, canonical: Canonicalization) => row(id, 'projection.corrective-replan.authority', field, kind, canonical);
|
|
18
|
+
|
|
19
|
+
const rows = [
|
|
20
|
+
protocol('transitionId', 'field.transition-id', 'literal', 'exact'), protocol('runtimeFingerprint', 'field.runtime-fingerprint', 'sha256', 'lowercase-hex'),
|
|
21
|
+
protocol('catalogHash', 'field.catalog-hash', 'sha256', 'lowercase-hex'), protocol('lifecycleEpoch', 'field.lifecycle-epoch', 'safe-integer', 'safe-nonnegative-integer'),
|
|
22
|
+
state('taskId', 'field.task-id', 'entity-id', 'nfc-trim'), state('revision', 'field.task-revision', 'safe-integer', 'safe-nonnegative-integer'),
|
|
23
|
+
state('currentPlanHash', 'field.current-plan-hash', 'sha256', 'lowercase-hex'), state('briefHash', 'field.brief-hash', 'sha256', 'lowercase-hex'),
|
|
24
|
+
state('knowledgeRevision', 'field.knowledge-revision', 'safe-integer', 'safe-nonnegative-integer'), state('knowledgeHash', 'field.knowledge-hash', 'sha256', 'lowercase-hex'),
|
|
25
|
+
candidateState('targetKnowledgeRevision', 'field.target-knowledge-revision', 'safe-integer', 'safe-nonnegative-integer'), candidate('targetKnowledgeHash', 'field.target-knowledge-hash'),
|
|
26
|
+
state('gitHead', 'field.git-head', 'git-oid', 'lowercase-git-oid'), state('guardedStepId', 'field.guarded-step-id', 'entity-id', 'nfc-trim'),
|
|
27
|
+
state('correctiveDecisionEventId', 'field.corrective-decision-event-id', 'entity-id', 'nfc-trim'), state('correctiveDecisionHash', 'field.corrective-decision-hash', 'sha256', 'lowercase-hex'),
|
|
28
|
+
state('attemptCount', 'field.attempt-count', 'safe-integer', 'safe-nonnegative-integer'), state('remediationTailHash', 'field.remediation-tail-hash', 'sha256', 'lowercase-hex'),
|
|
29
|
+
candidate('candidatePlanHash', 'field.candidate-plan-hash'), candidate('candidateStepSetHash', 'field.candidate-step-set-hash'),
|
|
30
|
+
candidate('planAuthorIdentityHash', 'field.plan-author-identity-hash'), candidate('planValidationHash', 'field.plan-validation-hash'),
|
|
31
|
+
candidate('planValidatorIdentityHash', 'field.plan-validator-identity-hash'), candidate('riskAuditHash', 'field.risk-audit-hash'), candidate('riskAuditorIdentityHash', 'field.risk-auditor-identity-hash'),
|
|
32
|
+
authority('actorIdentityHash', 'field.actor-identity-hash', 'sha256', 'lowercase-hex'), authority('actorRole', 'field.actor-role', 'actor-role', 'registered-enum'),
|
|
33
|
+
authority('humanPrincipalIdentityHash', 'field.human-principal-identity-hash', 'sha256', 'lowercase-hex'), authority('capabilityId', 'field.capability-id', 'literal', 'exact'),
|
|
34
|
+
authority('capabilityVersion', 'field.capability-version', 'safe-integer', 'safe-nonnegative-integer'),
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
const identity = { id: 'binding-manifest.corrective-replan.exact-v1' as const, version: 1 as const, rows };
|
|
38
|
+
export const correctiveReplanBindingManifest = deepFreeze({
|
|
39
|
+
...identity,
|
|
40
|
+
manifestSha256: hashCanonical({ ...identity, rows: [...rows].sort((a, b) => a.bindingId.localeCompare(b.bindingId)) }),
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
export const correctiveReplanBindingIds = deepFreeze(rows.map(({ bindingId }) => bindingId));
|
|
44
|
+
|
|
45
|
+
export const historicalCompletedCorrectiveReplanBindingIdentities = deepFreeze([
|
|
46
|
+
{
|
|
47
|
+
manifestSha256: 'edab7ee7ac4018e759f5fc702a11a0fd113ef4f2cf8ed8fa823a59fde3012a1a',
|
|
48
|
+
runtimeFingerprint: 'bd4e7a0ddfcb6abc0c5eb107775e6aea7e08c4606dda4d945d77af8a4520710d',
|
|
49
|
+
catalogHash: 'fa1d11860d4f07978c3b8e85d1f91cd3d15b17284b05f41e05d462d3bb092aed',
|
|
50
|
+
},
|
|
51
|
+
] as const);
|