@zq-silk/yui 0.14.1 → 0.15.0
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 +91 -47
- package/README.md +275 -145
- package/dist/agent/managedRuntimeEnvironment.js +5 -8
- package/dist/cli/commandCatalog.js +74 -109
- package/dist/cli/helpRenderer.js +1 -2
- package/dist/cli/interactionCandidates.js +10 -0
- package/dist/cli/interactionPolicy.js +76 -37
- package/dist/cli/roleWizard.js +5 -6
- package/dist/cli/updateCommand.js +17 -9
- package/dist/cli/updateOrchestrator.js +87 -19
- package/dist/cli/updatePorts.js +79 -13
- package/dist/cli/upgradeCommand.js +105 -23
- package/dist/cli.js +268 -231
- package/dist/commands/agentCommands.js +13 -0
- package/dist/commands/configCommands.js +7 -19
- package/dist/commands/configOverview.js +3 -2
- package/dist/commands/controllerCommands.js +1 -1
- package/dist/commands/durableJobCommands.js +1 -1
- package/dist/commands/executionAuditCommands.js +4 -9
- package/dist/commands/globalRoleCommands.js +17 -0
- package/dist/commands/profileCommands.js +200 -30
- package/dist/commands/sessionCommands.js +1 -1
- package/dist/commands/taskActor.js +29 -127
- package/dist/commands/taskChangeSetCommands.js +1 -3
- package/dist/commands/taskCommands.js +1771 -2107
- package/dist/commands/taskCompletionGate.js +27 -103
- package/dist/commands/taskContextCommand.js +47 -80
- package/dist/commands/taskExecutionCommands.js +9 -27
- package/dist/commands/taskInputCommands.js +10 -10
- package/dist/commands/taskIntegrationCommands.js +69 -61
- package/dist/commands/taskIntegrationQueueCommands.js +1 -1
- package/dist/commands/taskNextActionCommand.js +8 -29
- package/dist/commands/taskOverviewCommand.js +25 -3
- package/dist/commands/taskPublicationCommands.js +151 -85
- package/dist/commands/taskPublicationVerifyCommand.js +181 -0
- package/dist/commands/taskRemoteDeliveryCommand.js +134 -0
- package/dist/commands/taskRoleRuntimeStatus.js +5 -6
- package/dist/commands/taskUpstreamCommands.js +79 -95
- package/dist/config/configCatalog.js +1 -1
- package/dist/context/contextSnapshot.js +17 -2
- package/dist/context/sourceTurnContext.js +30 -0
- package/dist/context/turnContextPack.js +107 -48
- package/dist/context/turnInputContract.js +11 -4
- package/dist/context/wakeNotification.js +84 -22
- package/dist/controller/agentRuntimeObserver.js +9 -12
- package/dist/controller/clientRuntime.js +13 -13
- package/dist/controller/controller.js +11 -11
- package/dist/controller/fileSchedulerStoreAdapter.js +262 -219
- package/dist/controller/jobControl.js +11 -10
- package/dist/controller/resourceInventory.js +6 -6
- package/dist/controller/resourceInventoryLinux.js +2 -2
- package/dist/controller/runtime.js +18 -61
- package/dist/controller/runtimeEventInbox.js +57 -34
- package/dist/controller/runtimeEventProcessor.js +36 -96
- package/dist/controller/runtimeHookTurnFence.js +33 -57
- package/dist/controller/runtimeLaunchCoordinator.js +44 -40
- package/dist/controller/runtimeObservationHook.js +18 -12
- package/dist/controller/sessionNotify.js +43 -28
- package/dist/controller/sessionOwnerReconciliation.js +21 -21
- package/dist/controller/structuredProviderObservation.js +13 -16
- package/dist/coordination/workMailboxQueue.js +132 -0
- package/dist/core/controllerServer.js +6 -5
- package/dist/doctor/doctor.js +37 -14
- package/dist/domain/agentResultTransport.js +60 -0
- package/dist/execution/executionHealth.js +111 -118
- package/dist/execution/reviewMainTurn.js +161 -0
- package/dist/execution/workItemExecution.js +454 -0
- package/dist/execution/workItemExecutionProjection.js +309 -0
- package/dist/execution/workItemMainTurn.js +164 -0
- package/dist/executor/agentExecutor.js +19 -19
- package/dist/executor/executorRegistry.js +7 -28
- package/dist/executor/fileRoleLaunchPlanner.js +16 -82
- package/dist/external/githubPublicationVerifier.js +114 -0
- package/dist/external/gitlabPublicationVerifier.js +158 -0
- package/dist/external/pinnedCommandRunner.js +73 -0
- package/dist/integration/changeSetManifest.js +0 -3
- package/dist/integration/deliveryObligation.js +43 -6
- package/dist/integration/gitIntegrationService.js +284 -101
- package/dist/integration/integrationAttempt.js +64 -14
- package/dist/integration/integrationQueueEntry.js +5 -35
- package/dist/integration/integrationQueueService.js +65 -194
- package/dist/lifecycle/canonicalLifecycleEvent.js +16 -15
- package/dist/lifecycle/exactTurnTerminalization.js +100 -97
- package/dist/message/message.js +16 -0
- package/dist/observability/executionAudit.js +4 -25
- package/dist/observability/faultClassification.js +58 -70
- package/dist/observability/orchestrationMetrics.js +8 -50
- package/dist/observability/runtimeIdentity.js +48 -50
- package/dist/profile/agentProfile.js +60 -15
- package/dist/profile/agentProfileRuntime.js +100 -0
- package/dist/release/releaseWorkflowPorts.js +5 -68
- package/dist/release/runtimeRelease.js +9 -1
- package/dist/repository/executionLaneGitSnapshot.js +80 -0
- package/dist/repository/gitWorkspace.js +9 -67
- package/dist/repository/taskBaseFreshness.js +3 -1
- package/dist/repository/taskWorkspaceCoordinator.js +32 -4
- package/dist/repository/taskWorkspacePreparer.js +252 -411
- package/dist/resources/liveReferences.js +6 -55
- package/dist/resources/resourceDiscovery.js +1 -30
- package/dist/resources/resourceRegistrar.js +0 -23
- package/dist/review/deltaRecheck.js +8 -65
- package/dist/review/reviewAcceptance.js +22 -19
- package/dist/review/reviewConfig.js +0 -10
- package/dist/review/reviewDecision.js +2 -2
- package/dist/review/reviewRound.js +147 -273
- package/dist/review/reviewerAvailability.js +13 -23
- package/dist/role/role.js +3 -0
- package/dist/runtime/agentDriverObservation.js +1 -1
- package/dist/runtime/agentHost.js +51 -51
- package/dist/runtime/builtinAgentDrivers.js +9 -2
- package/dist/runtime/codexAppServerRuntime.js +23 -5
- package/dist/runtime/exactControlPlane.js +22 -296
- package/dist/runtime/firstProgressAdvisory.js +1 -1
- package/dist/runtime/index.js +1 -1
- package/dist/runtime/launchBroker.js +11 -11
- package/dist/runtime/lifecycleReservation.js +2 -2
- package/dist/runtime/managedCaller.js +111 -0
- package/dist/runtime/ports.js +16 -3
- package/dist/runtime/preallocatedNativeSession.js +2 -2
- package/dist/runtime/processExitObservation.js +3 -2
- package/dist/runtime/providerContinuationReconciliationService.js +1 -1
- package/dist/runtime/runtimeBinding.js +1 -1
- package/dist/runtime/runtimeObservation.js +33 -16
- package/dist/runtime/runtimeSessionCandidate.js +2 -2
- package/dist/runtime/runtimeStopReceipt.js +11 -11
- package/dist/runtime/sessionLaunchRequest.js +2 -2
- package/dist/runtime/sessionOwnerIdentity.js +9 -9
- package/dist/runtime/sessionOwnerRegistry.js +13 -13
- package/dist/runtime/sessionReconciliation.js +4 -4
- package/dist/runtime/sessionTerminationGuard.js +9 -9
- package/dist/runtime/sessionTokenMetrics.js +5 -7
- package/dist/runtime/structuredProviderHost.js +5 -5
- package/dist/runtime/taskRuntimeIsolation.js +24 -24
- package/dist/runtime/tmuxAdapters.js +32 -30
- package/dist/scheduler/activeRoleTurnDelivery.js +37 -15
- package/dist/scheduler/leaderWakeupProcessor.js +78 -55
- package/dist/scheduler/roleTurnLiveness.js +6 -6
- package/dist/scheduler/roleTurnStall.js +8 -9
- package/dist/scheduler/taskExecutionProjection.js +19 -41
- package/dist/scheduler/taskObservabilityProjection.js +75 -122
- package/dist/storage/currentTaskStore.js +6 -4
- package/dist/storage/sqliteSchema.js +135 -80
- package/dist/storage/sqliteStore.js +69 -52
- package/dist/storage/storageSchema.js +92 -230
- package/dist/storage/storageVersions.js +12 -13
- package/dist/storage/taskStore.js +8 -8
- package/dist/storage/upgrade/upgradeOrchestrator.js +276 -85
- package/dist/task/completionReadiness.js +17 -98
- package/dist/task/deliveryGuard.js +9 -11
- package/dist/task/draftPlan.js +142 -0
- package/dist/task/nextAction.js +179 -477
- package/dist/task/publicationVerification.js +1 -0
- package/dist/task/remoteDelivery.js +233 -0
- package/dist/task/task.js +108 -4
- package/dist/task/taskRecordReference.js +0 -1
- package/dist/turn/turn.js +109 -16
- package/dist/verification/verificationGateService.js +0 -26
- package/dist/version.js +3 -3
- package/dist/web/assets/client/components.js +99 -121
- package/dist/web/assets/client/i18n.js +136 -58
- package/dist/web/assets/client/view.js +44 -0
- package/dist/web/assets/styles/cards.js +3 -15
- package/dist/web/assets/styles/widgets.js +4 -0
- package/dist/web/webSnapshot.js +29 -14
- package/dist/workItem/dependencyGate.js +34 -0
- package/dist/workItem/workItem.js +99 -266
- package/dist/workspace/workItemChangeSetManager.js +15 -59
- package/docs/task-local-identity.md +9 -9
- package/i18n/README.zh-CN.md +189 -74
- package/package.json +1 -1
- package/skills/yui-leader/SKILL.md +272 -782
- package/skills/yui-operator/SKILL.md +190 -365
- package/skills/yui-reviewer/SKILL.md +40 -33
- package/skills/yui-runtime/SKILL.md +22 -0
- package/skills/yui-worker/SKILL.md +19 -6
- package/dist/execution/candidateConvergence.js +0 -623
- package/dist/execution/executionGroup.js +0 -854
- package/dist/execution/resourceBroker.js +0 -426
- package/dist/executor/launchPlan.js +0 -16
- package/dist/review/reviewFinding.js +0 -240
- package/dist/review/reviewFindingLedger.js +0 -550
- package/dist/review/reviewOutcomeClassifier.js +0 -255
- package/dist/scheduler/resourceQueueProjection.js +0 -71
- package/dist/storage/upgrade/recordVersions.js +0 -82
- package/dist/task/repairWave.js +0 -150
|
@@ -8,26 +8,20 @@
|
|
|
8
8
|
*
|
|
9
9
|
* - `global_sequences` (§5.3): global record ID high-water marks.
|
|
10
10
|
* - `outbox` (§5.4): durable outbox with `UNIQUE(request_id)` for exactly-once.
|
|
11
|
-
* - `config`: the `YuiConfig` singleton (
|
|
12
|
-
* identity/revision/versions; config is a separate singleton).
|
|
11
|
+
* - `config`: the `YuiConfig` singleton (`home_meta` keeps identity/revision).
|
|
13
12
|
*
|
|
14
13
|
* Record payloads are stored two ways, per §4: typed columns for fields that are
|
|
15
14
|
* queried/filtered/used-for-CAS, and a `payload` JSON column holding the full
|
|
16
|
-
*
|
|
17
|
-
*
|
|
15
|
+
* current record. Record-local `schemaVersion` tags remain validation guards;
|
|
16
|
+
* they are not independent Home compatibility axes. Any historical payload
|
|
17
|
+
* rewrite belongs to the ordered Home migration that introduced the new shape.
|
|
18
18
|
*
|
|
19
|
-
* The migration runner is idempotent: it records
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
* by SQLite (DDL is transactional) and re-applied on the next open.
|
|
19
|
+
* The migration runner is append-only and idempotent: it records one ordered
|
|
20
|
+
* Home version in `schema_migrations`. Re-running an already-current database
|
|
21
|
+
* is a no-op; a crash mid-upgrade rolls the whole migration transaction back.
|
|
23
22
|
*/
|
|
24
23
|
import { createHash } from "node:crypto";
|
|
25
|
-
|
|
26
|
-
export const SQLITE_LAYOUT_VERSION = 8;
|
|
27
|
-
/** The aggregate version of the normalized SQLite schema. */
|
|
28
|
-
export const SQLITE_AGGREGATE_VERSION = 2;
|
|
29
|
-
/** The current schema migration version. */
|
|
30
|
-
export const SQLITE_SCHEMA_VERSION = 1;
|
|
24
|
+
import { CURRENT_STORAGE_VERSION, MIN_SUPPORTED_STORAGE_VERSION } from "./storageVersions.js";
|
|
31
25
|
/** Telemetry retention bounds (§4.4). Open question 3 in §11; defaults from the design. */
|
|
32
26
|
export const TELEMETRY_KEEP_PER_GENERATION = 200;
|
|
33
27
|
export const TELEMETRY_TURN_CAP = 50_000;
|
|
@@ -38,20 +32,19 @@ export const TELEMETRY_TURN_CAP = 50_000;
|
|
|
38
32
|
* the store; `journal_mode=WAL` is a persistent database property set on open.
|
|
39
33
|
* The migration itself only contains schema objects.
|
|
40
34
|
*
|
|
41
|
-
* This is the
|
|
42
|
-
*
|
|
35
|
+
* This is the Yui 0.15.0 / storage-version-1 baseline. Future releases append
|
|
36
|
+
* migrations after it so fresh and upgraded databases converge on the same
|
|
37
|
+
* current contract.
|
|
43
38
|
*/
|
|
44
39
|
const BASELINE_CORE_SQL = `
|
|
45
40
|
-- Global catalog and coordination (§4.1) -------------------------------------
|
|
46
41
|
|
|
47
42
|
CREATE TABLE IF NOT EXISTS home_meta (
|
|
48
|
-
id
|
|
49
|
-
home_identity
|
|
50
|
-
revision
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
created_at TEXT NOT NULL,
|
|
54
|
-
updated_at TEXT NOT NULL
|
|
43
|
+
id INTEGER PRIMARY KEY CHECK (id = 1),
|
|
44
|
+
home_identity TEXT NOT NULL,
|
|
45
|
+
revision INTEGER NOT NULL,
|
|
46
|
+
created_at TEXT NOT NULL,
|
|
47
|
+
updated_at TEXT NOT NULL
|
|
55
48
|
);
|
|
56
49
|
|
|
57
50
|
CREATE TABLE IF NOT EXISTS config (
|
|
@@ -460,29 +453,11 @@ BEGIN
|
|
|
460
453
|
AND turn_id = NEW.turn_id AND generation = NEW.generation;
|
|
461
454
|
END;
|
|
462
455
|
`;
|
|
463
|
-
/**
|
|
464
|
-
* Cross-Round semantic review
|
|
465
|
-
* findings with stable keys and Leader dispositions. Failed execution attempts
|
|
466
|
-
* never create rows; only completed Rounds feed the ledger.
|
|
467
|
-
*/
|
|
468
|
-
const BASELINE_REVIEW_FINDING_SQL = `
|
|
469
|
-
CREATE TABLE IF NOT EXISTS review_findings (
|
|
470
|
-
task_id TEXT NOT NULL,
|
|
471
|
-
finding_id TEXT NOT NULL,
|
|
472
|
-
stable_key TEXT NOT NULL,
|
|
473
|
-
severity TEXT NOT NULL,
|
|
474
|
-
payload TEXT NOT NULL,
|
|
475
|
-
updated_at TEXT NOT NULL,
|
|
476
|
-
PRIMARY KEY (task_id, finding_id)
|
|
477
|
-
);
|
|
478
|
-
CREATE INDEX IF NOT EXISTS idx_review_findings_task ON review_findings(task_id);
|
|
479
|
-
CREATE INDEX IF NOT EXISTS idx_review_findings_stable_key ON review_findings(task_id, stable_key);
|
|
480
|
-
`;
|
|
481
456
|
/**
|
|
482
457
|
* Session owner physical identity records.
|
|
483
458
|
*
|
|
484
|
-
* One row per runtime generation, keyed by
|
|
485
|
-
* stores the full
|
|
459
|
+
* One row per runtime generation, keyed by runtime generation id. The payload column
|
|
460
|
+
* stores the full current JSON record; typed columns support the
|
|
486
461
|
* reconciliation queries (task/role lookup, PID liveness).
|
|
487
462
|
*/
|
|
488
463
|
const BASELINE_SESSION_OWNER_SQL = `
|
|
@@ -503,9 +478,8 @@ CREATE INDEX IF NOT EXISTS idx_session_owners_task
|
|
|
503
478
|
/**
|
|
504
479
|
* Resource GC registry.
|
|
505
480
|
*
|
|
506
|
-
* GC-owned table for resource lifecycle records.
|
|
507
|
-
* state
|
|
508
|
-
* versioning. Records are stored as full versioned JSON in `payload`, with
|
|
481
|
+
* GC-owned table for resource lifecycle records. The registry is GC's own
|
|
482
|
+
* state. Records are stored as full current JSON in `payload`, with
|
|
509
483
|
* typed columns for the fields GC queries (disposition, kind, task_id).
|
|
510
484
|
*/
|
|
511
485
|
const BASELINE_RESOURCE_REGISTRY_SQL = `
|
|
@@ -671,7 +645,6 @@ const MIGRATION_1_SQL = [
|
|
|
671
645
|
BASELINE_JOB_AND_RELEASE_SQL,
|
|
672
646
|
BASELINE_JOB_CALLER_SQL,
|
|
673
647
|
BASELINE_TELEMETRY_AGGREGATE_SQL,
|
|
674
|
-
BASELINE_REVIEW_FINDING_SQL,
|
|
675
648
|
BASELINE_SESSION_OWNER_SQL,
|
|
676
649
|
BASELINE_RESOURCE_REGISTRY_SQL,
|
|
677
650
|
BASELINE_GATE_ARTIFACT_SQL,
|
|
@@ -680,10 +653,26 @@ const MIGRATION_1_SQL = [
|
|
|
680
653
|
BASELINE_TASK_WAKE_SQL,
|
|
681
654
|
BASELINE_CONTEXT_SNAPSHOT_SQL
|
|
682
655
|
].join("\n");
|
|
683
|
-
/**
|
|
684
|
-
const MIGRATIONS = [
|
|
685
|
-
{
|
|
686
|
-
|
|
656
|
+
/** Released migrations are append-only and must never be rewritten. */
|
|
657
|
+
const MIGRATIONS = Object.freeze([
|
|
658
|
+
{
|
|
659
|
+
version: 1,
|
|
660
|
+
name: "v0.15.0-baseline",
|
|
661
|
+
introducedIn: "0.15.0",
|
|
662
|
+
sql: MIGRATION_1_SQL
|
|
663
|
+
}
|
|
664
|
+
]);
|
|
665
|
+
for (let index = 0; index < MIGRATIONS.length; index += 1) {
|
|
666
|
+
const expectedVersion = MIN_SUPPORTED_STORAGE_VERSION + index;
|
|
667
|
+
if (MIGRATIONS[index]?.version !== expectedVersion) {
|
|
668
|
+
throw new Error(`Storage migration registry must be contiguous from `
|
|
669
|
+
+ `${MIN_SUPPORTED_STORAGE_VERSION}; missing version ${expectedVersion}.`);
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
if (MIGRATIONS.at(-1)?.version !== CURRENT_STORAGE_VERSION) {
|
|
673
|
+
throw new Error(`Storage migration registry head ${String(MIGRATIONS.at(-1)?.version)} does not match `
|
|
674
|
+
+ `CURRENT_STORAGE_VERSION ${CURRENT_STORAGE_VERSION}.`);
|
|
675
|
+
}
|
|
687
676
|
/** Current hot-path indexes whose absence would invalidate a current Home. */
|
|
688
677
|
const REQUIRED_SCHEMA_INDEXES = [
|
|
689
678
|
"idx_mailboxes_ready",
|
|
@@ -707,11 +696,10 @@ export class SqliteSchemaMigrationError extends Error {
|
|
|
707
696
|
}
|
|
708
697
|
const SCHEMA_MIGRATIONS_SQL = `
|
|
709
698
|
CREATE TABLE schema_migrations (
|
|
710
|
-
version
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
checksum TEXT NOT NULL
|
|
699
|
+
version INTEGER PRIMARY KEY,
|
|
700
|
+
name TEXT NOT NULL,
|
|
701
|
+
applied_at TEXT NOT NULL,
|
|
702
|
+
checksum TEXT NOT NULL
|
|
715
703
|
)
|
|
716
704
|
`;
|
|
717
705
|
/**
|
|
@@ -739,10 +727,20 @@ function ensureMigrationLedger(db, mode) {
|
|
|
739
727
|
}
|
|
740
728
|
return false;
|
|
741
729
|
}
|
|
742
|
-
|
|
730
|
+
function validateMigrationLedgerColumns(db) {
|
|
731
|
+
const columns = new Set(db.prepare("PRAGMA table_info(schema_migrations)").all()
|
|
732
|
+
.flatMap(({ name }) => typeof name === "string" ? [name] : []));
|
|
733
|
+
if (columns.size === 4
|
|
734
|
+
&& ["version", "name", "applied_at", "checksum"].every((name) => columns.has(name))) {
|
|
735
|
+
return;
|
|
736
|
+
}
|
|
737
|
+
throw new SqliteSchemaMigrationError("schema_migrations columns do not match the storage-version-1 ledger");
|
|
738
|
+
}
|
|
739
|
+
/** Validate the applied linear prefix and return its current head. */
|
|
743
740
|
function validateAppliedMigrations(db, ledgerWasCreated) {
|
|
741
|
+
validateMigrationLedgerColumns(db);
|
|
744
742
|
const expected = new Map(MIGRATIONS.map((migration) => [migration.version, migration]));
|
|
745
|
-
const rows = db.prepare("SELECT version,
|
|
743
|
+
const rows = db.prepare("SELECT version, name, checksum FROM schema_migrations ORDER BY version").all();
|
|
746
744
|
if (rows.length === 0 && !ledgerWasCreated) {
|
|
747
745
|
throw new SqliteSchemaMigrationError("schema_migrations ledger is empty in an existing database");
|
|
748
746
|
}
|
|
@@ -753,19 +751,22 @@ function validateAppliedMigrations(db, ledgerWasCreated) {
|
|
|
753
751
|
}
|
|
754
752
|
const version = row.version;
|
|
755
753
|
const migration = expected.get(version);
|
|
756
|
-
if (migration === undefined) {
|
|
757
|
-
throw new SqliteSchemaMigrationError(`unknown migration version ${version}`);
|
|
758
|
-
}
|
|
759
754
|
if (applied.has(version)) {
|
|
760
755
|
throw new SqliteSchemaMigrationError(`duplicate migration version ${version}`);
|
|
761
756
|
}
|
|
762
757
|
applied.add(version);
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
758
|
+
if (migration === undefined) {
|
|
759
|
+
if (version <= CURRENT_STORAGE_VERSION) {
|
|
760
|
+
throw new SqliteSchemaMigrationError(`unknown migration version ${version}`);
|
|
761
|
+
}
|
|
762
|
+
if (typeof row.name !== "string" || row.name.length === 0
|
|
763
|
+
|| typeof row.checksum !== "string" || row.checksum.length === 0) {
|
|
764
|
+
throw new SqliteSchemaMigrationError(`future migration ${version} metadata is invalid`);
|
|
765
|
+
}
|
|
766
|
+
continue;
|
|
766
767
|
}
|
|
767
|
-
if (row.
|
|
768
|
-
throw new SqliteSchemaMigrationError(`migration ${version}
|
|
768
|
+
if (row.name !== migration.name) {
|
|
769
|
+
throw new SqliteSchemaMigrationError(`migration ${version} name ${String(row.name)} does not match ${migration.name}`);
|
|
769
770
|
}
|
|
770
771
|
const expectedChecksum = checksum(migration.sql);
|
|
771
772
|
if (row.checksum !== expectedChecksum) {
|
|
@@ -774,12 +775,18 @@ function validateAppliedMigrations(db, ledgerWasCreated) {
|
|
|
774
775
|
}
|
|
775
776
|
const versions = [...applied].sort((left, right) => left - right);
|
|
776
777
|
for (let index = 0; index < versions.length; index += 1) {
|
|
777
|
-
const expectedVersion =
|
|
778
|
+
const expectedVersion = MIN_SUPPORTED_STORAGE_VERSION + index;
|
|
778
779
|
if (versions[index] !== expectedVersion) {
|
|
779
780
|
throw new SqliteSchemaMigrationError(`migration ledger has a gap before version ${versions[index]}`);
|
|
780
781
|
}
|
|
781
782
|
}
|
|
782
|
-
|
|
783
|
+
const currentVersion = versions.at(-1) ?? 0;
|
|
784
|
+
const head = rows.at(-1);
|
|
785
|
+
return {
|
|
786
|
+
versions: applied,
|
|
787
|
+
currentVersion,
|
|
788
|
+
currentChecksum: typeof head?.checksum === "string" ? head.checksum : ""
|
|
789
|
+
};
|
|
783
790
|
}
|
|
784
791
|
/**
|
|
785
792
|
* Validate the physical objects promised by the migration ledger. Ledger
|
|
@@ -799,25 +806,63 @@ function validateSchemaObjects(db) {
|
|
|
799
806
|
throw new SqliteSchemaMigrationError(`required index '${index}' is missing or has the wrong type`, "schema object");
|
|
800
807
|
}
|
|
801
808
|
}
|
|
809
|
+
const homeMetaColumns = db.prepare("PRAGMA table_info(home_meta)").all().map(({ name }) => name);
|
|
810
|
+
const expectedHomeMetaColumns = [
|
|
811
|
+
"id",
|
|
812
|
+
"home_identity",
|
|
813
|
+
"revision",
|
|
814
|
+
"created_at",
|
|
815
|
+
"updated_at"
|
|
816
|
+
];
|
|
817
|
+
if (homeMetaColumns.length !== expectedHomeMetaColumns.length
|
|
818
|
+
|| homeMetaColumns.some((name, index) => name !== expectedHomeMetaColumns[index])) {
|
|
819
|
+
throw new SqliteSchemaMigrationError("home_meta columns do not match the current storage contract", "schema object");
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
/** Return the one linear upgrade path from a supported source to this release. */
|
|
823
|
+
export function storageMigrationPlan(currentVersion) {
|
|
824
|
+
if (!Number.isInteger(currentVersion)
|
|
825
|
+
|| currentVersion < MIN_SUPPORTED_STORAGE_VERSION
|
|
826
|
+
|| currentVersion > CURRENT_STORAGE_VERSION) {
|
|
827
|
+
return null;
|
|
828
|
+
}
|
|
829
|
+
return MIGRATIONS
|
|
830
|
+
.filter(({ version }) => version > currentVersion)
|
|
831
|
+
.map(({ version, name, introducedIn }) => ({
|
|
832
|
+
fromVersion: version - 1,
|
|
833
|
+
toVersion: version,
|
|
834
|
+
name,
|
|
835
|
+
introducedIn
|
|
836
|
+
}));
|
|
802
837
|
}
|
|
803
838
|
/** Inspect a recognized migration prefix without changing it. */
|
|
804
839
|
export function inspectSqliteSchemaMigrations(db) {
|
|
805
840
|
const ledgerWasCreated = ensureMigrationLedger(db, "validate");
|
|
806
841
|
const applied = validateAppliedMigrations(db, ledgerWasCreated);
|
|
842
|
+
if (applied.currentVersion > CURRENT_STORAGE_VERSION) {
|
|
843
|
+
return {
|
|
844
|
+
currentVersion: applied.currentVersion,
|
|
845
|
+
currentChecksum: applied.currentChecksum,
|
|
846
|
+
targetVersion: CURRENT_STORAGE_VERSION,
|
|
847
|
+
minimumSupportedVersion: MIN_SUPPORTED_STORAGE_VERSION,
|
|
848
|
+
targetChecksum: checksum(MIGRATIONS.at(-1).sql),
|
|
849
|
+
pendingVersions: []
|
|
850
|
+
};
|
|
851
|
+
}
|
|
807
852
|
const pendingVersions = MIGRATIONS
|
|
808
|
-
.filter((migration) => !applied.has(migration.version))
|
|
853
|
+
.filter((migration) => !applied.versions.has(migration.version))
|
|
809
854
|
.map((migration) => migration.version);
|
|
810
855
|
if (pendingVersions.length === 0)
|
|
811
856
|
validateSchemaObjects(db);
|
|
812
|
-
const current = MIGRATIONS[applied.size - 1];
|
|
813
857
|
const target = MIGRATIONS.at(-1);
|
|
814
|
-
if (
|
|
858
|
+
if (applied.currentVersion === 0) {
|
|
815
859
|
throw new SqliteSchemaMigrationError("schema_migrations ledger has no current head");
|
|
816
860
|
}
|
|
817
861
|
return {
|
|
818
|
-
currentVersion: applied.
|
|
819
|
-
currentChecksum:
|
|
820
|
-
targetVersion:
|
|
862
|
+
currentVersion: applied.currentVersion,
|
|
863
|
+
currentChecksum: applied.currentChecksum,
|
|
864
|
+
targetVersion: CURRENT_STORAGE_VERSION,
|
|
865
|
+
minimumSupportedVersion: MIN_SUPPORTED_STORAGE_VERSION,
|
|
821
866
|
targetChecksum: checksum(target.sql),
|
|
822
867
|
pendingVersions
|
|
823
868
|
};
|
|
@@ -838,15 +883,26 @@ export function migrateSqliteSchema(db, options) {
|
|
|
838
883
|
// prevents a manually altered or partially recorded ledger from silently
|
|
839
884
|
// skipping a later schema/data step.
|
|
840
885
|
const applied = validateAppliedMigrations(db, ledgerWasCreated);
|
|
841
|
-
|
|
886
|
+
if (applied.currentVersion > CURRENT_STORAGE_VERSION) {
|
|
887
|
+
throw new SqliteSchemaMigrationError(`storage version ${applied.currentVersion} is newer than supported `
|
|
888
|
+
+ `${CURRENT_STORAGE_VERSION}`, "admission");
|
|
889
|
+
}
|
|
890
|
+
if (applied.currentVersion !== 0
|
|
891
|
+
&& applied.currentVersion < MIN_SUPPORTED_STORAGE_VERSION) {
|
|
892
|
+
throw new SqliteSchemaMigrationError(`storage version ${applied.currentVersion} is older than the minimum supported `
|
|
893
|
+
+ `${MIN_SUPPORTED_STORAGE_VERSION}`, "admission");
|
|
894
|
+
}
|
|
895
|
+
const pending = MIGRATIONS.filter((migration) => !applied.versions.has(migration.version));
|
|
842
896
|
if (!ledgerWasCreated && pending.length > 0 && options.mode === "validate") {
|
|
843
|
-
throw new SqliteSchemaMigrationError(`
|
|
897
|
+
throw new SqliteSchemaMigrationError(`Storage version ${applied.currentVersion} requires an explicit upgrade to `
|
|
898
|
+
+ `${CURRENT_STORAGE_VERSION}`, "admission");
|
|
844
899
|
}
|
|
845
900
|
const newlyApplied = [];
|
|
846
901
|
for (const migration of pending) {
|
|
847
902
|
db.exec(migration.sql);
|
|
848
|
-
|
|
849
|
-
|
|
903
|
+
const appliedAt = new Date().toISOString();
|
|
904
|
+
db.prepare(`INSERT INTO schema_migrations (version, name, applied_at, checksum)
|
|
905
|
+
VALUES (?, ?, ?, ?)`).run(migration.version, migration.name, appliedAt, checksum(migration.sql));
|
|
850
906
|
newlyApplied.push(migration.version);
|
|
851
907
|
}
|
|
852
908
|
validateSchemaObjects(db);
|
|
@@ -855,7 +911,7 @@ export function migrateSqliteSchema(db, options) {
|
|
|
855
911
|
const newlyApplied = options.mode === "apply" && !db.inTransaction
|
|
856
912
|
? db.transaction(migrate)()
|
|
857
913
|
: migrate();
|
|
858
|
-
return { applied: newlyApplied, version:
|
|
914
|
+
return { applied: newlyApplied, version: CURRENT_STORAGE_VERSION };
|
|
859
915
|
}
|
|
860
916
|
/** The names of every table the schema creates (for tests/introspection). */
|
|
861
917
|
export const SQLITE_SCHEMA_TABLES = [
|
|
@@ -886,7 +942,6 @@ export const SQLITE_SCHEMA_TABLES = [
|
|
|
886
942
|
"turns",
|
|
887
943
|
"active_turns",
|
|
888
944
|
"review_rounds",
|
|
889
|
-
"review_findings",
|
|
890
945
|
"change_sets",
|
|
891
946
|
"integration_attempts",
|
|
892
947
|
"messages",
|
|
@@ -17,11 +17,12 @@
|
|
|
17
17
|
* on the durable outbox.
|
|
18
18
|
* - Crash recovery .............. WAL rollback of uncommitted transactions;
|
|
19
19
|
* outbox replay of committed-but-unacked effects.
|
|
20
|
-
* - Record
|
|
20
|
+
* - Record validation ........... current record (incl. local schemaVersion)
|
|
21
|
+
* in payload.
|
|
21
22
|
* - Evidence retention .......... events/review_rounds/change_sets/
|
|
22
23
|
* integration_attempts are never pruned.
|
|
23
24
|
*
|
|
24
|
-
* Records are stored as full
|
|
25
|
+
* Records are stored as full current JSON in `payload` columns, with typed
|
|
25
26
|
* columns for the fields that are queried/filtered/used-for-CAS (§4). A
|
|
26
27
|
* high-frequency runtime telemetry observation is a single-row upsert into
|
|
27
28
|
* `telemetry` scoped by its primary key — it never rewrites global
|
|
@@ -40,8 +41,7 @@ import Database from "better-sqlite3";
|
|
|
40
41
|
import { consumePendingBatch, mailboxTargetKey, validateWorkMailbox } from "../coordination/workMailbox.js";
|
|
41
42
|
import { validateContextSnapshot } from "../context/contextSnapshot.js";
|
|
42
43
|
import { compareRuntimeSessionCandidates, projectRuntimeSessionCandidate } from "../runtime/runtimeSessionCandidate.js";
|
|
43
|
-
import {
|
|
44
|
-
import { reviewFindingLedgerMode } from "../review/reviewFindingLedger.js";
|
|
44
|
+
import { validateReviewRound } from "../review/reviewRound.js";
|
|
45
45
|
import { generateHomeIdentity, validateHomeIdentity } from "../repository/homeIdentity.js";
|
|
46
46
|
import { validateIntegrationQueueEntry } from "../integration/integrationQueueEntry.js";
|
|
47
47
|
import { validDurableJobTransition, validateDurableJob } from "../job/durableJob.js";
|
|
@@ -52,7 +52,8 @@ import { managedWorkspaceKey } from "../worktree/managedWorkspace.js";
|
|
|
52
52
|
import { CURRENT_CONFIG_SCHEMA_VERSION, CURRENT_WORK_MAILBOX_SCHEMA_VERSION, executionLaneActiveTurnKey, executionLaneActiveTurnKeyParts, StorageConflictError, StorageCancelledError, StorageRecordError, storedCapabilityGrant, storedPublicationReference, storedReleaseWorkflow, isValidCapabilityGrantTransition, isValidReleaseWorkflowTransition, pendingWakeupProjection, validateYuiConfig } from "./taskStore.js";
|
|
53
53
|
import { publicationExternalKey } from "../task/publicationReference.js";
|
|
54
54
|
import { gateArtifactKey, validateGateArtifact } from "../verification/gateArtifact.js";
|
|
55
|
-
import { inspectSqliteSchemaMigrations, migrateSqliteSchema, SqliteSchemaMigrationError,
|
|
55
|
+
import { inspectSqliteSchemaMigrations, migrateSqliteSchema, SqliteSchemaMigrationError, TELEMETRY_KEEP_PER_GENERATION, TELEMETRY_TURN_CAP } from "./sqliteSchema.js";
|
|
56
|
+
import { StorageSchemaError } from "./storageSchema.js";
|
|
56
57
|
/** Read the immutable Home identity without opening a writable Store connection. */
|
|
57
58
|
export function readSqliteHomeIdentity(rootDir, databaseFilename = "yui.db") {
|
|
58
59
|
const database = new Database(join(rootDir, databaseFilename), {
|
|
@@ -123,6 +124,10 @@ export class SqliteTaskStore {
|
|
|
123
124
|
mkdirSync(rootDir, { recursive: true, mode: 0o700 });
|
|
124
125
|
const filename = _options.databaseFilename ?? "yui.db";
|
|
125
126
|
const databasePath = join(rootDir, filename);
|
|
127
|
+
// Remember whether the database file existed before this connection opened
|
|
128
|
+
// it. A store that creates the file is bootstrapping a fresh database and
|
|
129
|
+
// may seed its singleton rows; a store that opens an existing file missing
|
|
130
|
+
// those rows has found corruption, not a bootstrap opportunity.
|
|
126
131
|
const databaseExisted = existsSync(databasePath);
|
|
127
132
|
this.#db = new Database(databasePath);
|
|
128
133
|
try {
|
|
@@ -140,8 +145,7 @@ export class SqliteTaskStore {
|
|
|
140
145
|
version: schema.currentVersion,
|
|
141
146
|
checksum: schema.currentChecksum
|
|
142
147
|
};
|
|
143
|
-
this.#
|
|
144
|
-
this.#seedConfig();
|
|
148
|
+
this.#ensureSeedRows(databaseExisted);
|
|
145
149
|
}
|
|
146
150
|
catch (error) {
|
|
147
151
|
this.#db.close();
|
|
@@ -158,11 +162,40 @@ export class SqliteTaskStore {
|
|
|
158
162
|
*/
|
|
159
163
|
databaseHandle() { return this.#db; }
|
|
160
164
|
// -- transaction primitives -------------------------------------------------
|
|
165
|
+
/**
|
|
166
|
+
* Guarantee the `home_meta` and `config` singleton rows exist.
|
|
167
|
+
*
|
|
168
|
+
* A fresh database created by this connection is bootstrapped with defaults.
|
|
169
|
+
* An existing database that is missing either row is corrupt —
|
|
170
|
+
* truncated, partially migrated, or hand-edited — and must fail closed
|
|
171
|
+
* instead of silently masking the damage with fresh defaults (Issue 01
|
|
172
|
+
* cross-issue handoff: INSERT OR IGNORE could hide migration corruption).
|
|
173
|
+
*/
|
|
174
|
+
#ensureSeedRows(databaseExisted) {
|
|
175
|
+
const hasHomeMeta = this.#db.prepare("SELECT 1 FROM home_meta WHERE id = 1").get() !== undefined;
|
|
176
|
+
const hasConfig = this.#db.prepare("SELECT 1 FROM config WHERE id = 1").get() !== undefined;
|
|
177
|
+
if (hasHomeMeta && hasConfig)
|
|
178
|
+
return;
|
|
179
|
+
if (databaseExisted) {
|
|
180
|
+
const missing = [
|
|
181
|
+
!hasHomeMeta ? "home_meta" : null,
|
|
182
|
+
!hasConfig ? "config" : null
|
|
183
|
+
].filter((value) => value !== null).join(", ");
|
|
184
|
+
throw new StorageSchemaError("STORAGE_SCHEMA_INVALID", `SQLite database is corrupt: required singleton row(s) missing (${missing}). `
|
|
185
|
+
+ "The database may be truncated or partially migrated. Restore yui.db from a "
|
|
186
|
+
+ "backup, or preserve this Home for diagnosis and initialize a new Home.");
|
|
187
|
+
}
|
|
188
|
+
if (!hasHomeMeta)
|
|
189
|
+
this.#seedHomeMeta();
|
|
190
|
+
if (!hasConfig)
|
|
191
|
+
this.#seedConfig();
|
|
192
|
+
}
|
|
161
193
|
#seedHomeMeta() {
|
|
162
194
|
const now = new Date().toISOString();
|
|
163
195
|
const identity = generateHomeIdentity(new Date());
|
|
164
|
-
this.#db.prepare(`INSERT OR IGNORE INTO home_meta
|
|
165
|
-
|
|
196
|
+
this.#db.prepare(`INSERT OR IGNORE INTO home_meta
|
|
197
|
+
(id, home_identity, revision, created_at, updated_at)
|
|
198
|
+
VALUES (1, ?, 0, ?, ?)`).run(JSON.stringify(identity), now, now);
|
|
166
199
|
}
|
|
167
200
|
#seedConfig() {
|
|
168
201
|
this.#db.prepare(`INSERT OR IGNORE INTO config (id, payload, updated_at) VALUES (1, ?, ?)`).run(JSON.stringify(DEFAULT_CONFIG), new Date().toISOString());
|
|
@@ -731,9 +764,7 @@ export class SqliteTaskStore {
|
|
|
731
764
|
activeTurns: runs.filter((run) => run.status === "active"),
|
|
732
765
|
leaderTurns: runs.filter((run) => run.roleName === "leader"),
|
|
733
766
|
reviewOutcomeEvidence: {
|
|
734
|
-
turns: this.#sortById(this.#listPayload("turns", "task_id = ?", [taskId]).filter((run) => run.purpose === "review"), (run) => run.id)
|
|
735
|
-
reviewFindings: this.listReviewFindings(taskId),
|
|
736
|
-
events: events.filter((event) => event.type === "review.completed")
|
|
767
|
+
turns: this.#sortById(this.#listPayload("turns", "task_id = ?", [taskId]).filter((run) => run.purpose === "review"), (run) => run.id)
|
|
737
768
|
}
|
|
738
769
|
};
|
|
739
770
|
}
|
|
@@ -743,13 +774,9 @@ export class SqliteTaskStore {
|
|
|
743
774
|
return null;
|
|
744
775
|
return {
|
|
745
776
|
...base,
|
|
746
|
-
turns: operationalTaskRecords(this.listTurns(taskId), this.listEvents(taskId), "turn"),
|
|
747
777
|
managedWorkspaces: this.#sortById(this.#listPayload("managed_workspaces", "task_id = ?", [taskId]), (workspace) => managedWorkspaceKey(workspace.owner)),
|
|
748
778
|
durableJobs: this.#sortById(this.#listPayload("durable_jobs", "task_id = ?", [taskId]), (job) => job.id),
|
|
749
779
|
integrationQueueEntries: this.#sortById(this.#listPayload("integration_queue", "task_id = ?", [taskId]), (entry) => entry.id),
|
|
750
|
-
reviewFindings: this.#sortById(this.#listPayload("review_findings", "task_id = ?", [taskId]), (finding) => finding.id),
|
|
751
|
-
reviewFindingLedgerMode: reviewFindingLedgerMode(this.getConfig()),
|
|
752
|
-
events: this.#sortById(this.#listPayload("events", "task_id = ?", [taskId]), (event) => event.id)
|
|
753
780
|
};
|
|
754
781
|
}
|
|
755
782
|
/** Task ids flagged active in the catalog projection (the global active index). */
|
|
@@ -926,11 +953,11 @@ export class SqliteTaskStore {
|
|
|
926
953
|
role_name = excluded.role_name, agent_id = excluded.agent_id,
|
|
927
954
|
native_session_id = excluded.native_session_id,
|
|
928
955
|
provider_root_pid = excluded.provider_root_pid,
|
|
929
|
-
payload = excluded.payload, recorded_at = excluded.recorded_at`).run(identity.
|
|
956
|
+
payload = excluded.payload, recorded_at = excluded.recorded_at`).run(identity.runtimeGenerationId, owner.scope, owner.scope === "task" ? owner.taskId : null, owner.roleName, identity.agentId, identity.nativeSessionId ?? null, identity.providerRoot.pid, this.#json(identity), identity.recordedAt);
|
|
930
957
|
});
|
|
931
958
|
}
|
|
932
|
-
getSessionOwner(
|
|
933
|
-
const row = this.#db.prepare("SELECT payload FROM session_owners WHERE launch_id = ?").get(
|
|
959
|
+
getSessionOwner(runtimeGenerationId) {
|
|
960
|
+
const row = this.#db.prepare("SELECT payload FROM session_owners WHERE launch_id = ?").get(runtimeGenerationId);
|
|
934
961
|
return row === undefined ? null : this.#parse(row.payload);
|
|
935
962
|
}
|
|
936
963
|
listSessionOwners() {
|
|
@@ -942,9 +969,9 @@ export class SqliteTaskStore {
|
|
|
942
969
|
}
|
|
943
970
|
return this.#listPayload("session_owners", "scope = 'task' AND task_id = ? AND role_name = ?", [owner.taskId, owner.roleName]);
|
|
944
971
|
}
|
|
945
|
-
removeSessionOwner(
|
|
972
|
+
removeSessionOwner(runtimeGenerationId) {
|
|
946
973
|
this.#mutate(() => {
|
|
947
|
-
this.#db.prepare("DELETE FROM session_owners WHERE launch_id = ?").run(
|
|
974
|
+
this.#db.prepare("DELETE FROM session_owners WHERE launch_id = ?").run(runtimeGenerationId);
|
|
948
975
|
});
|
|
949
976
|
}
|
|
950
977
|
// -- task roles -------------------------------------------------------------
|
|
@@ -1058,7 +1085,7 @@ export class SqliteTaskStore {
|
|
|
1058
1085
|
? ""
|
|
1059
1086
|
: ` WHERE ${predicates.join(" AND ")}`;
|
|
1060
1087
|
const rows = this.#db.prepare(`SELECT scope, task_id, role_name, agent_id, adapter_id,
|
|
1061
|
-
native_session_id, launch_id, session_updated_at,
|
|
1088
|
+
native_session_id, launch_id AS runtime_generation_id, session_updated_at,
|
|
1062
1089
|
cleanup_required
|
|
1063
1090
|
FROM runtime_session_candidates${where}`).all(...parameters);
|
|
1064
1091
|
const candidates = rows.map((row) => ({
|
|
@@ -1068,7 +1095,7 @@ export class SqliteTaskStore {
|
|
|
1068
1095
|
agentId: row.agent_id,
|
|
1069
1096
|
adapterId: row.adapter_id,
|
|
1070
1097
|
nativeSessionId: row.native_session_id,
|
|
1071
|
-
...(row.
|
|
1098
|
+
...(row.runtime_generation_id === null ? {} : { runtimeGenerationId: row.runtime_generation_id }),
|
|
1072
1099
|
sessionUpdatedAt: row.session_updated_at,
|
|
1073
1100
|
cleanupRequired: row.cleanup_required === 1
|
|
1074
1101
|
})).sort(compareRuntimeSessionCandidates);
|
|
@@ -1123,7 +1150,7 @@ export class SqliteTaskStore {
|
|
|
1123
1150
|
json_extract(active_session, '$.agentId') AS agent_id,
|
|
1124
1151
|
json_extract(active_session, '$.adapterId') AS adapter_id,
|
|
1125
1152
|
json_extract(active_session, '$.nativeSessionId') AS native_session_id,
|
|
1126
|
-
json_extract(active_session, '$.
|
|
1153
|
+
json_extract(active_session, '$.runtimeGenerationId') AS runtime_generation_id,
|
|
1127
1154
|
CASE
|
|
1128
1155
|
WHEN json_extract(active_session, '$.status') = 'active'
|
|
1129
1156
|
THEN 1 ELSE 0
|
|
@@ -1131,7 +1158,7 @@ export class SqliteTaskStore {
|
|
|
1131
1158
|
json_extract(active_session, '$.updatedAt') AS session_updated_at,
|
|
1132
1159
|
CASE
|
|
1133
1160
|
WHEN json_extract(active_session, '$.status') = 'active'
|
|
1134
|
-
AND json_type(active_session, '$.
|
|
1161
|
+
AND json_type(active_session, '$.runtimeGenerationId') = 'text'
|
|
1135
1162
|
THEN 1 ELSE 0
|
|
1136
1163
|
END AS cleanup_required
|
|
1137
1164
|
FROM active`).get(...source.parameters);
|
|
@@ -1151,7 +1178,7 @@ export class SqliteTaskStore {
|
|
|
1151
1178
|
&& row.agent_id === candidate.agentId
|
|
1152
1179
|
&& row.adapter_id === candidate.adapterId
|
|
1153
1180
|
&& row.native_session_id === candidate.nativeSessionId
|
|
1154
|
-
&& (row.
|
|
1181
|
+
&& (row.runtime_generation_id ?? undefined) === candidate.runtimeGenerationId
|
|
1155
1182
|
&& row.is_active === 1
|
|
1156
1183
|
&& row.session_updated_at === candidate.sessionUpdatedAt
|
|
1157
1184
|
&& row.cleanup_required === (candidate.cleanupRequired ? 1 : 0);
|
|
@@ -1192,7 +1219,7 @@ export class SqliteTaskStore {
|
|
|
1192
1219
|
native_session_id = excluded.native_session_id,
|
|
1193
1220
|
launch_id = excluded.launch_id,
|
|
1194
1221
|
session_updated_at = excluded.session_updated_at,
|
|
1195
|
-
cleanup_required = excluded.cleanup_required`).run(candidate.owner.scope, taskId, candidate.owner.roleName, candidate.agentId, candidate.adapterId, candidate.nativeSessionId, candidate.
|
|
1222
|
+
cleanup_required = excluded.cleanup_required`).run(candidate.owner.scope, taskId, candidate.owner.roleName, candidate.agentId, candidate.adapterId, candidate.nativeSessionId, candidate.runtimeGenerationId ?? null, candidate.sessionUpdatedAt, candidate.cleanupRequired ? 1 : 0);
|
|
1196
1223
|
}
|
|
1197
1224
|
#deleteRuntimeSessionCandidate(owner) {
|
|
1198
1225
|
this.#db.prepare(`DELETE FROM runtime_session_candidates
|
|
@@ -1295,8 +1322,7 @@ export class SqliteTaskStore {
|
|
|
1295
1322
|
if (sameExternal.length > 0) {
|
|
1296
1323
|
const latest = latestSqlitePublicationReference(sameExternal);
|
|
1297
1324
|
if (stored.supersedes !== latest.id) {
|
|
1298
|
-
throw new StorageRecordError(`Publication external identity conflicts with ${latest.id};
|
|
1299
|
-
+ `explicitly supersede that record: ${key}.`);
|
|
1325
|
+
throw new StorageRecordError(`Publication external identity conflicts with current record ${latest.id}: ${key}.`);
|
|
1300
1326
|
}
|
|
1301
1327
|
}
|
|
1302
1328
|
else if (stored.supersedes !== undefined) {
|
|
@@ -1500,6 +1526,7 @@ export class SqliteTaskStore {
|
|
|
1500
1526
|
return this.#sortById(this.#listPayload("review_rounds", "task_id = ?", [taskId]), (round) => round.id);
|
|
1501
1527
|
}
|
|
1502
1528
|
saveReviewRound(taskId, round) {
|
|
1529
|
+
validateReviewRound(round);
|
|
1503
1530
|
if (round.taskId !== taskId)
|
|
1504
1531
|
throw new StorageRecordError(`Review round belongs to another Task: ${round.taskId}`);
|
|
1505
1532
|
this.#requireTask(taskId);
|
|
@@ -1509,27 +1536,6 @@ export class SqliteTaskStore {
|
|
|
1509
1536
|
payload = excluded.payload, updated_at = excluded.updated_at`).run(taskId, round.id, round.status, this.#json(round), this.#now());
|
|
1510
1537
|
});
|
|
1511
1538
|
}
|
|
1512
|
-
// -- review findings --------------------------------------------------------
|
|
1513
|
-
nextReviewFindingId(taskId) { return this.#nextTaskRecordId(taskId, "reviewFinding"); }
|
|
1514
|
-
getReviewFinding(taskId, findingId) {
|
|
1515
|
-
return this.#getPayload("review_findings", "task_id = ? AND finding_id = ?", [taskId, findingId]);
|
|
1516
|
-
}
|
|
1517
|
-
listReviewFindings(taskId) {
|
|
1518
|
-
return this.#sortById(this.#listPayload("review_findings", "task_id = ?", [taskId]), (finding) => finding.id);
|
|
1519
|
-
}
|
|
1520
|
-
saveReviewFinding(taskId, finding) {
|
|
1521
|
-
validateReviewFinding(finding);
|
|
1522
|
-
if (finding.taskId !== taskId) {
|
|
1523
|
-
throw new StorageRecordError(`Review finding belongs to another Task: ${finding.taskId}`);
|
|
1524
|
-
}
|
|
1525
|
-
this.#requireTask(taskId);
|
|
1526
|
-
this.#mutate(() => {
|
|
1527
|
-
this.#db.prepare(`INSERT INTO review_findings (task_id, finding_id, stable_key, severity, payload, updated_at)
|
|
1528
|
-
VALUES (?, ?, ?, ?, ?, ?)
|
|
1529
|
-
ON CONFLICT(task_id, finding_id) DO UPDATE SET stable_key = excluded.stable_key,
|
|
1530
|
-
severity = excluded.severity, payload = excluded.payload, updated_at = excluded.updated_at`).run(taskId, finding.id, finding.stableKey, finding.severity, this.#json(finding), this.#now());
|
|
1531
|
-
});
|
|
1532
|
-
}
|
|
1533
1539
|
// -- active turns ----------------------------------------------------------
|
|
1534
1540
|
#saveActiveTurn(taskId, pointer, turnId) {
|
|
1535
1541
|
this.#mutate(() => {
|
|
@@ -1721,6 +1727,18 @@ export class SqliteTaskStore {
|
|
|
1721
1727
|
this.#observeHighWater(taskId, "message", seq);
|
|
1722
1728
|
});
|
|
1723
1729
|
}
|
|
1730
|
+
updateMessage(taskId, message) {
|
|
1731
|
+
if (message.taskId !== taskId) {
|
|
1732
|
+
throw new StorageRecordError(`Message belongs to another Task: ${message.taskId}`);
|
|
1733
|
+
}
|
|
1734
|
+
this.#requireTask(taskId);
|
|
1735
|
+
this.#mutate(() => {
|
|
1736
|
+
const result = this.#db.prepare(`UPDATE messages SET payload = ? WHERE task_id = ? AND message_id = ?`).run(this.#json(message), taskId, message.id);
|
|
1737
|
+
if (result.changes !== 1) {
|
|
1738
|
+
throw new StorageRecordError(`Message does not exist: ${taskId}/${message.id}`);
|
|
1739
|
+
}
|
|
1740
|
+
});
|
|
1741
|
+
}
|
|
1724
1742
|
listMessages(taskId) {
|
|
1725
1743
|
return this.#sortById(this.#listPayload("messages", "task_id = ?", [taskId]), (message) => message.id);
|
|
1726
1744
|
}
|
|
@@ -2109,10 +2127,9 @@ function validIntegrationQueueTransition(before, after) {
|
|
|
2109
2127
|
|| before.createdAt !== after.createdAt)
|
|
2110
2128
|
return false;
|
|
2111
2129
|
const allowed = {
|
|
2112
|
-
queued: ["queued", "running", "
|
|
2130
|
+
queued: ["queued", "running", "superseded"],
|
|
2113
2131
|
running: ["running", "conflicted", "committed"],
|
|
2114
2132
|
conflicted: ["conflicted", "running", "committed", "queued", "superseded"],
|
|
2115
|
-
validated: ["validated", "running", "queued", "superseded"],
|
|
2116
2133
|
committed: ["committed"],
|
|
2117
2134
|
superseded: ["superseded"]
|
|
2118
2135
|
};
|