@zq-silk/yui 0.15.8 → 0.15.11
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/ARCHITECTURE.md +2 -0
- package/ARCHITECTURE.zh-CN.md +151 -0
- package/README.md +211 -14
- package/dist/agent/launchEnvironment.js +7 -0
- package/dist/artifacts/artifactCapability.js +74 -0
- package/dist/artifacts/artifactCommitLock.js +249 -0
- package/dist/artifacts/artifactPaths.js +151 -0
- package/dist/artifacts/gitArtifactRef.js +146 -0
- package/dist/artifacts/managedGit.js +332 -0
- package/dist/artifacts/taskArtifactRepository.js +277 -0
- package/dist/cli/commandCatalog.js +40 -16
- package/dist/cli/interactionPolicy.js +3 -3
- package/dist/cli/updateOrchestrator.js +24 -1
- package/dist/cli/updatePorts.js +7 -3
- package/dist/cli/upgradeCommand.js +42 -2
- package/dist/cli.js +403 -93
- package/dist/commands/globalRoleCommands.js +314 -4
- package/dist/commands/operatorCommands.js +33 -2
- package/dist/commands/projectCommands.js +6 -7
- package/dist/commands/releaseCommands.js +18 -0
- package/dist/commands/taskActivationCommands.js +22 -0
- package/dist/commands/taskActor.js +25 -0
- package/dist/commands/taskCommands.js +846 -155
- package/dist/commands/taskIntegrationCommands.js +16 -38
- package/dist/commands/taskIntegrationQueueCommands.js +1 -1
- package/dist/commands/taskRemoteDeliveryCommand.js +6 -6
- package/dist/commands/taskRoleRuntimeStatus.js +35 -0
- package/dist/context/runContextPack.js +28 -16
- package/dist/context/taskContext.js +64 -5
- package/dist/controller/agentHostObservation.js +155 -0
- package/dist/controller/clientRuntime.js +17 -2
- package/dist/controller/controller.js +11 -2
- package/dist/controller/fileSchedulerStoreAdapter.js +446 -13
- package/dist/controller/globalInputDelivery.js +119 -0
- package/dist/controller/jobControl.js +6 -2
- package/dist/controller/resourceInventory.js +14 -4
- package/dist/controller/resourceInventoryLinux.js +2 -6
- package/dist/controller/runtime.js +81 -6
- package/dist/controller/runtimeEventInbox.js +32 -3
- package/dist/controller/runtimeEventProcessor.js +26 -6
- package/dist/controller/runtimeHookRunFence.js +75 -19
- package/dist/controller/structuredProviderObservation.js +133 -70
- package/dist/coordination/workMailboxQueue.js +5 -0
- package/dist/execution/workItemExecutionProjection.js +1 -1
- package/dist/executor/agentExecutor.js +64 -4
- package/dist/executor/executorRegistry.js +3 -0
- package/dist/executor/fileRoleLaunchPlanner.js +78 -118
- package/dist/integration/deliveryObligation.js +2 -1
- package/dist/integration/gitIntegrationService.js +312 -382
- package/dist/integration/integrationAttempt.js +30 -4
- package/dist/integration/integrationQueueService.js +7 -7
- package/dist/integration/integrationSourceApplication.js +323 -0
- package/dist/kernel/builtinCapabilities.js +32 -24
- package/dist/message/globalInterrupt.js +33 -0
- package/dist/message/inputControlResolution.js +106 -0
- package/dist/message/message.js +423 -0
- package/dist/message/messageContinuation.js +126 -3
- package/dist/message/taskInterrupt.js +34 -0
- package/dist/observability/orchestrationMetrics.js +1 -1
- package/dist/plugins/pluginService.js +11 -3
- package/dist/release/releaseHandover.js +22 -0
- package/dist/release/releaseWorkflowPorts.js +15 -7
- package/dist/repository/gitWorkspace.js +72 -15
- package/dist/repository/taskWorkspaceCoordinator.js +134 -0
- package/dist/repository/taskWorkspacePreparer.js +120 -49
- package/dist/repository/workItemCandidateSnapshot.js +34 -0
- package/dist/resources/projectResource.js +0 -48
- package/dist/resources/projectResourceService.js +3 -81
- package/dist/resources/resourceDiscovery.js +3 -2
- package/dist/runtime/agentHost.js +152 -72
- package/dist/runtime/agentHostCompatibility.js +127 -0
- package/dist/runtime/agentHostProtocol.js +53 -0
- package/dist/runtime/executionEnvironment.js +0 -19
- package/dist/runtime/launchBroker.js +6 -0
- package/dist/runtime/sessionReconciliation.js +4 -4
- package/dist/runtime/taskRuntimeIsolation.js +30 -6
- package/dist/runtime/tmuxAdapters.js +5 -3
- package/dist/scheduler/operatorEvent.js +4 -0
- package/dist/scheduler/taskExecutionProjection.js +12 -1
- package/dist/scheduler/wakeReason.js +7 -1
- package/dist/scheduler/wakeupQueue.js +2 -0
- package/dist/setup/setupCommand.js +29 -16
- package/dist/storage/homeLayout.js +130 -0
- package/dist/storage/migrations/artifactsToGit.js +338 -0
- package/dist/storage/migrations/collapseWorktreeLayout.js +963 -0
- package/dist/storage/migrations/integrationContinuation.js +104 -0
- package/dist/storage/migrations/submitIntent.js +126 -0
- package/dist/storage/migrations/unifyHomeLayout.js +925 -0
- package/dist/storage/sqliteSchema.js +173 -7
- package/dist/storage/sqliteStore.js +41 -22
- package/dist/storage/storageVersions.js +1 -1
- package/dist/storage/storeRpc.js +2 -1
- package/dist/storage/upgrade/upgradeOrchestrator.js +95 -2
- package/dist/task/archiveDiagnostics.js +128 -0
- package/dist/task/nextAction.js +44 -11
- package/dist/task/taskActivation.js +26 -0
- package/dist/task/taskActivationService.js +85 -69
- package/dist/task/taskSubmission.js +236 -0
- package/dist/web/assets/client/app.js +58 -2
- package/dist/web/assets/client/components.js +1 -0
- package/dist/web/assets/client/i18n.js +6 -0
- package/dist/web/assets/client/taskSurface.js +202 -7
- package/dist/web/assets/client/view.js +7 -4
- package/dist/web/assets/shell.js +23 -0
- package/dist/web/assets/styles/layout.js +1 -1
- package/dist/web/assets/styles/widgets.js +12 -0
- package/dist/web/webServer.js +135 -4
- package/dist/web/webSnapshot.js +4 -3
- package/dist/web/webTaskSurface.js +225 -8
- package/dist/workItem/workItem.js +14 -10
- package/dist/workspace/workItemChangeSetManager.js +18 -2
- package/docs/agent-result-consumption.md +2 -0
- package/docs/agent-result-consumption.zh-CN.md +81 -0
- package/docs/agent-runtime-drivers.md +2 -0
- package/docs/agent-runtime-drivers.zh-CN.md +77 -0
- package/docs/architecture/README.md +44 -32
- package/docs/architecture/README.zh-CN.md +43 -0
- package/docs/architecture/capabilities-and-resources.md +118 -79
- package/docs/architecture/capabilities-and-resources.zh-CN.md +83 -0
- package/docs/managed-turn-and-session-runtime.md +2 -0
- package/docs/managed-turn-and-session-runtime.zh-CN.md +180 -0
- package/docs/observability/README.md +2 -0
- package/docs/observability/README.zh-CN.md +71 -0
- package/docs/plugin-sdk.md +320 -217
- package/docs/plugin-sdk.zh-CN.md +293 -0
- package/docs/provider-runtime.md +2 -0
- package/docs/provider-runtime.zh-CN.md +132 -0
- package/docs/release-workflow.md +41 -0
- package/docs/release-workflow.zh-CN.md +266 -0
- package/docs/roles-and-configuration.md +2 -0
- package/docs/roles-and-configuration.zh-CN.md +96 -0
- package/docs/sqlite-control-plane-design.md +225 -1
- package/docs/sqlite-control-plane-design.zh-CN.md +62 -0
- package/docs/task-dag-semantics.md +80 -57
- package/docs/task-dag-semantics.zh-CN.md +59 -0
- package/docs/task-delivery.md +2 -0
- package/docs/task-delivery.zh-CN.md +82 -0
- package/docs/task-local-identity.md +2 -0
- package/docs/task-local-identity.zh-CN.md +58 -0
- package/docs/testing/verification-levels.md +26 -0
- package/docs/testing/verification-levels.zh-CN.md +80 -0
- package/i18n/README.zh-CN.md +199 -10
- package/package.json +2 -1
- package/skills/yui-leader/SKILL.md +88 -331
- package/skills/yui-leader/references/execution.md +405 -0
- package/skills/yui-leader/references/integration.md +52 -2
- package/skills/yui-leader/references/planning.md +109 -0
- package/skills/yui-leader/references/task-plugins.md +8 -4
- package/skills/yui-operator/SKILL.md +22 -4
- package/skills/yui-runtime/SKILL.md +27 -0
- package/skills/yui-runtime/references/publication.md +20 -0
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import { durableJobIdempotencyKey } from "../../job/durableJob.js";
|
|
4
|
+
import { resolveProjectVerificationPlan, verificationPlanDigest, planBootstrapJobSteps, planL2JobSteps } from "../../verification/verificationPlan.js";
|
|
5
|
+
/** Classify only known v19 Git conflicts. Manual strategy and CAS blockers
|
|
6
|
+
* keep their meaning. Never synthesize candidate/Job success or edit history. */
|
|
7
|
+
export function migrateIntegrationContinuation(db) {
|
|
8
|
+
const rows = db.prepare("SELECT task_id, integration_id, payload FROM integration_attempts").all();
|
|
9
|
+
const update = db.prepare("UPDATE integration_attempts SET status = ?, payload = ? WHERE task_id = ? AND integration_id = ?");
|
|
10
|
+
for (const row of rows) {
|
|
11
|
+
const attempt = JSON.parse(row.payload);
|
|
12
|
+
if (attempt.schemaVersion !== 6)
|
|
13
|
+
continue;
|
|
14
|
+
const proven = migrateBoundFastForward(db, attempt);
|
|
15
|
+
if (proven !== undefined) {
|
|
16
|
+
update.run(proven.status, JSON.stringify(proven), row.task_id, row.integration_id);
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
if (attempt.status !== "blocked")
|
|
20
|
+
continue;
|
|
21
|
+
const source = attempt.source;
|
|
22
|
+
const summary = attempt.conflict?.summary;
|
|
23
|
+
if (typeof summary !== "string")
|
|
24
|
+
continue;
|
|
25
|
+
const ordinary = (source?.kind === "upstream" && source.strategy === "rebase"
|
|
26
|
+
&& summary.startsWith("Upstream rebase conflicts"))
|
|
27
|
+
|| (source?.kind === "work-item" && source.strategy === "merge"
|
|
28
|
+
&& summary.startsWith("WorkItem merge conflicts"))
|
|
29
|
+
|| (source?.kind === "work-item" && source.strategy === "cherry-pick"
|
|
30
|
+
&& summary.startsWith(`WorkItem ${source.workItemId} commit `)
|
|
31
|
+
&& summary.includes(" conflicts with "));
|
|
32
|
+
if (!ordinary)
|
|
33
|
+
continue;
|
|
34
|
+
update.run("conflicted", JSON.stringify({ ...attempt, status: "conflicted" }), row.task_id, row.integration_id);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/** v19 bound a Job only after successful source application. For FF, its
|
|
38
|
+
* exact checked head IS the immutable source result, so the existing records
|
|
39
|
+
* prove application without inventing a reflog action or a successful result.
|
|
40
|
+
* Other old source strategies need evidence not available in these DB records.
|
|
41
|
+
* Keep this interpretation at the migration boundary, never in runtime reads. */
|
|
42
|
+
function migrateBoundFastForward(db, attempt) {
|
|
43
|
+
if (!["running", "validating"].includes(attempt.status) || attempt.jobId === undefined
|
|
44
|
+
|| attempt.source.kind !== "work-item" || attempt.source.strategy !== "ff")
|
|
45
|
+
return;
|
|
46
|
+
const read = (sql, ...keys) => {
|
|
47
|
+
const row = db.prepare(sql).get(...keys);
|
|
48
|
+
return row === undefined ? undefined : JSON.parse(row.payload);
|
|
49
|
+
};
|
|
50
|
+
const task = read("SELECT payload FROM task_records WHERE task_id = ?", attempt.taskId);
|
|
51
|
+
const project = read("SELECT payload FROM projects WHERE id = ?", attempt.projectId);
|
|
52
|
+
const job = read("SELECT payload FROM durable_jobs WHERE task_id = ? AND job_id = ?", attempt.taskId, attempt.jobId);
|
|
53
|
+
const workspaces = db.prepare("SELECT payload FROM managed_workspaces WHERE task_id = ?").all(attempt.taskId)
|
|
54
|
+
.map(row => JSON.parse(row.payload));
|
|
55
|
+
const workspace = workspaces.find(w => w.owner.type === "integration-attempt"
|
|
56
|
+
&& w.owner.taskId === attempt.taskId && w.owner.integrationAttemptId === attempt.id);
|
|
57
|
+
const main = workspaces.find(w => w.owner.type === "task" && w.owner.taskId === attempt.taskId);
|
|
58
|
+
const entry = workspace?.entries.find(e => e.projectId === attempt.projectId && e.access === "write");
|
|
59
|
+
const mainEntry = main?.entries.find(e => e.projectId === attempt.projectId && e.access === "write");
|
|
60
|
+
const binding = task?.projectBindings.find(b => b.projectId === attempt.projectId);
|
|
61
|
+
if (task?.id !== attempt.taskId || project?.id !== attempt.projectId || binding === undefined
|
|
62
|
+
|| workspace === undefined || entry === undefined || mainEntry === undefined
|
|
63
|
+
|| workspace.entries.length !== 1 || entry.path !== workspace.root
|
|
64
|
+
|| entry.baseCommit !== attempt.beforeCommit
|
|
65
|
+
|| job?.id !== attempt.jobId || job.taskId !== attempt.taskId
|
|
66
|
+
|| job.owner.kind !== "integration-attempt" || job.owner.integrationAttemptId !== attempt.id
|
|
67
|
+
|| job.projectId !== attempt.projectId || job.workspace !== workspace.root
|
|
68
|
+
|| job.operation.targetId !== job.workspace
|
|
69
|
+
|| job.head !== attempt.source.resultCommit
|
|
70
|
+
|| (attempt.candidateCommit !== undefined && attempt.candidateCommit !== job.head)
|
|
71
|
+
|| job.operation.inputDigest !== durableJobIdempotencyKey(job))
|
|
72
|
+
return;
|
|
73
|
+
let steps = attempt.checkCommands.map((command, index) => ({
|
|
74
|
+
name: `check-${index + 1}`, command, timeoutMs: 30 * 60_000
|
|
75
|
+
}));
|
|
76
|
+
if (attempt.gatePlanDigest !== undefined) {
|
|
77
|
+
const plan = resolveProjectVerificationPlan(project);
|
|
78
|
+
if (plan === undefined || verificationPlanDigest(plan) !== attempt.gatePlanDigest)
|
|
79
|
+
return;
|
|
80
|
+
steps = [...planBootstrapJobSteps(plan), ...planL2JobSteps(plan)].map(step => ({
|
|
81
|
+
...step, timeoutMs: 30 * 60_000
|
|
82
|
+
}));
|
|
83
|
+
}
|
|
84
|
+
const inputDigest = durableJobIdempotencyKey(job);
|
|
85
|
+
if (inputDigest !== durableJobIdempotencyKey({ ...job, steps }))
|
|
86
|
+
return;
|
|
87
|
+
const branch = entry.branch;
|
|
88
|
+
return {
|
|
89
|
+
...attempt,
|
|
90
|
+
candidateCommit: job.head,
|
|
91
|
+
checkInputDigest: inputDigest,
|
|
92
|
+
sourceProgress: {
|
|
93
|
+
workspace: resolve(workspace.root), branch: fullRef(branch), head: job.head,
|
|
94
|
+
completedSteps: 1,
|
|
95
|
+
sourceDigest: createHash("sha256").update(JSON.stringify([
|
|
96
|
+
attempt.taskId, attempt.id, attempt.projectId, attempt.targetRef,
|
|
97
|
+
attempt.beforeCommit, attempt.source, resolve(workspace.root), branch
|
|
98
|
+
])).digest("hex")
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
function fullRef(branch) {
|
|
103
|
+
return branch.startsWith("refs/") ? branch : `refs/heads/${branch}`;
|
|
104
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { createTaskEvent } from "../../event/taskEvent.js";
|
|
2
|
+
/**
|
|
3
|
+
* Storage 18 -> 19 data step for task-32 Requirement A (submit intent & Draft
|
|
4
|
+
* auto-routing).
|
|
5
|
+
*
|
|
6
|
+
* This helper is INDEPENDENT, deterministic and idempotent. The unified 18 -> 19
|
|
7
|
+
* migration owned by the artifacts WorkItem REGISTERS it (as its `migrateData`)
|
|
8
|
+
* and bumps `CURRENT_STORAGE_VERSION`; this module neither registers a second
|
|
9
|
+
* migration nor changes any schema.
|
|
10
|
+
*
|
|
11
|
+
* Requirement A adds three optional fields inside `messages.payload`, all part of
|
|
12
|
+
* this same 18 -> 19 contract even though each is optional — "optional" governs a
|
|
13
|
+
* single record's shape, not the Home compatibility line, so the version
|
|
14
|
+
* transition is still declared here and carried by the unified migration:
|
|
15
|
+
*
|
|
16
|
+
* - `intent` (record | discuss | develop): an absent value already reads as
|
|
17
|
+
* `discuss` (never `develop`), so historical Messages need no rewrite.
|
|
18
|
+
* - `submissionKey`: the client-chosen idempotency key. Only ever set going
|
|
19
|
+
* forward; NO historical key is fabricated (task-32 §2.3, message review), so
|
|
20
|
+
* old Messages stay keyless and non-idempotent exactly as before.
|
|
21
|
+
* - `submissionReceipt`: the frozen disposition a keyed submission replays from.
|
|
22
|
+
* Written only alongside a new `submissionKey`, so it too never appears on
|
|
23
|
+
* historical data.
|
|
24
|
+
*
|
|
25
|
+
* Because none of the three needs a value backfilled onto old rows, the only data
|
|
26
|
+
* step this migration performs is the planning-entered reconstruction below.
|
|
27
|
+
*
|
|
28
|
+
* The one fact old data cannot express is "this Draft already entered planning".
|
|
29
|
+
* Before Requirement A a Leader-waking user/operator submission on a Draft WAS
|
|
30
|
+
* the planning discussion, but it left no `task.planning-entered` fact because
|
|
31
|
+
* that event type did not exist. After the upgrade `develop` auto-activates an
|
|
32
|
+
* *unplanned* Draft; without this step a Draft that was mid-discussion would be
|
|
33
|
+
* mis-read as unplanned and snatched into auto-activation (task-32 §2.2, §4).
|
|
34
|
+
*
|
|
35
|
+
* So for every Draft that received at least one legitimate historical planning
|
|
36
|
+
* submission this appends exactly one `task.planning-entered` event, which makes
|
|
37
|
+
* {@link draftHasEnteredPlanning} derive `true` and routes a later develop to
|
|
38
|
+
* manual activation. A qualifying submission is an *unaddressed* user/operator
|
|
39
|
+
* Message that was not save-only (`wakePolicy !== "none"`) — byte-for-byte the
|
|
40
|
+
* same shape the new discuss path routes through the shared submission service.
|
|
41
|
+
*
|
|
42
|
+
* This is deliberately the conservative direction: it can only ever make develop
|
|
43
|
+
* MORE cautious, never activate a Draft it should not. Save-only Messages (old
|
|
44
|
+
* `wakePolicy === "none"`, the shape `record` now names) are excluded, so a Draft
|
|
45
|
+
* that only ever recorded context stays legitimately unplanned and its first
|
|
46
|
+
* develop may still auto-activate. Non-Draft Tasks are skipped because
|
|
47
|
+
* {@link draftHasEnteredPlanning} answers only about Drafts, and Activation
|
|
48
|
+
* requests are left untouched — a legacy request's absent `origin` is the
|
|
49
|
+
* truthful marker that it stays behind explicit activation.
|
|
50
|
+
*/
|
|
51
|
+
export function migrateSubmitIntent(db) {
|
|
52
|
+
const drafts = db.prepare("SELECT task_id FROM tasks_catalog WHERE status = 'draft'").all();
|
|
53
|
+
if (drafts.length === 0)
|
|
54
|
+
return;
|
|
55
|
+
const listMessages = db.prepare("SELECT payload FROM messages WHERE task_id = ? ORDER BY seq ASC");
|
|
56
|
+
const listEventRows = db.prepare("SELECT event_id, type FROM events WHERE task_id = ?");
|
|
57
|
+
const readHighWater = db.prepare("SELECT high_water FROM id_sequences WHERE task_id = ? AND kind = 'event'");
|
|
58
|
+
// Advance the per-Task event high-water to exactly the id we just consumed, so
|
|
59
|
+
// the runtime keeps allocating strictly above every migrated event (mirrors
|
|
60
|
+
// sqliteStore's id_sequences bookkeeping).
|
|
61
|
+
const upsertHighWater = db.prepare(`INSERT INTO id_sequences (task_id, kind, high_water) VALUES (?, 'event', ?)
|
|
62
|
+
ON CONFLICT(task_id, kind) DO UPDATE SET high_water = ?`);
|
|
63
|
+
const insertEvent = db.prepare("INSERT INTO events (task_id, event_id, type, occurred_at, payload) VALUES (?, ?, ?, ?, ?)");
|
|
64
|
+
for (const { task_id: taskId } of drafts) {
|
|
65
|
+
const eventRows = listEventRows.all(taskId);
|
|
66
|
+
// Idempotent: a Draft that already carries the fact (from a prior run of this
|
|
67
|
+
// migration, or from a real runtime planning entry) is left untouched.
|
|
68
|
+
if (eventRows.some((row) => row.type === PLANNING_ENTERED_EVENT_TYPE))
|
|
69
|
+
continue;
|
|
70
|
+
const entry = firstPlanningSubmission(listMessages.all(taskId));
|
|
71
|
+
if (entry === undefined)
|
|
72
|
+
continue;
|
|
73
|
+
// Allocate the next event id from the greater of the recorded high-water and
|
|
74
|
+
// the largest event id actually present, so a fixture or historical database
|
|
75
|
+
// whose id_sequences lags the events table can never produce a colliding id.
|
|
76
|
+
const highWater = readHighWater.get(taskId)?.high_water ?? 0;
|
|
77
|
+
const maxEventSeq = eventRows.reduce((max, row) => Math.max(max, eventSequence(row.event_id)), 0);
|
|
78
|
+
const seq = Math.max(highWater, maxEventSeq) + 1;
|
|
79
|
+
// Build through the shared constructor so a migrated event is byte-identical
|
|
80
|
+
// in shape to one the shared submission service writes at runtime, and dated
|
|
81
|
+
// to the historical submission rather than the wall-clock upgrade moment.
|
|
82
|
+
const event = createTaskEvent(`event-${seq}`, taskId, PLANNING_ENTERED_EVENT_TYPE, { messageId: entry.id, intent: "discuss", derivedBy: MIGRATION_PROVENANCE }, new Date(entry.createdAt));
|
|
83
|
+
insertEvent.run(taskId, event.id, event.type, event.createdAt, JSON.stringify(event));
|
|
84
|
+
upsertHighWater.run(taskId, seq, seq);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* The never-compacted event type recording a Draft entered planning. Inlined as a
|
|
89
|
+
* literal rather than imported so this released migration stays frozen against a
|
|
90
|
+
* future runtime rename of the constant; `TASK_PLANNING_ENTERED_EVENT` is the
|
|
91
|
+
* runtime owner, and a tripwire test asserts the two never diverge.
|
|
92
|
+
*/
|
|
93
|
+
const PLANNING_ENTERED_EVENT_TYPE = "task.planning-entered";
|
|
94
|
+
/** Marks the event as reconstructed by this migration, for later audit. */
|
|
95
|
+
const MIGRATION_PROVENANCE = "submit-intent-migration";
|
|
96
|
+
/**
|
|
97
|
+
* The earliest Message on a Draft that represents a historical planning
|
|
98
|
+
* submission, or `undefined` if the Draft only ever recorded save-only context.
|
|
99
|
+
*
|
|
100
|
+
* A qualifying Message is an unaddressed (`recipient === undefined`) user or
|
|
101
|
+
* operator Message whose wake policy was not `none`. An absent wake policy
|
|
102
|
+
* qualifies: before Requirement A that was the default Leader-waking submission,
|
|
103
|
+
* exactly the shape an omitted intent normalizes to `discuss` today.
|
|
104
|
+
*/
|
|
105
|
+
function firstPlanningSubmission(rows) {
|
|
106
|
+
for (const row of rows) {
|
|
107
|
+
const message = JSON.parse(row.payload);
|
|
108
|
+
if (message.kind !== "user" && message.kind !== "operator")
|
|
109
|
+
continue;
|
|
110
|
+
if (message.recipient !== undefined)
|
|
111
|
+
continue; // an addressed continuation, not a submission
|
|
112
|
+
if (message.wakePolicy === "none")
|
|
113
|
+
continue; // old save-only == record, never planning
|
|
114
|
+
if (typeof message.id !== "string")
|
|
115
|
+
continue;
|
|
116
|
+
if (typeof message.createdAt !== "string")
|
|
117
|
+
continue;
|
|
118
|
+
return { id: message.id, createdAt: message.createdAt };
|
|
119
|
+
}
|
|
120
|
+
return undefined;
|
|
121
|
+
}
|
|
122
|
+
/** The numeric suffix of an `event-<n>` id, or 0 for any unexpected shape. */
|
|
123
|
+
function eventSequence(eventId) {
|
|
124
|
+
const match = /^event-(\d+)$/.exec(eventId);
|
|
125
|
+
return match === null ? 0 : Number.parseInt(match[1], 10);
|
|
126
|
+
}
|