codex-workflow-v2 2.0.0-beta.13.2 → 2.0.0-beta.13.5
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 +1 -1
- package/dist/reviewer-runtime-build.json +18 -10
- package/dist/src/alpha6/plan-risk.d.ts +1 -0
- package/dist/src/alpha6/plan-risk.js +9 -6
- package/dist/src/alpha6/plan-risk.js.map +1 -1
- package/dist/src/alpha6/preexecution-replan.d.ts +38 -0
- package/dist/src/alpha6/preexecution-replan.js +130 -0
- package/dist/src/alpha6/preexecution-replan.js.map +1 -0
- package/dist/src/alpha6/remediation.d.ts +4 -2
- package/dist/src/alpha6/remediation.js +155 -20
- package/dist/src/alpha6/remediation.js.map +1 -1
- package/dist/src/cli-actions.d.ts +1 -1
- package/dist/src/cli-actions.js +1 -0
- package/dist/src/cli-actions.js.map +1 -1
- package/dist/src/cli.js +44 -0
- package/dist/src/cli.js.map +1 -1
- package/dist/src/domain/plan-semantics.d.ts +11 -0
- package/dist/src/domain/plan-semantics.js +49 -0
- package/dist/src/domain/plan-semantics.js.map +1 -0
- package/dist/src/state/corrective-yield-executor.js +10 -1
- package/dist/src/state/corrective-yield-executor.js.map +1 -1
- package/dist/src/version.d.ts +1 -1
- package/dist/src/version.js +1 -1
- package/dist/src/workflow.d.ts +19 -1
- package/dist/src/workflow.js +518 -84
- package/dist/src/workflow.js.map +1 -1
- 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/sources/codex-workflow-v2-architecture-ru.md +1 -1
- package/docs/pdf/sources/codex-workflow-v2-chat-only-guide-ru.md +2 -2
- package/docs/pdf/sources/codex-workflow-v2-technical-reference-ru.md +3 -3
- package/docs/release.md +59 -2
- package/docs/updating-existing-project.md +10 -2
- package/package.json +1 -1
- package/plugins/codex-workflow-gateway/references/chat-dispatch.md +128 -0
- package/plugins/codex-workflow-gateway/references/protocol.md +4 -0
- package/plugins/codex-workflow-gateway/scripts/chat-dispatch.mjs +185 -0
- package/plugins/codex-workflow-gateway/scripts/chat-model-policy.mjs +43 -0
- package/plugins/codex-workflow-gateway/scripts/chat-registry.mjs +50 -3
- package/plugins/codex-workflow-gateway/skills/codex-workflow-gateway/SKILL.md +63 -6
- package/schemas/preexecution-replan-event.schema.json +45 -0
|
@@ -12,6 +12,7 @@ import os from 'node:os';
|
|
|
12
12
|
import path from 'node:path';
|
|
13
13
|
import process from 'node:process';
|
|
14
14
|
import { randomUUID } from 'node:crypto';
|
|
15
|
+
import { dispatchAction, dispatchInput } from './chat-dispatch.mjs';
|
|
15
16
|
|
|
16
17
|
const TYPES = new Set(['coordinator', 'task', 'step-review', 'final-review', 'corrective-audit', 'plan-audit']);
|
|
17
18
|
const LOCK_STALE_MS = 30_000;
|
|
@@ -25,6 +26,14 @@ export function allocateChat(options) {
|
|
|
25
26
|
const sequence = registry.nextSequence;
|
|
26
27
|
const reservationId = randomUUID();
|
|
27
28
|
const candidates = buildTitleCandidates({ ...options, sequence });
|
|
29
|
+
const existing = registry.entries.find(entry => entry.status !== 'abandoned'
|
|
30
|
+
&& entry.type === options.type && entry.entityId === options.entityId
|
|
31
|
+
&& entry.milestoneOrdinal === requiredOrdinal(options.milestoneOrdinal, 'M')
|
|
32
|
+
&& entry.taskOrdinal === optionalOrdinal(options.taskOrdinal, 'T')
|
|
33
|
+
&& entry.stepOrdinal === optionalOrdinal(options.stepOrdinal, 'S')
|
|
34
|
+
&& (['task', 'coordinator'].includes(options.type)
|
|
35
|
+
|| entry.attempt === positiveInteger(options.attempt ?? 1, 'attempt')));
|
|
36
|
+
if (existing) return { ...publicEntry(existing), reused: true };
|
|
28
37
|
const entry = {
|
|
29
38
|
sequence,
|
|
30
39
|
reservationId,
|
|
@@ -92,6 +101,17 @@ export function bindChat(options) {
|
|
|
92
101
|
const registry = readRegistry(file, options.projectId);
|
|
93
102
|
const entry = requireEntry(registry, options.reservationId);
|
|
94
103
|
if (!entry.titleVerified) throw new Error('Chat title must pass exact readback before binding.');
|
|
104
|
+
if (options.threadId?.startsWith('client-')) throw new Error('Cannot bind a pending clientThreadId.');
|
|
105
|
+
if (entry.threadId && (entry.threadId !== options.threadId || entry.hostId !== options.hostId)) {
|
|
106
|
+
throw new Error('Reservation already bound to another thread.');
|
|
107
|
+
}
|
|
108
|
+
if (registry.entries.some(other => other !== entry && other.threadId === options.threadId
|
|
109
|
+
&& other.hostId === options.hostId)) throw new Error('Thread already belongs to another reservation.');
|
|
110
|
+
if (entry.dispatch && (entry.dispatch.state !== 'resolved' || entry.dispatch.candidateThreadId !== options.threadId
|
|
111
|
+
|| entry.dispatch.hostId !== options.hostId
|
|
112
|
+
|| entry.dispatch.observations.at(-1)?.title !== entry.observedTitle)) {
|
|
113
|
+
throw new Error('Tracked creation must pass correlated read_thread observation before binding.');
|
|
114
|
+
}
|
|
95
115
|
entry.threadId = requiredText(options.threadId, 'threadId');
|
|
96
116
|
entry.hostId = requiredText(options.hostId, 'hostId');
|
|
97
117
|
entry.status = 'bound';
|
|
@@ -106,6 +126,9 @@ export function retitleChat(options) {
|
|
|
106
126
|
return withRegistryLock(options.projectId, options.registryHome, (file) => {
|
|
107
127
|
const registry = readRegistry(file, options.projectId);
|
|
108
128
|
const entry = requireEntry(registry, options.reservationId);
|
|
129
|
+
if (entry.dispatch && options.entityId !== entry.entityId) {
|
|
130
|
+
throw new Error('Tracked dispatch entity is immutable; retitle cannot replace its identity.');
|
|
131
|
+
}
|
|
109
132
|
entry.entityId = requiredText(options.entityId, 'entityId');
|
|
110
133
|
entry.semanticTitle = requiredText(options.semanticTitle, 'semanticTitle');
|
|
111
134
|
entry.candidates = buildTitleCandidates({
|
|
@@ -134,6 +157,7 @@ export function abandonChat(options) {
|
|
|
134
157
|
return withRegistryLock(options.projectId, options.registryHome, (file) => {
|
|
135
158
|
const registry = readRegistry(file, options.projectId);
|
|
136
159
|
const entry = requireEntry(registry, options.reservationId);
|
|
160
|
+
if (entry.dispatch || entry.threadId) throw new Error('Cannot abandon a started creation or bound chat to enable a duplicate.');
|
|
137
161
|
entry.status = 'abandoned';
|
|
138
162
|
entry.updatedAt = new Date().toISOString();
|
|
139
163
|
writeRegistry(file, registry);
|
|
@@ -141,6 +165,20 @@ export function abandonChat(options) {
|
|
|
141
165
|
});
|
|
142
166
|
}
|
|
143
167
|
|
|
168
|
+
export function dispatchChat(options) {
|
|
169
|
+
validateProjectId(options.projectId);
|
|
170
|
+
return withRegistryLock(options.projectId, options.registryHome, file => {
|
|
171
|
+
const registry = readRegistry(file, options.projectId);
|
|
172
|
+
const entry = requireEntry(registry, options.reservationId);
|
|
173
|
+
const result = dispatchAction(options.action, entry, options.input);
|
|
174
|
+
if (options.action !== 'dispatch-status') {
|
|
175
|
+
entry.updatedAt = new Date().toISOString();
|
|
176
|
+
writeRegistry(file, registry);
|
|
177
|
+
}
|
|
178
|
+
return result;
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
|
|
144
182
|
export function showRegistry(options) {
|
|
145
183
|
validateProjectId(options.projectId);
|
|
146
184
|
const file = registryFile(options.projectId, options.registryHome);
|
|
@@ -217,7 +255,10 @@ function withRegistryLock(projectId, registryHome, operation) {
|
|
|
217
255
|
|
|
218
256
|
function lockIsStale(lock) {
|
|
219
257
|
try {
|
|
220
|
-
|
|
258
|
+
if (Date.now() - statSync(lock).mtimeMs <= LOCK_STALE_MS) return false;
|
|
259
|
+
const owner = JSON.parse(readFileSync(path.join(lock, 'owner.json'), 'utf8'));
|
|
260
|
+
if (!Number.isSafeInteger(owner.pid) || owner.pid < 1) return false;
|
|
261
|
+
try { process.kill(owner.pid, 0); return false; } catch (error) { return error?.code === 'ESRCH'; }
|
|
221
262
|
} catch {
|
|
222
263
|
return false;
|
|
223
264
|
}
|
|
@@ -253,7 +294,7 @@ function readRegistry(file, projectId) {
|
|
|
253
294
|
|
|
254
295
|
function writeRegistry(file, registry) {
|
|
255
296
|
const temporary = `${file}.${process.pid}.${randomUUID()}.tmp`;
|
|
256
|
-
writeFileSync(temporary, `${JSON.stringify(registry, null, 2)}\n`, { flag: 'wx' });
|
|
297
|
+
writeFileSync(temporary, `${JSON.stringify(registry, null, 2)}\n`, { flag: 'wx', mode: 0o600 });
|
|
257
298
|
renameSync(temporary, file);
|
|
258
299
|
}
|
|
259
300
|
|
|
@@ -275,6 +316,7 @@ function publicEntry(entry) {
|
|
|
275
316
|
threadId: entry.threadId,
|
|
276
317
|
hostId: entry.hostId,
|
|
277
318
|
status: entry.status,
|
|
319
|
+
dispatchState: entry.dispatch?.state ?? null,
|
|
278
320
|
};
|
|
279
321
|
}
|
|
280
322
|
|
|
@@ -334,6 +376,11 @@ function cli() {
|
|
|
334
376
|
projectId: option(args, 'project-id'),
|
|
335
377
|
registryHome: option(args, 'registry-home'),
|
|
336
378
|
};
|
|
379
|
+
if (action?.startsWith('dispatch-') || action === 'supervision-record' || action === 'model-select') {
|
|
380
|
+
const file = option(args, 'file');
|
|
381
|
+
return dispatchChat({ ...common, action, reservationId: option(args, 'reservation-id'),
|
|
382
|
+
input: file ? dispatchInput(file) : {} });
|
|
383
|
+
}
|
|
337
384
|
if (action === 'allocate') return allocateChat({
|
|
338
385
|
...common,
|
|
339
386
|
type: option(args, 'type'),
|
|
@@ -363,7 +410,7 @@ function cli() {
|
|
|
363
410
|
});
|
|
364
411
|
if (action === 'abandon') return abandonChat({ ...common, reservationId: option(args, 'reservation-id') });
|
|
365
412
|
if (action === 'show') return showRegistry(common);
|
|
366
|
-
throw new Error('Use allocate, retitle, readback, bind, abandon, or
|
|
413
|
+
throw new Error('Use allocate, retitle, readback, bind, abandon, show, dispatch-begin/result/discover/resolve-duplicates/observe/status, supervision-record, or model-select.');
|
|
367
414
|
}
|
|
368
415
|
|
|
369
416
|
if (process.argv[1] && path.resolve(process.argv[1]) === path.resolve(new URL(import.meta.url).pathname)) {
|
|
@@ -92,6 +92,29 @@ active writer token when the C1 contract requires it. The recovery records only
|
|
|
92
92
|
`task run` merely to manufacture the second failure, and never invoke this recovery for a generic
|
|
93
93
|
test failure, a writable manifest, or unconfined changes.
|
|
94
94
|
|
|
95
|
+
When fresh `next` includes `preExecutionReplanOption.state=eligible`, an independent Plan auditor
|
|
96
|
+
may stop a `ready` Task before its next Step because the current implementation Plan is obstructed.
|
|
97
|
+
Use only `task preexecution-replan` with the exact advertised Task revision, Brief hash, Plan hash,
|
|
98
|
+
current Plan Risk Audit event hash, and HEAD. The audit file must be a genuine independent
|
|
99
|
+
`stop-escalate` replacement audit: its planner remains the original planner, its auditor is the
|
|
100
|
+
transition actor, and it retains current guarded categories, review requirements, failure modes,
|
|
101
|
+
evidence commands, and proof bindings while recording the obstruction. This transition requires a
|
|
102
|
+
clean checkout with no Step in progress, C1 posture, or writer lease. It appends immutable audit and
|
|
103
|
+
provenance records, supersedes only current execution authorization, preserves completed Step
|
|
104
|
+
authority and existing delegation scope, and does not require a new approval. After success, run
|
|
105
|
+
sequential `status` then `next`; Core must advertise ordinary `task plan-set`, not audited-remediation
|
|
106
|
+
topology. Submit a semantically changed Plan with the exact Task objective, requirements, and
|
|
107
|
+
acceptance, preserve completed Steps and prior safety boundaries, attach a fresh Plan Risk Audit,
|
|
108
|
+
then obtain fresh execution authorization. Never append an approved audit to the unchanged Plan,
|
|
109
|
+
rebind context around the marker, invent a failure/corrective cause, or invoke the command when the
|
|
110
|
+
option is absent or blocked.
|
|
111
|
+
|
|
112
|
+
For this route, Core treats each `(Step ID, failureMode, evidence)` tuple as the immutable proof
|
|
113
|
+
binding. It does not claim to understand whether revised `counterexample` or `expectedResult` prose
|
|
114
|
+
is semantically stronger. Changing that prose therefore requires an independent
|
|
115
|
+
`approved-with-rationale` audit, a changed substantive summary, and an explicit rationale; the
|
|
116
|
+
earlier audit remains immutable history for human review.
|
|
117
|
+
|
|
95
118
|
When fresh `next` advertises `task downstream-proof-recover`, the active proof Step has produced
|
|
96
119
|
uncommitted files inside its own `allowedWrites` and has also exposed required changes owned by one
|
|
97
120
|
or more completed transitive predecessor Steps. Do not invoke `task step-complete`: its scope is
|
|
@@ -329,9 +352,11 @@ A Milestone chat and its Task chats are separate user-visible execution contexts
|
|
|
329
352
|
conversation. Create every Task chat as a brand-new Codex task with `create_thread`, an explicit
|
|
330
353
|
Task-only prompt, and an explicit title. Never use `fork_thread`, thread handoff, or a continuation
|
|
331
354
|
of the Milestone chat to create a Task chat: those paths carry parent history into the child. A
|
|
332
|
-
failure to create a new task is a routing blocker.
|
|
333
|
-
|
|
334
|
-
|
|
355
|
+
confirmed failure to create a new task is a routing blocker. A pending `clientThreadId`, timeout,
|
|
356
|
+
lost response, or absence from `list_threads` does not establish failure and never permits a
|
|
357
|
+
second creation. Persist the attempt before calling the App, resolve its actual ID and confirm
|
|
358
|
+
it with `read_thread`; never fall back to a fork. Read and follow
|
|
359
|
+
`../../references/chat-dispatch.md` for the executable dispatch/recovery and model-selection contract.
|
|
335
360
|
|
|
336
361
|
Codex App may wrap the supplied prompt in a host-generated `codex_delegation` envelope containing
|
|
337
362
|
only routing provenance such as `source_thread_id`. That envelope is permitted and is not parent
|
|
@@ -376,6 +401,22 @@ chat type, Milestone/Task/Step membership ordinals, semantic title, authoritativ
|
|
|
376
401
|
review attempt. Use returned `requestedTitle` verbatim. Never derive its prefix from
|
|
377
402
|
`list_threads`, sidebar order, a count, or `count + 1`: allocation atomically reserves the
|
|
378
403
|
project-wide monotonically increasing `#NNN`, and an abandoned number is never reused.
|
|
404
|
+
Allocation reuses an existing matching reservation. For a new dispatch, call `dispatch-begin`
|
|
405
|
+
with the closed packet and explicit model request, then call the App exactly once only when
|
|
406
|
+
`createAllowed=true`, using returned `createArgs` unchanged. Record the full result via
|
|
407
|
+
`dispatch-result`, resolve/read back via `dispatch-discover` and `dispatch-observe`, and bind.
|
|
408
|
+
After restart use `dispatch-status`; an unresolved creation cannot be abandoned to create another.
|
|
409
|
+
For legacy reservations without a dispatch marker, first reconcile existing App/source evidence;
|
|
410
|
+
absence of the new marker does not prove no earlier creation occurred.
|
|
411
|
+
|
|
412
|
+
When the user authorizes model selection, use the versioned role/phase/complexity matrix rather
|
|
413
|
+
than coordinator inheritance. Both `model` and App `thinking` must be explicit in create arguments.
|
|
414
|
+
Use the destination host's current supported model/effort catalog; no guessed aliases or silent
|
|
415
|
+
fallback. Before a materially different phase, `model-select` returns explicit follow-up settings
|
|
416
|
+
for the same bound thread. Routine lifecycle work should not retain planning/review reasoning by
|
|
417
|
+
accident; security-sensitive and complex work must not be classified as routine to save tokens.
|
|
418
|
+
Without user authorization for model selection, do not manufacture it from a registry field;
|
|
419
|
+
resolve that policy choice before starting a new dispatch.
|
|
379
420
|
|
|
380
421
|
The builder owns these compact title families; callers must not hand-compose variants:
|
|
381
422
|
|
|
@@ -397,7 +438,7 @@ Put `#NNN` and membership ordinals first so clipped sidebar titles remain distin
|
|
|
397
438
|
inherit the parent title, use a generic title such as `Milestone recovery` / `Task execution`, or
|
|
398
439
|
reuse one title for different entity IDs. Maintain the registry-backed
|
|
399
440
|
`Task ID -> thread ID -> title` mapping in
|
|
400
|
-
the Milestone chat. Before
|
|
441
|
+
the Milestone chat. Before supervision, verify from actual `read_thread` evidence that the title contains the exact
|
|
401
442
|
ordinal and entity ID, is unique, and the new chat input contains only its TaskContextPacket plus
|
|
402
443
|
the permitted host routing envelope. Never trust the `create_thread` title argument without a
|
|
403
444
|
readback: pass the observed title to registry `readback`. If it returns `renameRequired=true`,
|
|
@@ -406,6 +447,12 @@ compact the role while preserving `#NNN`, membership ordinals, attempt, and the
|
|
|
406
447
|
Never invent another fallback or abbreviate the ID. Bind the verified entry to `threadId` and
|
|
407
448
|
`hostId`; if all candidates fail, mark it blocked and stop routing. Visual sidebar clipping is
|
|
408
449
|
harmless only when exact API readback still matches. Do not dispatch while title/context is wrong.
|
|
450
|
+
Persist the single supervisor, cursor and next action using `supervision-record`. Large tool/CLI
|
|
451
|
+
JSON must be saved completely to a private local file and parsed there, with only bounded non-secret
|
|
452
|
+
summaries printed. For an uncertain mutation outcome, run fresh `status -> next` before recovery;
|
|
453
|
+
never replay a mutation merely because displayed output was truncated. Review the exact serialized
|
|
454
|
+
external-review envelope passed to Core, not a separately reconstructed object. Public CREF expiry
|
|
455
|
+
is independent of lease expiry and must be checked before every credential-consuming transition.
|
|
409
456
|
|
|
410
457
|
After every significant Task Step boundary—completion, failure, block, skip, sealed-review result,
|
|
411
458
|
or corrective decision—the Task chat prints a compact Task progress Markdown table from fresh
|
|
@@ -562,7 +609,13 @@ When `next.strictStepReview.recommendedMode` is `external-sealed`, do not launch
|
|
|
562
609
|
reviewer from inside a Codex App sandbox. Run the advertised read-only `task step-review-packet`,
|
|
563
610
|
create a separate user-visible reviewer chat for that exact packet, and require one closed
|
|
564
611
|
`ReviewInput` JSON result bound to its reviewed commit. The reviewer must not modify repository or
|
|
565
|
-
Workflow state.
|
|
612
|
+
Workflow state. The `--file` passed to `task step-review-record` must be the full
|
|
613
|
+
`ExternalStrictStepReviewInput` envelope: top-level `protocol`, `packetHash`,
|
|
614
|
+
`repositorySealHash`, `reviewerThreadId`, and `reviewMode` (`ordinary` or `security`), plus the
|
|
615
|
+
nested `review` object containing `status`, `reviewer`, `summary`, and `findings`. Copy both hashes
|
|
616
|
+
and the mode from the packet; do not flatten `review`, and do not pass `--review-mode` to the record
|
|
617
|
+
command because it reads the mode from the JSON file. Run `task step-review-record --help` for a
|
|
618
|
+
complete minimal example. Then run only the advertised `task step-review-record` with the unchanged packet
|
|
566
619
|
and repository-seal hashes, the separate reviewer thread ID, the lifecycle actor from `next`, and
|
|
567
620
|
the active writer token when required. Never author the independent review in the Worker or
|
|
568
621
|
Delivery Coordinator chat, never alter the packet, and never treat an unverified local reviewer
|
|
@@ -573,7 +626,11 @@ When `next.strictTaskReview.recommendedMode` is `external-sealed`, apply the sam
|
|
|
573
626
|
the final submitted-Task review. Run the advertised read-only `task review-packet`, send the exact
|
|
574
627
|
packet to a separate user-visible Independent Reviewer chat, and accept only a closed `ReviewInput`
|
|
575
628
|
bound to the unchanged packet and repository-seal hashes. Record it only through the advertised
|
|
576
|
-
`task review-sealed-record
|
|
629
|
+
`task review-sealed-record`. Its `--file` must be the analogous full
|
|
630
|
+
`ExternalStrictTaskReviewInput` envelope using protocol
|
|
631
|
+
`codex-workflow-external-strict-task-review-v1`, the packet and repository-seal hashes,
|
|
632
|
+
`reviewerThreadId`, top-level `reviewMode`, and nested `review`; its `--help` contains a minimal
|
|
633
|
+
example. Use the lifecycle actor from `next` and the active writer token when
|
|
577
634
|
required. Do not call `task review-launch` from inside a Codex App sandbox, do not use the Worker or
|
|
578
635
|
Delivery Coordinator as reviewer, and discard the result if a fresh packet changes either hash.
|
|
579
636
|
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://codex-workflow.local/schemas/preexecution-replan-event.schema.json",
|
|
4
|
+
"title": "Codex Workflow V2 Pre-execution Replan Event",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"additionalProperties": false,
|
|
7
|
+
"required": [
|
|
8
|
+
"eventId", "taskId", "sourceTaskRevision", "resultTaskRevision", "briefHash",
|
|
9
|
+
"sourcePlanHash", "knowledgeMapRevision", "knowledgeMapHash",
|
|
10
|
+
"sourcePlanRiskAuditEventId", "sourcePlanRiskAuditEventHash",
|
|
11
|
+
"obstructionPlanRiskAuditEventId", "obstructionPlanRiskAuditEventHash",
|
|
12
|
+
"sourceExecutionAuthorizationHash", "sourceHeadCommit", "completedStepIds",
|
|
13
|
+
"completedStepsHash", "actor", "reason", "recordedAt", "previousEventHash", "eventHash"
|
|
14
|
+
],
|
|
15
|
+
"properties": {
|
|
16
|
+
"eventId": { "type": "string", "pattern": "^PER-[0-9A-HJKMNP-TV-Z]{26}$" },
|
|
17
|
+
"taskId": { "type": "string", "pattern": "^TASK-[0-9A-HJKMNP-TV-Z]{26}$" },
|
|
18
|
+
"sourceTaskRevision": { "type": "integer", "minimum": 1 },
|
|
19
|
+
"resultTaskRevision": { "type": "integer", "minimum": 2 },
|
|
20
|
+
"briefHash": { "$ref": "#/$defs/hash64" },
|
|
21
|
+
"sourcePlanHash": { "$ref": "#/$defs/hash64" },
|
|
22
|
+
"knowledgeMapRevision": { "type": "integer", "minimum": 1 },
|
|
23
|
+
"knowledgeMapHash": { "$ref": "#/$defs/hash64" },
|
|
24
|
+
"sourcePlanRiskAuditEventId": { "type": "string", "pattern": "^PRA-[0-9A-HJKMNP-TV-Z]{26}$" },
|
|
25
|
+
"sourcePlanRiskAuditEventHash": { "$ref": "#/$defs/hash64" },
|
|
26
|
+
"obstructionPlanRiskAuditEventId": { "type": "string", "pattern": "^PRA-[0-9A-HJKMNP-TV-Z]{26}$" },
|
|
27
|
+
"obstructionPlanRiskAuditEventHash": { "$ref": "#/$defs/hash64" },
|
|
28
|
+
"sourceExecutionAuthorizationHash": { "$ref": "#/$defs/hash64" },
|
|
29
|
+
"sourceHeadCommit": { "type": "string", "pattern": "^[a-f0-9]{40}$" },
|
|
30
|
+
"completedStepIds": {
|
|
31
|
+
"type": "array",
|
|
32
|
+
"uniqueItems": true,
|
|
33
|
+
"items": { "type": "string", "pattern": "^STEP-[0-9]{3}(?:-[A-Z0-9-]+)?$" }
|
|
34
|
+
},
|
|
35
|
+
"completedStepsHash": { "$ref": "#/$defs/hash64" },
|
|
36
|
+
"actor": { "type": "string", "minLength": 1 },
|
|
37
|
+
"reason": { "type": "string", "minLength": 1, "maxLength": 2048 },
|
|
38
|
+
"recordedAt": { "type": "string", "format": "date-time" },
|
|
39
|
+
"previousEventHash": { "anyOf": [{ "$ref": "#/$defs/hash64" }, { "type": "null" }] },
|
|
40
|
+
"eventHash": { "$ref": "#/$defs/hash64" }
|
|
41
|
+
},
|
|
42
|
+
"$defs": {
|
|
43
|
+
"hash64": { "type": "string", "pattern": "^[a-f0-9]{64}$" }
|
|
44
|
+
}
|
|
45
|
+
}
|