@hasna/todos 0.11.94 → 0.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -5
- package/dist/cli/cloud-router.d.ts +17 -1
- package/dist/cli/cloud-router.d.ts.map +1 -1
- package/dist/cli/commands/agent-commands.d.ts.map +1 -1
- package/dist/cli/commands/api-key-commands.d.ts.map +1 -1
- package/dist/cli/commands/config-serve-commands.d.ts.map +1 -1
- package/dist/cli/commands/help-commands.d.ts +2 -1
- package/dist/cli/commands/help-commands.d.ts.map +1 -1
- package/dist/cli/commands/knowledge-commands.d.ts.map +1 -1
- package/dist/cli/commands/mcp-hooks-commands.d.ts.map +1 -1
- package/dist/cli/commands/plan-template-commands.d.ts.map +1 -1
- package/dist/cli/commands/pr-group-commands.d.ts +3 -0
- package/dist/cli/commands/pr-group-commands.d.ts.map +1 -0
- package/dist/cli/commands/project-commands.d.ts.map +1 -1
- package/dist/cli/commands/query-commands.d.ts.map +1 -1
- package/dist/cli/commands/risk-commands.d.ts.map +1 -1
- package/dist/cli/commands/task-commands.d.ts.map +1 -1
- package/dist/cli/commands/usage-ledger-commands.d.ts.map +1 -1
- package/dist/cli/helpers.d.ts +23 -0
- package/dist/cli/helpers.d.ts.map +1 -1
- package/dist/cli/index.js +8539 -1940
- package/dist/cli/stage-a.d.ts +17 -0
- package/dist/cli/stage-a.d.ts.map +1 -1
- package/dist/contracts.js +1665 -523
- package/dist/db/agent-names.d.ts +13 -1
- package/dist/db/agent-names.d.ts.map +1 -1
- package/dist/db/agents.d.ts +4 -0
- package/dist/db/agents.d.ts.map +1 -1
- package/dist/db/audit.d.ts.map +1 -1
- package/dist/db/database.d.ts.map +1 -1
- package/dist/db/dispatches.d.ts.map +1 -1
- package/dist/db/identity-mapping.d.ts +40 -0
- package/dist/db/identity-mapping.d.ts.map +1 -0
- package/dist/db/inbox.d.ts.map +1 -1
- package/dist/db/migrations.d.ts.map +1 -1
- package/dist/db/schema.d.ts.map +1 -1
- package/dist/db/storage-tombstones.d.ts +1 -1
- package/dist/db/storage-tombstones.d.ts.map +1 -1
- package/dist/db/task-commits.d.ts.map +1 -1
- package/dist/db/task-crud.d.ts.map +1 -1
- package/dist/db/task-lifecycle.d.ts.map +1 -1
- package/dist/db/templates.d.ts.map +1 -1
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4784 -882
- package/dist/json-contracts.d.ts +4 -2
- package/dist/json-contracts.d.ts.map +1 -1
- package/dist/lib/activity-audit.d.ts.map +1 -1
- package/dist/lib/artifact-store.d.ts.map +1 -1
- package/dist/lib/cli-help.d.ts +14 -3
- package/dist/lib/cli-help.d.ts.map +1 -1
- package/dist/lib/evidence-redaction.d.ts +72 -0
- package/dist/lib/evidence-redaction.d.ts.map +1 -0
- package/dist/lib/headless-boundaries.d.ts +1 -1
- package/dist/lib/headless-boundaries.d.ts.map +1 -1
- package/dist/lib/import-export-bridge.d.ts.map +1 -1
- package/dist/lib/local-bridge.d.ts +1 -0
- package/dist/lib/local-bridge.d.ts.map +1 -1
- package/dist/lib/local-encryption.d.ts.map +1 -1
- package/dist/lib/prewrite-secrets.d.ts +29 -0
- package/dist/lib/prewrite-secrets.d.ts.map +1 -0
- package/dist/lib/redaction.d.ts.map +1 -1
- package/dist/lib/search.d.ts +3 -0
- package/dist/lib/search.d.ts.map +1 -1
- package/dist/lib/task-route-sources.d.ts +2 -0
- package/dist/lib/task-route-sources.d.ts.map +1 -1
- package/dist/lib/task-routing.d.ts.map +1 -1
- package/dist/lib/template-semantics.d.ts +8 -0
- package/dist/lib/template-semantics.d.ts.map +1 -0
- package/dist/mcp/index.js +6631 -1062
- package/dist/mcp.js +1 -1
- package/dist/pr-groups/http-client.d.ts +24 -0
- package/dist/pr-groups/http-client.d.ts.map +1 -0
- package/dist/pr-groups/index.d.ts +9 -0
- package/dist/pr-groups/index.d.ts.map +1 -0
- package/dist/pr-groups/ledger.d.ts +20 -0
- package/dist/pr-groups/ledger.d.ts.map +1 -0
- package/dist/pr-groups/postgres.d.ts +21 -0
- package/dist/pr-groups/postgres.d.ts.map +1 -0
- package/dist/pr-groups/sqlite.d.ts +16 -0
- package/dist/pr-groups/sqlite.d.ts.map +1 -0
- package/dist/pr-groups/types.d.ts +320 -0
- package/dist/pr-groups/types.d.ts.map +1 -0
- package/dist/registry.js +1665 -523
- package/dist/release-provenance.json +5 -5
- package/dist/sdk/client.d.ts +16 -0
- package/dist/sdk/client.d.ts.map +1 -1
- package/dist/sdk/index.d.ts +2 -1
- package/dist/sdk/index.d.ts.map +1 -1
- package/dist/sdk/index.js +99 -0
- package/dist/sdk/v1.generated.d.ts +388 -1
- package/dist/sdk/v1.generated.d.ts.map +1 -1
- package/dist/server/cloud.d.ts +3 -0
- package/dist/server/cloud.d.ts.map +1 -1
- package/dist/server/index.js +6538 -969
- package/dist/server/openapi.d.ts +2020 -278
- package/dist/server/openapi.d.ts.map +1 -1
- package/dist/server/pr-groups.d.ts +7 -0
- package/dist/server/pr-groups.d.ts.map +1 -0
- package/dist/server/serve.d.ts.map +1 -1
- package/dist/server/v1.d.ts +2 -1
- package/dist/server/v1.d.ts.map +1 -1
- package/dist/storage/cloud-client.d.ts +1 -0
- package/dist/storage/cloud-client.d.ts.map +1 -1
- package/dist/storage/config.d.ts +5 -5
- package/dist/storage/config.d.ts.map +1 -1
- package/dist/storage/index.d.ts +1 -1
- package/dist/storage/index.d.ts.map +1 -1
- package/dist/storage/interfaces.d.ts +3 -2
- package/dist/storage/interfaces.d.ts.map +1 -1
- package/dist/storage/local-sqlite.d.ts.map +1 -1
- package/dist/storage/postgres-adapter.d.ts.map +1 -1
- package/dist/storage/postgres-sync.d.ts +1 -1
- package/dist/storage/postgres-sync.d.ts.map +1 -1
- package/dist/storage/shadow-outbox.d.ts.map +1 -1
- package/dist/storage/shadow.d.ts +1 -1
- package/dist/storage/shadow.d.ts.map +1 -1
- package/dist/storage/sqlite-snapshot.d.ts.map +1 -1
- package/dist/storage.d.ts +1 -1
- package/dist/storage.d.ts.map +1 -1
- package/dist/storage.js +1958 -524
- package/dist/types/index.d.ts +90 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/registry.js
CHANGED
|
@@ -1203,27 +1203,478 @@ var init_migrations = __esm(() => {
|
|
|
1203
1203
|
ALTER TABLE plans ADD COLUMN slug TEXT;
|
|
1204
1204
|
CREATE INDEX IF NOT EXISTS idx_plans_slug ON plans(slug);
|
|
1205
1205
|
INSERT OR IGNORE INTO _migrations (id) VALUES (64);
|
|
1206
|
+
`,
|
|
1207
|
+
`
|
|
1208
|
+
ALTER TABLE agents ADD COLUMN identity_id TEXT;
|
|
1209
|
+
ALTER TABLE agents ADD COLUMN project_id TEXT;
|
|
1210
|
+
ALTER TABLE agents ADD COLUMN runtime_instance_id TEXT;
|
|
1211
|
+
|
|
1212
|
+
CREATE TABLE IF NOT EXISTS agent_identity_source_mappings (
|
|
1213
|
+
id TEXT PRIMARY KEY,
|
|
1214
|
+
local_agent_id TEXT REFERENCES agents(id) ON DELETE RESTRICT,
|
|
1215
|
+
identity_id TEXT,
|
|
1216
|
+
source_authority TEXT NOT NULL CHECK(length(trim(source_authority)) > 0),
|
|
1217
|
+
source_tenant_id TEXT NOT NULL CHECK(length(trim(source_tenant_id)) > 0),
|
|
1218
|
+
source_namespace TEXT NOT NULL CHECK(length(trim(source_namespace)) > 0),
|
|
1219
|
+
source_entity_type TEXT NOT NULL CHECK(length(trim(source_entity_type)) > 0),
|
|
1220
|
+
source_record_id TEXT NOT NULL CHECK(length(trim(source_record_id)) > 0),
|
|
1221
|
+
observed_label TEXT,
|
|
1222
|
+
evidence TEXT NOT NULL DEFAULT '{}',
|
|
1223
|
+
mapping_basis TEXT NOT NULL CHECK(mapping_basis IN ('authoritative', 'imported', 'candidate', 'name_similarity')),
|
|
1224
|
+
status TEXT NOT NULL CHECK(status IN ('active', 'retired', 'quarantined', 'revoked')),
|
|
1225
|
+
revision INTEGER NOT NULL CHECK(revision > 0),
|
|
1226
|
+
created_at TEXT NOT NULL,
|
|
1227
|
+
updated_at TEXT NOT NULL,
|
|
1228
|
+
CHECK(mapping_basis IN ('authoritative', 'imported') OR status IN ('quarantined', 'revoked')),
|
|
1229
|
+
CHECK(status != 'active' OR (mapping_basis IN ('authoritative', 'imported') AND identity_id IS NOT NULL))
|
|
1230
|
+
);
|
|
1231
|
+
DROP INDEX IF EXISTS idx_agent_identity_source_active_unique;
|
|
1232
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_agent_identity_source_revision_unique
|
|
1233
|
+
ON agent_identity_source_mappings (
|
|
1234
|
+
source_authority, source_tenant_id, source_namespace, source_entity_type, source_record_id, revision
|
|
1235
|
+
)
|
|
1236
|
+
WHERE mapping_basis IN ('authoritative', 'imported');
|
|
1237
|
+
CREATE INDEX IF NOT EXISTS idx_agent_identity_source_identity
|
|
1238
|
+
ON agent_identity_source_mappings(identity_id);
|
|
1239
|
+
CREATE INDEX IF NOT EXISTS idx_agent_identity_source_local
|
|
1240
|
+
ON agent_identity_source_mappings(local_agent_id);
|
|
1241
|
+
CREATE TABLE IF NOT EXISTS agent_identity_aliases (
|
|
1242
|
+
id TEXT PRIMARY KEY,
|
|
1243
|
+
local_agent_id TEXT NOT NULL REFERENCES agents(id) ON DELETE RESTRICT,
|
|
1244
|
+
label TEXT NOT NULL,
|
|
1245
|
+
normalized_label TEXT NOT NULL,
|
|
1246
|
+
alias_kind TEXT NOT NULL CHECK(alias_kind IN ('historical', 'candidate')),
|
|
1247
|
+
status TEXT NOT NULL CHECK(status IN ('active', 'quarantined', 'revoked')),
|
|
1248
|
+
created_at TEXT NOT NULL,
|
|
1249
|
+
UNIQUE(local_agent_id, normalized_label)
|
|
1250
|
+
);
|
|
1251
|
+
CREATE INDEX IF NOT EXISTS idx_agent_identity_alias_lookup
|
|
1252
|
+
ON agent_identity_aliases(normalized_label, status);
|
|
1253
|
+
|
|
1254
|
+
CREATE TRIGGER IF NOT EXISTS trg_agents_identity_id_immutable
|
|
1255
|
+
BEFORE UPDATE OF identity_id ON agents
|
|
1256
|
+
WHEN OLD.identity_id IS NOT NULL AND OLD.identity_id IS NOT NEW.identity_id
|
|
1257
|
+
BEGIN
|
|
1258
|
+
SELECT RAISE(ABORT, 'IDENTITY_ID_IMMUTABLE');
|
|
1259
|
+
END;
|
|
1260
|
+
|
|
1261
|
+
CREATE TRIGGER IF NOT EXISTS trg_agent_identity_mapping_identity_immutable
|
|
1262
|
+
BEFORE UPDATE OF identity_id ON agent_identity_source_mappings
|
|
1263
|
+
WHEN OLD.identity_id IS NOT NEW.identity_id
|
|
1264
|
+
BEGIN
|
|
1265
|
+
SELECT RAISE(ABORT, 'IDENTITY_ID_IMMUTABLE');
|
|
1266
|
+
END;
|
|
1267
|
+
|
|
1268
|
+
CREATE TRIGGER IF NOT EXISTS trg_agent_identity_mapping_lineage_immutable
|
|
1269
|
+
BEFORE UPDATE OF source_authority, source_tenant_id, source_namespace, source_entity_type, source_record_id
|
|
1270
|
+
ON agent_identity_source_mappings
|
|
1271
|
+
WHEN OLD.source_authority IS NOT NEW.source_authority
|
|
1272
|
+
OR OLD.source_tenant_id IS NOT NEW.source_tenant_id
|
|
1273
|
+
OR OLD.source_namespace IS NOT NEW.source_namespace
|
|
1274
|
+
OR OLD.source_entity_type IS NOT NEW.source_entity_type
|
|
1275
|
+
OR OLD.source_record_id IS NOT NEW.source_record_id
|
|
1276
|
+
BEGIN
|
|
1277
|
+
SELECT RAISE(ABORT, 'IDENTITY_SOURCE_LINEAGE_IMMUTABLE');
|
|
1278
|
+
END;
|
|
1279
|
+
|
|
1280
|
+
CREATE TRIGGER IF NOT EXISTS trg_agent_identity_mapping_history_immutable
|
|
1281
|
+
BEFORE UPDATE OF local_agent_id, observed_label, evidence, mapping_basis, status, revision, created_at
|
|
1282
|
+
ON agent_identity_source_mappings
|
|
1283
|
+
BEGIN
|
|
1284
|
+
SELECT RAISE(ABORT, 'IDENTITY_MAPPING_HISTORY_IMMUTABLE');
|
|
1285
|
+
END;
|
|
1286
|
+
|
|
1287
|
+
CREATE TRIGGER IF NOT EXISTS trg_agent_identity_mapping_history_append_only
|
|
1288
|
+
BEFORE DELETE ON agent_identity_source_mappings
|
|
1289
|
+
BEGIN
|
|
1290
|
+
SELECT RAISE(ABORT, 'IDENTITY_MAPPING_HISTORY_IMMUTABLE');
|
|
1291
|
+
END;
|
|
1292
|
+
|
|
1293
|
+
INSERT OR IGNORE INTO _migrations (id) VALUES (65);
|
|
1294
|
+
`,
|
|
1295
|
+
`
|
|
1296
|
+
CREATE TABLE IF NOT EXISTS pr_groups (
|
|
1297
|
+
schema_version INTEGER NOT NULL DEFAULT 1,
|
|
1298
|
+
id TEXT PRIMARY KEY,
|
|
1299
|
+
identity_key TEXT NOT NULL UNIQUE,
|
|
1300
|
+
root_request_id TEXT NOT NULL,
|
|
1301
|
+
repository TEXT NOT NULL,
|
|
1302
|
+
state TEXT NOT NULL,
|
|
1303
|
+
active_attempt_id TEXT,
|
|
1304
|
+
active_generation TEXT,
|
|
1305
|
+
terminal_attempt_id TEXT,
|
|
1306
|
+
terminal_generation TEXT,
|
|
1307
|
+
terminal_outcome TEXT,
|
|
1308
|
+
terminal_head_sha TEXT,
|
|
1309
|
+
terminal_at TEXT,
|
|
1310
|
+
cleanup_eligible_at TEXT,
|
|
1311
|
+
revision INTEGER NOT NULL DEFAULT 1,
|
|
1312
|
+
created_at TEXT NOT NULL,
|
|
1313
|
+
updated_at TEXT NOT NULL
|
|
1314
|
+
);
|
|
1315
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_pr_groups_identity ON pr_groups(identity_key);
|
|
1316
|
+
CREATE INDEX IF NOT EXISTS idx_pr_groups_root_repository ON pr_groups(root_request_id, repository);
|
|
1317
|
+
CREATE INDEX IF NOT EXISTS idx_pr_groups_active_generation ON pr_groups(active_generation);
|
|
1318
|
+
|
|
1319
|
+
CREATE TABLE IF NOT EXISTS pr_group_attempts (
|
|
1320
|
+
schema_version INTEGER NOT NULL DEFAULT 1,
|
|
1321
|
+
id TEXT PRIMARY KEY,
|
|
1322
|
+
group_id TEXT NOT NULL REFERENCES pr_groups(id) ON DELETE CASCADE,
|
|
1323
|
+
leaf_task_id TEXT NOT NULL,
|
|
1324
|
+
dispatch_attempt TEXT NOT NULL,
|
|
1325
|
+
writer_generation TEXT NOT NULL,
|
|
1326
|
+
previous_attempt_id TEXT REFERENCES pr_group_attempts(id) ON DELETE SET NULL,
|
|
1327
|
+
worktree TEXT NOT NULL,
|
|
1328
|
+
branch TEXT NOT NULL,
|
|
1329
|
+
provider TEXT,
|
|
1330
|
+
provider_run_id TEXT,
|
|
1331
|
+
profile_alias TEXT,
|
|
1332
|
+
status TEXT NOT NULL,
|
|
1333
|
+
admitted_at TEXT NOT NULL,
|
|
1334
|
+
started_at TEXT,
|
|
1335
|
+
last_heartbeat_at TEXT,
|
|
1336
|
+
handed_off_at TEXT,
|
|
1337
|
+
fenced_at TEXT,
|
|
1338
|
+
terminal_at TEXT,
|
|
1339
|
+
created_at TEXT NOT NULL,
|
|
1340
|
+
updated_at TEXT NOT NULL,
|
|
1341
|
+
UNIQUE(group_id, leaf_task_id, dispatch_attempt),
|
|
1342
|
+
UNIQUE(group_id, writer_generation)
|
|
1343
|
+
);
|
|
1344
|
+
CREATE INDEX IF NOT EXISTS idx_pr_group_attempts_group ON pr_group_attempts(group_id, created_at, id);
|
|
1345
|
+
CREATE INDEX IF NOT EXISTS idx_pr_group_attempts_generation ON pr_group_attempts(group_id, writer_generation);
|
|
1346
|
+
|
|
1347
|
+
CREATE TABLE IF NOT EXISTS pr_group_events (
|
|
1348
|
+
schema_version INTEGER NOT NULL DEFAULT 1,
|
|
1349
|
+
id TEXT PRIMARY KEY,
|
|
1350
|
+
group_id TEXT NOT NULL REFERENCES pr_groups(id) ON DELETE CASCADE,
|
|
1351
|
+
attempt_id TEXT NOT NULL REFERENCES pr_group_attempts(id) ON DELETE CASCADE,
|
|
1352
|
+
writer_generation TEXT NOT NULL,
|
|
1353
|
+
sequence INTEGER NOT NULL,
|
|
1354
|
+
idempotency_key TEXT NOT NULL,
|
|
1355
|
+
event_type TEXT NOT NULL,
|
|
1356
|
+
state TEXT NOT NULL,
|
|
1357
|
+
message TEXT,
|
|
1358
|
+
head_sha TEXT,
|
|
1359
|
+
receipt_key TEXT,
|
|
1360
|
+
outcome TEXT,
|
|
1361
|
+
metadata TEXT NOT NULL DEFAULT '{}',
|
|
1362
|
+
payload_hash TEXT NOT NULL,
|
|
1363
|
+
created_at TEXT NOT NULL,
|
|
1364
|
+
UNIQUE(group_id, sequence),
|
|
1365
|
+
UNIQUE(group_id, idempotency_key),
|
|
1366
|
+
UNIQUE(group_id, receipt_key)
|
|
1367
|
+
);
|
|
1368
|
+
CREATE INDEX IF NOT EXISTS idx_pr_group_events_group_sequence ON pr_group_events(group_id, sequence);
|
|
1369
|
+
CREATE INDEX IF NOT EXISTS idx_pr_group_events_attempt ON pr_group_events(attempt_id, sequence);
|
|
1370
|
+
CREATE INDEX IF NOT EXISTS idx_pr_group_events_receipt ON pr_group_events(group_id, receipt_key);
|
|
1371
|
+
INSERT OR IGNORE INTO _migrations (id) VALUES (66);
|
|
1372
|
+
`,
|
|
1373
|
+
`
|
|
1374
|
+
ALTER TABLE pr_groups ADD COLUMN leaf_task_id TEXT;
|
|
1375
|
+
ALTER TABLE pr_groups ADD COLUMN branch TEXT;
|
|
1376
|
+
ALTER TABLE pr_groups ADD COLUMN pr_number INTEGER;
|
|
1377
|
+
ALTER TABLE pr_groups ADD COLUMN base_sha TEXT;
|
|
1378
|
+
ALTER TABLE pr_groups ADD COLUMN repair_cycle_count INTEGER NOT NULL DEFAULT 0;
|
|
1379
|
+
ALTER TABLE pr_groups ADD COLUMN repair_cycle_limit INTEGER NOT NULL DEFAULT 2;
|
|
1380
|
+
ALTER TABLE pr_group_attempts ADD COLUMN repository TEXT;
|
|
1381
|
+
ALTER TABLE pr_group_attempts ADD COLUMN pr_number INTEGER;
|
|
1382
|
+
ALTER TABLE pr_group_attempts ADD COLUMN base_sha TEXT;
|
|
1383
|
+
ALTER TABLE pr_group_events ADD COLUMN review_receipt_key TEXT;
|
|
1384
|
+
ALTER TABLE pr_group_events ADD COLUMN conditional_merge_receipt_key TEXT;
|
|
1385
|
+
ALTER TABLE pr_group_events ADD COLUMN repository TEXT;
|
|
1386
|
+
ALTER TABLE pr_group_events ADD COLUMN pr_number INTEGER;
|
|
1387
|
+
ALTER TABLE pr_group_events ADD COLUMN base_sha TEXT;
|
|
1388
|
+
ALTER TABLE pr_group_events ADD COLUMN actor_id TEXT;
|
|
1389
|
+
ALTER TABLE pr_group_events ADD COLUMN actor_run_id TEXT;
|
|
1390
|
+
ALTER TABLE pr_group_events ADD COLUMN expected_reviewer_id TEXT;
|
|
1391
|
+
ALTER TABLE pr_group_events ADD COLUMN expected_reviewer_run_id TEXT;
|
|
1392
|
+
ALTER TABLE pr_group_events ADD COLUMN repair_cycle INTEGER;
|
|
1393
|
+
ALTER TABLE pr_group_events ADD COLUMN cleanup_proof TEXT;
|
|
1394
|
+
|
|
1395
|
+
UPDATE pr_groups
|
|
1396
|
+
SET leaf_task_id = COALESCE(leaf_task_id, (
|
|
1397
|
+
SELECT attempt.leaf_task_id
|
|
1398
|
+
FROM pr_group_attempts AS attempt
|
|
1399
|
+
WHERE attempt.group_id = pr_groups.id
|
|
1400
|
+
ORDER BY CASE WHEN attempt.id = pr_groups.active_attempt_id THEN 0 ELSE 1 END,
|
|
1401
|
+
attempt.created_at ASC, attempt.id ASC
|
|
1402
|
+
LIMIT 1
|
|
1403
|
+
)),
|
|
1404
|
+
branch = COALESCE(branch, (
|
|
1405
|
+
SELECT attempt.branch
|
|
1406
|
+
FROM pr_group_attempts AS attempt
|
|
1407
|
+
WHERE attempt.group_id = pr_groups.id
|
|
1408
|
+
ORDER BY CASE WHEN attempt.id = pr_groups.active_attempt_id THEN 0 ELSE 1 END,
|
|
1409
|
+
attempt.created_at ASC, attempt.id ASC
|
|
1410
|
+
LIMIT 1
|
|
1411
|
+
));
|
|
1412
|
+
|
|
1413
|
+
UPDATE pr_group_attempts
|
|
1414
|
+
SET repository = COALESCE(repository, (
|
|
1415
|
+
SELECT groups.repository FROM pr_groups AS groups
|
|
1416
|
+
WHERE groups.id = pr_group_attempts.group_id
|
|
1417
|
+
)),
|
|
1418
|
+
pr_number = COALESCE(pr_number, (
|
|
1419
|
+
SELECT groups.pr_number FROM pr_groups AS groups
|
|
1420
|
+
WHERE groups.id = pr_group_attempts.group_id
|
|
1421
|
+
)),
|
|
1422
|
+
base_sha = COALESCE(base_sha, (
|
|
1423
|
+
SELECT groups.base_sha FROM pr_groups AS groups
|
|
1424
|
+
WHERE groups.id = pr_group_attempts.group_id
|
|
1425
|
+
));
|
|
1426
|
+
|
|
1427
|
+
UPDATE pr_groups
|
|
1428
|
+
SET pr_number = COALESCE(pr_number, (
|
|
1429
|
+
SELECT attempt.pr_number
|
|
1430
|
+
FROM pr_group_attempts AS attempt
|
|
1431
|
+
WHERE attempt.group_id = pr_groups.id AND attempt.pr_number IS NOT NULL
|
|
1432
|
+
ORDER BY CASE WHEN attempt.id = pr_groups.active_attempt_id THEN 0 ELSE 1 END,
|
|
1433
|
+
attempt.created_at ASC, attempt.id ASC
|
|
1434
|
+
LIMIT 1
|
|
1435
|
+
)),
|
|
1436
|
+
base_sha = COALESCE(base_sha, (
|
|
1437
|
+
SELECT attempt.base_sha
|
|
1438
|
+
FROM pr_group_attempts AS attempt
|
|
1439
|
+
WHERE attempt.group_id = pr_groups.id AND attempt.base_sha IS NOT NULL
|
|
1440
|
+
ORDER BY CASE WHEN attempt.id = pr_groups.active_attempt_id THEN 0 ELSE 1 END,
|
|
1441
|
+
attempt.created_at ASC, attempt.id ASC
|
|
1442
|
+
LIMIT 1
|
|
1443
|
+
));
|
|
1444
|
+
|
|
1445
|
+
UPDATE pr_group_attempts
|
|
1446
|
+
SET pr_number = COALESCE(pr_number, (
|
|
1447
|
+
SELECT groups.pr_number FROM pr_groups AS groups
|
|
1448
|
+
WHERE groups.id = pr_group_attempts.group_id
|
|
1449
|
+
)),
|
|
1450
|
+
base_sha = COALESCE(base_sha, (
|
|
1451
|
+
SELECT groups.base_sha FROM pr_groups AS groups
|
|
1452
|
+
WHERE groups.id = pr_group_attempts.group_id
|
|
1453
|
+
));
|
|
1454
|
+
|
|
1455
|
+
UPDATE pr_group_events
|
|
1456
|
+
SET repository = COALESCE(repository, (
|
|
1457
|
+
SELECT attempt.repository FROM pr_group_attempts AS attempt
|
|
1458
|
+
WHERE attempt.id = pr_group_events.attempt_id
|
|
1459
|
+
), (
|
|
1460
|
+
SELECT groups.repository FROM pr_groups AS groups
|
|
1461
|
+
WHERE groups.id = pr_group_events.group_id
|
|
1462
|
+
)),
|
|
1463
|
+
pr_number = COALESCE(pr_number, (
|
|
1464
|
+
SELECT attempt.pr_number FROM pr_group_attempts AS attempt
|
|
1465
|
+
WHERE attempt.id = pr_group_events.attempt_id
|
|
1466
|
+
), (
|
|
1467
|
+
SELECT groups.pr_number FROM pr_groups AS groups
|
|
1468
|
+
WHERE groups.id = pr_group_events.group_id
|
|
1469
|
+
)),
|
|
1470
|
+
base_sha = COALESCE(base_sha, (
|
|
1471
|
+
SELECT attempt.base_sha FROM pr_group_attempts AS attempt
|
|
1472
|
+
WHERE attempt.id = pr_group_events.attempt_id
|
|
1473
|
+
), (
|
|
1474
|
+
SELECT groups.base_sha FROM pr_groups AS groups
|
|
1475
|
+
WHERE groups.id = pr_group_events.group_id
|
|
1476
|
+
));
|
|
1477
|
+
|
|
1478
|
+
INSERT OR IGNORE INTO _migrations (id)
|
|
1479
|
+
SELECT 66
|
|
1480
|
+
WHERE NOT EXISTS (
|
|
1481
|
+
SELECT 1 FROM pr_groups WHERE leaf_task_id IS NULL OR branch IS NULL
|
|
1482
|
+
)
|
|
1483
|
+
AND NOT EXISTS (
|
|
1484
|
+
SELECT 1 FROM pr_group_attempts WHERE repository IS NULL
|
|
1485
|
+
)
|
|
1486
|
+
AND NOT EXISTS (
|
|
1487
|
+
SELECT 1 FROM pr_group_events WHERE repository IS NULL
|
|
1488
|
+
);
|
|
1489
|
+
`,
|
|
1490
|
+
`
|
|
1491
|
+
PRAGMA foreign_keys = OFF;
|
|
1492
|
+
BEGIN IMMEDIATE;
|
|
1493
|
+
|
|
1494
|
+
ALTER TABLE pr_group_events RENAME TO pr_group_events_v66;
|
|
1495
|
+
ALTER TABLE pr_group_attempts RENAME TO pr_group_attempts_v66;
|
|
1496
|
+
ALTER TABLE pr_groups RENAME TO pr_groups_v66;
|
|
1497
|
+
|
|
1498
|
+
CREATE TABLE pr_groups (
|
|
1499
|
+
schema_version INTEGER NOT NULL DEFAULT 1,
|
|
1500
|
+
id TEXT PRIMARY KEY,
|
|
1501
|
+
identity_key TEXT NOT NULL UNIQUE,
|
|
1502
|
+
root_request_id TEXT NOT NULL,
|
|
1503
|
+
repository TEXT NOT NULL,
|
|
1504
|
+
leaf_task_id TEXT NOT NULL,
|
|
1505
|
+
branch TEXT NOT NULL,
|
|
1506
|
+
pr_number INTEGER,
|
|
1507
|
+
base_sha TEXT,
|
|
1508
|
+
state TEXT NOT NULL,
|
|
1509
|
+
active_attempt_id TEXT,
|
|
1510
|
+
active_generation TEXT,
|
|
1511
|
+
repair_cycle_count INTEGER NOT NULL DEFAULT 0,
|
|
1512
|
+
repair_cycle_limit INTEGER NOT NULL DEFAULT 2,
|
|
1513
|
+
terminal_attempt_id TEXT,
|
|
1514
|
+
terminal_generation TEXT,
|
|
1515
|
+
terminal_outcome TEXT,
|
|
1516
|
+
terminal_head_sha TEXT,
|
|
1517
|
+
terminal_at TEXT,
|
|
1518
|
+
cleanup_eligible_at TEXT,
|
|
1519
|
+
revision INTEGER NOT NULL DEFAULT 1,
|
|
1520
|
+
created_at TEXT NOT NULL,
|
|
1521
|
+
updated_at TEXT NOT NULL
|
|
1522
|
+
);
|
|
1523
|
+
|
|
1524
|
+
CREATE TABLE pr_group_attempts (
|
|
1525
|
+
schema_version INTEGER NOT NULL DEFAULT 1,
|
|
1526
|
+
id TEXT PRIMARY KEY,
|
|
1527
|
+
group_id TEXT NOT NULL REFERENCES pr_groups(id) ON DELETE CASCADE,
|
|
1528
|
+
leaf_task_id TEXT NOT NULL,
|
|
1529
|
+
dispatch_attempt TEXT NOT NULL,
|
|
1530
|
+
writer_generation TEXT NOT NULL,
|
|
1531
|
+
previous_attempt_id TEXT REFERENCES pr_group_attempts(id) ON DELETE SET NULL,
|
|
1532
|
+
worktree TEXT NOT NULL,
|
|
1533
|
+
branch TEXT NOT NULL,
|
|
1534
|
+
repository TEXT NOT NULL,
|
|
1535
|
+
pr_number INTEGER,
|
|
1536
|
+
base_sha TEXT,
|
|
1537
|
+
provider TEXT,
|
|
1538
|
+
provider_run_id TEXT,
|
|
1539
|
+
profile_alias TEXT,
|
|
1540
|
+
status TEXT NOT NULL,
|
|
1541
|
+
admitted_at TEXT NOT NULL,
|
|
1542
|
+
started_at TEXT,
|
|
1543
|
+
last_heartbeat_at TEXT,
|
|
1544
|
+
handed_off_at TEXT,
|
|
1545
|
+
fenced_at TEXT,
|
|
1546
|
+
terminal_at TEXT,
|
|
1547
|
+
created_at TEXT NOT NULL,
|
|
1548
|
+
updated_at TEXT NOT NULL,
|
|
1549
|
+
UNIQUE(group_id, leaf_task_id, dispatch_attempt),
|
|
1550
|
+
UNIQUE(group_id, writer_generation)
|
|
1551
|
+
);
|
|
1552
|
+
|
|
1553
|
+
CREATE TABLE pr_group_events (
|
|
1554
|
+
schema_version INTEGER NOT NULL DEFAULT 1,
|
|
1555
|
+
id TEXT PRIMARY KEY,
|
|
1556
|
+
group_id TEXT NOT NULL REFERENCES pr_groups(id) ON DELETE CASCADE,
|
|
1557
|
+
attempt_id TEXT NOT NULL REFERENCES pr_group_attempts(id) ON DELETE CASCADE,
|
|
1558
|
+
writer_generation TEXT NOT NULL,
|
|
1559
|
+
sequence INTEGER NOT NULL,
|
|
1560
|
+
idempotency_key TEXT NOT NULL,
|
|
1561
|
+
event_type TEXT NOT NULL,
|
|
1562
|
+
state TEXT NOT NULL,
|
|
1563
|
+
message TEXT,
|
|
1564
|
+
head_sha TEXT,
|
|
1565
|
+
receipt_key TEXT UNIQUE,
|
|
1566
|
+
review_receipt_key TEXT,
|
|
1567
|
+
conditional_merge_receipt_key TEXT,
|
|
1568
|
+
outcome TEXT,
|
|
1569
|
+
repository TEXT NOT NULL,
|
|
1570
|
+
pr_number INTEGER,
|
|
1571
|
+
base_sha TEXT,
|
|
1572
|
+
actor_id TEXT,
|
|
1573
|
+
actor_run_id TEXT,
|
|
1574
|
+
expected_reviewer_id TEXT,
|
|
1575
|
+
expected_reviewer_run_id TEXT,
|
|
1576
|
+
repair_cycle INTEGER,
|
|
1577
|
+
ci_proof TEXT,
|
|
1578
|
+
cleanup_proof TEXT,
|
|
1579
|
+
metadata TEXT NOT NULL DEFAULT '{}',
|
|
1580
|
+
payload_hash TEXT NOT NULL,
|
|
1581
|
+
created_at TEXT NOT NULL,
|
|
1582
|
+
UNIQUE(group_id, sequence),
|
|
1583
|
+
UNIQUE(group_id, idempotency_key)
|
|
1584
|
+
);
|
|
1585
|
+
|
|
1586
|
+
INSERT INTO pr_groups (
|
|
1587
|
+
schema_version,id,identity_key,root_request_id,repository,leaf_task_id,branch,
|
|
1588
|
+
pr_number,base_sha,state,active_attempt_id,active_generation,
|
|
1589
|
+
repair_cycle_count,repair_cycle_limit,terminal_attempt_id,terminal_generation,
|
|
1590
|
+
terminal_outcome,terminal_head_sha,terminal_at,cleanup_eligible_at,
|
|
1591
|
+
revision,created_at,updated_at
|
|
1592
|
+
)
|
|
1593
|
+
SELECT
|
|
1594
|
+
schema_version,id,identity_key,root_request_id,repository,leaf_task_id,branch,
|
|
1595
|
+
pr_number,base_sha,state,active_attempt_id,active_generation,
|
|
1596
|
+
repair_cycle_count,repair_cycle_limit,terminal_attempt_id,terminal_generation,
|
|
1597
|
+
terminal_outcome,terminal_head_sha,terminal_at,cleanup_eligible_at,
|
|
1598
|
+
revision,created_at,updated_at
|
|
1599
|
+
FROM pr_groups_v66;
|
|
1600
|
+
|
|
1601
|
+
INSERT INTO pr_group_attempts (
|
|
1602
|
+
schema_version,id,group_id,leaf_task_id,dispatch_attempt,writer_generation,
|
|
1603
|
+
previous_attempt_id,worktree,branch,repository,pr_number,base_sha,
|
|
1604
|
+
provider,provider_run_id,profile_alias,status,admitted_at,started_at,
|
|
1605
|
+
last_heartbeat_at,handed_off_at,fenced_at,terminal_at,created_at,updated_at
|
|
1606
|
+
)
|
|
1607
|
+
SELECT
|
|
1608
|
+
schema_version,id,group_id,leaf_task_id,dispatch_attempt,writer_generation,
|
|
1609
|
+
previous_attempt_id,worktree,branch,repository,pr_number,base_sha,
|
|
1610
|
+
provider,provider_run_id,profile_alias,status,admitted_at,started_at,
|
|
1611
|
+
last_heartbeat_at,handed_off_at,fenced_at,terminal_at,created_at,updated_at
|
|
1612
|
+
FROM pr_group_attempts_v66;
|
|
1613
|
+
|
|
1614
|
+
INSERT INTO pr_group_events (
|
|
1615
|
+
schema_version,id,group_id,attempt_id,writer_generation,sequence,
|
|
1616
|
+
idempotency_key,event_type,state,message,head_sha,receipt_key,
|
|
1617
|
+
review_receipt_key,conditional_merge_receipt_key,outcome,repository,
|
|
1618
|
+
pr_number,base_sha,actor_id,actor_run_id,expected_reviewer_id,
|
|
1619
|
+
expected_reviewer_run_id,repair_cycle,ci_proof,cleanup_proof,
|
|
1620
|
+
metadata,payload_hash,created_at
|
|
1621
|
+
)
|
|
1622
|
+
SELECT
|
|
1623
|
+
schema_version,id,group_id,attempt_id,writer_generation,sequence,
|
|
1624
|
+
idempotency_key,event_type,state,message,head_sha,receipt_key,
|
|
1625
|
+
review_receipt_key,conditional_merge_receipt_key,outcome,repository,
|
|
1626
|
+
pr_number,base_sha,actor_id,actor_run_id,expected_reviewer_id,
|
|
1627
|
+
expected_reviewer_run_id,repair_cycle,NULL,cleanup_proof,
|
|
1628
|
+
metadata,payload_hash,created_at
|
|
1629
|
+
FROM pr_group_events_v66;
|
|
1630
|
+
|
|
1631
|
+
DROP TABLE pr_group_events_v66;
|
|
1632
|
+
DROP TABLE pr_group_attempts_v66;
|
|
1633
|
+
DROP TABLE pr_groups_v66;
|
|
1634
|
+
|
|
1635
|
+
CREATE UNIQUE INDEX idx_pr_groups_identity ON pr_groups(identity_key);
|
|
1636
|
+
CREATE INDEX idx_pr_groups_root_repository ON pr_groups(root_request_id, repository);
|
|
1637
|
+
CREATE INDEX idx_pr_groups_active_generation ON pr_groups(active_generation);
|
|
1638
|
+
CREATE INDEX idx_pr_group_attempts_group ON pr_group_attempts(group_id, created_at, id);
|
|
1639
|
+
CREATE INDEX idx_pr_group_attempts_generation ON pr_group_attempts(group_id, writer_generation);
|
|
1640
|
+
CREATE INDEX idx_pr_group_events_group_sequence ON pr_group_events(group_id, sequence);
|
|
1641
|
+
CREATE INDEX idx_pr_group_events_attempt ON pr_group_events(attempt_id, sequence);
|
|
1642
|
+
CREATE INDEX idx_pr_group_events_receipt ON pr_group_events(receipt_key);
|
|
1643
|
+
|
|
1644
|
+
INSERT OR IGNORE INTO _migrations (id) VALUES (68);
|
|
1645
|
+
COMMIT;
|
|
1646
|
+
PRAGMA foreign_keys = ON;
|
|
1206
1647
|
`
|
|
1207
1648
|
];
|
|
1208
1649
|
});
|
|
1209
1650
|
|
|
1210
1651
|
// src/db/schema.ts
|
|
1211
1652
|
function runMigrations(db) {
|
|
1653
|
+
let strictMigrationFailure = null;
|
|
1654
|
+
const executeMigration = (migration, index) => {
|
|
1655
|
+
try {
|
|
1656
|
+
db.exec(migration);
|
|
1657
|
+
} catch (error) {
|
|
1658
|
+
try {
|
|
1659
|
+
db.exec("ROLLBACK");
|
|
1660
|
+
} catch {}
|
|
1661
|
+
if (index + 1 >= 68)
|
|
1662
|
+
strictMigrationFailure = error;
|
|
1663
|
+
}
|
|
1664
|
+
};
|
|
1212
1665
|
try {
|
|
1213
1666
|
const result = db.query("SELECT MAX(id) as max_id FROM _migrations").get();
|
|
1214
1667
|
const currentLevel = result?.max_id ?? 0;
|
|
1215
1668
|
for (let i = currentLevel;i < MIGRATIONS.length; i++) {
|
|
1216
|
-
|
|
1217
|
-
db.exec(MIGRATIONS[i]);
|
|
1218
|
-
} catch {}
|
|
1669
|
+
executeMigration(MIGRATIONS[i], i);
|
|
1219
1670
|
}
|
|
1220
1671
|
} catch {
|
|
1221
|
-
for (const migration of MIGRATIONS) {
|
|
1222
|
-
|
|
1223
|
-
db.exec(migration);
|
|
1224
|
-
} catch {}
|
|
1672
|
+
for (const [index, migration] of MIGRATIONS.entries()) {
|
|
1673
|
+
executeMigration(migration, index);
|
|
1225
1674
|
}
|
|
1226
1675
|
}
|
|
1676
|
+
if (strictMigrationFailure)
|
|
1677
|
+
throw strictMigrationFailure;
|
|
1227
1678
|
ensureSchema(db);
|
|
1228
1679
|
}
|
|
1229
1680
|
function planSlugBase(value) {
|
|
@@ -2392,6 +2843,246 @@ function ensureSchema(db) {
|
|
|
2392
2843
|
)`);
|
|
2393
2844
|
ensureIndex("CREATE INDEX IF NOT EXISTS idx_api_keys_prefix ON api_keys(prefix)");
|
|
2394
2845
|
ensureIndex("CREATE INDEX IF NOT EXISTS idx_api_keys_active ON api_keys(revoked_at, expires_at)");
|
|
2846
|
+
ensureTable("pr_groups", `
|
|
2847
|
+
CREATE TABLE pr_groups (
|
|
2848
|
+
schema_version INTEGER NOT NULL DEFAULT 1,
|
|
2849
|
+
id TEXT PRIMARY KEY,
|
|
2850
|
+
identity_key TEXT NOT NULL UNIQUE,
|
|
2851
|
+
root_request_id TEXT NOT NULL,
|
|
2852
|
+
repository TEXT NOT NULL,
|
|
2853
|
+
leaf_task_id TEXT NOT NULL,
|
|
2854
|
+
branch TEXT NOT NULL,
|
|
2855
|
+
pr_number INTEGER,
|
|
2856
|
+
base_sha TEXT,
|
|
2857
|
+
state TEXT NOT NULL,
|
|
2858
|
+
active_attempt_id TEXT,
|
|
2859
|
+
active_generation TEXT,
|
|
2860
|
+
repair_cycle_count INTEGER NOT NULL DEFAULT 0,
|
|
2861
|
+
repair_cycle_limit INTEGER NOT NULL DEFAULT 2,
|
|
2862
|
+
terminal_attempt_id TEXT,
|
|
2863
|
+
terminal_generation TEXT,
|
|
2864
|
+
terminal_outcome TEXT,
|
|
2865
|
+
terminal_head_sha TEXT,
|
|
2866
|
+
terminal_at TEXT,
|
|
2867
|
+
cleanup_eligible_at TEXT,
|
|
2868
|
+
revision INTEGER NOT NULL DEFAULT 1,
|
|
2869
|
+
created_at TEXT NOT NULL,
|
|
2870
|
+
updated_at TEXT NOT NULL
|
|
2871
|
+
)`);
|
|
2872
|
+
ensureIndex("CREATE UNIQUE INDEX IF NOT EXISTS idx_pr_groups_identity ON pr_groups(identity_key)");
|
|
2873
|
+
ensureIndex("CREATE INDEX IF NOT EXISTS idx_pr_groups_root_repository ON pr_groups(root_request_id, repository)");
|
|
2874
|
+
ensureIndex("CREATE INDEX IF NOT EXISTS idx_pr_groups_active_generation ON pr_groups(active_generation)");
|
|
2875
|
+
ensureTable("pr_group_attempts", `
|
|
2876
|
+
CREATE TABLE pr_group_attempts (
|
|
2877
|
+
schema_version INTEGER NOT NULL DEFAULT 1,
|
|
2878
|
+
id TEXT PRIMARY KEY,
|
|
2879
|
+
group_id TEXT NOT NULL REFERENCES pr_groups(id) ON DELETE CASCADE,
|
|
2880
|
+
leaf_task_id TEXT NOT NULL,
|
|
2881
|
+
dispatch_attempt TEXT NOT NULL,
|
|
2882
|
+
writer_generation TEXT NOT NULL,
|
|
2883
|
+
previous_attempt_id TEXT REFERENCES pr_group_attempts(id) ON DELETE SET NULL,
|
|
2884
|
+
worktree TEXT NOT NULL,
|
|
2885
|
+
branch TEXT NOT NULL,
|
|
2886
|
+
repository TEXT NOT NULL,
|
|
2887
|
+
pr_number INTEGER,
|
|
2888
|
+
base_sha TEXT,
|
|
2889
|
+
provider TEXT,
|
|
2890
|
+
provider_run_id TEXT,
|
|
2891
|
+
profile_alias TEXT,
|
|
2892
|
+
status TEXT NOT NULL,
|
|
2893
|
+
admitted_at TEXT NOT NULL,
|
|
2894
|
+
started_at TEXT,
|
|
2895
|
+
last_heartbeat_at TEXT,
|
|
2896
|
+
handed_off_at TEXT,
|
|
2897
|
+
fenced_at TEXT,
|
|
2898
|
+
terminal_at TEXT,
|
|
2899
|
+
created_at TEXT NOT NULL,
|
|
2900
|
+
updated_at TEXT NOT NULL,
|
|
2901
|
+
UNIQUE(group_id, leaf_task_id, dispatch_attempt),
|
|
2902
|
+
UNIQUE(group_id, writer_generation)
|
|
2903
|
+
)`);
|
|
2904
|
+
ensureIndex("CREATE INDEX IF NOT EXISTS idx_pr_group_attempts_group ON pr_group_attempts(group_id, created_at, id)");
|
|
2905
|
+
ensureIndex("CREATE INDEX IF NOT EXISTS idx_pr_group_attempts_generation ON pr_group_attempts(group_id, writer_generation)");
|
|
2906
|
+
ensureTable("pr_group_events", `
|
|
2907
|
+
CREATE TABLE pr_group_events (
|
|
2908
|
+
schema_version INTEGER NOT NULL DEFAULT 1,
|
|
2909
|
+
id TEXT PRIMARY KEY,
|
|
2910
|
+
group_id TEXT NOT NULL REFERENCES pr_groups(id) ON DELETE CASCADE,
|
|
2911
|
+
attempt_id TEXT NOT NULL REFERENCES pr_group_attempts(id) ON DELETE CASCADE,
|
|
2912
|
+
writer_generation TEXT NOT NULL,
|
|
2913
|
+
sequence INTEGER NOT NULL,
|
|
2914
|
+
idempotency_key TEXT NOT NULL,
|
|
2915
|
+
event_type TEXT NOT NULL,
|
|
2916
|
+
state TEXT NOT NULL,
|
|
2917
|
+
message TEXT,
|
|
2918
|
+
head_sha TEXT,
|
|
2919
|
+
receipt_key TEXT UNIQUE,
|
|
2920
|
+
review_receipt_key TEXT,
|
|
2921
|
+
conditional_merge_receipt_key TEXT,
|
|
2922
|
+
outcome TEXT,
|
|
2923
|
+
repository TEXT NOT NULL,
|
|
2924
|
+
pr_number INTEGER,
|
|
2925
|
+
base_sha TEXT,
|
|
2926
|
+
actor_id TEXT,
|
|
2927
|
+
actor_run_id TEXT,
|
|
2928
|
+
expected_reviewer_id TEXT,
|
|
2929
|
+
expected_reviewer_run_id TEXT,
|
|
2930
|
+
repair_cycle INTEGER,
|
|
2931
|
+
ci_proof TEXT,
|
|
2932
|
+
cleanup_proof TEXT,
|
|
2933
|
+
metadata TEXT NOT NULL DEFAULT '{}',
|
|
2934
|
+
payload_hash TEXT NOT NULL,
|
|
2935
|
+
created_at TEXT NOT NULL,
|
|
2936
|
+
UNIQUE(group_id, sequence),
|
|
2937
|
+
UNIQUE(group_id, idempotency_key)
|
|
2938
|
+
)`);
|
|
2939
|
+
ensureIndex("CREATE INDEX IF NOT EXISTS idx_pr_group_events_group_sequence ON pr_group_events(group_id, sequence)");
|
|
2940
|
+
ensureIndex("CREATE INDEX IF NOT EXISTS idx_pr_group_events_attempt ON pr_group_events(attempt_id, sequence)");
|
|
2941
|
+
ensureIndex("CREATE INDEX IF NOT EXISTS idx_pr_group_events_receipt ON pr_group_events(receipt_key)");
|
|
2942
|
+
ensureColumn("pr_groups", "leaf_task_id", "TEXT");
|
|
2943
|
+
ensureColumn("pr_groups", "branch", "TEXT");
|
|
2944
|
+
ensureColumn("pr_groups", "pr_number", "INTEGER");
|
|
2945
|
+
ensureColumn("pr_groups", "base_sha", "TEXT");
|
|
2946
|
+
ensureColumn("pr_groups", "repair_cycle_count", "INTEGER NOT NULL DEFAULT 0");
|
|
2947
|
+
ensureColumn("pr_groups", "repair_cycle_limit", "INTEGER NOT NULL DEFAULT 2");
|
|
2948
|
+
ensureColumn("pr_group_attempts", "repository", "TEXT");
|
|
2949
|
+
ensureColumn("pr_group_attempts", "pr_number", "INTEGER");
|
|
2950
|
+
ensureColumn("pr_group_attempts", "base_sha", "TEXT");
|
|
2951
|
+
ensureColumn("pr_group_events", "review_receipt_key", "TEXT");
|
|
2952
|
+
ensureColumn("pr_group_events", "conditional_merge_receipt_key", "TEXT");
|
|
2953
|
+
ensureColumn("pr_group_events", "repository", "TEXT");
|
|
2954
|
+
ensureColumn("pr_group_events", "pr_number", "INTEGER");
|
|
2955
|
+
ensureColumn("pr_group_events", "base_sha", "TEXT");
|
|
2956
|
+
ensureColumn("pr_group_events", "actor_id", "TEXT");
|
|
2957
|
+
ensureColumn("pr_group_events", "actor_run_id", "TEXT");
|
|
2958
|
+
ensureColumn("pr_group_events", "expected_reviewer_id", "TEXT");
|
|
2959
|
+
ensureColumn("pr_group_events", "expected_reviewer_run_id", "TEXT");
|
|
2960
|
+
ensureColumn("pr_group_events", "repair_cycle", "INTEGER");
|
|
2961
|
+
ensureColumn("pr_group_events", "ci_proof", "TEXT");
|
|
2962
|
+
ensureColumn("pr_group_events", "cleanup_proof", "TEXT");
|
|
2963
|
+
try {
|
|
2964
|
+
db.exec(`
|
|
2965
|
+
UPDATE pr_groups
|
|
2966
|
+
SET leaf_task_id = COALESCE(leaf_task_id, (
|
|
2967
|
+
SELECT attempt.leaf_task_id
|
|
2968
|
+
FROM pr_group_attempts AS attempt
|
|
2969
|
+
WHERE attempt.group_id = pr_groups.id
|
|
2970
|
+
ORDER BY CASE WHEN attempt.id = pr_groups.active_attempt_id THEN 0 ELSE 1 END,
|
|
2971
|
+
attempt.created_at ASC, attempt.id ASC
|
|
2972
|
+
LIMIT 1
|
|
2973
|
+
)),
|
|
2974
|
+
branch = COALESCE(branch, (
|
|
2975
|
+
SELECT attempt.branch
|
|
2976
|
+
FROM pr_group_attempts AS attempt
|
|
2977
|
+
WHERE attempt.group_id = pr_groups.id
|
|
2978
|
+
ORDER BY CASE WHEN attempt.id = pr_groups.active_attempt_id THEN 0 ELSE 1 END,
|
|
2979
|
+
attempt.created_at ASC, attempt.id ASC
|
|
2980
|
+
LIMIT 1
|
|
2981
|
+
));
|
|
2982
|
+
UPDATE pr_group_attempts
|
|
2983
|
+
SET repository = COALESCE(repository, (
|
|
2984
|
+
SELECT groups.repository FROM pr_groups AS groups
|
|
2985
|
+
WHERE groups.id = pr_group_attempts.group_id
|
|
2986
|
+
)),
|
|
2987
|
+
pr_number = COALESCE(pr_number, (
|
|
2988
|
+
SELECT groups.pr_number FROM pr_groups AS groups
|
|
2989
|
+
WHERE groups.id = pr_group_attempts.group_id
|
|
2990
|
+
)),
|
|
2991
|
+
base_sha = COALESCE(base_sha, (
|
|
2992
|
+
SELECT groups.base_sha FROM pr_groups AS groups
|
|
2993
|
+
WHERE groups.id = pr_group_attempts.group_id
|
|
2994
|
+
));
|
|
2995
|
+
UPDATE pr_groups
|
|
2996
|
+
SET pr_number = COALESCE(pr_number, (
|
|
2997
|
+
SELECT attempt.pr_number
|
|
2998
|
+
FROM pr_group_attempts AS attempt
|
|
2999
|
+
WHERE attempt.group_id = pr_groups.id AND attempt.pr_number IS NOT NULL
|
|
3000
|
+
ORDER BY CASE WHEN attempt.id = pr_groups.active_attempt_id THEN 0 ELSE 1 END,
|
|
3001
|
+
attempt.created_at ASC, attempt.id ASC
|
|
3002
|
+
LIMIT 1
|
|
3003
|
+
)),
|
|
3004
|
+
base_sha = COALESCE(base_sha, (
|
|
3005
|
+
SELECT attempt.base_sha
|
|
3006
|
+
FROM pr_group_attempts AS attempt
|
|
3007
|
+
WHERE attempt.group_id = pr_groups.id AND attempt.base_sha IS NOT NULL
|
|
3008
|
+
ORDER BY CASE WHEN attempt.id = pr_groups.active_attempt_id THEN 0 ELSE 1 END,
|
|
3009
|
+
attempt.created_at ASC, attempt.id ASC
|
|
3010
|
+
LIMIT 1
|
|
3011
|
+
));
|
|
3012
|
+
UPDATE pr_group_attempts
|
|
3013
|
+
SET pr_number = COALESCE(pr_number, (
|
|
3014
|
+
SELECT groups.pr_number FROM pr_groups AS groups
|
|
3015
|
+
WHERE groups.id = pr_group_attempts.group_id
|
|
3016
|
+
)),
|
|
3017
|
+
base_sha = COALESCE(base_sha, (
|
|
3018
|
+
SELECT groups.base_sha FROM pr_groups AS groups
|
|
3019
|
+
WHERE groups.id = pr_group_attempts.group_id
|
|
3020
|
+
));
|
|
3021
|
+
UPDATE pr_group_events
|
|
3022
|
+
SET repository = COALESCE(repository, (
|
|
3023
|
+
SELECT attempt.repository FROM pr_group_attempts AS attempt
|
|
3024
|
+
WHERE attempt.id = pr_group_events.attempt_id
|
|
3025
|
+
), (
|
|
3026
|
+
SELECT groups.repository FROM pr_groups AS groups
|
|
3027
|
+
WHERE groups.id = pr_group_events.group_id
|
|
3028
|
+
)),
|
|
3029
|
+
pr_number = COALESCE(pr_number, (
|
|
3030
|
+
SELECT attempt.pr_number FROM pr_group_attempts AS attempt
|
|
3031
|
+
WHERE attempt.id = pr_group_events.attempt_id
|
|
3032
|
+
), (
|
|
3033
|
+
SELECT groups.pr_number FROM pr_groups AS groups
|
|
3034
|
+
WHERE groups.id = pr_group_events.group_id
|
|
3035
|
+
)),
|
|
3036
|
+
base_sha = COALESCE(base_sha, (
|
|
3037
|
+
SELECT attempt.base_sha FROM pr_group_attempts AS attempt
|
|
3038
|
+
WHERE attempt.id = pr_group_events.attempt_id
|
|
3039
|
+
), (
|
|
3040
|
+
SELECT groups.base_sha FROM pr_groups AS groups
|
|
3041
|
+
WHERE groups.id = pr_group_events.group_id
|
|
3042
|
+
));
|
|
3043
|
+
INSERT OR IGNORE INTO _migrations (id)
|
|
3044
|
+
SELECT 66
|
|
3045
|
+
WHERE NOT EXISTS (
|
|
3046
|
+
SELECT 1 FROM pr_groups WHERE leaf_task_id IS NULL OR branch IS NULL
|
|
3047
|
+
)
|
|
3048
|
+
AND NOT EXISTS (
|
|
3049
|
+
SELECT 1 FROM pr_group_attempts WHERE repository IS NULL
|
|
3050
|
+
)
|
|
3051
|
+
AND NOT EXISTS (
|
|
3052
|
+
SELECT 1 FROM pr_group_events WHERE repository IS NULL
|
|
3053
|
+
);
|
|
3054
|
+
`);
|
|
3055
|
+
} catch {}
|
|
3056
|
+
const strictColumns = [
|
|
3057
|
+
["pr_groups", "leaf_task_id"],
|
|
3058
|
+
["pr_groups", "branch"],
|
|
3059
|
+
["pr_group_attempts", "repository"],
|
|
3060
|
+
["pr_group_events", "repository"]
|
|
3061
|
+
];
|
|
3062
|
+
for (const [table, column] of strictColumns) {
|
|
3063
|
+
const definition = db.query(`PRAGMA table_info(${table})`).all();
|
|
3064
|
+
if (definition.find((entry) => entry.name === column)?.notnull !== 1) {
|
|
3065
|
+
throw new Error(`PR-group schema integrity failure: ${table}.${column} is not NOT NULL`);
|
|
3066
|
+
}
|
|
3067
|
+
}
|
|
3068
|
+
const eventColumns = db.query("PRAGMA table_info(pr_group_events)").all();
|
|
3069
|
+
if (!eventColumns.some((entry) => entry.name === "ci_proof")) {
|
|
3070
|
+
throw new Error("PR-group schema integrity failure: pr_group_events.ci_proof is missing");
|
|
3071
|
+
}
|
|
3072
|
+
const receiptIndexes = db.query("PRAGMA index_list(pr_group_events)").all();
|
|
3073
|
+
const hasGlobalReceiptUniqueness = receiptIndexes.some((index) => {
|
|
3074
|
+
if (index.unique !== 1)
|
|
3075
|
+
return false;
|
|
3076
|
+
const columns = db.query(`PRAGMA index_info(${JSON.stringify(index.name)})`).all();
|
|
3077
|
+
return columns.length === 1 && columns[0]?.name === "receipt_key";
|
|
3078
|
+
});
|
|
3079
|
+
if (!hasGlobalReceiptUniqueness) {
|
|
3080
|
+
throw new Error("PR-group schema integrity failure: receipt_key is not globally unique");
|
|
3081
|
+
}
|
|
3082
|
+
const strictMigration = db.query("SELECT 1 AS ok FROM _migrations WHERE id = 68").get();
|
|
3083
|
+
if (strictMigration?.ok !== 1) {
|
|
3084
|
+
throw new Error("PR-group schema integrity failure: migration 68 is not recorded");
|
|
3085
|
+
}
|
|
2395
3086
|
}
|
|
2396
3087
|
function backfillTaskTags(db) {
|
|
2397
3088
|
try {
|
|
@@ -2732,6 +3423,334 @@ var init_machines = __esm(() => {
|
|
|
2732
3423
|
];
|
|
2733
3424
|
});
|
|
2734
3425
|
|
|
3426
|
+
// src/types/index.ts
|
|
3427
|
+
var TASK_STATUSES, TASK_PRIORITIES, PLAN_STATUSES, VersionConflictError, TaskNotFoundError, ProjectNotFoundError, ResourceConflictError, PlanNotFoundError, LockError, AgentNotFoundError, IdentityAliasAmbiguousError, IdentityIdImmutableError, TaskListNotFoundError, DependencyCycleError, CompletionGuardError, DISPATCH_STATUSES, DispatchNotFoundError;
|
|
3428
|
+
var init_types = __esm(() => {
|
|
3429
|
+
TASK_STATUSES = [
|
|
3430
|
+
"pending",
|
|
3431
|
+
"in_progress",
|
|
3432
|
+
"completed",
|
|
3433
|
+
"failed",
|
|
3434
|
+
"cancelled"
|
|
3435
|
+
];
|
|
3436
|
+
TASK_PRIORITIES = [
|
|
3437
|
+
"low",
|
|
3438
|
+
"medium",
|
|
3439
|
+
"high",
|
|
3440
|
+
"critical"
|
|
3441
|
+
];
|
|
3442
|
+
PLAN_STATUSES = ["active", "completed", "archived"];
|
|
3443
|
+
VersionConflictError = class VersionConflictError extends Error {
|
|
3444
|
+
taskId;
|
|
3445
|
+
expectedVersion;
|
|
3446
|
+
actualVersion;
|
|
3447
|
+
static code = "VERSION_CONFLICT";
|
|
3448
|
+
static suggestion = "Fetch the task with get_task to get the current version before updating.";
|
|
3449
|
+
constructor(taskId, expectedVersion, actualVersion) {
|
|
3450
|
+
super(`Version conflict for task ${taskId}: expected ${expectedVersion}, got ${actualVersion}`);
|
|
3451
|
+
this.taskId = taskId;
|
|
3452
|
+
this.expectedVersion = expectedVersion;
|
|
3453
|
+
this.actualVersion = actualVersion;
|
|
3454
|
+
this.name = "VersionConflictError";
|
|
3455
|
+
}
|
|
3456
|
+
};
|
|
3457
|
+
TaskNotFoundError = class TaskNotFoundError extends Error {
|
|
3458
|
+
taskId;
|
|
3459
|
+
static code = "TASK_NOT_FOUND";
|
|
3460
|
+
static suggestion = "Verify the task ID. Use list_tasks or search_tasks to find the correct ID.";
|
|
3461
|
+
constructor(taskId) {
|
|
3462
|
+
super(`Task not found: ${taskId}`);
|
|
3463
|
+
this.taskId = taskId;
|
|
3464
|
+
this.name = "TaskNotFoundError";
|
|
3465
|
+
}
|
|
3466
|
+
};
|
|
3467
|
+
ProjectNotFoundError = class ProjectNotFoundError extends Error {
|
|
3468
|
+
projectId;
|
|
3469
|
+
static code = "PROJECT_NOT_FOUND";
|
|
3470
|
+
static suggestion = "Use list_projects to see available projects.";
|
|
3471
|
+
constructor(projectId) {
|
|
3472
|
+
super(`Project not found: ${projectId}`);
|
|
3473
|
+
this.projectId = projectId;
|
|
3474
|
+
this.name = "ProjectNotFoundError";
|
|
3475
|
+
}
|
|
3476
|
+
};
|
|
3477
|
+
ResourceConflictError = class ResourceConflictError extends Error {
|
|
3478
|
+
code;
|
|
3479
|
+
constructor(code, message) {
|
|
3480
|
+
super(message);
|
|
3481
|
+
this.code = code;
|
|
3482
|
+
this.name = "ResourceConflictError";
|
|
3483
|
+
}
|
|
3484
|
+
};
|
|
3485
|
+
PlanNotFoundError = class PlanNotFoundError extends Error {
|
|
3486
|
+
planId;
|
|
3487
|
+
static code = "PLAN_NOT_FOUND";
|
|
3488
|
+
static suggestion = "Use list_plans to see available plans.";
|
|
3489
|
+
constructor(planId) {
|
|
3490
|
+
super(`Plan not found: ${planId}`);
|
|
3491
|
+
this.planId = planId;
|
|
3492
|
+
this.name = "PlanNotFoundError";
|
|
3493
|
+
}
|
|
3494
|
+
};
|
|
3495
|
+
LockError = class LockError extends Error {
|
|
3496
|
+
taskId;
|
|
3497
|
+
lockedBy;
|
|
3498
|
+
static code = "LOCK_ERROR";
|
|
3499
|
+
static suggestion = "Wait for the lock to expire (30 min) or contact the lock holder.";
|
|
3500
|
+
constructor(taskId, lockedBy) {
|
|
3501
|
+
super(`Task ${taskId} is locked by ${lockedBy}`);
|
|
3502
|
+
this.taskId = taskId;
|
|
3503
|
+
this.lockedBy = lockedBy;
|
|
3504
|
+
this.name = "LockError";
|
|
3505
|
+
}
|
|
3506
|
+
};
|
|
3507
|
+
AgentNotFoundError = class AgentNotFoundError extends Error {
|
|
3508
|
+
agentId;
|
|
3509
|
+
static code = "AGENT_NOT_FOUND";
|
|
3510
|
+
static suggestion = "Use register_agent to create the agent first, or list_agents to find existing ones.";
|
|
3511
|
+
constructor(agentId) {
|
|
3512
|
+
super(`Agent not found: ${agentId}`);
|
|
3513
|
+
this.agentId = agentId;
|
|
3514
|
+
this.name = "AgentNotFoundError";
|
|
3515
|
+
}
|
|
3516
|
+
};
|
|
3517
|
+
IdentityAliasAmbiguousError = class IdentityAliasAmbiguousError extends Error {
|
|
3518
|
+
code = "IDENTITY_ALIAS_AMBIGUOUS";
|
|
3519
|
+
candidates;
|
|
3520
|
+
constructor(subject, candidates = []) {
|
|
3521
|
+
super(`Identity alias or source is ambiguous: ${subject}`);
|
|
3522
|
+
this.name = "IdentityAliasAmbiguousError";
|
|
3523
|
+
this.candidates = [...new Set(candidates)].sort();
|
|
3524
|
+
}
|
|
3525
|
+
};
|
|
3526
|
+
IdentityIdImmutableError = class IdentityIdImmutableError extends Error {
|
|
3527
|
+
code = "IDENTITY_ID_IMMUTABLE";
|
|
3528
|
+
constructor(agentId) {
|
|
3529
|
+
super(`IDENTITY_ID_IMMUTABLE: canonical identity for agent ${agentId} cannot be replaced`);
|
|
3530
|
+
this.name = "IdentityIdImmutableError";
|
|
3531
|
+
}
|
|
3532
|
+
};
|
|
3533
|
+
TaskListNotFoundError = class TaskListNotFoundError extends Error {
|
|
3534
|
+
taskListId;
|
|
3535
|
+
static code = "TASK_LIST_NOT_FOUND";
|
|
3536
|
+
static suggestion = "Use list_task_lists to see available lists.";
|
|
3537
|
+
constructor(taskListId) {
|
|
3538
|
+
super(`Task list not found: ${taskListId}`);
|
|
3539
|
+
this.taskListId = taskListId;
|
|
3540
|
+
this.name = "TaskListNotFoundError";
|
|
3541
|
+
}
|
|
3542
|
+
};
|
|
3543
|
+
DependencyCycleError = class DependencyCycleError extends Error {
|
|
3544
|
+
taskId;
|
|
3545
|
+
dependsOn;
|
|
3546
|
+
static code = "DEPENDENCY_CYCLE";
|
|
3547
|
+
static suggestion = "Check the dependency chain with get_task to avoid circular references.";
|
|
3548
|
+
constructor(taskId, dependsOn) {
|
|
3549
|
+
super(`Adding dependency ${taskId} -> ${dependsOn} would create a cycle`);
|
|
3550
|
+
this.taskId = taskId;
|
|
3551
|
+
this.dependsOn = dependsOn;
|
|
3552
|
+
this.name = "DependencyCycleError";
|
|
3553
|
+
}
|
|
3554
|
+
};
|
|
3555
|
+
CompletionGuardError = class CompletionGuardError extends Error {
|
|
3556
|
+
reason;
|
|
3557
|
+
retryAfterSeconds;
|
|
3558
|
+
static code = "COMPLETION_BLOCKED";
|
|
3559
|
+
static suggestion = "Wait for the cooldown period, then retry.";
|
|
3560
|
+
constructor(reason, retryAfterSeconds) {
|
|
3561
|
+
super(reason);
|
|
3562
|
+
this.reason = reason;
|
|
3563
|
+
this.retryAfterSeconds = retryAfterSeconds;
|
|
3564
|
+
this.name = "CompletionGuardError";
|
|
3565
|
+
}
|
|
3566
|
+
};
|
|
3567
|
+
DISPATCH_STATUSES = ["pending", "sent", "failed", "cancelled"];
|
|
3568
|
+
DispatchNotFoundError = class DispatchNotFoundError extends Error {
|
|
3569
|
+
dispatchId;
|
|
3570
|
+
static code = "DISPATCH_NOT_FOUND";
|
|
3571
|
+
static suggestion = "Check the dispatch ID with list_dispatches.";
|
|
3572
|
+
constructor(dispatchId) {
|
|
3573
|
+
super(`Dispatch not found: ${dispatchId}`);
|
|
3574
|
+
this.dispatchId = dispatchId;
|
|
3575
|
+
this.name = "DispatchNotFoundError";
|
|
3576
|
+
}
|
|
3577
|
+
};
|
|
3578
|
+
});
|
|
3579
|
+
|
|
3580
|
+
// src/db/identity-mapping.ts
|
|
3581
|
+
function timestamp() {
|
|
3582
|
+
return new Date().toISOString();
|
|
3583
|
+
}
|
|
3584
|
+
function mappingId() {
|
|
3585
|
+
return crypto.randomUUID();
|
|
3586
|
+
}
|
|
3587
|
+
function normalizedAlias(label) {
|
|
3588
|
+
const normalized = label.trim().toLocaleLowerCase("en-US");
|
|
3589
|
+
if (!normalized)
|
|
3590
|
+
throw new Error("IDENTITY_ALIAS_INVALID: label is required");
|
|
3591
|
+
return normalized;
|
|
3592
|
+
}
|
|
3593
|
+
function ensureColumn(db, table, column, type) {
|
|
3594
|
+
const columns = db.query(`PRAGMA table_info(${table})`).all();
|
|
3595
|
+
if (!columns.some((item) => item.name === column)) {
|
|
3596
|
+
db.exec(`ALTER TABLE ${table} ADD COLUMN ${column} ${type}`);
|
|
3597
|
+
}
|
|
3598
|
+
}
|
|
3599
|
+
function ensureAgentIdentitySchema(db) {
|
|
3600
|
+
const install = db.transaction(() => {
|
|
3601
|
+
ensureColumn(db, "agents", "identity_id", "TEXT");
|
|
3602
|
+
ensureColumn(db, "agents", "project_id", "TEXT");
|
|
3603
|
+
ensureColumn(db, "agents", "runtime_instance_id", "TEXT");
|
|
3604
|
+
db.exec(`
|
|
3605
|
+
CREATE TABLE IF NOT EXISTS agent_identity_source_mappings (
|
|
3606
|
+
id TEXT PRIMARY KEY,
|
|
3607
|
+
local_agent_id TEXT REFERENCES agents(id) ON DELETE RESTRICT,
|
|
3608
|
+
identity_id TEXT,
|
|
3609
|
+
source_authority TEXT NOT NULL CHECK(length(trim(source_authority)) > 0),
|
|
3610
|
+
source_tenant_id TEXT NOT NULL CHECK(length(trim(source_tenant_id)) > 0),
|
|
3611
|
+
source_namespace TEXT NOT NULL CHECK(length(trim(source_namespace)) > 0),
|
|
3612
|
+
source_entity_type TEXT NOT NULL CHECK(length(trim(source_entity_type)) > 0),
|
|
3613
|
+
source_record_id TEXT NOT NULL CHECK(length(trim(source_record_id)) > 0),
|
|
3614
|
+
observed_label TEXT,
|
|
3615
|
+
evidence TEXT NOT NULL DEFAULT '{}',
|
|
3616
|
+
mapping_basis TEXT NOT NULL CHECK(mapping_basis IN ('authoritative', 'imported', 'candidate', 'name_similarity')),
|
|
3617
|
+
status TEXT NOT NULL CHECK(status IN ('active', 'retired', 'quarantined', 'revoked')),
|
|
3618
|
+
revision INTEGER NOT NULL CHECK(revision > 0),
|
|
3619
|
+
created_at TEXT NOT NULL,
|
|
3620
|
+
updated_at TEXT NOT NULL,
|
|
3621
|
+
CHECK(mapping_basis IN ('authoritative', 'imported') OR status IN ('quarantined', 'revoked')),
|
|
3622
|
+
CHECK(status != 'active' OR (mapping_basis IN ('authoritative', 'imported') AND identity_id IS NOT NULL))
|
|
3623
|
+
);
|
|
3624
|
+
`);
|
|
3625
|
+
ensureColumn(db, "agent_identity_source_mappings", "revision", "INTEGER NOT NULL DEFAULT 1");
|
|
3626
|
+
ensureColumn(db, "agent_identity_source_mappings", "evidence", "TEXT NOT NULL DEFAULT '{}'");
|
|
3627
|
+
db.exec(`
|
|
3628
|
+
DROP INDEX IF EXISTS idx_agent_identity_source_active_unique;
|
|
3629
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_agent_identity_source_revision_unique
|
|
3630
|
+
ON agent_identity_source_mappings (
|
|
3631
|
+
source_authority, source_tenant_id, source_namespace, source_entity_type, source_record_id, revision
|
|
3632
|
+
)
|
|
3633
|
+
WHERE mapping_basis IN ('authoritative', 'imported');
|
|
3634
|
+
CREATE INDEX IF NOT EXISTS idx_agent_identity_source_identity
|
|
3635
|
+
ON agent_identity_source_mappings(identity_id);
|
|
3636
|
+
CREATE INDEX IF NOT EXISTS idx_agent_identity_source_local
|
|
3637
|
+
ON agent_identity_source_mappings(local_agent_id);
|
|
3638
|
+
DROP INDEX IF EXISTS idx_agents_identity_unique;
|
|
3639
|
+
|
|
3640
|
+
CREATE TABLE IF NOT EXISTS agent_identity_aliases (
|
|
3641
|
+
id TEXT PRIMARY KEY,
|
|
3642
|
+
local_agent_id TEXT NOT NULL REFERENCES agents(id) ON DELETE RESTRICT,
|
|
3643
|
+
label TEXT NOT NULL,
|
|
3644
|
+
normalized_label TEXT NOT NULL,
|
|
3645
|
+
alias_kind TEXT NOT NULL CHECK(alias_kind IN ('historical', 'candidate')),
|
|
3646
|
+
status TEXT NOT NULL CHECK(status IN ('active', 'quarantined', 'revoked')),
|
|
3647
|
+
created_at TEXT NOT NULL,
|
|
3648
|
+
UNIQUE(local_agent_id, normalized_label)
|
|
3649
|
+
);
|
|
3650
|
+
CREATE INDEX IF NOT EXISTS idx_agent_identity_alias_lookup
|
|
3651
|
+
ON agent_identity_aliases(normalized_label, status);
|
|
3652
|
+
|
|
3653
|
+
CREATE TRIGGER IF NOT EXISTS trg_agents_identity_id_immutable
|
|
3654
|
+
BEFORE UPDATE OF identity_id ON agents
|
|
3655
|
+
WHEN OLD.identity_id IS NOT NULL AND OLD.identity_id IS NOT NEW.identity_id
|
|
3656
|
+
BEGIN
|
|
3657
|
+
SELECT RAISE(ABORT, 'IDENTITY_ID_IMMUTABLE');
|
|
3658
|
+
END;
|
|
3659
|
+
|
|
3660
|
+
CREATE TRIGGER IF NOT EXISTS trg_agent_identity_mapping_identity_immutable
|
|
3661
|
+
BEFORE UPDATE OF identity_id ON agent_identity_source_mappings
|
|
3662
|
+
WHEN OLD.identity_id IS NOT NEW.identity_id
|
|
3663
|
+
BEGIN
|
|
3664
|
+
SELECT RAISE(ABORT, 'IDENTITY_ID_IMMUTABLE');
|
|
3665
|
+
END;
|
|
3666
|
+
|
|
3667
|
+
CREATE TRIGGER IF NOT EXISTS trg_agent_identity_mapping_lineage_immutable
|
|
3668
|
+
BEFORE UPDATE OF source_authority, source_tenant_id, source_namespace, source_entity_type, source_record_id
|
|
3669
|
+
ON agent_identity_source_mappings
|
|
3670
|
+
WHEN OLD.source_authority IS NOT NEW.source_authority
|
|
3671
|
+
OR OLD.source_tenant_id IS NOT NEW.source_tenant_id
|
|
3672
|
+
OR OLD.source_namespace IS NOT NEW.source_namespace
|
|
3673
|
+
OR OLD.source_entity_type IS NOT NEW.source_entity_type
|
|
3674
|
+
OR OLD.source_record_id IS NOT NEW.source_record_id
|
|
3675
|
+
BEGIN
|
|
3676
|
+
SELECT RAISE(ABORT, 'IDENTITY_SOURCE_LINEAGE_IMMUTABLE');
|
|
3677
|
+
END;
|
|
3678
|
+
|
|
3679
|
+
DROP TRIGGER IF EXISTS trg_agent_identity_mapping_history_immutable;
|
|
3680
|
+
CREATE TRIGGER trg_agent_identity_mapping_history_immutable
|
|
3681
|
+
BEFORE UPDATE OF local_agent_id, observed_label, evidence, mapping_basis, status, revision, created_at
|
|
3682
|
+
ON agent_identity_source_mappings
|
|
3683
|
+
BEGIN
|
|
3684
|
+
SELECT RAISE(ABORT, 'IDENTITY_MAPPING_HISTORY_IMMUTABLE');
|
|
3685
|
+
END;
|
|
3686
|
+
|
|
3687
|
+
DROP TRIGGER IF EXISTS trg_agent_identity_mapping_history_append_only;
|
|
3688
|
+
CREATE TRIGGER trg_agent_identity_mapping_history_append_only
|
|
3689
|
+
BEFORE DELETE ON agent_identity_source_mappings
|
|
3690
|
+
BEGIN
|
|
3691
|
+
SELECT RAISE(ABORT, 'IDENTITY_MAPPING_HISTORY_IMMUTABLE');
|
|
3692
|
+
END;
|
|
3693
|
+
`);
|
|
3694
|
+
db.run("INSERT OR IGNORE INTO _migrations (id) VALUES (?)", [IDENTITY_MIGRATION_ID]);
|
|
3695
|
+
});
|
|
3696
|
+
install.immediate();
|
|
3697
|
+
}
|
|
3698
|
+
function storeAgentAliasInCurrentTransaction(agentId, label, aliasKind, status, db) {
|
|
3699
|
+
const normalized = normalizedAlias(label);
|
|
3700
|
+
const exists = db.query("SELECT id FROM agents WHERE id = ?").get(agentId);
|
|
3701
|
+
if (!exists)
|
|
3702
|
+
throw new Error(`Agent not found: ${agentId}`);
|
|
3703
|
+
const existing = db.query(`
|
|
3704
|
+
SELECT * FROM agent_identity_aliases
|
|
3705
|
+
WHERE local_agent_id = ? AND normalized_label = ?
|
|
3706
|
+
`).get(agentId, normalized);
|
|
3707
|
+
if (existing) {
|
|
3708
|
+
if (aliasKind === "historical" && (existing.alias_kind !== "historical" || existing.status !== "active")) {
|
|
3709
|
+
db.run("UPDATE agent_identity_aliases SET label = ?, alias_kind = 'historical', status = 'active' WHERE id = ?", [label.trim(), existing.id]);
|
|
3710
|
+
return db.query("SELECT * FROM agent_identity_aliases WHERE id = ?").get(existing.id);
|
|
3711
|
+
}
|
|
3712
|
+
return existing;
|
|
3713
|
+
}
|
|
3714
|
+
const id = mappingId();
|
|
3715
|
+
db.run(`
|
|
3716
|
+
INSERT INTO agent_identity_aliases (
|
|
3717
|
+
id, local_agent_id, label, normalized_label, alias_kind, status, created_at
|
|
3718
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
3719
|
+
`, [id, agentId, label.trim(), normalized, aliasKind, status, timestamp()]);
|
|
3720
|
+
return db.query("SELECT * FROM agent_identity_aliases WHERE id = ?").get(id);
|
|
3721
|
+
}
|
|
3722
|
+
function storeAgentAlias(agentId, label, aliasKind, status, db) {
|
|
3723
|
+
if (db.inTransaction) {
|
|
3724
|
+
return storeAgentAliasInCurrentTransaction(agentId, label, aliasKind, status, db);
|
|
3725
|
+
}
|
|
3726
|
+
const store = db.transaction(() => storeAgentAliasInCurrentTransaction(agentId, label, aliasKind, status, db));
|
|
3727
|
+
return store.immediate();
|
|
3728
|
+
}
|
|
3729
|
+
function recordAgentAlias(agentId, label, db) {
|
|
3730
|
+
return storeAgentAlias(agentId, label, "historical", "active", db);
|
|
3731
|
+
}
|
|
3732
|
+
var IDENTITY_PROJECTION_CONTRACT, IDENTITY_MIGRATION_ID = 65;
|
|
3733
|
+
var init_identity_mapping = __esm(() => {
|
|
3734
|
+
init_types();
|
|
3735
|
+
init_types();
|
|
3736
|
+
IDENTITY_PROJECTION_CONTRACT = Object.freeze({
|
|
3737
|
+
name: "hasna.todos.agent-identity-projection/v1",
|
|
3738
|
+
version: 1,
|
|
3739
|
+
foundation_repository: "hasna/identities",
|
|
3740
|
+
foundation_pull_request: 14,
|
|
3741
|
+
foundation_contract: "hasna.identities.agent-identity/v1",
|
|
3742
|
+
foundation_version: 1,
|
|
3743
|
+
foundation_commit: "14d83e3e45df5748944a871a07c4aacece893169",
|
|
3744
|
+
foundation_fixture_id: "hasna.identities.agent-identity/v1/conformance/1",
|
|
3745
|
+
foundation_fixture_source_path: "docs/fixtures/agent-identity-v1.conformance.json",
|
|
3746
|
+
foundation_fixture_local_path: "src/db/fixtures/agent-identity-v1.conformance.json",
|
|
3747
|
+
foundation_fixture_sha256: "sha256:2d22e72b7315d0b06b3c67c71674cfd8c2dff552727eea00efb325f52c9420af",
|
|
3748
|
+
foundation_default_read_preference: "canonical_first",
|
|
3749
|
+
consumer_rollout_default_read_preference: "legacy_first",
|
|
3750
|
+
lease_fence_authority: "external Runtime Coordination"
|
|
3751
|
+
});
|
|
3752
|
+
});
|
|
3753
|
+
|
|
2735
3754
|
// src/storage/config.ts
|
|
2736
3755
|
var exports_config = {};
|
|
2737
3756
|
__export(exports_config, {
|
|
@@ -2755,15 +3774,15 @@ __export(exports_config, {
|
|
|
2755
3774
|
TODOS_STORAGE_FALLBACK_ENV: () => TODOS_STORAGE_FALLBACK_ENV,
|
|
2756
3775
|
TODOS_STORAGE_ENV: () => TODOS_STORAGE_ENV,
|
|
2757
3776
|
STORAGE_TABLES: () => STORAGE_TABLES,
|
|
2758
|
-
|
|
3777
|
+
CANONICAL_TODOS_RDS_RUNTIME_PATH_ENV: () => CANONICAL_TODOS_RDS_RUNTIME_PATH_ENV,
|
|
2759
3778
|
CANONICAL_TODOS_RDS_DATABASE: () => CANONICAL_TODOS_RDS_DATABASE,
|
|
2760
|
-
|
|
3779
|
+
CANONICAL_TODOS_RDS_CLUSTER_ENV: () => CANONICAL_TODOS_RDS_CLUSTER_ENV
|
|
2761
3780
|
});
|
|
2762
|
-
function getCanonicalTodosRdsConfig() {
|
|
3781
|
+
function getCanonicalTodosRdsConfig(env = process.env) {
|
|
2763
3782
|
return {
|
|
2764
|
-
cluster:
|
|
3783
|
+
cluster: clean(env[CANONICAL_TODOS_RDS_CLUSTER_ENV]) ?? null,
|
|
2765
3784
|
database: CANONICAL_TODOS_RDS_DATABASE,
|
|
2766
|
-
runtimeSecretPath:
|
|
3785
|
+
runtimeSecretPath: clean(env[CANONICAL_TODOS_RDS_RUNTIME_PATH_ENV]) ?? null,
|
|
2767
3786
|
primaryEnv: TODOS_STORAGE_ENV.databaseUrl,
|
|
2768
3787
|
fallbackEnv: TODOS_STORAGE_FALLBACK_ENV.databaseUrl
|
|
2769
3788
|
};
|
|
@@ -2896,7 +3915,7 @@ function parsePositiveInteger(value, fallback) {
|
|
|
2896
3915
|
}
|
|
2897
3916
|
return parsed;
|
|
2898
3917
|
}
|
|
2899
|
-
var TODOS_STORAGE_TABLES, STORAGE_TABLES, TODOS_STORAGE_ENV, TODOS_STORAGE_FALLBACK_ENV,
|
|
3918
|
+
var TODOS_STORAGE_TABLES, STORAGE_TABLES, TODOS_STORAGE_ENV, TODOS_STORAGE_FALLBACK_ENV, CANONICAL_TODOS_RDS_CLUSTER_ENV = "HASNA_TODOS_RDS_CLUSTER", CANONICAL_TODOS_RDS_RUNTIME_PATH_ENV = "HASNA_TODOS_RDS_RUNTIME_PATH", CANONICAL_TODOS_RDS_DATABASE = "todos";
|
|
2900
3919
|
var init_config = __esm(() => {
|
|
2901
3920
|
TODOS_STORAGE_TABLES = [
|
|
2902
3921
|
"todos_sync_records",
|
|
@@ -2958,7 +3977,7 @@ function installShadowOutboxSchema(db) {
|
|
|
2958
3977
|
status TEXT NOT NULL DEFAULT 'pending',
|
|
2959
3978
|
revision INTEGER NOT NULL DEFAULT 0
|
|
2960
3979
|
)`);
|
|
2961
|
-
|
|
3980
|
+
ensureColumn2(db, "shadow_outbox", "revision", "INTEGER NOT NULL DEFAULT 0");
|
|
2962
3981
|
db.run(`CREATE UNIQUE INDEX IF NOT EXISTS shadow_outbox_pending_uq
|
|
2963
3982
|
ON shadow_outbox(object_type, object_id) WHERE status='pending'`);
|
|
2964
3983
|
db.run(`CREATE INDEX IF NOT EXISTS shadow_outbox_ready_idx
|
|
@@ -2980,7 +3999,7 @@ function installShadowOutboxSchema(db) {
|
|
|
2980
3999
|
}
|
|
2981
4000
|
}
|
|
2982
4001
|
}
|
|
2983
|
-
function
|
|
4002
|
+
function ensureColumn2(db, table, column, definition) {
|
|
2984
4003
|
const rows = db.query(`PRAGMA table_info(${table})`).all();
|
|
2985
4004
|
if (rows.some((row) => row.name === column))
|
|
2986
4005
|
return;
|
|
@@ -3108,6 +4127,7 @@ function openDatabase(path) {
|
|
|
3108
4127
|
db.run("PRAGMA busy_timeout = 5000");
|
|
3109
4128
|
db.run("PRAGMA foreign_keys = ON");
|
|
3110
4129
|
runMigrations(db);
|
|
4130
|
+
ensureAgentIdentitySchema(db);
|
|
3111
4131
|
backfillTaskTags(db);
|
|
3112
4132
|
backfillMachineId(db);
|
|
3113
4133
|
maybeInstallShadowCapture(db);
|
|
@@ -3186,185 +4206,59 @@ function resolvePartialId(db, table, partialId) {
|
|
|
3186
4206
|
if (rows.length === 1) {
|
|
3187
4207
|
return rows[0].id;
|
|
3188
4208
|
}
|
|
3189
|
-
if (rows.length > 1) {
|
|
3190
|
-
return null;
|
|
3191
|
-
}
|
|
3192
|
-
if (table === "tasks") {
|
|
3193
|
-
const shortIdRows = db.query("SELECT id FROM tasks WHERE short_id = ?").all(partialId);
|
|
3194
|
-
if (shortIdRows.length === 1) {
|
|
3195
|
-
return shortIdRows[0].id;
|
|
3196
|
-
}
|
|
3197
|
-
}
|
|
3198
|
-
if (table === "task_lists") {
|
|
3199
|
-
const slugRow = db.query("SELECT id FROM task_lists WHERE slug = ?").get(partialId);
|
|
3200
|
-
if (slugRow)
|
|
3201
|
-
return slugRow.id;
|
|
3202
|
-
}
|
|
3203
|
-
if (table === "plans") {
|
|
3204
|
-
const slug = slugifyRef(partialId);
|
|
3205
|
-
if (slug) {
|
|
3206
|
-
const slugRows = db.query("SELECT id FROM plans WHERE slug = ?").all(slug);
|
|
3207
|
-
if (slugRows.length === 1)
|
|
3208
|
-
return slugRows[0].id;
|
|
3209
|
-
if (slugRows.length > 1)
|
|
3210
|
-
return null;
|
|
3211
|
-
}
|
|
3212
|
-
}
|
|
3213
|
-
if (table === "projects") {
|
|
3214
|
-
const nameRow = db.query("SELECT id FROM projects WHERE lower(name) = ?").get(partialId.toLowerCase());
|
|
3215
|
-
if (nameRow)
|
|
3216
|
-
return nameRow.id;
|
|
3217
|
-
}
|
|
3218
|
-
if (table === "agents") {
|
|
3219
|
-
const nameRow = db.query("SELECT id FROM agents WHERE lower(name) = ?").get(partialId.toLowerCase());
|
|
3220
|
-
if (nameRow)
|
|
3221
|
-
return nameRow.id;
|
|
3222
|
-
}
|
|
3223
|
-
return null;
|
|
3224
|
-
}
|
|
3225
|
-
var LOCK_EXPIRY_MINUTES = 30, _db = null, _dbPath = null, ALLOWED_TABLES;
|
|
3226
|
-
var init_database = __esm(() => {
|
|
3227
|
-
init_schema();
|
|
3228
|
-
init_machines();
|
|
3229
|
-
ALLOWED_TABLES = new Set(["tasks", "projects", "agents", "plans", "task_lists", "task_templates", "project_knowledge_records", "project_risks", "local_retrospectives"]);
|
|
3230
|
-
});
|
|
3231
|
-
|
|
3232
|
-
// src/types/index.ts
|
|
3233
|
-
var TASK_STATUSES, TASK_PRIORITIES, PLAN_STATUSES, VersionConflictError, TaskNotFoundError, ProjectNotFoundError, ResourceConflictError, PlanNotFoundError, LockError, AgentNotFoundError, TaskListNotFoundError, DependencyCycleError, CompletionGuardError, DISPATCH_STATUSES, DispatchNotFoundError;
|
|
3234
|
-
var init_types = __esm(() => {
|
|
3235
|
-
TASK_STATUSES = [
|
|
3236
|
-
"pending",
|
|
3237
|
-
"in_progress",
|
|
3238
|
-
"completed",
|
|
3239
|
-
"failed",
|
|
3240
|
-
"cancelled"
|
|
3241
|
-
];
|
|
3242
|
-
TASK_PRIORITIES = [
|
|
3243
|
-
"low",
|
|
3244
|
-
"medium",
|
|
3245
|
-
"high",
|
|
3246
|
-
"critical"
|
|
3247
|
-
];
|
|
3248
|
-
PLAN_STATUSES = ["active", "completed", "archived"];
|
|
3249
|
-
VersionConflictError = class VersionConflictError extends Error {
|
|
3250
|
-
taskId;
|
|
3251
|
-
expectedVersion;
|
|
3252
|
-
actualVersion;
|
|
3253
|
-
static code = "VERSION_CONFLICT";
|
|
3254
|
-
static suggestion = "Fetch the task with get_task to get the current version before updating.";
|
|
3255
|
-
constructor(taskId, expectedVersion, actualVersion) {
|
|
3256
|
-
super(`Version conflict for task ${taskId}: expected ${expectedVersion}, got ${actualVersion}`);
|
|
3257
|
-
this.taskId = taskId;
|
|
3258
|
-
this.expectedVersion = expectedVersion;
|
|
3259
|
-
this.actualVersion = actualVersion;
|
|
3260
|
-
this.name = "VersionConflictError";
|
|
3261
|
-
}
|
|
3262
|
-
};
|
|
3263
|
-
TaskNotFoundError = class TaskNotFoundError extends Error {
|
|
3264
|
-
taskId;
|
|
3265
|
-
static code = "TASK_NOT_FOUND";
|
|
3266
|
-
static suggestion = "Verify the task ID. Use list_tasks or search_tasks to find the correct ID.";
|
|
3267
|
-
constructor(taskId) {
|
|
3268
|
-
super(`Task not found: ${taskId}`);
|
|
3269
|
-
this.taskId = taskId;
|
|
3270
|
-
this.name = "TaskNotFoundError";
|
|
3271
|
-
}
|
|
3272
|
-
};
|
|
3273
|
-
ProjectNotFoundError = class ProjectNotFoundError extends Error {
|
|
3274
|
-
projectId;
|
|
3275
|
-
static code = "PROJECT_NOT_FOUND";
|
|
3276
|
-
static suggestion = "Use list_projects to see available projects.";
|
|
3277
|
-
constructor(projectId) {
|
|
3278
|
-
super(`Project not found: ${projectId}`);
|
|
3279
|
-
this.projectId = projectId;
|
|
3280
|
-
this.name = "ProjectNotFoundError";
|
|
3281
|
-
}
|
|
3282
|
-
};
|
|
3283
|
-
ResourceConflictError = class ResourceConflictError extends Error {
|
|
3284
|
-
code;
|
|
3285
|
-
constructor(code, message) {
|
|
3286
|
-
super(message);
|
|
3287
|
-
this.code = code;
|
|
3288
|
-
this.name = "ResourceConflictError";
|
|
3289
|
-
}
|
|
3290
|
-
};
|
|
3291
|
-
PlanNotFoundError = class PlanNotFoundError extends Error {
|
|
3292
|
-
planId;
|
|
3293
|
-
static code = "PLAN_NOT_FOUND";
|
|
3294
|
-
static suggestion = "Use list_plans to see available plans.";
|
|
3295
|
-
constructor(planId) {
|
|
3296
|
-
super(`Plan not found: ${planId}`);
|
|
3297
|
-
this.planId = planId;
|
|
3298
|
-
this.name = "PlanNotFoundError";
|
|
3299
|
-
}
|
|
3300
|
-
};
|
|
3301
|
-
LockError = class LockError extends Error {
|
|
3302
|
-
taskId;
|
|
3303
|
-
lockedBy;
|
|
3304
|
-
static code = "LOCK_ERROR";
|
|
3305
|
-
static suggestion = "Wait for the lock to expire (30 min) or contact the lock holder.";
|
|
3306
|
-
constructor(taskId, lockedBy) {
|
|
3307
|
-
super(`Task ${taskId} is locked by ${lockedBy}`);
|
|
3308
|
-
this.taskId = taskId;
|
|
3309
|
-
this.lockedBy = lockedBy;
|
|
3310
|
-
this.name = "LockError";
|
|
3311
|
-
}
|
|
3312
|
-
};
|
|
3313
|
-
AgentNotFoundError = class AgentNotFoundError extends Error {
|
|
3314
|
-
agentId;
|
|
3315
|
-
static code = "AGENT_NOT_FOUND";
|
|
3316
|
-
static suggestion = "Use register_agent to create the agent first, or list_agents to find existing ones.";
|
|
3317
|
-
constructor(agentId) {
|
|
3318
|
-
super(`Agent not found: ${agentId}`);
|
|
3319
|
-
this.agentId = agentId;
|
|
3320
|
-
this.name = "AgentNotFoundError";
|
|
3321
|
-
}
|
|
3322
|
-
};
|
|
3323
|
-
TaskListNotFoundError = class TaskListNotFoundError extends Error {
|
|
3324
|
-
taskListId;
|
|
3325
|
-
static code = "TASK_LIST_NOT_FOUND";
|
|
3326
|
-
static suggestion = "Use list_task_lists to see available lists.";
|
|
3327
|
-
constructor(taskListId) {
|
|
3328
|
-
super(`Task list not found: ${taskListId}`);
|
|
3329
|
-
this.taskListId = taskListId;
|
|
3330
|
-
this.name = "TaskListNotFoundError";
|
|
3331
|
-
}
|
|
3332
|
-
};
|
|
3333
|
-
DependencyCycleError = class DependencyCycleError extends Error {
|
|
3334
|
-
taskId;
|
|
3335
|
-
dependsOn;
|
|
3336
|
-
static code = "DEPENDENCY_CYCLE";
|
|
3337
|
-
static suggestion = "Check the dependency chain with get_task to avoid circular references.";
|
|
3338
|
-
constructor(taskId, dependsOn) {
|
|
3339
|
-
super(`Adding dependency ${taskId} -> ${dependsOn} would create a cycle`);
|
|
3340
|
-
this.taskId = taskId;
|
|
3341
|
-
this.dependsOn = dependsOn;
|
|
3342
|
-
this.name = "DependencyCycleError";
|
|
3343
|
-
}
|
|
3344
|
-
};
|
|
3345
|
-
CompletionGuardError = class CompletionGuardError extends Error {
|
|
3346
|
-
reason;
|
|
3347
|
-
retryAfterSeconds;
|
|
3348
|
-
static code = "COMPLETION_BLOCKED";
|
|
3349
|
-
static suggestion = "Wait for the cooldown period, then retry.";
|
|
3350
|
-
constructor(reason, retryAfterSeconds) {
|
|
3351
|
-
super(reason);
|
|
3352
|
-
this.reason = reason;
|
|
3353
|
-
this.retryAfterSeconds = retryAfterSeconds;
|
|
3354
|
-
this.name = "CompletionGuardError";
|
|
4209
|
+
if (rows.length > 1) {
|
|
4210
|
+
return null;
|
|
4211
|
+
}
|
|
4212
|
+
if (table === "tasks") {
|
|
4213
|
+
const shortIdRows = db.query("SELECT id FROM tasks WHERE short_id = ?").all(partialId);
|
|
4214
|
+
if (shortIdRows.length === 1) {
|
|
4215
|
+
return shortIdRows[0].id;
|
|
3355
4216
|
}
|
|
3356
|
-
}
|
|
3357
|
-
|
|
3358
|
-
|
|
3359
|
-
|
|
3360
|
-
|
|
3361
|
-
|
|
3362
|
-
|
|
3363
|
-
|
|
3364
|
-
|
|
3365
|
-
|
|
4217
|
+
}
|
|
4218
|
+
if (table === "task_lists") {
|
|
4219
|
+
const slugRow = db.query("SELECT id FROM task_lists WHERE slug = ?").get(partialId);
|
|
4220
|
+
if (slugRow)
|
|
4221
|
+
return slugRow.id;
|
|
4222
|
+
}
|
|
4223
|
+
if (table === "plans") {
|
|
4224
|
+
const slug = slugifyRef(partialId);
|
|
4225
|
+
if (slug) {
|
|
4226
|
+
const slugRows = db.query("SELECT id FROM plans WHERE slug = ?").all(slug);
|
|
4227
|
+
if (slugRows.length === 1)
|
|
4228
|
+
return slugRows[0].id;
|
|
4229
|
+
if (slugRows.length > 1)
|
|
4230
|
+
return null;
|
|
3366
4231
|
}
|
|
3367
|
-
}
|
|
4232
|
+
}
|
|
4233
|
+
if (table === "projects") {
|
|
4234
|
+
const nameRow = db.query("SELECT id FROM projects WHERE lower(name) = ?").get(partialId.toLowerCase());
|
|
4235
|
+
if (nameRow)
|
|
4236
|
+
return nameRow.id;
|
|
4237
|
+
}
|
|
4238
|
+
if (table === "agents") {
|
|
4239
|
+
const normalized = partialId.trim().toLowerCase();
|
|
4240
|
+
const matches = db.query(`
|
|
4241
|
+
SELECT id FROM agents WHERE lower(name) = ?
|
|
4242
|
+
UNION
|
|
4243
|
+
SELECT local_agent_id AS id FROM agent_identity_aliases
|
|
4244
|
+
WHERE normalized_label = ? AND status = 'active'
|
|
4245
|
+
ORDER BY id
|
|
4246
|
+
`).all(normalized, normalized);
|
|
4247
|
+
if (matches.length === 1)
|
|
4248
|
+
return matches[0].id;
|
|
4249
|
+
if (matches.length > 1) {
|
|
4250
|
+
throw new IdentityAliasAmbiguousError(partialId, matches.map((match) => match.id));
|
|
4251
|
+
}
|
|
4252
|
+
}
|
|
4253
|
+
return null;
|
|
4254
|
+
}
|
|
4255
|
+
var LOCK_EXPIRY_MINUTES = 30, _db = null, _dbPath = null, ALLOWED_TABLES;
|
|
4256
|
+
var init_database = __esm(() => {
|
|
4257
|
+
init_schema();
|
|
4258
|
+
init_machines();
|
|
4259
|
+
init_identity_mapping();
|
|
4260
|
+
init_types();
|
|
4261
|
+
ALLOWED_TABLES = new Set(["tasks", "projects", "agents", "plans", "task_lists", "task_templates", "project_knowledge_records", "project_risks", "local_retrospectives"]);
|
|
3368
4262
|
});
|
|
3369
4263
|
|
|
3370
4264
|
// src/lib/sync-utils.ts
|
|
@@ -3706,7 +4600,7 @@ function createProject(input, db) {
|
|
|
3706
4600
|
const d = db || getDatabase();
|
|
3707
4601
|
return d.transaction(() => {
|
|
3708
4602
|
const id = uuid();
|
|
3709
|
-
const
|
|
4603
|
+
const timestamp2 = now();
|
|
3710
4604
|
const derivedSlug = slugify(input.name);
|
|
3711
4605
|
const taskListId = input.task_list_id === undefined ? `todos-${derivedSlug}` : slugify(input.task_list_id);
|
|
3712
4606
|
if (!derivedSlug || !taskListId)
|
|
@@ -3718,7 +4612,7 @@ function createProject(input, db) {
|
|
|
3718
4612
|
const taskPrefix = input.task_prefix || generatePrefix(input.name, d);
|
|
3719
4613
|
const machineId = currentStorageMachineId(d);
|
|
3720
4614
|
d.run(`INSERT INTO projects (id, name, path, description, task_list_id, task_prefix, task_counter, created_at, updated_at, machine_id)
|
|
3721
|
-
VALUES (?, ?, ?, ?, ?, ?, 0, ?, ?, ?)`, [id, input.name, input.path, input.description || null, taskListId, taskPrefix,
|
|
4615
|
+
VALUES (?, ?, ?, ?, ?, ?, 0, ?, ?, ?)`, [id, input.name, input.path, input.description || null, taskListId, taskPrefix, timestamp2, timestamp2, machineId]);
|
|
3722
4616
|
return getProject(id, d);
|
|
3723
4617
|
})();
|
|
3724
4618
|
}
|
|
@@ -3831,7 +4725,7 @@ function rowToSource(row) {
|
|
|
3831
4725
|
function addProjectSource(input, db) {
|
|
3832
4726
|
const d = db || getDatabase();
|
|
3833
4727
|
const id = uuid();
|
|
3834
|
-
const
|
|
4728
|
+
const timestamp2 = now();
|
|
3835
4729
|
d.run(`INSERT INTO project_sources (id, project_id, type, name, uri, description, metadata, created_at, updated_at)
|
|
3836
4730
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
3837
4731
|
id,
|
|
@@ -3841,8 +4735,8 @@ function addProjectSource(input, db) {
|
|
|
3841
4735
|
input.uri,
|
|
3842
4736
|
input.description || null,
|
|
3843
4737
|
JSON.stringify(input.metadata || {}),
|
|
3844
|
-
|
|
3845
|
-
|
|
4738
|
+
timestamp2,
|
|
4739
|
+
timestamp2
|
|
3846
4740
|
]);
|
|
3847
4741
|
return rowToSource(d.query("SELECT * FROM project_sources WHERE id = ?").get(id));
|
|
3848
4742
|
}
|
|
@@ -4060,6 +4954,11 @@ function customPatterns() {
|
|
|
4060
4954
|
function secretPatterns() {
|
|
4061
4955
|
return [...customPatterns(), ...DEFAULT_SECRET_PATTERNS];
|
|
4062
4956
|
}
|
|
4957
|
+
function isRedactionPlaceholderMatch(match) {
|
|
4958
|
+
const placeholder = String.raw`\[REDACTED(?:_[A-Z_]+)?\]`;
|
|
4959
|
+
const trimmed = match.trim();
|
|
4960
|
+
return new RegExp(`^${placeholder}$`).test(trimmed) || new RegExp(`=\\s*['"]?${placeholder}['"]?$`).test(trimmed);
|
|
4961
|
+
}
|
|
4063
4962
|
function isSecretKey(key) {
|
|
4064
4963
|
if (NON_SECRET_USAGE_KEYS.has(key.toLowerCase()))
|
|
4065
4964
|
return false;
|
|
@@ -4097,7 +4996,7 @@ function redactValue(value) {
|
|
|
4097
4996
|
function listSecretFindings(value) {
|
|
4098
4997
|
const findings = [];
|
|
4099
4998
|
for (const pattern of secretPatterns()) {
|
|
4100
|
-
const matches = value.match(cloneRegex(pattern.regex));
|
|
4999
|
+
const matches = value.match(cloneRegex(pattern.regex))?.filter((match) => !isRedactionPlaceholderMatch(match));
|
|
4101
5000
|
if (matches?.length)
|
|
4102
5001
|
findings.push({ pattern: pattern.name, count: matches.length });
|
|
4103
5002
|
}
|
|
@@ -4128,6 +5027,9 @@ var init_redaction = __esm(() => {
|
|
|
4128
5027
|
{ name: "aws-access-key", regex: /\b(AKIA|ASIA)[0-9A-Z]{16}\b/g, replacement: "[REDACTED_AWS_KEY]" },
|
|
4129
5028
|
{ name: "private-key", regex: /-----BEGIN (?:RSA |EC |OPENSSH |)PRIVATE KEY-----[\s\S]*?-----END (?:RSA |EC |OPENSSH |)PRIVATE KEY-----/g, replacement: "[REDACTED_PRIVATE_KEY]" },
|
|
4130
5029
|
{ name: "openai-token", regex: /\bsk-[A-Za-z0-9_-]{12,}\b/g, replacement: "[REDACTED_TOKEN]" },
|
|
5030
|
+
{ name: "npm-token", regex: /\bnpm_[A-Za-z0-9]{20,}\b/g, replacement: "[REDACTED_NPM_TOKEN]" },
|
|
5031
|
+
{ name: "github-fine-grained-token", regex: /\bgithub_pat_[A-Za-z0-9_]{20,}\b/g, replacement: "[REDACTED_GITHUB_TOKEN]" },
|
|
5032
|
+
{ name: "github-token", regex: /\bgh[opsu]_[A-Za-z0-9]{20,}\b/g, replacement: "[REDACTED_GITHUB_TOKEN]" },
|
|
4131
5033
|
{ name: "env-secret-assignment", regex: /\b([A-Za-z0-9_]*(?:API_KEY|TOKEN|SECRET|PASSWORD)[A-Za-z0-9_]*)\s*=\s*['"]?[^'"\s]{8,}/gi, replacement: "$1=[REDACTED]" },
|
|
4132
5034
|
{ name: "bearer-token", regex: /\b(bearer)\s+[A-Za-z0-9._~+/=-]{12,}/gi, replacement: "$1 [REDACTED]" }
|
|
4133
5035
|
];
|
|
@@ -4202,7 +5104,7 @@ function upsertWorkspaceTrustProfile(input) {
|
|
|
4202
5104
|
const preset = input.preset || existing?.preset || "standard";
|
|
4203
5105
|
const presetChanged = Boolean(existing && input.preset && input.preset !== existing.preset);
|
|
4204
5106
|
const base = presetChanged ? defaultProfile(root, preset) : existing || defaultProfile(root, preset);
|
|
4205
|
-
const
|
|
5107
|
+
const timestamp2 = new Date().toISOString();
|
|
4206
5108
|
const profile = {
|
|
4207
5109
|
...base,
|
|
4208
5110
|
...PRESET_DEFAULTS[preset],
|
|
@@ -4215,8 +5117,8 @@ function upsertWorkspaceTrustProfile(input) {
|
|
|
4215
5117
|
write_scopes: unique2(input.write_scopes ?? base.write_scopes ?? PRESET_DEFAULTS[preset].write_scopes),
|
|
4216
5118
|
env_redactions: unique2(input.env_redactions ?? base.env_redactions ?? PRESET_DEFAULTS[preset].env_redactions),
|
|
4217
5119
|
require_prompt_for_unsafe: input.require_prompt_for_unsafe ?? base.require_prompt_for_unsafe ?? PRESET_DEFAULTS[preset].require_prompt_for_unsafe,
|
|
4218
|
-
created_at: existing?.created_at ||
|
|
4219
|
-
updated_at:
|
|
5120
|
+
created_at: existing?.created_at || timestamp2,
|
|
5121
|
+
updated_at: timestamp2
|
|
4220
5122
|
};
|
|
4221
5123
|
saveConfig({
|
|
4222
5124
|
...config,
|
|
@@ -4417,7 +5319,7 @@ function upsertRunnerSandboxProfile(input) {
|
|
|
4417
5319
|
const existing = config.runner_sandboxes?.[input.name];
|
|
4418
5320
|
const root = normalizePath2(input.root || existing?.root || process.cwd());
|
|
4419
5321
|
const base = existing || defaultProfile2(input.name, root);
|
|
4420
|
-
const
|
|
5322
|
+
const timestamp2 = new Date().toISOString();
|
|
4421
5323
|
const profile = {
|
|
4422
5324
|
...base,
|
|
4423
5325
|
name: input.name,
|
|
@@ -4431,8 +5333,8 @@ function upsertRunnerSandboxProfile(input) {
|
|
|
4431
5333
|
network_policy: input.network_policy || base.network_policy,
|
|
4432
5334
|
require_approval: input.require_approval ?? base.require_approval,
|
|
4433
5335
|
audit_evidence: input.audit_evidence ?? base.audit_evidence,
|
|
4434
|
-
created_at: existing?.created_at ||
|
|
4435
|
-
updated_at:
|
|
5336
|
+
created_at: existing?.created_at || timestamp2,
|
|
5337
|
+
updated_at: timestamp2
|
|
4436
5338
|
};
|
|
4437
5339
|
saveConfig({
|
|
4438
5340
|
...config,
|
|
@@ -4549,7 +5451,7 @@ function normalizeHook(input, existing) {
|
|
|
4549
5451
|
throw new Error("socket event hooks require socket_path");
|
|
4550
5452
|
if (input.target === "script" && !input.command && !existing?.command)
|
|
4551
5453
|
throw new Error("script event hooks require command");
|
|
4552
|
-
const
|
|
5454
|
+
const timestamp2 = new Date().toISOString();
|
|
4553
5455
|
return {
|
|
4554
5456
|
...existing,
|
|
4555
5457
|
name: safeName(input.name),
|
|
@@ -4566,8 +5468,8 @@ function normalizeHook(input, existing) {
|
|
|
4566
5468
|
attempts: clampAttempts(input.retry?.attempts ?? existing?.retry?.attempts ?? 1),
|
|
4567
5469
|
backoff_ms: Math.max(0, input.retry?.backoff_ms ?? existing?.retry?.backoff_ms ?? 0)
|
|
4568
5470
|
},
|
|
4569
|
-
created_at: existing?.created_at ||
|
|
4570
|
-
updated_at:
|
|
5471
|
+
created_at: existing?.created_at || timestamp2,
|
|
5472
|
+
updated_at: timestamp2
|
|
4571
5473
|
};
|
|
4572
5474
|
}
|
|
4573
5475
|
function clampAttempts(value) {
|
|
@@ -4581,11 +5483,11 @@ function eventMatches(hook, eventType) {
|
|
|
4581
5483
|
function canonicalEvent(input) {
|
|
4582
5484
|
return JSON.stringify(input);
|
|
4583
5485
|
}
|
|
4584
|
-
function buildEnvelope(type, payload,
|
|
5486
|
+
function buildEnvelope(type, payload, timestamp2 = new Date().toISOString()) {
|
|
4585
5487
|
const base = {
|
|
4586
5488
|
id: randomUUID(),
|
|
4587
5489
|
type,
|
|
4588
|
-
timestamp,
|
|
5490
|
+
timestamp: timestamp2,
|
|
4589
5491
|
payload: redactValue(payload ?? {}),
|
|
4590
5492
|
source: { package: "@hasna/todos", local_only: true }
|
|
4591
5493
|
};
|
|
@@ -4999,11 +5901,11 @@ class JsonEventsStore {
|
|
|
4999
5901
|
});
|
|
5000
5902
|
}
|
|
5001
5903
|
}
|
|
5002
|
-
function buildSignatureBase(
|
|
5003
|
-
return `${
|
|
5904
|
+
function buildSignatureBase(timestamp2, body) {
|
|
5905
|
+
return `${timestamp2}.${body}`;
|
|
5004
5906
|
}
|
|
5005
|
-
function signPayload(secret,
|
|
5006
|
-
const digest = createHmac("sha256", secret).update(buildSignatureBase(
|
|
5907
|
+
function signPayload(secret, timestamp2, body) {
|
|
5908
|
+
const digest = createHmac("sha256", secret).update(buildSignatureBase(timestamp2, body)).digest("hex");
|
|
5007
5909
|
return `sha256=${digest}`;
|
|
5008
5910
|
}
|
|
5009
5911
|
function now2() {
|
|
@@ -5016,17 +5918,17 @@ function buildWebhookRequest(event, channel) {
|
|
|
5016
5918
|
if (!channel.webhook)
|
|
5017
5919
|
throw new Error(`Channel ${channel.id} has no webhook config`);
|
|
5018
5920
|
const body = JSON.stringify(event);
|
|
5019
|
-
const
|
|
5921
|
+
const timestamp2 = event.time;
|
|
5020
5922
|
const headers = {
|
|
5021
5923
|
"Content-Type": "application/json",
|
|
5022
5924
|
"User-Agent": "@hasna/events",
|
|
5023
5925
|
"X-Hasna-Event-Id": event.id,
|
|
5024
5926
|
"X-Hasna-Event-Type": event.type,
|
|
5025
|
-
"X-Hasna-Timestamp":
|
|
5927
|
+
"X-Hasna-Timestamp": timestamp2,
|
|
5026
5928
|
...channel.webhook.headers
|
|
5027
5929
|
};
|
|
5028
5930
|
if (channel.webhook.secret) {
|
|
5029
|
-
headers["X-Hasna-Signature"] = signPayload(channel.webhook.secret,
|
|
5931
|
+
headers["X-Hasna-Signature"] = signPayload(channel.webhook.secret, timestamp2, body);
|
|
5030
5932
|
}
|
|
5031
5933
|
return { body, headers };
|
|
5032
5934
|
}
|
|
@@ -5180,11 +6082,11 @@ class EventsClient {
|
|
|
5180
6082
|
this.transportOptions = { fetchImpl: options.fetchImpl };
|
|
5181
6083
|
}
|
|
5182
6084
|
async addChannel(input) {
|
|
5183
|
-
const
|
|
6085
|
+
const timestamp2 = new Date().toISOString();
|
|
5184
6086
|
return this.store.addChannel({
|
|
5185
6087
|
...input,
|
|
5186
|
-
createdAt: input.createdAt ??
|
|
5187
|
-
updatedAt: input.updatedAt ??
|
|
6088
|
+
createdAt: input.createdAt ?? timestamp2,
|
|
6089
|
+
updatedAt: input.updatedAt ?? timestamp2
|
|
5188
6090
|
});
|
|
5189
6091
|
}
|
|
5190
6092
|
async listChannels() {
|
|
@@ -5256,12 +6158,12 @@ class EventsClient {
|
|
|
5256
6158
|
const match = await this.matchChannel(id, input);
|
|
5257
6159
|
const event = match.event;
|
|
5258
6160
|
if (options.honorFilters && !match.matched) {
|
|
5259
|
-
const
|
|
6161
|
+
const timestamp2 = new Date().toISOString();
|
|
5260
6162
|
const result2 = createDeliveryResult(event, channel, [{
|
|
5261
6163
|
attempt: 1,
|
|
5262
6164
|
status: "skipped",
|
|
5263
|
-
startedAt:
|
|
5264
|
-
completedAt:
|
|
6165
|
+
startedAt: timestamp2,
|
|
6166
|
+
completedAt: timestamp2,
|
|
5265
6167
|
error: match.reason
|
|
5266
6168
|
}]);
|
|
5267
6169
|
result2.metadata = { reason: "filter_mismatch" };
|
|
@@ -5382,7 +6284,7 @@ function createTaskList(input, db) {
|
|
|
5382
6284
|
const d = db || getDatabase();
|
|
5383
6285
|
return d.transaction(() => {
|
|
5384
6286
|
const id = uuid();
|
|
5385
|
-
const
|
|
6287
|
+
const timestamp2 = now();
|
|
5386
6288
|
const slug = normalizeSlug(input.slug === undefined ? input.name : input.slug);
|
|
5387
6289
|
if (!slug)
|
|
5388
6290
|
throw new Error("Invalid task-list slug \u2014 must be non-empty kebab-case");
|
|
@@ -5393,7 +6295,7 @@ function createTaskList(input, db) {
|
|
|
5393
6295
|
throw new ResourceConflictError("TASK_LIST_SLUG_CONFLICT", `Task list with slug "${slug}" already exists in this scope`);
|
|
5394
6296
|
}
|
|
5395
6297
|
d.run(`INSERT INTO task_lists (id, project_id, slug, name, description, metadata, created_at, updated_at, machine_id)
|
|
5396
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, [id, input.project_id || null, slug, input.name, input.description || null, JSON.stringify(input.metadata || {}),
|
|
6298
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, [id, input.project_id || null, slug, input.name, input.description || null, JSON.stringify(input.metadata || {}), timestamp2, timestamp2, machineId]);
|
|
5397
6299
|
return getTaskList(id, d);
|
|
5398
6300
|
})();
|
|
5399
6301
|
}
|
|
@@ -5866,6 +6768,110 @@ var init_secret_redaction = __esm(() => {
|
|
|
5866
6768
|
customRedactors = [];
|
|
5867
6769
|
});
|
|
5868
6770
|
|
|
6771
|
+
// src/lib/prewrite-secrets.ts
|
|
6772
|
+
function mergeFindings(findings) {
|
|
6773
|
+
const byPattern = new Map;
|
|
6774
|
+
for (const finding of findings) {
|
|
6775
|
+
const existing = byPattern.get(finding.pattern);
|
|
6776
|
+
if (!existing) {
|
|
6777
|
+
byPattern.set(finding.pattern, {
|
|
6778
|
+
pattern: finding.pattern,
|
|
6779
|
+
count: finding.count,
|
|
6780
|
+
lines: Array.from(new Set(finding.lines)).sort((a, b) => a - b)
|
|
6781
|
+
});
|
|
6782
|
+
continue;
|
|
6783
|
+
}
|
|
6784
|
+
existing.count += finding.count;
|
|
6785
|
+
existing.lines = Array.from(new Set([...existing.lines, ...finding.lines])).sort((a, b) => a - b);
|
|
6786
|
+
}
|
|
6787
|
+
return [...byPattern.values()].sort((left, right) => left.pattern.localeCompare(right.pattern));
|
|
6788
|
+
}
|
|
6789
|
+
function redactPreWriteText(value) {
|
|
6790
|
+
return redactText(redactEvidenceText(value));
|
|
6791
|
+
}
|
|
6792
|
+
function scanPreWriteText(value, context = "text") {
|
|
6793
|
+
const redacted = redactPreWriteText(value);
|
|
6794
|
+
if (redacted === value) {
|
|
6795
|
+
return {
|
|
6796
|
+
schema_version: PREWRITE_SECRET_SCAN_SCHEMA,
|
|
6797
|
+
clean: true,
|
|
6798
|
+
context,
|
|
6799
|
+
findings: []
|
|
6800
|
+
};
|
|
6801
|
+
}
|
|
6802
|
+
const structural = scanTextForSecrets(value).matches.map((match) => ({
|
|
6803
|
+
pattern: match.pattern,
|
|
6804
|
+
count: 1,
|
|
6805
|
+
lines: match.line ? [match.line] : []
|
|
6806
|
+
}));
|
|
6807
|
+
const configured = listSecretFindings(value).map((finding) => ({
|
|
6808
|
+
pattern: finding.pattern,
|
|
6809
|
+
count: finding.count,
|
|
6810
|
+
lines: []
|
|
6811
|
+
}));
|
|
6812
|
+
return {
|
|
6813
|
+
schema_version: PREWRITE_SECRET_SCAN_SCHEMA,
|
|
6814
|
+
clean: false,
|
|
6815
|
+
context,
|
|
6816
|
+
findings: mergeFindings([...structural, ...configured])
|
|
6817
|
+
};
|
|
6818
|
+
}
|
|
6819
|
+
function assertPreWriteTextClean(value, context = "text") {
|
|
6820
|
+
const scan = scanPreWriteText(value, context);
|
|
6821
|
+
if (!scan.clean)
|
|
6822
|
+
throw new PreWriteSecretError(context, scan.findings);
|
|
6823
|
+
}
|
|
6824
|
+
function sanitizePreWriteText(value, context = "text", options = {}) {
|
|
6825
|
+
const mode = options.mode ?? "redact";
|
|
6826
|
+
if (mode === "block") {
|
|
6827
|
+
assertPreWriteTextClean(value, context);
|
|
6828
|
+
return value;
|
|
6829
|
+
}
|
|
6830
|
+
return redactPreWriteText(value);
|
|
6831
|
+
}
|
|
6832
|
+
function sanitizePreWriteValue(value, context = "value", options = {}) {
|
|
6833
|
+
if (typeof value === "string")
|
|
6834
|
+
return sanitizePreWriteText(value, context, options);
|
|
6835
|
+
if (Array.isArray(value)) {
|
|
6836
|
+
return value.map((item, index) => sanitizePreWriteValue(item, `${context}[${index}]`, options));
|
|
6837
|
+
}
|
|
6838
|
+
if (value && typeof value === "object") {
|
|
6839
|
+
const sanitized = {};
|
|
6840
|
+
for (const [key, child] of Object.entries(value)) {
|
|
6841
|
+
const safeKey = sanitizePreWriteText(key, `${context}.$key`, options);
|
|
6842
|
+
sanitized[safeKey] = sanitizePreWriteValue(child, `${context}.${safeKey}`, options);
|
|
6843
|
+
}
|
|
6844
|
+
return redactValue(sanitized);
|
|
6845
|
+
}
|
|
6846
|
+
return value;
|
|
6847
|
+
}
|
|
6848
|
+
function sanitizePreWriteJsonString(value, context = "json") {
|
|
6849
|
+
if (value === undefined || value === null)
|
|
6850
|
+
return null;
|
|
6851
|
+
try {
|
|
6852
|
+
return JSON.stringify(sanitizePreWriteValue(JSON.parse(value), context));
|
|
6853
|
+
} catch {
|
|
6854
|
+
return sanitizePreWriteText(value, context);
|
|
6855
|
+
}
|
|
6856
|
+
}
|
|
6857
|
+
var PREWRITE_SECRET_SCAN_SCHEMA, PreWriteSecretError;
|
|
6858
|
+
var init_prewrite_secrets = __esm(() => {
|
|
6859
|
+
init_redaction();
|
|
6860
|
+
init_secret_redaction();
|
|
6861
|
+
PREWRITE_SECRET_SCAN_SCHEMA = ["todos", "prewrite_secret_scan", "v1"].join(".");
|
|
6862
|
+
PreWriteSecretError = class PreWriteSecretError extends Error {
|
|
6863
|
+
context;
|
|
6864
|
+
findings;
|
|
6865
|
+
constructor(context, findings) {
|
|
6866
|
+
const summary = findings.map((finding) => `${finding.pattern}:${finding.count}`).join(", ");
|
|
6867
|
+
super(`Secret pattern detected before persistence in ${context}: ${summary}`);
|
|
6868
|
+
this.name = "PreWriteSecretError";
|
|
6869
|
+
this.context = context;
|
|
6870
|
+
this.findings = findings;
|
|
6871
|
+
}
|
|
6872
|
+
};
|
|
6873
|
+
});
|
|
6874
|
+
|
|
5869
6875
|
// src/lib/activity-audit.ts
|
|
5870
6876
|
var exports_activity_audit = {};
|
|
5871
6877
|
__export(exports_activity_audit, {
|
|
@@ -5914,6 +6920,9 @@ function logActivity(input, db) {
|
|
|
5914
6920
|
const id = uuid();
|
|
5915
6921
|
const ts = input.created_at ?? now();
|
|
5916
6922
|
const machineId = input.machine_id ?? getMachineId2();
|
|
6923
|
+
const oldValue = input.old_value == null ? null : sanitizePreWriteText(input.old_value, "activity.old_value");
|
|
6924
|
+
const newValue = input.new_value == null ? null : sanitizePreWriteText(input.new_value, "activity.new_value");
|
|
6925
|
+
const metadata = sanitizePreWriteValue(input.metadata ?? {}, "activity.metadata");
|
|
5917
6926
|
d.run(`INSERT INTO activity_log (
|
|
5918
6927
|
id, entity_type, entity_id, action, field, old_value, new_value,
|
|
5919
6928
|
actor_id, session_id, machine_id, metadata, created_at
|
|
@@ -5923,12 +6932,12 @@ function logActivity(input, db) {
|
|
|
5923
6932
|
input.entity_id,
|
|
5924
6933
|
input.action,
|
|
5925
6934
|
input.field ?? null,
|
|
5926
|
-
|
|
5927
|
-
|
|
6935
|
+
oldValue,
|
|
6936
|
+
newValue,
|
|
5928
6937
|
input.actor_id ?? null,
|
|
5929
6938
|
input.session_id ?? null,
|
|
5930
6939
|
machineId,
|
|
5931
|
-
JSON.stringify(
|
|
6940
|
+
JSON.stringify(metadata),
|
|
5932
6941
|
ts
|
|
5933
6942
|
]);
|
|
5934
6943
|
return getActivityRecord(id, d);
|
|
@@ -6039,6 +7048,7 @@ var ACTIVITY_LOG_SCHEMA = "todos.activity_log.v1", ACTIVITY_ENTITY_TYPES;
|
|
|
6039
7048
|
var init_activity_audit = __esm(() => {
|
|
6040
7049
|
init_database();
|
|
6041
7050
|
init_secret_redaction();
|
|
7051
|
+
init_prewrite_secrets();
|
|
6042
7052
|
ACTIVITY_ENTITY_TYPES = [
|
|
6043
7053
|
"task",
|
|
6044
7054
|
"project",
|
|
@@ -6051,13 +7061,18 @@ var init_activity_audit = __esm(() => {
|
|
|
6051
7061
|
});
|
|
6052
7062
|
|
|
6053
7063
|
// src/db/audit.ts
|
|
7064
|
+
function sanitizeHistoryValue(value, context) {
|
|
7065
|
+
return value === undefined || value === null ? null : sanitizePreWriteText(String(value), context);
|
|
7066
|
+
}
|
|
6054
7067
|
function logTaskChange(taskId, action, field2, oldValue, newValue, agentId, db) {
|
|
6055
7068
|
const d = db || getDatabase();
|
|
6056
7069
|
const id = uuid();
|
|
6057
|
-
const
|
|
7070
|
+
const timestamp2 = now();
|
|
6058
7071
|
const machineId = currentStorageMachineId(d);
|
|
7072
|
+
const safeOldValue = sanitizeHistoryValue(oldValue, "task_history.old_value");
|
|
7073
|
+
const safeNewValue = sanitizeHistoryValue(newValue, "task_history.new_value");
|
|
6059
7074
|
d.run(`INSERT INTO task_history (id, task_id, action, field, old_value, new_value, agent_id, created_at, machine_id)
|
|
6060
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, [id, taskId, action, field2 || null,
|
|
7075
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, [id, taskId, action, field2 || null, safeOldValue, safeNewValue, agentId || null, timestamp2, machineId]);
|
|
6061
7076
|
try {
|
|
6062
7077
|
const { logActivity: logActivity2 } = (init_activity_audit(), __toCommonJS(exports_activity_audit));
|
|
6063
7078
|
logActivity2({
|
|
@@ -6065,12 +7080,12 @@ function logTaskChange(taskId, action, field2, oldValue, newValue, agentId, db)
|
|
|
6065
7080
|
entity_id: taskId,
|
|
6066
7081
|
action,
|
|
6067
7082
|
field: field2,
|
|
6068
|
-
old_value:
|
|
6069
|
-
new_value:
|
|
7083
|
+
old_value: safeOldValue,
|
|
7084
|
+
new_value: safeNewValue,
|
|
6070
7085
|
actor_id: agentId ?? undefined
|
|
6071
7086
|
}, d);
|
|
6072
7087
|
} catch {}
|
|
6073
|
-
return { id, task_id: taskId, action, field: field2 || null, old_value:
|
|
7088
|
+
return { id, task_id: taskId, action, field: field2 || null, old_value: safeOldValue, new_value: safeNewValue, agent_id: agentId || null, created_at: timestamp2, machine_id: machineId };
|
|
6074
7089
|
}
|
|
6075
7090
|
function getTaskHistory(taskId, db) {
|
|
6076
7091
|
const d = db || getDatabase();
|
|
@@ -6109,6 +7124,7 @@ function getRecap(hours = 8, projectId, db) {
|
|
|
6109
7124
|
var init_audit = __esm(() => {
|
|
6110
7125
|
init_database();
|
|
6111
7126
|
init_storage_tombstones();
|
|
7127
|
+
init_prewrite_secrets();
|
|
6112
7128
|
});
|
|
6113
7129
|
|
|
6114
7130
|
// src/db/webhooks.ts
|
|
@@ -6341,27 +7357,27 @@ function getChecklist(taskId, db) {
|
|
|
6341
7357
|
function addChecklistItem(input, db) {
|
|
6342
7358
|
const d = db || getDatabase();
|
|
6343
7359
|
const id = uuid();
|
|
6344
|
-
const
|
|
7360
|
+
const timestamp2 = now();
|
|
6345
7361
|
let position = input.position;
|
|
6346
7362
|
if (position === undefined) {
|
|
6347
7363
|
const maxRow = d.query("SELECT MAX(position) as max_pos FROM task_checklists WHERE task_id = ?").get(input.task_id);
|
|
6348
7364
|
position = (maxRow?.max_pos ?? -1) + 1;
|
|
6349
7365
|
}
|
|
6350
|
-
d.run("INSERT INTO task_checklists (id, task_id, position, text, checked, created_at, updated_at) VALUES (?, ?, ?, ?, 0, ?, ?)", [id, input.task_id, position, input.text,
|
|
7366
|
+
d.run("INSERT INTO task_checklists (id, task_id, position, text, checked, created_at, updated_at) VALUES (?, ?, ?, ?, 0, ?, ?)", [id, input.task_id, position, input.text, timestamp2, timestamp2]);
|
|
6351
7367
|
return rowToItem(d.query("SELECT * FROM task_checklists WHERE id = ?").get(id));
|
|
6352
7368
|
}
|
|
6353
7369
|
function checkChecklistItem(id, checked, db) {
|
|
6354
7370
|
const d = db || getDatabase();
|
|
6355
|
-
const
|
|
6356
|
-
const result = d.run("UPDATE task_checklists SET checked = ?, updated_at = ? WHERE id = ?", [checked ? 1 : 0,
|
|
7371
|
+
const timestamp2 = now();
|
|
7372
|
+
const result = d.run("UPDATE task_checklists SET checked = ?, updated_at = ? WHERE id = ?", [checked ? 1 : 0, timestamp2, id]);
|
|
6357
7373
|
if (result.changes === 0)
|
|
6358
7374
|
return null;
|
|
6359
7375
|
return rowToItem(d.query("SELECT * FROM task_checklists WHERE id = ?").get(id));
|
|
6360
7376
|
}
|
|
6361
7377
|
function updateChecklistItemText(id, text, db) {
|
|
6362
7378
|
const d = db || getDatabase();
|
|
6363
|
-
const
|
|
6364
|
-
const result = d.run("UPDATE task_checklists SET text = ?, updated_at = ? WHERE id = ?", [text,
|
|
7379
|
+
const timestamp2 = now();
|
|
7380
|
+
const result = d.run("UPDATE task_checklists SET text = ?, updated_at = ? WHERE id = ?", [text, timestamp2, id]);
|
|
6365
7381
|
if (result.changes === 0)
|
|
6366
7382
|
return null;
|
|
6367
7383
|
return rowToItem(d.query("SELECT * FROM task_checklists WHERE id = ?").get(id));
|
|
@@ -6483,6 +7499,50 @@ var init_recurrence = __esm(() => {
|
|
|
6483
7499
|
};
|
|
6484
7500
|
});
|
|
6485
7501
|
|
|
7502
|
+
// src/lib/template-semantics.ts
|
|
7503
|
+
function resolveTemplateVariables(templateVars, provided) {
|
|
7504
|
+
const merged = { ...provided };
|
|
7505
|
+
for (const variable of templateVars) {
|
|
7506
|
+
if (merged[variable.name] === undefined && variable.default !== undefined) {
|
|
7507
|
+
merged[variable.name] = variable.default;
|
|
7508
|
+
}
|
|
7509
|
+
}
|
|
7510
|
+
const missing = templateVars.filter((variable) => variable.required && merged[variable.name] === undefined).map((variable) => variable.name);
|
|
7511
|
+
if (missing.length > 0) {
|
|
7512
|
+
throw new Error(`Missing required template variable(s): ${missing.join(", ")}`);
|
|
7513
|
+
}
|
|
7514
|
+
return merged;
|
|
7515
|
+
}
|
|
7516
|
+
function substituteTemplateVariables(text, variables) {
|
|
7517
|
+
let result = text;
|
|
7518
|
+
for (const [key, value] of Object.entries(variables)) {
|
|
7519
|
+
result = result.replace(new RegExp(`\\{${key}\\}`, "g"), value);
|
|
7520
|
+
}
|
|
7521
|
+
return result;
|
|
7522
|
+
}
|
|
7523
|
+
function evaluateTemplateCondition(condition, variables) {
|
|
7524
|
+
if (!condition || condition.trim() === "")
|
|
7525
|
+
return true;
|
|
7526
|
+
const trimmed = condition.trim();
|
|
7527
|
+
const equal = trimmed.match(/^\{([^}]+)\}\s*==\s*(.+)$/);
|
|
7528
|
+
if (equal)
|
|
7529
|
+
return (variables[equal[1]] ?? "") === equal[2].trim();
|
|
7530
|
+
const unequal = trimmed.match(/^\{([^}]+)\}\s*!=\s*(.+)$/);
|
|
7531
|
+
if (unequal)
|
|
7532
|
+
return (variables[unequal[1]] ?? "") !== unequal[2].trim();
|
|
7533
|
+
const falsy = trimmed.match(/^!\{([^}]+)\}$/);
|
|
7534
|
+
if (falsy) {
|
|
7535
|
+
const value = variables[falsy[1]];
|
|
7536
|
+
return !value || value === "" || value === "false";
|
|
7537
|
+
}
|
|
7538
|
+
const truthy = trimmed.match(/^\{([^}]+)\}$/);
|
|
7539
|
+
if (truthy) {
|
|
7540
|
+
const value = variables[truthy[1]];
|
|
7541
|
+
return !!value && value !== "" && value !== "false";
|
|
7542
|
+
}
|
|
7543
|
+
return true;
|
|
7544
|
+
}
|
|
7545
|
+
|
|
6486
7546
|
// src/db/templates.ts
|
|
6487
7547
|
function rowToTemplate(row) {
|
|
6488
7548
|
return {
|
|
@@ -6558,6 +7618,14 @@ function deleteTemplate(id, db) {
|
|
|
6558
7618
|
payload: template,
|
|
6559
7619
|
version: template.version
|
|
6560
7620
|
}, d);
|
|
7621
|
+
for (const task of getTemplateTasks(resolved, d)) {
|
|
7622
|
+
recordStorageTombstone({
|
|
7623
|
+
object_type: "template_tasks",
|
|
7624
|
+
object_id: task.id,
|
|
7625
|
+
payload: task,
|
|
7626
|
+
version: 1
|
|
7627
|
+
}, d);
|
|
7628
|
+
}
|
|
6561
7629
|
return d.run("DELETE FROM task_templates WHERE id = ?", [resolved]).changes > 0;
|
|
6562
7630
|
}
|
|
6563
7631
|
function updateTemplate(id, updates, db) {
|
|
@@ -6689,34 +7757,7 @@ function getTemplateTasks(templateId, db) {
|
|
|
6689
7757
|
return rows.map(rowToTemplateTask);
|
|
6690
7758
|
}
|
|
6691
7759
|
function evaluateCondition(condition, variables) {
|
|
6692
|
-
|
|
6693
|
-
return true;
|
|
6694
|
-
const trimmed = condition.trim();
|
|
6695
|
-
const eqMatch = trimmed.match(/^\{([^}]+)\}\s*==\s*(.+)$/);
|
|
6696
|
-
if (eqMatch) {
|
|
6697
|
-
const varName = eqMatch[1];
|
|
6698
|
-
const expected = eqMatch[2].trim();
|
|
6699
|
-
return (variables[varName] ?? "") === expected;
|
|
6700
|
-
}
|
|
6701
|
-
const neqMatch = trimmed.match(/^\{([^}]+)\}\s*!=\s*(.+)$/);
|
|
6702
|
-
if (neqMatch) {
|
|
6703
|
-
const varName = neqMatch[1];
|
|
6704
|
-
const expected = neqMatch[2].trim();
|
|
6705
|
-
return (variables[varName] ?? "") !== expected;
|
|
6706
|
-
}
|
|
6707
|
-
const falsyMatch = trimmed.match(/^!\{([^}]+)\}$/);
|
|
6708
|
-
if (falsyMatch) {
|
|
6709
|
-
const varName = falsyMatch[1];
|
|
6710
|
-
const val = variables[varName];
|
|
6711
|
-
return !val || val === "" || val === "false";
|
|
6712
|
-
}
|
|
6713
|
-
const truthyMatch = trimmed.match(/^\{([^}]+)\}$/);
|
|
6714
|
-
if (truthyMatch) {
|
|
6715
|
-
const varName = truthyMatch[1];
|
|
6716
|
-
const val = variables[varName];
|
|
6717
|
-
return !!val && val !== "" && val !== "false";
|
|
6718
|
-
}
|
|
6719
|
-
return true;
|
|
7760
|
+
return evaluateTemplateCondition(condition, variables);
|
|
6720
7761
|
}
|
|
6721
7762
|
function exportTemplate(id, db) {
|
|
6722
7763
|
const d = db || getDatabase();
|
|
@@ -6789,29 +7830,10 @@ function listTemplateVersions(id, db) {
|
|
|
6789
7830
|
return d.query("SELECT * FROM template_versions WHERE template_id = ? ORDER BY version DESC").all(resolved);
|
|
6790
7831
|
}
|
|
6791
7832
|
function resolveVariables(templateVars, provided) {
|
|
6792
|
-
|
|
6793
|
-
for (const v of templateVars) {
|
|
6794
|
-
if (merged[v.name] === undefined && v.default !== undefined) {
|
|
6795
|
-
merged[v.name] = v.default;
|
|
6796
|
-
}
|
|
6797
|
-
}
|
|
6798
|
-
const missing = [];
|
|
6799
|
-
for (const v of templateVars) {
|
|
6800
|
-
if (v.required && merged[v.name] === undefined) {
|
|
6801
|
-
missing.push(v.name);
|
|
6802
|
-
}
|
|
6803
|
-
}
|
|
6804
|
-
if (missing.length > 0) {
|
|
6805
|
-
throw new Error(`Missing required template variable(s): ${missing.join(", ")}`);
|
|
6806
|
-
}
|
|
6807
|
-
return merged;
|
|
7833
|
+
return resolveTemplateVariables(templateVars, provided);
|
|
6808
7834
|
}
|
|
6809
7835
|
function substituteVars(text, variables) {
|
|
6810
|
-
|
|
6811
|
-
for (const [key, val] of Object.entries(variables)) {
|
|
6812
|
-
result = result.replace(new RegExp(`\\{${key}\\}`, "g"), val);
|
|
6813
|
-
}
|
|
6814
|
-
return result;
|
|
7836
|
+
return substituteTemplateVariables(text, variables);
|
|
6815
7837
|
}
|
|
6816
7838
|
function tasksFromTemplate(templateId, projectId, variables, taskListId, db, _visitedTemplateIds) {
|
|
6817
7839
|
const d = db || getDatabase();
|
|
@@ -7133,9 +8155,9 @@ function startTask(id, agentId, db) {
|
|
|
7133
8155
|
throw new Error(`Task is blocked by ${blocking.length} unfinished dependency(ies): ${blockerIds}`);
|
|
7134
8156
|
}
|
|
7135
8157
|
const cutoff = lockExpiryCutoff();
|
|
7136
|
-
const
|
|
8158
|
+
const timestamp2 = now();
|
|
7137
8159
|
const result = d.run(`UPDATE tasks SET status = 'in_progress', assigned_to = ?, locked_by = ?, locked_at = ?, started_at = COALESCE(started_at, ?), version = version + 1, updated_at = ?
|
|
7138
|
-
WHERE id = ? AND status IN ('pending', 'in_progress') AND (locked_by IS NULL OR locked_by = ? OR locked_at < ?)`, [agentId, agentId,
|
|
8160
|
+
WHERE id = ? AND status IN ('pending', 'in_progress') AND (locked_by IS NULL OR locked_by = ? OR locked_at < ?)`, [agentId, agentId, timestamp2, timestamp2, timestamp2, id, agentId, cutoff]);
|
|
7139
8161
|
if (result.changes === 0) {
|
|
7140
8162
|
const current = getTask(id, d);
|
|
7141
8163
|
if (!current)
|
|
@@ -7147,7 +8169,7 @@ function startTask(id, agentId, db) {
|
|
|
7147
8169
|
throw new Error(`Task ${id} could not be started because it changed during claim`);
|
|
7148
8170
|
}
|
|
7149
8171
|
logTaskChange(id, "start", "status", "pending", "in_progress", agentId, d);
|
|
7150
|
-
const startedTask = { ...task, status: "in_progress", assigned_to: agentId, locked_by: agentId, locked_at:
|
|
8172
|
+
const startedTask = { ...task, status: "in_progress", assigned_to: agentId, locked_by: agentId, locked_at: timestamp2, started_at: task.started_at || timestamp2, version: task.version + 1, updated_at: timestamp2 };
|
|
7151
8173
|
const payload = taskEventData(startedTask, { agent_id: agentId });
|
|
7152
8174
|
dispatchWebhook2("task.started", payload, d).catch(() => {});
|
|
7153
8175
|
emitLocalEventHooksQuiet({ type: "task.started", payload, databasePath });
|
|
@@ -7170,7 +8192,13 @@ function completeTask(id, agentId, db, options) {
|
|
|
7170
8192
|
throw new LockError(id, task.locked_by);
|
|
7171
8193
|
}
|
|
7172
8194
|
checkCompletionGuard(task, agentId || null, d);
|
|
7173
|
-
const evidence = options ? {
|
|
8195
|
+
const evidence = options ? sanitizePreWriteValue({
|
|
8196
|
+
files_changed: options.files_changed,
|
|
8197
|
+
test_results: options.test_results,
|
|
8198
|
+
commit_hash: options.commit_hash,
|
|
8199
|
+
notes: options.notes,
|
|
8200
|
+
attachment_ids: options.attachment_ids
|
|
8201
|
+
}, "task.completion_evidence") : undefined;
|
|
7174
8202
|
const hasEvidence = evidence && (evidence.files_changed || evidence.test_results || evidence.commit_hash || evidence.notes || evidence.attachment_ids);
|
|
7175
8203
|
const completionMeta = {};
|
|
7176
8204
|
if (hasEvidence)
|
|
@@ -7179,21 +8207,21 @@ function completeTask(id, agentId, db, options) {
|
|
|
7179
8207
|
completionMeta._completion = { confidence: options.confidence };
|
|
7180
8208
|
}
|
|
7181
8209
|
const hasMeta = Object.keys(completionMeta).length > 0;
|
|
7182
|
-
const
|
|
8210
|
+
const timestamp2 = options?.completed_at || now();
|
|
7183
8211
|
const confidence = options?.confidence !== undefined ? options.confidence : task.confidence;
|
|
7184
8212
|
const versionBeforeStatus = task.version + (hasMeta ? 1 : 0);
|
|
7185
8213
|
const finalVersion = versionBeforeStatus + 1;
|
|
7186
8214
|
const tx = d.transaction(() => {
|
|
7187
8215
|
if (hasMeta) {
|
|
7188
|
-
const meta2 = { ...task.metadata, ...completionMeta };
|
|
7189
|
-
const metaResult = d.run("UPDATE tasks SET metadata = ?, version = version + 1, updated_at = ? WHERE id = ? AND version = ?", [JSON.stringify(meta2),
|
|
8216
|
+
const meta2 = sanitizePreWriteValue({ ...task.metadata, ...completionMeta }, "task.metadata");
|
|
8217
|
+
const metaResult = d.run("UPDATE tasks SET metadata = ?, version = version + 1, updated_at = ? WHERE id = ? AND version = ?", [JSON.stringify(meta2), timestamp2, id, task.version]);
|
|
7190
8218
|
if (metaResult.changes === 0) {
|
|
7191
8219
|
const current = getTask(id, d);
|
|
7192
8220
|
throw new VersionConflictError(id, task.version, current?.version ?? -1);
|
|
7193
8221
|
}
|
|
7194
8222
|
}
|
|
7195
8223
|
const statusResult = d.run(`UPDATE tasks SET status = 'completed', locked_by = NULL, locked_at = NULL, completed_at = ?, confidence = ?, version = version + 1, updated_at = ?
|
|
7196
|
-
WHERE id = ? AND version = ?`, [
|
|
8224
|
+
WHERE id = ? AND version = ?`, [timestamp2, confidence, timestamp2, id, versionBeforeStatus]);
|
|
7197
8225
|
if (statusResult.changes === 0) {
|
|
7198
8226
|
const current = getTask(id, d);
|
|
7199
8227
|
throw new VersionConflictError(id, versionBeforeStatus, current?.version ?? -1);
|
|
@@ -7206,20 +8234,20 @@ function completeTask(id, agentId, db, options) {
|
|
|
7206
8234
|
status: "completed",
|
|
7207
8235
|
locked_by: null,
|
|
7208
8236
|
locked_at: null,
|
|
7209
|
-
completed_at:
|
|
8237
|
+
completed_at: timestamp2,
|
|
7210
8238
|
confidence,
|
|
7211
8239
|
version: finalVersion,
|
|
7212
|
-
updated_at:
|
|
8240
|
+
updated_at: timestamp2,
|
|
7213
8241
|
metadata: hasMeta ? { ...task.metadata, ...completionMeta } : task.metadata
|
|
7214
8242
|
};
|
|
7215
|
-
const completionPayload = taskEventData(completedTaskForEvent, { agent_id: agentId, completed_at:
|
|
8243
|
+
const completionPayload = taskEventData(completedTaskForEvent, { agent_id: agentId, completed_at: timestamp2 });
|
|
7216
8244
|
dispatchWebhook2("task.completed", completionPayload, d).catch(() => {});
|
|
7217
8245
|
emitLocalEventHooksQuiet({ type: "task.completed", payload: completionPayload, databasePath });
|
|
7218
|
-
emitSharedTaskEventQuiet({ type: "task.completed", task: completedTaskForEvent, data: { agent_id: agentId, completed_at:
|
|
8246
|
+
emitSharedTaskEventQuiet({ type: "task.completed", task: completedTaskForEvent, data: { agent_id: agentId, completed_at: timestamp2 }, databasePath });
|
|
7219
8247
|
let spawnedTask = null;
|
|
7220
8248
|
if (task.recurrence_rule && !options?.skip_recurrence) {
|
|
7221
8249
|
try {
|
|
7222
|
-
spawnedTask = spawnNextRecurrence(task, d,
|
|
8250
|
+
spawnedTask = spawnNextRecurrence(task, d, timestamp2);
|
|
7223
8251
|
} catch (e) {
|
|
7224
8252
|
spawnedTask = null;
|
|
7225
8253
|
console.warn(`[tasks] failed to spawn next recurrence for ${id}: ${e instanceof Error ? e.message : String(e)}`);
|
|
@@ -7269,7 +8297,7 @@ function completeTask(id, agentId, db, options) {
|
|
|
7269
8297
|
emitSharedTaskEventQuiet({ type: "task.unblocked", task: depTask, data: { unblocked_by: id }, databasePath });
|
|
7270
8298
|
}
|
|
7271
8299
|
}
|
|
7272
|
-
return { ...task, status: "completed", locked_by: null, locked_at: null, completed_at:
|
|
8300
|
+
return { ...task, status: "completed", locked_by: null, locked_at: null, completed_at: timestamp2, confidence, version: finalVersion, updated_at: timestamp2, metadata: meta };
|
|
7273
8301
|
}
|
|
7274
8302
|
function lockTask(id, agentId, db) {
|
|
7275
8303
|
const d = db || getDatabase();
|
|
@@ -7283,15 +8311,15 @@ function lockTask(id, agentId, db) {
|
|
|
7283
8311
|
};
|
|
7284
8312
|
}
|
|
7285
8313
|
if (task.locked_by === agentId && !isLockExpired(task.locked_at)) {
|
|
7286
|
-
const
|
|
7287
|
-
d.run(`UPDATE tasks SET locked_at = ?, updated_at = ?, version = version + 1 WHERE id = ? AND locked_by = ?`, [
|
|
8314
|
+
const timestamp3 = now();
|
|
8315
|
+
d.run(`UPDATE tasks SET locked_at = ?, updated_at = ?, version = version + 1 WHERE id = ? AND locked_by = ?`, [timestamp3, timestamp3, id, agentId]);
|
|
7288
8316
|
logTaskChange(id, "lock_renew", "locked_by", agentId, agentId, agentId, d);
|
|
7289
|
-
return { success: true, locked_by: agentId, locked_at:
|
|
8317
|
+
return { success: true, locked_by: agentId, locked_at: timestamp3, expires_at: lockExpiresAt(timestamp3) };
|
|
7290
8318
|
}
|
|
7291
8319
|
const cutoff = lockExpiryCutoff();
|
|
7292
|
-
const
|
|
8320
|
+
const timestamp2 = now();
|
|
7293
8321
|
const result = d.run(`UPDATE tasks SET locked_by = ?, locked_at = ?, version = version + 1, updated_at = ?
|
|
7294
|
-
WHERE id = ? AND status NOT IN ('completed', 'cancelled') AND (locked_by IS NULL OR locked_by = ? OR locked_at < ?)`, [agentId,
|
|
8322
|
+
WHERE id = ? AND status NOT IN ('completed', 'cancelled') AND (locked_by IS NULL OR locked_by = ? OR locked_at < ?)`, [agentId, timestamp2, timestamp2, id, agentId, cutoff]);
|
|
7295
8323
|
if (result.changes === 0) {
|
|
7296
8324
|
const current = getTask(id, d);
|
|
7297
8325
|
if (!current)
|
|
@@ -7316,7 +8344,7 @@ function lockTask(id, agentId, db) {
|
|
|
7316
8344
|
};
|
|
7317
8345
|
}
|
|
7318
8346
|
logTaskChange(id, "lock", "locked_by", task.locked_by, agentId, agentId, d);
|
|
7319
|
-
return { success: true, locked_by: agentId, locked_at:
|
|
8347
|
+
return { success: true, locked_by: agentId, locked_at: timestamp2, expires_at: lockExpiresAt(timestamp2) };
|
|
7320
8348
|
}
|
|
7321
8349
|
function unlockTask(id, agentId, db) {
|
|
7322
8350
|
const d = db || getDatabase();
|
|
@@ -7326,9 +8354,9 @@ function unlockTask(id, agentId, db) {
|
|
|
7326
8354
|
if (agentId && task.locked_by && task.locked_by !== agentId) {
|
|
7327
8355
|
throw new LockError(id, task.locked_by);
|
|
7328
8356
|
}
|
|
7329
|
-
const
|
|
8357
|
+
const timestamp2 = now();
|
|
7330
8358
|
d.run(`UPDATE tasks SET locked_by = NULL, locked_at = NULL, version = version + 1, updated_at = ?
|
|
7331
|
-
WHERE id = ?`, [
|
|
8359
|
+
WHERE id = ?`, [timestamp2, id]);
|
|
7332
8360
|
return true;
|
|
7333
8361
|
}
|
|
7334
8362
|
function getTaskLockStatus(id, db) {
|
|
@@ -7453,20 +8481,23 @@ function failTask(id, agentId, reason, options, db) {
|
|
|
7453
8481
|
const task = getTask(id, d);
|
|
7454
8482
|
if (!task)
|
|
7455
8483
|
throw new TaskNotFoundError(id);
|
|
8484
|
+
const safeReason = sanitizePreWriteText(reason || "Unknown failure", "task.failure.reason");
|
|
8485
|
+
const safeErrorCode = options?.error_code ? sanitizePreWriteText(options.error_code, "task.failure.error_code") : null;
|
|
7456
8486
|
const meta = {
|
|
7457
8487
|
...task.metadata,
|
|
7458
8488
|
_failure: {
|
|
7459
|
-
reason:
|
|
7460
|
-
error_code:
|
|
8489
|
+
reason: safeReason,
|
|
8490
|
+
error_code: safeErrorCode,
|
|
7461
8491
|
failed_by: agentId || null,
|
|
7462
8492
|
failed_at: now(),
|
|
7463
8493
|
retry_requested: options?.retry || false
|
|
7464
8494
|
}
|
|
7465
8495
|
};
|
|
7466
|
-
const
|
|
8496
|
+
const safeMeta = sanitizePreWriteValue(meta, "task.failure.metadata");
|
|
8497
|
+
const timestamp2 = now();
|
|
7467
8498
|
const failTx = d.transaction(() => {
|
|
7468
8499
|
const res = d.run(`UPDATE tasks SET status = 'failed', locked_by = NULL, locked_at = NULL, metadata = ?, version = version + 1, updated_at = ?
|
|
7469
|
-
WHERE id = ? AND version = ?`, [JSON.stringify(
|
|
8500
|
+
WHERE id = ? AND version = ?`, [JSON.stringify(safeMeta), timestamp2, id, task.version]);
|
|
7470
8501
|
if (res.changes === 0) {
|
|
7471
8502
|
const current = getTask(id, d);
|
|
7472
8503
|
throw new VersionConflictError(id, task.version, current?.version ?? -1);
|
|
@@ -7478,22 +8509,22 @@ function failTask(id, agentId, reason, options, db) {
|
|
|
7478
8509
|
status: "failed",
|
|
7479
8510
|
locked_by: null,
|
|
7480
8511
|
locked_at: null,
|
|
7481
|
-
metadata:
|
|
8512
|
+
metadata: safeMeta,
|
|
7482
8513
|
version: task.version + 1,
|
|
7483
|
-
updated_at:
|
|
8514
|
+
updated_at: timestamp2
|
|
7484
8515
|
};
|
|
7485
8516
|
logTaskChange(id, "fail", "status", task.status, "failed", agentId || null, d);
|
|
7486
|
-
const failurePayload = taskEventData(failedTask, { reason, error_code:
|
|
8517
|
+
const failurePayload = taskEventData(failedTask, { reason: safeReason, error_code: safeErrorCode, agent_id: agentId });
|
|
7487
8518
|
dispatchWebhook2("task.failed", failurePayload, d).catch(() => {});
|
|
7488
8519
|
emitLocalEventHooksQuiet({ type: "task.failed", payload: failurePayload, databasePath });
|
|
7489
|
-
emitSharedTaskEventQuiet({ type: "task.failed", task: failedTask, data: { reason, error_code:
|
|
8520
|
+
emitSharedTaskEventQuiet({ type: "task.failed", task: failedTask, data: { reason: safeReason, error_code: safeErrorCode, agent_id: agentId }, severity: "warning", databasePath });
|
|
7490
8521
|
let retryTask;
|
|
7491
8522
|
if (options?.retry) {
|
|
7492
8523
|
const retryCount = (task.retry_count || 0) + 1;
|
|
7493
8524
|
const maxRetries = task.max_retries || 3;
|
|
7494
8525
|
if (retryCount > maxRetries) {
|
|
7495
8526
|
d.run("UPDATE tasks SET metadata = ? WHERE id = ?", [
|
|
7496
|
-
JSON.stringify({ ...
|
|
8527
|
+
JSON.stringify(sanitizePreWriteValue({ ...safeMeta, _retry_exhausted: { retry_count: retryCount - 1, max_retries: maxRetries } }, "task.retry_exhausted.metadata")),
|
|
7497
8528
|
id
|
|
7498
8529
|
]);
|
|
7499
8530
|
} else {
|
|
@@ -7512,7 +8543,7 @@ function failTask(id, agentId, reason, options, db) {
|
|
|
7512
8543
|
plan_id: task.plan_id ?? undefined,
|
|
7513
8544
|
assigned_to: task.assigned_to ?? undefined,
|
|
7514
8545
|
tags: task.tags,
|
|
7515
|
-
metadata: { ...task.metadata, _retry: { original_id: task.id, retry_count: retryCount, max_retries: maxRetries, retry_after: retryAfter, failure_reason:
|
|
8546
|
+
metadata: { ...task.metadata, _retry: { original_id: task.id, retry_count: retryCount, max_retries: maxRetries, retry_after: retryAfter, failure_reason: safeReason } },
|
|
7516
8547
|
estimated_minutes: task.estimated_minutes ?? undefined,
|
|
7517
8548
|
recurrence_rule: task.recurrence_rule ?? undefined,
|
|
7518
8549
|
due_at: retryAfter
|
|
@@ -7554,15 +8585,15 @@ function stealTask(agentId, opts, db) {
|
|
|
7554
8585
|
const priorityOrder = { critical: 0, high: 1, medium: 2, low: 3 };
|
|
7555
8586
|
staleTasks.sort((a, b) => (priorityOrder[a.priority] ?? 9) - (priorityOrder[b.priority] ?? 9));
|
|
7556
8587
|
const target = staleTasks[0];
|
|
7557
|
-
const
|
|
8588
|
+
const timestamp2 = now();
|
|
7558
8589
|
const cutoff = new Date(Date.now() - staleMinutes * 60 * 1000).toISOString();
|
|
7559
8590
|
const result = d.run(`UPDATE tasks SET assigned_to = ?, locked_by = ?, locked_at = ?, updated_at = ?, version = version + 1
|
|
7560
|
-
WHERE id = ? AND status = 'in_progress' AND (updated_at < ? OR (locked_at IS NOT NULL AND locked_at < ?))`, [agentId, agentId,
|
|
8591
|
+
WHERE id = ? AND status = 'in_progress' AND (updated_at < ? OR (locked_at IS NOT NULL AND locked_at < ?))`, [agentId, agentId, timestamp2, timestamp2, target.id, cutoff, cutoff]);
|
|
7561
8592
|
if (result.changes === 0)
|
|
7562
8593
|
return null;
|
|
7563
8594
|
logTaskChange(target.id, "steal", "assigned_to", target.assigned_to, agentId, agentId, d);
|
|
7564
8595
|
logTaskChange(target.id, "steal", "locked_by", target.locked_by, agentId, agentId, d);
|
|
7565
|
-
const stolenTask = { ...target, assigned_to: agentId, locked_by: agentId, locked_at:
|
|
8596
|
+
const stolenTask = { ...target, assigned_to: agentId, locked_by: agentId, locked_at: timestamp2, updated_at: timestamp2, version: target.version + 1 };
|
|
7566
8597
|
const payload = taskEventData(stolenTask, { agent_id: agentId, stolen_from: target.assigned_to });
|
|
7567
8598
|
dispatchWebhook2("task.assigned", payload, d).catch(() => {});
|
|
7568
8599
|
emitLocalEventHooksQuiet({ type: "task.assigned", payload, databasePath });
|
|
@@ -7623,6 +8654,7 @@ var init_task_lifecycle = __esm(() => {
|
|
|
7623
8654
|
init_templates();
|
|
7624
8655
|
init_task_crud();
|
|
7625
8656
|
init_task_graph();
|
|
8657
|
+
init_prewrite_secrets();
|
|
7626
8658
|
});
|
|
7627
8659
|
|
|
7628
8660
|
// src/db/task-crud.ts
|
|
@@ -7660,9 +8692,29 @@ function addMetadataConditions(metadata, conditions, params) {
|
|
|
7660
8692
|
params.push(value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean" ? value : JSON.stringify(value));
|
|
7661
8693
|
}
|
|
7662
8694
|
}
|
|
8695
|
+
function sanitizeCreateTaskInput(input) {
|
|
8696
|
+
return {
|
|
8697
|
+
...input,
|
|
8698
|
+
title: sanitizePreWriteText(input.title, "task.title"),
|
|
8699
|
+
description: input.description !== undefined ? sanitizePreWriteText(input.description, "task.description") : undefined,
|
|
8700
|
+
tags: input.tags !== undefined ? sanitizePreWriteValue(input.tags, "task.tags") : undefined,
|
|
8701
|
+
metadata: input.metadata !== undefined ? sanitizePreWriteValue(input.metadata, "task.metadata") : undefined,
|
|
8702
|
+
reason: input.reason !== undefined ? sanitizePreWriteText(input.reason, "task.reason") : undefined
|
|
8703
|
+
};
|
|
8704
|
+
}
|
|
8705
|
+
function sanitizeUpdateTaskInput(input) {
|
|
8706
|
+
return {
|
|
8707
|
+
...input,
|
|
8708
|
+
title: input.title !== undefined ? sanitizePreWriteText(input.title, "task.title") : undefined,
|
|
8709
|
+
description: input.description !== undefined ? sanitizePreWriteText(input.description, "task.description") : undefined,
|
|
8710
|
+
tags: input.tags !== undefined ? sanitizePreWriteValue(input.tags, "task.tags") : undefined,
|
|
8711
|
+
metadata: input.metadata !== undefined ? sanitizePreWriteValue(input.metadata, "task.metadata") : undefined
|
|
8712
|
+
};
|
|
8713
|
+
}
|
|
7663
8714
|
function createTask(input, db) {
|
|
8715
|
+
input = sanitizeCreateTaskInput(input);
|
|
7664
8716
|
const d = db || getDatabase();
|
|
7665
|
-
const
|
|
8717
|
+
const timestamp2 = now();
|
|
7666
8718
|
const tags = input.tags || [];
|
|
7667
8719
|
const machineId = currentStorageMachineId(d);
|
|
7668
8720
|
const assignedBy = input.assigned_by || input.agent_id;
|
|
@@ -7689,8 +8741,8 @@ function createTask(input, db) {
|
|
|
7689
8741
|
input.working_dir || null,
|
|
7690
8742
|
JSON.stringify(tags),
|
|
7691
8743
|
JSON.stringify(input.metadata || {}),
|
|
7692
|
-
|
|
7693
|
-
|
|
8744
|
+
timestamp2,
|
|
8745
|
+
timestamp2,
|
|
7694
8746
|
input.due_at || null,
|
|
7695
8747
|
input.estimated_minutes || null,
|
|
7696
8748
|
input.sla_minutes ?? null,
|
|
@@ -8014,10 +9066,11 @@ function updateTask(id, input, db) {
|
|
|
8014
9066
|
if (task.version !== input.version) {
|
|
8015
9067
|
throw new VersionConflictError(id, input.version, task.version);
|
|
8016
9068
|
}
|
|
8017
|
-
|
|
8018
|
-
const
|
|
9069
|
+
input = sanitizeUpdateTaskInput(input);
|
|
9070
|
+
const timestamp2 = now();
|
|
9071
|
+
const completionTimestamp = input.completed_at ?? timestamp2;
|
|
8019
9072
|
const sets = ["version = version + 1", "updated_at = ?"];
|
|
8020
|
-
const params = [
|
|
9073
|
+
const params = [timestamp2];
|
|
8021
9074
|
if (input.title !== undefined) {
|
|
8022
9075
|
sets.push("title = ?");
|
|
8023
9076
|
params.push(input.title);
|
|
@@ -8166,7 +9219,7 @@ function updateTask(id, input, db) {
|
|
|
8166
9219
|
tags: input.tags ?? task.tags,
|
|
8167
9220
|
metadata: input.metadata ?? task.metadata,
|
|
8168
9221
|
version: task.version + 1,
|
|
8169
|
-
updated_at:
|
|
9222
|
+
updated_at: timestamp2,
|
|
8170
9223
|
locked_by: completedNow ? null : task.locked_by,
|
|
8171
9224
|
locked_at: completedNow ? null : task.locked_at,
|
|
8172
9225
|
completed_at: completedNow ? completionTimestamp : reopened ? null : input.completed_at !== undefined ? input.completed_at : task.completed_at,
|
|
@@ -8178,7 +9231,7 @@ function updateTask(id, input, db) {
|
|
|
8178
9231
|
retry_after: input.retry_after !== undefined ? input.retry_after : task.retry_after,
|
|
8179
9232
|
requires_approval: input.requires_approval !== undefined ? input.requires_approval : task.requires_approval,
|
|
8180
9233
|
approved_by: input.approved_by ?? task.approved_by,
|
|
8181
|
-
approved_at: input.approved_by ?
|
|
9234
|
+
approved_at: input.approved_by ? timestamp2 : task.approved_at
|
|
8182
9235
|
};
|
|
8183
9236
|
const databasePath = databasePathFromDatabase(d);
|
|
8184
9237
|
if (input.assigned_to !== undefined && input.assigned_to !== task.assigned_to) {
|
|
@@ -8227,6 +9280,7 @@ var init_task_crud = __esm(() => {
|
|
|
8227
9280
|
init_webhooks();
|
|
8228
9281
|
init_checklists();
|
|
8229
9282
|
init_storage_tombstones();
|
|
9283
|
+
init_prewrite_secrets();
|
|
8230
9284
|
});
|
|
8231
9285
|
|
|
8232
9286
|
// src/db/task-status.ts
|
|
@@ -8358,10 +9412,10 @@ function redistributeStaleTasks(agentId, options, db) {
|
|
|
8358
9412
|
const maxAge = options?.max_age_minutes ?? 60;
|
|
8359
9413
|
const stale = getStaleTasks(maxAge, options?.project_id ? { project_id: options.project_id } : undefined, d);
|
|
8360
9414
|
const limited = options?.limit ? stale.slice(0, options.limit) : stale;
|
|
8361
|
-
const
|
|
9415
|
+
const timestamp2 = now();
|
|
8362
9416
|
const released = [];
|
|
8363
9417
|
for (const t of limited) {
|
|
8364
|
-
d.run(`UPDATE tasks SET locked_by = NULL, locked_at = NULL, status = 'pending', version = version + 1, updated_at = ? WHERE id = ?`, [
|
|
9418
|
+
d.run(`UPDATE tasks SET locked_by = NULL, locked_at = NULL, status = 'pending', version = version + 1, updated_at = ? WHERE id = ?`, [timestamp2, t.id]);
|
|
8365
9419
|
released.push({ ...t, locked_by: null, locked_at: null, status: "pending" });
|
|
8366
9420
|
}
|
|
8367
9421
|
const claimed = released.length > 0 ? claimNextTask(agentId, options?.project_id ? { project_id: options.project_id } : undefined, d) : null;
|
|
@@ -8833,7 +9887,7 @@ function resolveCreateSlug(input, projectId, db) {
|
|
|
8833
9887
|
function createPlan(input, db) {
|
|
8834
9888
|
const d = db || getDatabase();
|
|
8835
9889
|
const id = uuid();
|
|
8836
|
-
const
|
|
9890
|
+
const timestamp2 = now();
|
|
8837
9891
|
const projectId = input.project_id || null;
|
|
8838
9892
|
const slug = resolveCreateSlug(input, projectId, d);
|
|
8839
9893
|
const machineId = currentStorageMachineId(d);
|
|
@@ -8847,8 +9901,8 @@ function createPlan(input, db) {
|
|
|
8847
9901
|
input.name,
|
|
8848
9902
|
input.description || null,
|
|
8849
9903
|
input.status || "active",
|
|
8850
|
-
|
|
8851
|
-
|
|
9904
|
+
timestamp2,
|
|
9905
|
+
timestamp2,
|
|
8852
9906
|
machineId
|
|
8853
9907
|
]);
|
|
8854
9908
|
return getPlan(id, d);
|
|
@@ -8985,7 +10039,7 @@ function maybeFilter(value) {
|
|
|
8985
10039
|
function createTaskBoard(input, db) {
|
|
8986
10040
|
const d = db || getDatabase();
|
|
8987
10041
|
const id = uuid();
|
|
8988
|
-
const
|
|
10042
|
+
const timestamp2 = now();
|
|
8989
10043
|
const scope = input.scope || "tasks";
|
|
8990
10044
|
const lanes = normalizeLanes(scope, input.lanes);
|
|
8991
10045
|
d.run(`INSERT INTO task_boards (id, name, scope, project_id, task_list_id, plan_id, agent_id, lanes, filters, created_at, updated_at)
|
|
@@ -8999,8 +10053,8 @@ function createTaskBoard(input, db) {
|
|
|
8999
10053
|
input.agent_id || null,
|
|
9000
10054
|
JSON.stringify(lanes),
|
|
9001
10055
|
JSON.stringify(input.filters || {}),
|
|
9002
|
-
|
|
9003
|
-
|
|
10056
|
+
timestamp2,
|
|
10057
|
+
timestamp2
|
|
9004
10058
|
]);
|
|
9005
10059
|
return getTaskBoard(id, d);
|
|
9006
10060
|
}
|
|
@@ -9401,14 +10455,14 @@ function storeArtifactContent(input) {
|
|
|
9401
10455
|
return null;
|
|
9402
10456
|
const sourceStat = statSync2(sourcePath);
|
|
9403
10457
|
if (!sourceStat.isFile())
|
|
9404
|
-
throw new Error(`Artifact path is not a file: ${input.path}`);
|
|
10458
|
+
throw new Error(`Artifact path is not a file: ${sanitizePreWriteText(input.path, "artifact.path")}`);
|
|
9405
10459
|
const sourceBuffer = readFileSync3(sourcePath);
|
|
9406
10460
|
const sourceSha = sha256(sourceBuffer);
|
|
9407
10461
|
const textLike = isTextLike(sourceBuffer, input.path);
|
|
9408
10462
|
let storedBuffer = sourceBuffer;
|
|
9409
10463
|
let redactionStatus = textLike ? "clean" : "binary_or_unknown";
|
|
9410
10464
|
if (textLike) {
|
|
9411
|
-
const redactedText =
|
|
10465
|
+
const redactedText = sanitizePreWriteText(sourceBuffer.toString("utf8"), "artifact.content");
|
|
9412
10466
|
storedBuffer = Buffer.from(redactedText);
|
|
9413
10467
|
if (!storedBuffer.equals(sourceBuffer))
|
|
9414
10468
|
redactionStatus = "redacted";
|
|
@@ -9442,7 +10496,7 @@ function storeArtifactContent(input) {
|
|
|
9442
10496
|
expires_at: retentionDays === null ? null : retentionExpiresAt(createdAt, retentionDays)
|
|
9443
10497
|
},
|
|
9444
10498
|
source: {
|
|
9445
|
-
path: input.path,
|
|
10499
|
+
path: sanitizePreWriteText(input.path, "artifact.source_path"),
|
|
9446
10500
|
size_bytes: sourceBuffer.length,
|
|
9447
10501
|
sha256: sourceSha
|
|
9448
10502
|
}
|
|
@@ -9622,6 +10676,7 @@ var DEFAULT_DELETED_RETENTION_DAYS = 30;
|
|
|
9622
10676
|
var init_artifact_store = __esm(() => {
|
|
9623
10677
|
init_database();
|
|
9624
10678
|
init_redaction();
|
|
10679
|
+
init_prewrite_secrets();
|
|
9625
10680
|
});
|
|
9626
10681
|
|
|
9627
10682
|
// src/db/comments.ts
|
|
@@ -9631,17 +10686,17 @@ function addComment(input, db) {
|
|
|
9631
10686
|
throw new TaskNotFoundError(input.task_id);
|
|
9632
10687
|
}
|
|
9633
10688
|
const id = uuid();
|
|
9634
|
-
const
|
|
10689
|
+
const timestamp2 = now();
|
|
9635
10690
|
d.run(`INSERT INTO task_comments (id, task_id, agent_id, session_id, content, type, progress_pct, created_at)
|
|
9636
10691
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
9637
10692
|
id,
|
|
9638
10693
|
input.task_id,
|
|
9639
10694
|
input.agent_id || null,
|
|
9640
10695
|
input.session_id || null,
|
|
9641
|
-
|
|
10696
|
+
sanitizePreWriteText(input.content, "comment.content"),
|
|
9642
10697
|
input.type || "comment",
|
|
9643
10698
|
input.progress_pct ?? null,
|
|
9644
|
-
|
|
10699
|
+
timestamp2
|
|
9645
10700
|
]);
|
|
9646
10701
|
return getComment(id, d);
|
|
9647
10702
|
}
|
|
@@ -9665,21 +10720,21 @@ var init_comments = __esm(() => {
|
|
|
9665
10720
|
init_types();
|
|
9666
10721
|
init_database();
|
|
9667
10722
|
init_tasks();
|
|
9668
|
-
|
|
10723
|
+
init_prewrite_secrets();
|
|
9669
10724
|
});
|
|
9670
10725
|
|
|
9671
10726
|
// src/db/task-files.ts
|
|
9672
10727
|
function addTaskFile(input, db) {
|
|
9673
10728
|
const d = db || getDatabase();
|
|
9674
10729
|
const id = uuid();
|
|
9675
|
-
const
|
|
10730
|
+
const timestamp2 = now();
|
|
9676
10731
|
const existing = d.query("SELECT id FROM task_files WHERE task_id = ? AND path = ?").get(input.task_id, input.path);
|
|
9677
10732
|
if (existing) {
|
|
9678
|
-
d.run("UPDATE task_files SET status = ?, agent_id = ?, note = ?, updated_at = ? WHERE id = ?", [input.status || "active", input.agent_id || null, input.note || null,
|
|
10733
|
+
d.run("UPDATE task_files SET status = ?, agent_id = ?, note = ?, updated_at = ? WHERE id = ?", [input.status || "active", input.agent_id || null, input.note || null, timestamp2, existing.id]);
|
|
9679
10734
|
return getTaskFile(existing.id, d);
|
|
9680
10735
|
}
|
|
9681
10736
|
d.run(`INSERT INTO task_files (id, task_id, path, status, agent_id, note, created_at, updated_at)
|
|
9682
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [id, input.task_id, input.path, input.status || "active", input.agent_id || null, input.note || null,
|
|
10737
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [id, input.task_id, input.path, input.status || "active", input.agent_id || null, input.note || null, timestamp2, timestamp2]);
|
|
9683
10738
|
return getTaskFile(id, d);
|
|
9684
10739
|
}
|
|
9685
10740
|
function getTaskFile(id, db) {
|
|
@@ -9696,8 +10751,8 @@ function findTasksByFile(path, db) {
|
|
|
9696
10751
|
}
|
|
9697
10752
|
function updateTaskFileStatus(taskId, path, status, agentId, db) {
|
|
9698
10753
|
const d = db || getDatabase();
|
|
9699
|
-
const
|
|
9700
|
-
d.run("UPDATE task_files SET status = ?, agent_id = COALESCE(?, agent_id), updated_at = ? WHERE task_id = ? AND path = ?", [status, agentId || null,
|
|
10754
|
+
const timestamp2 = now();
|
|
10755
|
+
d.run("UPDATE task_files SET status = ?, agent_id = COALESCE(?, agent_id), updated_at = ? WHERE task_id = ? AND path = ?", [status, agentId || null, timestamp2, taskId, path]);
|
|
9701
10756
|
const row = d.query("SELECT * FROM task_files WHERE task_id = ? AND path = ?").get(taskId, path);
|
|
9702
10757
|
return row;
|
|
9703
10758
|
}
|
|
@@ -9742,8 +10797,16 @@ function rowToVerification(row) {
|
|
|
9742
10797
|
function linkTaskToCommit(input, db) {
|
|
9743
10798
|
const d = db || getDatabase();
|
|
9744
10799
|
const existing = d.query("SELECT * FROM task_commits WHERE task_id = ? AND sha = ?").get(input.task_id, input.sha);
|
|
9745
|
-
const ciJson = input.ci_snapshot ? JSON.stringify(input.ci_snapshot) : null;
|
|
9746
|
-
const traceJson = input.traceability ? JSON.stringify(input.traceability) : null;
|
|
10800
|
+
const ciJson = input.ci_snapshot ? JSON.stringify(sanitizePreWriteValue(input.ci_snapshot, "commit.ci_snapshot")) : null;
|
|
10801
|
+
const traceJson = input.traceability ? JSON.stringify(sanitizePreWriteValue(input.traceability, "commit.traceability")) : null;
|
|
10802
|
+
const message = input.message ? sanitizePreWriteText(input.message, "commit.message") : null;
|
|
10803
|
+
const author = input.author ? sanitizePreWriteText(input.author, "commit.author") : null;
|
|
10804
|
+
const filesChanged = input.files_changed ? JSON.stringify(sanitizePreWriteValue(input.files_changed, "commit.files_changed")) : null;
|
|
10805
|
+
const branch = input.branch ? sanitizePreWriteText(input.branch, "commit.branch") : null;
|
|
10806
|
+
const prUrl = input.pr_url ? sanitizePreWriteText(input.pr_url, "commit.pr_url") : null;
|
|
10807
|
+
const prState = input.pr_state ? sanitizePreWriteText(input.pr_state, "commit.pr_state") : null;
|
|
10808
|
+
const releaseTag = input.release_tag ? sanitizePreWriteText(input.release_tag, "commit.release_tag") : null;
|
|
10809
|
+
const repoPath = input.repo_path ? sanitizePreWriteText(input.repo_path, "commit.repo_path") : null;
|
|
9747
10810
|
if (existing) {
|
|
9748
10811
|
d.run(`UPDATE task_commits SET
|
|
9749
10812
|
message = COALESCE(?, message),
|
|
@@ -9759,17 +10822,17 @@ function linkTaskToCommit(input, db) {
|
|
|
9759
10822
|
repo_path = COALESCE(?, repo_path),
|
|
9760
10823
|
traceability = COALESCE(?, traceability)
|
|
9761
10824
|
WHERE id = ?`, [
|
|
9762
|
-
|
|
9763
|
-
|
|
9764
|
-
|
|
10825
|
+
message,
|
|
10826
|
+
author,
|
|
10827
|
+
filesChanged,
|
|
9765
10828
|
input.committed_at ?? null,
|
|
9766
|
-
|
|
9767
|
-
|
|
10829
|
+
branch,
|
|
10830
|
+
prUrl,
|
|
9768
10831
|
input.pr_number ?? null,
|
|
9769
|
-
|
|
10832
|
+
prState,
|
|
9770
10833
|
ciJson,
|
|
9771
|
-
|
|
9772
|
-
|
|
10834
|
+
releaseTag,
|
|
10835
|
+
repoPath,
|
|
9773
10836
|
traceJson,
|
|
9774
10837
|
existing.id
|
|
9775
10838
|
]);
|
|
@@ -9783,17 +10846,17 @@ function linkTaskToCommit(input, db) {
|
|
|
9783
10846
|
id,
|
|
9784
10847
|
input.task_id,
|
|
9785
10848
|
input.sha,
|
|
9786
|
-
|
|
9787
|
-
|
|
9788
|
-
|
|
10849
|
+
message,
|
|
10850
|
+
author,
|
|
10851
|
+
filesChanged,
|
|
9789
10852
|
input.committed_at ?? null,
|
|
9790
|
-
|
|
9791
|
-
|
|
10853
|
+
branch,
|
|
10854
|
+
prUrl,
|
|
9792
10855
|
input.pr_number ?? null,
|
|
9793
|
-
|
|
10856
|
+
prState,
|
|
9794
10857
|
ciJson,
|
|
9795
|
-
|
|
9796
|
-
|
|
10858
|
+
releaseTag,
|
|
10859
|
+
repoPath,
|
|
9797
10860
|
traceJson ?? "{}",
|
|
9798
10861
|
now()
|
|
9799
10862
|
]);
|
|
@@ -9822,13 +10885,16 @@ function addTaskVerification(input, db) {
|
|
|
9822
10885
|
const d = db || getDatabase();
|
|
9823
10886
|
const id = uuid();
|
|
9824
10887
|
const runAt = input.run_at || now();
|
|
10888
|
+
const command = sanitizePreWriteText(input.command, "verification.command");
|
|
10889
|
+
const outputSummary = input.output_summary ? sanitizePreWriteText(input.output_summary, "verification.output_summary") : null;
|
|
10890
|
+
const artifactPath = input.artifact_path ? sanitizePreWriteText(input.artifact_path, "verification.artifact_path") : null;
|
|
9825
10891
|
d.run("INSERT INTO task_verifications (id, task_id, command, status, output_summary, artifact_path, agent_id, run_at, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)", [
|
|
9826
10892
|
id,
|
|
9827
10893
|
input.task_id,
|
|
9828
|
-
|
|
10894
|
+
command,
|
|
9829
10895
|
input.status || "unknown",
|
|
9830
|
-
|
|
9831
|
-
|
|
10896
|
+
outputSummary,
|
|
10897
|
+
artifactPath,
|
|
9832
10898
|
input.agent_id ?? null,
|
|
9833
10899
|
runAt,
|
|
9834
10900
|
now()
|
|
@@ -9858,6 +10924,7 @@ function getTaskTraceability(taskId, db) {
|
|
|
9858
10924
|
}
|
|
9859
10925
|
var init_task_commits = __esm(() => {
|
|
9860
10926
|
init_database();
|
|
10927
|
+
init_prewrite_secrets();
|
|
9861
10928
|
});
|
|
9862
10929
|
|
|
9863
10930
|
// src/db/task-runs.ts
|
|
@@ -9968,7 +11035,7 @@ function startTaskRun(input, db) {
|
|
|
9968
11035
|
if (!getTask(input.task_id, d))
|
|
9969
11036
|
throw new TaskNotFoundError(input.task_id);
|
|
9970
11037
|
const id = input.id ?? uuid();
|
|
9971
|
-
const
|
|
11038
|
+
const timestamp2 = input.started_at || now();
|
|
9972
11039
|
if (input.claim && input.agent_id) {
|
|
9973
11040
|
startTask(input.task_id, input.agent_id, d);
|
|
9974
11041
|
}
|
|
@@ -9976,12 +11043,12 @@ function startTaskRun(input, db) {
|
|
|
9976
11043
|
id,
|
|
9977
11044
|
input.task_id,
|
|
9978
11045
|
input.agent_id ?? null,
|
|
9979
|
-
input.title ?
|
|
9980
|
-
input.summary ?
|
|
9981
|
-
JSON.stringify(
|
|
9982
|
-
|
|
9983
|
-
|
|
9984
|
-
|
|
11046
|
+
input.title ? sanitizePreWriteText(input.title, "run.title") : null,
|
|
11047
|
+
input.summary ? sanitizePreWriteText(input.summary, "run.summary") : null,
|
|
11048
|
+
JSON.stringify(sanitizePreWriteValue(input.metadata || {}, "run.metadata")),
|
|
11049
|
+
timestamp2,
|
|
11050
|
+
timestamp2,
|
|
11051
|
+
timestamp2
|
|
9985
11052
|
]);
|
|
9986
11053
|
addTaskRunEvent({
|
|
9987
11054
|
run_id: id,
|
|
@@ -9989,7 +11056,7 @@ function startTaskRun(input, db) {
|
|
|
9989
11056
|
message: input.summary || input.title || "run started",
|
|
9990
11057
|
data: { title: input.title, claim: Boolean(input.claim) },
|
|
9991
11058
|
agent_id: input.agent_id,
|
|
9992
|
-
created_at:
|
|
11059
|
+
created_at: timestamp2
|
|
9993
11060
|
}, d);
|
|
9994
11061
|
if (input.claim && input.agent_id) {
|
|
9995
11062
|
addTaskRunEvent({
|
|
@@ -9998,7 +11065,7 @@ function startTaskRun(input, db) {
|
|
|
9998
11065
|
message: `claimed by ${input.agent_id}`,
|
|
9999
11066
|
data: { agent_id: input.agent_id },
|
|
10000
11067
|
agent_id: input.agent_id,
|
|
10001
|
-
created_at:
|
|
11068
|
+
created_at: timestamp2
|
|
10002
11069
|
}, d);
|
|
10003
11070
|
}
|
|
10004
11071
|
const run = getTaskRun(id, d);
|
|
@@ -10013,7 +11080,7 @@ function beginTaskRunTransaction(input, db) {
|
|
|
10013
11080
|
const d = db || getDatabase();
|
|
10014
11081
|
if (!getTask(input.task_id, d))
|
|
10015
11082
|
throw new TaskNotFoundError(input.task_id);
|
|
10016
|
-
const
|
|
11083
|
+
const timestamp2 = input.started_at || now();
|
|
10017
11084
|
const key = normalizeTransactionKey(input);
|
|
10018
11085
|
const existing = findTaskRunByTransactionKey(key, input.task_id, d);
|
|
10019
11086
|
const dryRun = !input.apply;
|
|
@@ -10022,7 +11089,7 @@ function beginTaskRunTransaction(input, db) {
|
|
|
10022
11089
|
schema_version: LOOP_RUN_TRANSACTION_SCHEMA_VERSION,
|
|
10023
11090
|
local_only: true,
|
|
10024
11091
|
dry_run: dryRun,
|
|
10025
|
-
processed_at:
|
|
11092
|
+
processed_at: timestamp2,
|
|
10026
11093
|
action: "matched",
|
|
10027
11094
|
key,
|
|
10028
11095
|
run: summarizeTaskRun(existing),
|
|
@@ -10035,7 +11102,7 @@ function beginTaskRunTransaction(input, db) {
|
|
|
10035
11102
|
schema_version: LOOP_RUN_TRANSACTION_SCHEMA_VERSION,
|
|
10036
11103
|
local_only: true,
|
|
10037
11104
|
dry_run: true,
|
|
10038
|
-
processed_at:
|
|
11105
|
+
processed_at: timestamp2,
|
|
10039
11106
|
action: "preview",
|
|
10040
11107
|
key,
|
|
10041
11108
|
run: null,
|
|
@@ -10043,14 +11110,14 @@ function beginTaskRunTransaction(input, db) {
|
|
|
10043
11110
|
commands: loopRunCommands(null, key)
|
|
10044
11111
|
};
|
|
10045
11112
|
}
|
|
10046
|
-
const metadata =
|
|
11113
|
+
const metadata = sanitizePreWriteValue({
|
|
10047
11114
|
...input.metadata || {},
|
|
10048
11115
|
loop_transaction: {
|
|
10049
11116
|
schema_version: LOOP_RUN_TRANSACTION_SCHEMA_VERSION,
|
|
10050
11117
|
idempotency_key: key,
|
|
10051
11118
|
loop_id: input.loop_id ?? null,
|
|
10052
11119
|
loop_run_id: input.loop_run_id ?? null,
|
|
10053
|
-
first_seen_at:
|
|
11120
|
+
first_seen_at: timestamp2
|
|
10054
11121
|
},
|
|
10055
11122
|
idempotency_key: key
|
|
10056
11123
|
});
|
|
@@ -10064,8 +11131,8 @@ function beginTaskRunTransaction(input, db) {
|
|
|
10064
11131
|
input.loop_id ?? null,
|
|
10065
11132
|
input.loop_run_id ?? null,
|
|
10066
11133
|
JSON.stringify(metadata),
|
|
10067
|
-
|
|
10068
|
-
|
|
11134
|
+
timestamp2,
|
|
11135
|
+
timestamp2
|
|
10069
11136
|
]);
|
|
10070
11137
|
const transaction = getTaskRunTransactionByKey(key, input.task_id, d);
|
|
10071
11138
|
if (!transaction)
|
|
@@ -10083,16 +11150,16 @@ function beginTaskRunTransaction(input, db) {
|
|
|
10083
11150
|
summary: input.summary,
|
|
10084
11151
|
metadata,
|
|
10085
11152
|
claim: input.claim,
|
|
10086
|
-
started_at:
|
|
11153
|
+
started_at: timestamp2
|
|
10087
11154
|
}, d);
|
|
10088
|
-
d.run("UPDATE task_run_transactions SET run_id = ?, loop_id = COALESCE(?, loop_id), loop_run_id = COALESCE(?, loop_run_id), metadata = ?, updated_at = ? WHERE id = ?", [run.id, input.loop_id ?? null, input.loop_run_id ?? null, JSON.stringify(metadata),
|
|
11155
|
+
d.run("UPDATE task_run_transactions SET run_id = ?, loop_id = COALESCE(?, loop_id), loop_run_id = COALESCE(?, loop_run_id), metadata = ?, updated_at = ? WHERE id = ?", [run.id, input.loop_id ?? null, input.loop_run_id ?? null, JSON.stringify(metadata), timestamp2, transaction.id]);
|
|
10089
11156
|
return { run, action: "created" };
|
|
10090
11157
|
})();
|
|
10091
11158
|
return {
|
|
10092
11159
|
schema_version: LOOP_RUN_TRANSACTION_SCHEMA_VERSION,
|
|
10093
11160
|
local_only: true,
|
|
10094
11161
|
dry_run: false,
|
|
10095
|
-
processed_at:
|
|
11162
|
+
processed_at: timestamp2,
|
|
10096
11163
|
action: created.action,
|
|
10097
11164
|
key,
|
|
10098
11165
|
run: summarizeTaskRun(created.run),
|
|
@@ -10107,19 +11174,19 @@ function addTaskRunEvent(input, db) {
|
|
|
10107
11174
|
if (!run)
|
|
10108
11175
|
throw new Error(`Run not found: ${input.run_id}`);
|
|
10109
11176
|
const id = uuid();
|
|
10110
|
-
const
|
|
11177
|
+
const timestamp2 = input.created_at || now();
|
|
10111
11178
|
d.run("INSERT INTO task_run_events (id, run_id, task_id, event_type, message, data, agent_id, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", [
|
|
10112
11179
|
id,
|
|
10113
11180
|
run.id,
|
|
10114
11181
|
run.task_id,
|
|
10115
11182
|
input.event_type,
|
|
10116
|
-
input.message ?
|
|
10117
|
-
JSON.stringify(
|
|
11183
|
+
input.message ? sanitizePreWriteText(input.message, "run_event.message") : null,
|
|
11184
|
+
JSON.stringify(sanitizePreWriteValue(input.data || {}, "run_event.data")),
|
|
10118
11185
|
input.agent_id ?? run.agent_id,
|
|
10119
|
-
|
|
11186
|
+
timestamp2
|
|
10120
11187
|
]);
|
|
10121
11188
|
if (input.event_type === "comment" && input.message) {
|
|
10122
|
-
addComment({ task_id: run.task_id, content:
|
|
11189
|
+
addComment({ task_id: run.task_id, content: sanitizePreWriteText(input.message, "run_event.comment"), type: "comment", agent_id: input.agent_id ?? run.agent_id ?? undefined }, d);
|
|
10123
11190
|
}
|
|
10124
11191
|
return rowToEvent(d.query("SELECT * FROM task_run_events WHERE id = ?").get(id));
|
|
10125
11192
|
}
|
|
@@ -10131,10 +11198,10 @@ function addTaskRunCommand(input, db) {
|
|
|
10131
11198
|
throw new Error(`Run not found: ${input.run_id}`);
|
|
10132
11199
|
const id = uuid();
|
|
10133
11200
|
const status = input.status || "unknown";
|
|
10134
|
-
const
|
|
10135
|
-
const command =
|
|
10136
|
-
const outputSummary = input.output_summary ?
|
|
10137
|
-
const artifactPath = input.artifact_path ?
|
|
11201
|
+
const timestamp2 = now();
|
|
11202
|
+
const command = sanitizePreWriteText(input.command, "run_command.command");
|
|
11203
|
+
const outputSummary = input.output_summary ? sanitizePreWriteText(input.output_summary, "run_command.output_summary") : null;
|
|
11204
|
+
const artifactPath = input.artifact_path ? sanitizePreWriteText(input.artifact_path, "run_command.artifact_path") : null;
|
|
10138
11205
|
d.run("INSERT INTO task_run_commands (id, run_id, task_id, command, status, exit_code, output_summary, artifact_path, agent_id, started_at, completed_at, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", [
|
|
10139
11206
|
id,
|
|
10140
11207
|
run.id,
|
|
@@ -10146,8 +11213,8 @@ function addTaskRunCommand(input, db) {
|
|
|
10146
11213
|
artifactPath,
|
|
10147
11214
|
input.agent_id ?? run.agent_id,
|
|
10148
11215
|
input.started_at ?? null,
|
|
10149
|
-
input.completed_at ??
|
|
10150
|
-
|
|
11216
|
+
input.completed_at ?? timestamp2,
|
|
11217
|
+
timestamp2
|
|
10151
11218
|
]);
|
|
10152
11219
|
addTaskVerification({
|
|
10153
11220
|
task_id: run.task_id,
|
|
@@ -10156,7 +11223,7 @@ function addTaskRunCommand(input, db) {
|
|
|
10156
11223
|
output_summary: outputSummary ?? undefined,
|
|
10157
11224
|
artifact_path: artifactPath ?? undefined,
|
|
10158
11225
|
agent_id: input.agent_id ?? run.agent_id ?? undefined,
|
|
10159
|
-
run_at: input.completed_at ??
|
|
11226
|
+
run_at: input.completed_at ?? timestamp2
|
|
10160
11227
|
}, d);
|
|
10161
11228
|
addTaskRunEvent({
|
|
10162
11229
|
run_id: run.id,
|
|
@@ -10175,7 +11242,7 @@ function addTaskRunCommand(input, db) {
|
|
|
10175
11242
|
}
|
|
10176
11243
|
},
|
|
10177
11244
|
agent_id: input.agent_id ?? run.agent_id ?? undefined,
|
|
10178
|
-
created_at:
|
|
11245
|
+
created_at: timestamp2
|
|
10179
11246
|
}, d);
|
|
10180
11247
|
return d.query("SELECT * FROM task_run_commands WHERE id = ?").get(id);
|
|
10181
11248
|
}
|
|
@@ -10189,7 +11256,7 @@ function addTaskRunFile(input, db) {
|
|
|
10189
11256
|
task_id: run.task_id,
|
|
10190
11257
|
path: input.path,
|
|
10191
11258
|
status: input.status || "modified",
|
|
10192
|
-
note: input.note ?
|
|
11259
|
+
note: input.note ? sanitizePreWriteText(input.note, "run_file.note") : undefined,
|
|
10193
11260
|
agent_id: input.agent_id ?? run.agent_id ?? undefined
|
|
10194
11261
|
}, d);
|
|
10195
11262
|
addTaskRunEvent({
|
|
@@ -10208,19 +11275,19 @@ function addTaskRunArtifact(input, db) {
|
|
|
10208
11275
|
if (!run)
|
|
10209
11276
|
throw new Error(`Run not found: ${input.run_id}`);
|
|
10210
11277
|
const id = uuid();
|
|
10211
|
-
const
|
|
10212
|
-
const path =
|
|
10213
|
-
const description = input.description ?
|
|
10214
|
-
const metadata =
|
|
11278
|
+
const timestamp2 = now();
|
|
11279
|
+
const path = sanitizePreWriteText(input.path, "run_artifact.path");
|
|
11280
|
+
const description = input.description ? sanitizePreWriteText(input.description, "run_artifact.description") : null;
|
|
11281
|
+
const metadata = sanitizePreWriteValue(input.metadata || {}, "run_artifact.metadata");
|
|
10215
11282
|
let sizeBytes = input.size_bytes ?? null;
|
|
10216
11283
|
let digest = input.sha256 ?? null;
|
|
10217
|
-
const stored = input.store_content !== false ? storeArtifactContent({ path: input.path, metadata, retention_days: input.retention_days, created_at:
|
|
11284
|
+
const stored = input.store_content !== false ? storeArtifactContent({ path: input.path, metadata, retention_days: input.retention_days, created_at: timestamp2 }) : null;
|
|
10218
11285
|
if (stored) {
|
|
10219
11286
|
sizeBytes = stored.size_bytes;
|
|
10220
11287
|
digest = stored.sha256;
|
|
10221
11288
|
metadata["artifact_store"] = stored.store;
|
|
10222
11289
|
} else if (input.store_content === true) {
|
|
10223
|
-
throw new Error(`Artifact file not found: ${
|
|
11290
|
+
throw new Error(`Artifact file not found: ${path}`);
|
|
10224
11291
|
}
|
|
10225
11292
|
d.run("INSERT INTO task_run_artifacts (id, run_id, task_id, path, artifact_type, description, size_bytes, sha256, metadata, agent_id, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", [
|
|
10226
11293
|
id,
|
|
@@ -10233,7 +11300,7 @@ function addTaskRunArtifact(input, db) {
|
|
|
10233
11300
|
digest,
|
|
10234
11301
|
JSON.stringify(metadata),
|
|
10235
11302
|
input.agent_id ?? run.agent_id,
|
|
10236
|
-
|
|
11303
|
+
timestamp2
|
|
10237
11304
|
]);
|
|
10238
11305
|
addTaskRunEvent({
|
|
10239
11306
|
run_id: run.id,
|
|
@@ -10241,7 +11308,7 @@ function addTaskRunArtifact(input, db) {
|
|
|
10241
11308
|
message: description || path,
|
|
10242
11309
|
data: { path, artifact_type: input.artifact_type, size_bytes: sizeBytes, sha256: digest, stored: Boolean(stored) },
|
|
10243
11310
|
agent_id: input.agent_id ?? run.agent_id ?? undefined,
|
|
10244
|
-
created_at:
|
|
11311
|
+
created_at: timestamp2
|
|
10245
11312
|
}, d);
|
|
10246
11313
|
return rowToArtifact(d.query("SELECT * FROM task_run_artifacts WHERE id = ?").get(id));
|
|
10247
11314
|
}
|
|
@@ -10261,28 +11328,28 @@ function finishTaskRun(input, db) {
|
|
|
10261
11328
|
const run = getTaskRun(runId, d);
|
|
10262
11329
|
if (!run)
|
|
10263
11330
|
throw new Error(`Run not found: ${input.run_id}`);
|
|
10264
|
-
const
|
|
10265
|
-
const summary = input.summary ?
|
|
10266
|
-
d.run("UPDATE task_runs SET status = ?, summary = COALESCE(?, summary), completed_at = ?, updated_at = ? WHERE id = ?", [input.status, summary,
|
|
11331
|
+
const timestamp2 = input.completed_at || now();
|
|
11332
|
+
const summary = input.summary ? sanitizePreWriteText(input.summary, "run.summary") : null;
|
|
11333
|
+
d.run("UPDATE task_runs SET status = ?, summary = COALESCE(?, summary), completed_at = ?, updated_at = ? WHERE id = ?", [input.status, summary, timestamp2, timestamp2, run.id]);
|
|
10267
11334
|
addTaskRunEvent({
|
|
10268
11335
|
run_id: run.id,
|
|
10269
11336
|
event_type: input.status,
|
|
10270
11337
|
message: summary || `run ${input.status}`,
|
|
10271
11338
|
data: { status: input.status },
|
|
10272
11339
|
agent_id: input.agent_id ?? run.agent_id ?? undefined,
|
|
10273
|
-
created_at:
|
|
11340
|
+
created_at: timestamp2
|
|
10274
11341
|
}, d);
|
|
10275
11342
|
const updated = getTaskRun(run.id, d);
|
|
10276
11343
|
emitLocalEventHooksQuiet({
|
|
10277
11344
|
type: `run.${input.status}`,
|
|
10278
|
-
payload: { id: updated.id, task_id: updated.task_id, agent_id: updated.agent_id, status: updated.status, summary: updated.summary, completed_at:
|
|
11345
|
+
payload: { id: updated.id, task_id: updated.task_id, agent_id: updated.agent_id, status: updated.status, summary: updated.summary, completed_at: timestamp2 },
|
|
10279
11346
|
databasePath: databasePathFromDatabase(d)
|
|
10280
11347
|
});
|
|
10281
11348
|
return updated;
|
|
10282
11349
|
}
|
|
10283
11350
|
function finishTaskRunTransaction(input, db) {
|
|
10284
11351
|
const d = db || getDatabase();
|
|
10285
|
-
const
|
|
11352
|
+
const timestamp2 = input.completed_at || now();
|
|
10286
11353
|
const status = input.status || "completed";
|
|
10287
11354
|
const key = input.key ? normalizeTransactionKey({ key: input.key }) : "";
|
|
10288
11355
|
const run = input.run_id ? getTaskRun(resolveTaskRunId(input.run_id, d), d) : key ? findTaskRunByTransactionKey(key, input.task_id, d) : null;
|
|
@@ -10300,7 +11367,7 @@ function finishTaskRunTransaction(input, db) {
|
|
|
10300
11367
|
schema_version: LOOP_RUN_TRANSACTION_SCHEMA_VERSION,
|
|
10301
11368
|
local_only: true,
|
|
10302
11369
|
dry_run: dryRun,
|
|
10303
|
-
processed_at:
|
|
11370
|
+
processed_at: timestamp2,
|
|
10304
11371
|
action: conflict ? "conflict" : "matched",
|
|
10305
11372
|
key: resolvedKey,
|
|
10306
11373
|
run: summarizeTaskRun(run),
|
|
@@ -10313,10 +11380,10 @@ function finishTaskRunTransaction(input, db) {
|
|
|
10313
11380
|
schema_version: LOOP_RUN_TRANSACTION_SCHEMA_VERSION,
|
|
10314
11381
|
local_only: true,
|
|
10315
11382
|
dry_run: true,
|
|
10316
|
-
processed_at:
|
|
11383
|
+
processed_at: timestamp2,
|
|
10317
11384
|
action: "preview",
|
|
10318
11385
|
key: resolvedKey,
|
|
10319
|
-
run: summarizeTaskRun({ ...run, status, summary: input.summary ?? run.summary, completed_at:
|
|
11386
|
+
run: summarizeTaskRun({ ...run, status, summary: input.summary ?? run.summary, completed_at: timestamp2, updated_at: timestamp2 }),
|
|
10320
11387
|
warnings: [],
|
|
10321
11388
|
commands: loopRunCommands(run, resolvedKey)
|
|
10322
11389
|
};
|
|
@@ -10326,13 +11393,13 @@ function finishTaskRunTransaction(input, db) {
|
|
|
10326
11393
|
status,
|
|
10327
11394
|
summary: input.summary,
|
|
10328
11395
|
agent_id: input.agent_id,
|
|
10329
|
-
completed_at:
|
|
11396
|
+
completed_at: timestamp2
|
|
10330
11397
|
}, d);
|
|
10331
11398
|
return {
|
|
10332
11399
|
schema_version: LOOP_RUN_TRANSACTION_SCHEMA_VERSION,
|
|
10333
11400
|
local_only: true,
|
|
10334
11401
|
dry_run: false,
|
|
10335
|
-
processed_at:
|
|
11402
|
+
processed_at: timestamp2,
|
|
10336
11403
|
action: "finished",
|
|
10337
11404
|
key: resolvedKey,
|
|
10338
11405
|
run: summarizeTaskRun(finished),
|
|
@@ -10362,7 +11429,7 @@ var init_task_runs = __esm(() => {
|
|
|
10362
11429
|
init_artifact_store();
|
|
10363
11430
|
init_event_emission_safety();
|
|
10364
11431
|
init_event_hooks();
|
|
10365
|
-
|
|
11432
|
+
init_prewrite_secrets();
|
|
10366
11433
|
init_types();
|
|
10367
11434
|
init_comments();
|
|
10368
11435
|
init_database();
|
|
@@ -10490,7 +11557,7 @@ function inWindow(event, query) {
|
|
|
10490
11557
|
function createCalendarItem(input, db) {
|
|
10491
11558
|
const d = db || getDatabase();
|
|
10492
11559
|
const id = uuid();
|
|
10493
|
-
const
|
|
11560
|
+
const timestamp2 = now();
|
|
10494
11561
|
const kind = input.kind || "work_block";
|
|
10495
11562
|
d.run(`INSERT INTO local_calendar_items (
|
|
10496
11563
|
id, kind, title, description, starts_at, ends_at, timezone, project_id, task_id, plan_id, run_id,
|
|
@@ -10509,8 +11576,8 @@ function createCalendarItem(input, db) {
|
|
|
10509
11576
|
input.run_id || null,
|
|
10510
11577
|
input.recurrence_rule || null,
|
|
10511
11578
|
JSON.stringify(input.metadata || {}),
|
|
10512
|
-
|
|
10513
|
-
|
|
11579
|
+
timestamp2,
|
|
11580
|
+
timestamp2
|
|
10514
11581
|
]);
|
|
10515
11582
|
return getCalendarItem(id, d);
|
|
10516
11583
|
}
|
|
@@ -10756,7 +11823,7 @@ var init_tasks = __esm(() => {
|
|
|
10756
11823
|
// package.json
|
|
10757
11824
|
var package_default = {
|
|
10758
11825
|
name: "@hasna/todos",
|
|
10759
|
-
version: "0.
|
|
11826
|
+
version: "0.12.1",
|
|
10760
11827
|
description: "Universal task management for AI coding agents - CLI + MCP server + interactive TUI",
|
|
10761
11828
|
type: "module",
|
|
10762
11829
|
main: "dist/index.js",
|
|
@@ -11390,12 +12457,66 @@ function contract(input) {
|
|
|
11390
12457
|
}
|
|
11391
12458
|
};
|
|
11392
12459
|
}
|
|
12460
|
+
function closedContract(input) {
|
|
12461
|
+
return {
|
|
12462
|
+
...input,
|
|
12463
|
+
additionalProperties: false,
|
|
12464
|
+
evolution: {
|
|
12465
|
+
additionalFields: "forbidden",
|
|
12466
|
+
removingRequiredFields: "breaking",
|
|
12467
|
+
changingRequiredFieldTypes: "breaking",
|
|
12468
|
+
nullableToNonNullable: "breaking"
|
|
12469
|
+
}
|
|
12470
|
+
};
|
|
12471
|
+
}
|
|
11393
12472
|
var idField = field("string", "Stable UUID or short identifier.");
|
|
11394
12473
|
var nullableIdField = field(["string", "null"], "Stable identifier when attached to another object.", true);
|
|
11395
12474
|
var isoDateField = field("string", "ISO-8601 timestamp string.");
|
|
11396
12475
|
var nullableIsoDateField = field(["string", "null"], "ISO-8601 timestamp string, or null when not set.", true);
|
|
11397
12476
|
var metadataField = field("object", "JSON object metadata. Unknown keys are extension data.");
|
|
11398
12477
|
var tagsField = field("array", "Array of string tags.");
|
|
12478
|
+
var TODOS_AUTHORITATIVE_JSON_PROJECTIONS = [
|
|
12479
|
+
closedContract({
|
|
12480
|
+
id: "pr_group_ci_proof",
|
|
12481
|
+
name: "Authoritative PR Group CI Proof",
|
|
12482
|
+
description: "Closed successful provider-CI evidence bound to one repository, PR, base, and exact head before conditional merge authorization.",
|
|
12483
|
+
surfaces: ["api", "sdk"],
|
|
12484
|
+
stability: "stable",
|
|
12485
|
+
required: {
|
|
12486
|
+
provider: field("string", "Authoritative CI provider identity."),
|
|
12487
|
+
provider_run_id: field("string", "Provider-owned CI run or check-suite identity."),
|
|
12488
|
+
status: field("string", "Terminal successful CI status."),
|
|
12489
|
+
repository: field("string", "Canonical repository identity."),
|
|
12490
|
+
pr_number: field("integer", "Concrete pull-request number."),
|
|
12491
|
+
base_sha: field("string", "Exact base commit SHA."),
|
|
12492
|
+
head_sha: field("string", "Exact reviewed and merge-authorized head commit SHA.")
|
|
12493
|
+
},
|
|
12494
|
+
optional: {}
|
|
12495
|
+
}),
|
|
12496
|
+
closedContract({
|
|
12497
|
+
id: "pr_group_state_view",
|
|
12498
|
+
name: "Authoritative PR Group State View",
|
|
12499
|
+
description: "Closed package-owned projection for immutable task-to-PR lineage, repair accounting, append-only receipts, adapters, and diagnostics.",
|
|
12500
|
+
surfaces: ["cli", "api", "sdk"],
|
|
12501
|
+
stability: "stable",
|
|
12502
|
+
required: {
|
|
12503
|
+
schema_version: field("integer", "PR-group ledger schema version."),
|
|
12504
|
+
authoritative: field("boolean", "Always true for package-owned ledger projections."),
|
|
12505
|
+
authority: field("string", "Authority kind: local or remote."),
|
|
12506
|
+
group: field("object", "Immutable PR-group lineage and current durable state."),
|
|
12507
|
+
attempts: field("array", "Bounded writer-generation attempt history."),
|
|
12508
|
+
latest_event: field(["object", "null"], "Latest append-only lifecycle event, or null.", true),
|
|
12509
|
+
review_receipts: field("array", "Exact-head review receipts."),
|
|
12510
|
+
conditional_merge_receipts: field("array", "Sequence-aware conditional merge receipts."),
|
|
12511
|
+
merge_receipts: field("array", "Exact-head merge outcome receipts."),
|
|
12512
|
+
cleanup_receipts: field("array", "Durable cleanup authorization receipts."),
|
|
12513
|
+
cleanup_eligible: field("boolean", "Whether the complete cleanup proof has been accepted."),
|
|
12514
|
+
adapters: field("object", "Closed WorkRun, EvidenceRef, ProofBundle, and DecisionEnvelope projections."),
|
|
12515
|
+
diagnostics: field("object", "Bounded-projection completeness and limit diagnostics.")
|
|
12516
|
+
},
|
|
12517
|
+
optional: {}
|
|
12518
|
+
})
|
|
12519
|
+
];
|
|
11399
12520
|
var TODOS_JSON_CONTRACTS = [
|
|
11400
12521
|
contract({
|
|
11401
12522
|
id: "task",
|
|
@@ -13417,7 +14538,7 @@ function matchesType(value, expected) {
|
|
|
13417
14538
|
return actual === "integer" && expected.includes("number");
|
|
13418
14539
|
}
|
|
13419
14540
|
function getJsonContract(contractId) {
|
|
13420
|
-
return TODOS_JSON_CONTRACTS.find((contractItem) => contractItem.id === contractId) ?? null;
|
|
14541
|
+
return [...TODOS_JSON_CONTRACTS, ...TODOS_AUTHORITATIVE_JSON_PROJECTIONS].find((contractItem) => contractItem.id === contractId) ?? null;
|
|
13421
14542
|
}
|
|
13422
14543
|
function validateJsonContract(contractId, value) {
|
|
13423
14544
|
const contractItem = getJsonContract(contractId);
|
|
@@ -13427,6 +14548,7 @@ function validateJsonContract(contractId, value) {
|
|
|
13427
14548
|
const record = value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
13428
14549
|
const missingRequired = [];
|
|
13429
14550
|
const typeMismatches = [];
|
|
14551
|
+
const unknownFields = contractItem.additionalProperties ? [] : Object.keys(record).filter((fieldName) => !(fieldName in contractItem.required) && !(fieldName in contractItem.optional));
|
|
13430
14552
|
for (const [fieldName, fieldContract] of Object.entries(contractItem.required)) {
|
|
13431
14553
|
if (!(fieldName in record)) {
|
|
13432
14554
|
missingRequired.push(fieldName);
|
|
@@ -13439,7 +14561,7 @@ function validateJsonContract(contractId, value) {
|
|
|
13439
14561
|
}
|
|
13440
14562
|
}
|
|
13441
14563
|
return {
|
|
13442
|
-
ok: missingRequired.length === 0 && typeMismatches.length === 0,
|
|
14564
|
+
ok: missingRequired.length === 0 && typeMismatches.length === 0 && unknownFields.length === 0,
|
|
13443
14565
|
contractId,
|
|
13444
14566
|
missingRequired,
|
|
13445
14567
|
typeMismatches
|
|
@@ -13451,7 +14573,8 @@ function createJsonContractsManifest(options = {}) {
|
|
|
13451
14573
|
schemaVersion: 1,
|
|
13452
14574
|
generatedAt: options.generatedAt ?? new Date().toISOString(),
|
|
13453
14575
|
package: source2(version),
|
|
13454
|
-
contracts: TODOS_JSON_CONTRACTS
|
|
14576
|
+
contracts: TODOS_JSON_CONTRACTS,
|
|
14577
|
+
authoritativeProjections: TODOS_AUTHORITATIVE_JSON_PROJECTIONS
|
|
13455
14578
|
};
|
|
13456
14579
|
}
|
|
13457
14580
|
var TODOS_JSON_CONTRACTS_MANIFEST = createJsonContractsManifest({
|
|
@@ -13467,6 +14590,7 @@ init_tasks();
|
|
|
13467
14590
|
init_artifact_store();
|
|
13468
14591
|
init_sync_utils();
|
|
13469
14592
|
init_redaction();
|
|
14593
|
+
init_prewrite_secrets();
|
|
13470
14594
|
var TODOS_LOCAL_BRIDGE_KIND = "hasna.todos.local-bridge";
|
|
13471
14595
|
var TODOS_LOCAL_BRIDGE_SCHEMA_VERSION = 1;
|
|
13472
14596
|
var dataKeys = [
|
|
@@ -13583,6 +14707,24 @@ var tableByKey = {
|
|
|
13583
14707
|
local_calendar_items: "local_calendar_items"
|
|
13584
14708
|
};
|
|
13585
14709
|
var jsonColumns = new Set(["metadata", "tags", "data", "files_changed", "filters", "lanes"]);
|
|
14710
|
+
var structuralColumns = new Set([
|
|
14711
|
+
"id",
|
|
14712
|
+
"task_id",
|
|
14713
|
+
"run_id",
|
|
14714
|
+
"project_id",
|
|
14715
|
+
"plan_id",
|
|
14716
|
+
"task_list_id",
|
|
14717
|
+
"parent_id",
|
|
14718
|
+
"cycle_id",
|
|
14719
|
+
"depends_on",
|
|
14720
|
+
"external_project_id",
|
|
14721
|
+
"external_task_id",
|
|
14722
|
+
"short_id",
|
|
14723
|
+
"sha",
|
|
14724
|
+
"machine_id",
|
|
14725
|
+
"version",
|
|
14726
|
+
"schema_version"
|
|
14727
|
+
]);
|
|
13586
14728
|
function packageSource(version) {
|
|
13587
14729
|
return {
|
|
13588
14730
|
packageName: "@hasna/todos",
|
|
@@ -13677,7 +14819,7 @@ function createLocalBridgeBundle(options = {}, db) {
|
|
|
13677
14819
|
const runRows = queryByTaskIds(d, "SELECT * FROM task_runs WHERE task_id IN (__TASK_IDS__) ORDER BY started_at, created_at", taskIds);
|
|
13678
14820
|
const runIds = runRows.map((row) => String(row.id));
|
|
13679
14821
|
const project = options.project_id ? d.query("SELECT * FROM projects WHERE id = ?").get(options.project_id) : null;
|
|
13680
|
-
const
|
|
14822
|
+
const rawData = {
|
|
13681
14823
|
projects: options.project_id ? project ? [project] : [] : d.query("SELECT * FROM projects ORDER BY name").all(),
|
|
13682
14824
|
task_lists: (options.project_id ? d.query("SELECT * FROM task_lists WHERE project_id = ? ORDER BY name").all(options.project_id) : d.query("SELECT * FROM task_lists ORDER BY name").all()).map(rowToTaskList2),
|
|
13683
14825
|
plans: options.project_id ? d.query("SELECT * FROM plans WHERE project_id = ? ORDER BY created_at").all(options.project_id) : d.query("SELECT * FROM plans ORDER BY created_at").all(),
|
|
@@ -13695,7 +14837,8 @@ function createLocalBridgeBundle(options = {}, db) {
|
|
|
13695
14837
|
saved_views: (options.project_id ? d.query("SELECT * FROM saved_search_views WHERE json_extract(filters, '$.project_id') = ? ORDER BY name").all(options.project_id) : d.query("SELECT * FROM saved_search_views ORDER BY name").all()).map(rowToSavedView),
|
|
13696
14838
|
task_boards: (options.project_id ? d.query("SELECT * FROM task_boards WHERE project_id = ? ORDER BY name").all(options.project_id) : d.query("SELECT * FROM task_boards ORDER BY name").all()).map(rowToTaskBoard2),
|
|
13697
14839
|
local_calendar_items: (options.project_id ? d.query("SELECT * FROM local_calendar_items WHERE project_id = ? ORDER BY starts_at, created_at").all(options.project_id) : d.query("SELECT * FROM local_calendar_items ORDER BY starts_at, created_at").all()).map(rowToCalendarItem2)
|
|
13698
|
-
}
|
|
14840
|
+
};
|
|
14841
|
+
const data = options.redaction === "unsafe_plaintext" ? rawData : sanitizePreWriteValue(redactValue(rawData), "local_bridge.export");
|
|
13699
14842
|
const artifactContents = data.run_artifacts.map((artifact) => exportStoredArtifactContent({
|
|
13700
14843
|
id: artifact.id,
|
|
13701
14844
|
path: artifact.path,
|
|
@@ -13783,6 +14926,13 @@ function prepareValue(column, value) {
|
|
|
13783
14926
|
return JSON.stringify(value ?? (column === "tags" || column === "files_changed" ? [] : {}));
|
|
13784
14927
|
return value === undefined ? null : value;
|
|
13785
14928
|
}
|
|
14929
|
+
function sanitizeImportRecord(tableKey, row) {
|
|
14930
|
+
const sanitized = {};
|
|
14931
|
+
for (const [column, value] of Object.entries(row)) {
|
|
14932
|
+
sanitized[column] = structuralColumns.has(column) ? value : sanitizePreWriteValue(value, `local_bridge.${tableKey}.${column}`);
|
|
14933
|
+
}
|
|
14934
|
+
return sanitized;
|
|
14935
|
+
}
|
|
13786
14936
|
function slugifyPlanValue(value) {
|
|
13787
14937
|
return typeof value === "string" ? value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") : "";
|
|
13788
14938
|
}
|
|
@@ -13817,10 +14967,11 @@ function insertRecord(db, tableKey, row) {
|
|
|
13817
14967
|
const table = tableByKey[tableKey];
|
|
13818
14968
|
const columns = insertColumns[tableKey];
|
|
13819
14969
|
const placeholders = columns.map(() => "?").join(", ");
|
|
13820
|
-
const
|
|
14970
|
+
const safeRow = sanitizeImportRecord(tableKey, row);
|
|
14971
|
+
const params = columns.map((column) => prepareValue(column, safeRow[column]));
|
|
13821
14972
|
const result = db.run(`INSERT OR IGNORE INTO ${table} (${columns.join(", ")}) VALUES (${placeholders})`, params);
|
|
13822
14973
|
if (tableKey === "tasks" && result.changes > 0) {
|
|
13823
|
-
const tags = parseJsonArray2(
|
|
14974
|
+
const tags = parseJsonArray2(safeRow.tags);
|
|
13824
14975
|
const stmt = db.prepare("INSERT OR IGNORE INTO task_tags (task_id, tag) VALUES (?, ?)");
|
|
13825
14976
|
for (const tag of tags) {
|
|
13826
14977
|
if (tag)
|
|
@@ -17281,14 +18432,14 @@ function getTaskContract(taskId, db) {
|
|
|
17281
18432
|
}
|
|
17282
18433
|
function requestTaskReview(input, db) {
|
|
17283
18434
|
const task2 = taskOrThrow(input.task_id, db);
|
|
17284
|
-
const
|
|
18435
|
+
const timestamp2 = now();
|
|
17285
18436
|
const previous = readReview(task2.id, task2.metadata);
|
|
17286
18437
|
const entry = {
|
|
17287
18438
|
state: "requested",
|
|
17288
18439
|
actor: input.requester,
|
|
17289
18440
|
notes: input.notes ?? null,
|
|
17290
18441
|
changes_requested: [],
|
|
17291
|
-
at:
|
|
18442
|
+
at: timestamp2
|
|
17292
18443
|
};
|
|
17293
18444
|
const review = {
|
|
17294
18445
|
task_id: task2.id,
|
|
@@ -17297,7 +18448,7 @@ function requestTaskReview(input, db) {
|
|
|
17297
18448
|
reviewer: input.reviewer ?? previous.reviewer,
|
|
17298
18449
|
notes: input.notes ?? previous.notes,
|
|
17299
18450
|
changes_requested: [],
|
|
17300
|
-
requested_at:
|
|
18451
|
+
requested_at: timestamp2,
|
|
17301
18452
|
reviewed_at: null,
|
|
17302
18453
|
history: [...previous.history, entry]
|
|
17303
18454
|
};
|
|
@@ -17306,7 +18457,7 @@ function requestTaskReview(input, db) {
|
|
|
17306
18457
|
}
|
|
17307
18458
|
function recordTaskReview(input, db) {
|
|
17308
18459
|
const task2 = taskOrThrow(input.task_id, db);
|
|
17309
|
-
const
|
|
18460
|
+
const timestamp2 = now();
|
|
17310
18461
|
const previous = readReview(task2.id, task2.metadata);
|
|
17311
18462
|
const changes = cleanList(input.changes_requested);
|
|
17312
18463
|
const entry = {
|
|
@@ -17314,7 +18465,7 @@ function recordTaskReview(input, db) {
|
|
|
17314
18465
|
actor: input.reviewer,
|
|
17315
18466
|
notes: input.notes ?? null,
|
|
17316
18467
|
changes_requested: changes,
|
|
17317
|
-
at:
|
|
18468
|
+
at: timestamp2
|
|
17318
18469
|
};
|
|
17319
18470
|
const review = {
|
|
17320
18471
|
task_id: task2.id,
|
|
@@ -17324,7 +18475,7 @@ function recordTaskReview(input, db) {
|
|
|
17324
18475
|
notes: input.notes ?? previous.notes,
|
|
17325
18476
|
changes_requested: input.state === "changes_requested" ? changes : [],
|
|
17326
18477
|
requested_at: previous.requested_at,
|
|
17327
|
-
reviewed_at:
|
|
18478
|
+
reviewed_at: timestamp2,
|
|
17328
18479
|
history: [...previous.history, entry]
|
|
17329
18480
|
};
|
|
17330
18481
|
writeMetadata(task2.id, { ...task2.metadata, [REVIEW_KEY]: review }, db);
|
|
@@ -17399,7 +18550,7 @@ function taskOrThrow2(taskId, db) {
|
|
|
17399
18550
|
return task2;
|
|
17400
18551
|
}
|
|
17401
18552
|
function normalizeRule(input, existing) {
|
|
17402
|
-
const
|
|
18553
|
+
const timestamp2 = now();
|
|
17403
18554
|
const name = input.name.trim();
|
|
17404
18555
|
if (!name)
|
|
17405
18556
|
throw new Error("Review routing rule name is required");
|
|
@@ -17411,8 +18562,8 @@ function normalizeRule(input, existing) {
|
|
|
17411
18562
|
tags: input.tags === undefined ? existing?.tags ?? [] : cleanList2(input.tags),
|
|
17412
18563
|
priorities: input.priorities === undefined ? existing?.priorities ?? [] : cleanPriorityList(input.priorities),
|
|
17413
18564
|
project_id: input.project_id ?? existing?.project_id ?? null,
|
|
17414
|
-
created_at: existing?.created_at ??
|
|
17415
|
-
updated_at:
|
|
18565
|
+
created_at: existing?.created_at ?? timestamp2,
|
|
18566
|
+
updated_at: timestamp2
|
|
17416
18567
|
};
|
|
17417
18568
|
}
|
|
17418
18569
|
function readQueue(task2) {
|
|
@@ -17620,7 +18771,7 @@ function requestReviewQueue(input, db) {
|
|
|
17620
18771
|
notes: input.notes ?? input.reason
|
|
17621
18772
|
}, d);
|
|
17622
18773
|
const updatedTask = taskOrThrow2(task2.id, d);
|
|
17623
|
-
const
|
|
18774
|
+
const timestamp2 = now();
|
|
17624
18775
|
const previous = readQueue(updatedTask);
|
|
17625
18776
|
const queue = {
|
|
17626
18777
|
schema_version: 1,
|
|
@@ -17633,10 +18784,10 @@ function requestReviewQueue(input, db) {
|
|
|
17633
18784
|
notes: input.notes ?? previous?.notes ?? null,
|
|
17634
18785
|
changes_requested: [],
|
|
17635
18786
|
routing_rule: route.rule,
|
|
17636
|
-
requested_at:
|
|
18787
|
+
requested_at: timestamp2,
|
|
17637
18788
|
claimed_at: null,
|
|
17638
18789
|
decided_at: null,
|
|
17639
|
-
updated_at:
|
|
18790
|
+
updated_at: timestamp2,
|
|
17640
18791
|
history: appendHistory(previous ?? {
|
|
17641
18792
|
schema_version: 1,
|
|
17642
18793
|
state: "requested",
|
|
@@ -17651,7 +18802,7 @@ function requestReviewQueue(input, db) {
|
|
|
17651
18802
|
requested_at: null,
|
|
17652
18803
|
claimed_at: null,
|
|
17653
18804
|
decided_at: null,
|
|
17654
|
-
updated_at:
|
|
18805
|
+
updated_at: timestamp2,
|
|
17655
18806
|
history: []
|
|
17656
18807
|
}, "requested", input.requester, input.notes ?? input.reason)
|
|
17657
18808
|
};
|
|
@@ -17663,14 +18814,14 @@ function claimReviewItem(input, db) {
|
|
|
17663
18814
|
const previous = derivedQueue(task2, d);
|
|
17664
18815
|
if (!previous)
|
|
17665
18816
|
throw new Error(`Task is not in a review queue: ${task2.id}`);
|
|
17666
|
-
const
|
|
18817
|
+
const timestamp2 = now();
|
|
17667
18818
|
const queue = {
|
|
17668
18819
|
...previous,
|
|
17669
18820
|
state: "claimed",
|
|
17670
18821
|
reviewer: input.reviewer,
|
|
17671
18822
|
claimed_by: input.reviewer,
|
|
17672
|
-
claimed_at:
|
|
17673
|
-
updated_at:
|
|
18823
|
+
claimed_at: timestamp2,
|
|
18824
|
+
updated_at: timestamp2,
|
|
17674
18825
|
history: appendHistory(previous, "claimed", input.reviewer, input.note)
|
|
17675
18826
|
};
|
|
17676
18827
|
return writeQueue(task2, queue, input.reviewer, "claimed", d);
|
|
@@ -17683,15 +18834,15 @@ function approveReviewItem(input, db) {
|
|
|
17683
18834
|
throw new Error(`Task is not in a review queue: ${task2.id}`);
|
|
17684
18835
|
recordTaskReview({ task_id: task2.id, state: "approved", reviewer: input.reviewer, notes: input.note }, d);
|
|
17685
18836
|
const updatedTask = taskOrThrow2(task2.id, d);
|
|
17686
|
-
const
|
|
18837
|
+
const timestamp2 = now();
|
|
17687
18838
|
const queue = {
|
|
17688
18839
|
...previous,
|
|
17689
18840
|
state: "approved",
|
|
17690
18841
|
reviewer: input.reviewer,
|
|
17691
18842
|
claimed_by: previous.claimed_by ?? input.reviewer,
|
|
17692
18843
|
changes_requested: [],
|
|
17693
|
-
decided_at:
|
|
17694
|
-
updated_at:
|
|
18844
|
+
decided_at: timestamp2,
|
|
18845
|
+
updated_at: timestamp2,
|
|
17695
18846
|
history: appendHistory(previous, "approved", input.reviewer, input.note)
|
|
17696
18847
|
};
|
|
17697
18848
|
return writeQueue(updatedTask, queue, input.reviewer, "approved", d);
|
|
@@ -17711,15 +18862,15 @@ function returnReviewItem(input, db) {
|
|
|
17711
18862
|
changes_requested: changes
|
|
17712
18863
|
}, d);
|
|
17713
18864
|
const updatedTask = taskOrThrow2(task2.id, d);
|
|
17714
|
-
const
|
|
18865
|
+
const timestamp2 = now();
|
|
17715
18866
|
const queue = {
|
|
17716
18867
|
...previous,
|
|
17717
18868
|
state: "returned",
|
|
17718
18869
|
reviewer: input.reviewer,
|
|
17719
18870
|
claimed_by: previous.claimed_by ?? input.reviewer,
|
|
17720
18871
|
changes_requested: changes,
|
|
17721
|
-
decided_at:
|
|
17722
|
-
updated_at:
|
|
18872
|
+
decided_at: timestamp2,
|
|
18873
|
+
updated_at: timestamp2,
|
|
17723
18874
|
history: appendHistory(previous, "returned", input.reviewer, input.note)
|
|
17724
18875
|
};
|
|
17725
18876
|
return writeQueue(updatedTask, queue, input.reviewer, "returned", d);
|
|
@@ -17732,15 +18883,15 @@ function reopenReviewItem(input, db) {
|
|
|
17732
18883
|
throw new Error(`Task is not in a review queue: ${task2.id}`);
|
|
17733
18884
|
recordTaskReview({ task_id: task2.id, state: "reopened", reviewer: input.reviewer, notes: input.note }, d);
|
|
17734
18885
|
const updatedTask = taskOrThrow2(task2.id, d);
|
|
17735
|
-
const
|
|
18886
|
+
const timestamp2 = now();
|
|
17736
18887
|
const queue = {
|
|
17737
18888
|
...previous,
|
|
17738
18889
|
state: "reopened",
|
|
17739
18890
|
reviewer: input.reviewer,
|
|
17740
18891
|
claimed_by: null,
|
|
17741
18892
|
changes_requested: [],
|
|
17742
|
-
decided_at:
|
|
17743
|
-
updated_at:
|
|
18893
|
+
decided_at: timestamp2,
|
|
18894
|
+
updated_at: timestamp2,
|
|
17744
18895
|
history: appendHistory(previous, "reopened", input.reviewer, input.note)
|
|
17745
18896
|
};
|
|
17746
18897
|
return writeQueue(updatedTask, queue, input.reviewer, "reopened", d);
|
|
@@ -17785,7 +18936,7 @@ init_plans();
|
|
|
17785
18936
|
init_task_runs();
|
|
17786
18937
|
init_config2();
|
|
17787
18938
|
var LOCAL_ROADMAP_SCHEMA_VERSION = 1;
|
|
17788
|
-
function
|
|
18939
|
+
function timestamp2() {
|
|
17789
18940
|
return new Date().toISOString();
|
|
17790
18941
|
}
|
|
17791
18942
|
function newId(prefix) {
|
|
@@ -17876,7 +19027,7 @@ function createRoadmap(input) {
|
|
|
17876
19027
|
if (!name)
|
|
17877
19028
|
throw new Error("Roadmap name is required");
|
|
17878
19029
|
const store = readStore();
|
|
17879
|
-
const now3 =
|
|
19030
|
+
const now3 = timestamp2();
|
|
17880
19031
|
const roadmap = {
|
|
17881
19032
|
id: newId("roadmap"),
|
|
17882
19033
|
name,
|
|
@@ -17918,7 +19069,7 @@ function updateRoadmap(idOrName, input) {
|
|
|
17918
19069
|
owner: input.owner === undefined ? existing.owner : cleanString(input.owner),
|
|
17919
19070
|
agent_id: input.agent_id === undefined ? existing.agent_id : cleanString(input.agent_id),
|
|
17920
19071
|
release: input.release === undefined ? existing.release : cleanString(input.release),
|
|
17921
|
-
updated_at:
|
|
19072
|
+
updated_at: timestamp2()
|
|
17922
19073
|
};
|
|
17923
19074
|
store.roadmaps[id] = updated;
|
|
17924
19075
|
writeStore(store);
|
|
@@ -17948,7 +19099,7 @@ function createMilestone(input) {
|
|
|
17948
19099
|
const title = input.title.trim();
|
|
17949
19100
|
if (!title)
|
|
17950
19101
|
throw new Error("Milestone title is required");
|
|
17951
|
-
const now3 =
|
|
19102
|
+
const now3 = timestamp2();
|
|
17952
19103
|
const milestone = {
|
|
17953
19104
|
id: newId("milestone"),
|
|
17954
19105
|
roadmap_id: roadmapId,
|
|
@@ -17995,7 +19146,7 @@ function updateMilestone(idOrTitle, input) {
|
|
|
17995
19146
|
run_ids: input.run_ids === undefined ? existing.run_ids : cleanList3(input.run_ids),
|
|
17996
19147
|
release: input.release === undefined ? existing.release : cleanString(input.release),
|
|
17997
19148
|
tags: input.tags === undefined ? existing.tags : cleanList3(input.tags),
|
|
17998
|
-
updated_at:
|
|
19149
|
+
updated_at: timestamp2()
|
|
17999
19150
|
};
|
|
18000
19151
|
store.milestones[id] = updated;
|
|
18001
19152
|
writeStore(store);
|
|
@@ -18010,11 +19161,11 @@ function deleteMilestone(idOrTitle) {
|
|
|
18010
19161
|
delete store.milestones[id];
|
|
18011
19162
|
const roadmap = store.roadmaps[milestone.roadmap_id];
|
|
18012
19163
|
if (roadmap)
|
|
18013
|
-
store.roadmaps[roadmap.id] = { ...roadmap, milestone_ids: roadmap.milestone_ids.filter((item) => item !== id), updated_at:
|
|
19164
|
+
store.roadmaps[roadmap.id] = { ...roadmap, milestone_ids: roadmap.milestone_ids.filter((item) => item !== id), updated_at: timestamp2() };
|
|
18014
19165
|
for (const key of Object.keys(store.releases)) {
|
|
18015
19166
|
const release = store.releases[key];
|
|
18016
19167
|
if (release.milestone_ids.includes(id))
|
|
18017
|
-
store.releases[key] = { ...release, milestone_ids: release.milestone_ids.filter((item) => item !== id), updated_at:
|
|
19168
|
+
store.releases[key] = { ...release, milestone_ids: release.milestone_ids.filter((item) => item !== id), updated_at: timestamp2() };
|
|
18018
19169
|
}
|
|
18019
19170
|
writeStore(store);
|
|
18020
19171
|
return true;
|
|
@@ -18027,7 +19178,7 @@ function upsertReleaseGroup(input) {
|
|
|
18027
19178
|
throw new Error("Release group name is required");
|
|
18028
19179
|
const key = releaseKey(roadmapId, name);
|
|
18029
19180
|
const existing = store.releases[key];
|
|
18030
|
-
const now3 =
|
|
19181
|
+
const now3 = timestamp2();
|
|
18031
19182
|
const release = {
|
|
18032
19183
|
name,
|
|
18033
19184
|
version: input.version === undefined ? existing?.version ?? null : cleanString(input.version),
|
|
@@ -18096,7 +19247,7 @@ function exportRoadmapBundle(idOrName) {
|
|
|
18096
19247
|
schema_version: LOCAL_ROADMAP_SCHEMA_VERSION,
|
|
18097
19248
|
kind: "hasna.todos.roadmap-bundle",
|
|
18098
19249
|
local_only: true,
|
|
18099
|
-
exported_at:
|
|
19250
|
+
exported_at: timestamp2(),
|
|
18100
19251
|
roadmap,
|
|
18101
19252
|
milestones: roadmap.milestone_ids.map((milestoneId) => store.milestones[milestoneId]).filter(isMilestone),
|
|
18102
19253
|
releases: listReleaseGroups(roadmap.id)
|
|
@@ -18747,7 +19898,7 @@ function issueToTask(issue, opts) {
|
|
|
18747
19898
|
}
|
|
18748
19899
|
|
|
18749
19900
|
// src/db/inbox.ts
|
|
18750
|
-
|
|
19901
|
+
init_prewrite_secrets();
|
|
18751
19902
|
init_database();
|
|
18752
19903
|
init_tasks();
|
|
18753
19904
|
function parseMetadata3(value) {
|
|
@@ -18768,7 +19919,7 @@ function compactWhitespace(value) {
|
|
|
18768
19919
|
}
|
|
18769
19920
|
function fingerprintInboxInput(input) {
|
|
18770
19921
|
const sourceType = input.source_type || detectInboxSourceType(input.body, input.source_url);
|
|
18771
|
-
const normalized = compactWhitespace(
|
|
19922
|
+
const normalized = compactWhitespace(sanitizePreWriteText(input.body, "inbox.fingerprint")).slice(0, 8000);
|
|
18772
19923
|
return createHash6("sha256").update(`${sourceType}
|
|
18773
19924
|
${input.source_url || ""}
|
|
18774
19925
|
${normalized}`).digest("hex");
|
|
@@ -18797,34 +19948,20 @@ function deriveInboxTitle(body, sourceType, sourceUrl) {
|
|
|
18797
19948
|
return `${prefix}: ${compactWhitespace(firstUseful).slice(0, 90)}`;
|
|
18798
19949
|
}
|
|
18799
19950
|
function redactedMetadata(value) {
|
|
18800
|
-
|
|
18801
|
-
for (const [key, child] of Object.entries(value)) {
|
|
18802
|
-
if (/api[_-]?key|token|secret|password/i.test(key)) {
|
|
18803
|
-
redacted[key] = "[REDACTED]";
|
|
18804
|
-
} else if (typeof child === "string") {
|
|
18805
|
-
redacted[key] = redactEvidenceText(child);
|
|
18806
|
-
} else if (child && typeof child === "object" && !Array.isArray(child)) {
|
|
18807
|
-
redacted[key] = redactedMetadata(child);
|
|
18808
|
-
} else if (Array.isArray(child)) {
|
|
18809
|
-
redacted[key] = child.map((item) => typeof item === "string" ? redactEvidenceText(item) : item);
|
|
18810
|
-
} else {
|
|
18811
|
-
redacted[key] = child;
|
|
18812
|
-
}
|
|
18813
|
-
}
|
|
18814
|
-
return redacted;
|
|
19951
|
+
return sanitizePreWriteValue(value, "inbox.metadata");
|
|
18815
19952
|
}
|
|
18816
19953
|
function createInboxItem(input, db) {
|
|
18817
19954
|
const d = db || getDatabase();
|
|
18818
|
-
const body =
|
|
19955
|
+
const body = sanitizePreWriteText(input.body, "inbox.body");
|
|
18819
19956
|
const sourceType = input.source_type || detectInboxSourceType(body, input.source_url);
|
|
18820
|
-
const title =
|
|
19957
|
+
const title = sanitizePreWriteText(input.title || deriveInboxTitle(body, sourceType, input.source_url), "inbox.title");
|
|
18821
19958
|
const fingerprint = fingerprintInboxInput({ body, source_type: sourceType, source_url: input.source_url });
|
|
18822
19959
|
const existing = d.query("SELECT * FROM inbox_items WHERE fingerprint = ?").get(fingerprint);
|
|
18823
19960
|
if (existing) {
|
|
18824
19961
|
const item = rowToInboxItem(existing);
|
|
18825
19962
|
return { item, task: item.task_id ? getTask(item.task_id, d) : null, duplicate: true };
|
|
18826
19963
|
}
|
|
18827
|
-
const
|
|
19964
|
+
const timestamp3 = now();
|
|
18828
19965
|
let task2 = null;
|
|
18829
19966
|
const metadata = {
|
|
18830
19967
|
...input.metadata || {},
|
|
@@ -18836,8 +19973,8 @@ function createInboxItem(input, db) {
|
|
|
18836
19973
|
title,
|
|
18837
19974
|
description: [
|
|
18838
19975
|
`**Inbox source:** ${sourceType}`,
|
|
18839
|
-
input.source_name ? `**Source name:** ${
|
|
18840
|
-
input.source_url ? `**Source URL:** ${
|
|
19976
|
+
input.source_name ? `**Source name:** ${sanitizePreWriteText(input.source_name, "inbox.source_name")}` : null,
|
|
19977
|
+
input.source_url ? `**Source URL:** ${sanitizePreWriteText(input.source_url, "inbox.source_url")}` : null,
|
|
18841
19978
|
`**Captured context:**
|
|
18842
19979
|
\`\`\`
|
|
18843
19980
|
${body.slice(0, 4000)}
|
|
@@ -18858,15 +19995,15 @@ ${body.slice(0, 4000)}
|
|
|
18858
19995
|
id,
|
|
18859
19996
|
task2?.id ?? null,
|
|
18860
19997
|
sourceType,
|
|
18861
|
-
input.source_name ?
|
|
18862
|
-
input.source_url ?
|
|
19998
|
+
input.source_name ? sanitizePreWriteText(input.source_name, "inbox.source_name") : null,
|
|
19999
|
+
input.source_url ? sanitizePreWriteText(input.source_url, "inbox.source_url") : null,
|
|
18863
20000
|
title,
|
|
18864
20001
|
body,
|
|
18865
20002
|
fingerprint,
|
|
18866
20003
|
input.status || "triaged",
|
|
18867
20004
|
JSON.stringify(redactedMetadata(metadata)),
|
|
18868
|
-
|
|
18869
|
-
|
|
20005
|
+
timestamp3,
|
|
20006
|
+
timestamp3
|
|
18870
20007
|
]);
|
|
18871
20008
|
return { item: getInboxItem(id, d), task: task2, duplicate: false };
|
|
18872
20009
|
}
|
|
@@ -18920,7 +20057,7 @@ function rowToRelationship(row) {
|
|
|
18920
20057
|
function addTaskRelationship(input, db) {
|
|
18921
20058
|
const d = db || getDatabase();
|
|
18922
20059
|
const id = uuid();
|
|
18923
|
-
const
|
|
20060
|
+
const timestamp3 = now();
|
|
18924
20061
|
if (input.source_task_id === input.target_task_id) {
|
|
18925
20062
|
throw new Error("Cannot create a relationship between a task and itself");
|
|
18926
20063
|
}
|
|
@@ -18946,7 +20083,7 @@ function addTaskRelationship(input, db) {
|
|
|
18946
20083
|
input.relationship_type,
|
|
18947
20084
|
JSON.stringify(input.metadata || {}),
|
|
18948
20085
|
input.created_by || null,
|
|
18949
|
-
|
|
20086
|
+
timestamp3
|
|
18950
20087
|
]);
|
|
18951
20088
|
return getTaskRelationship(id, d);
|
|
18952
20089
|
}
|
|
@@ -20175,15 +21312,15 @@ function storedReportSummary(report) {
|
|
|
20175
21312
|
occurred_at: report.occurred_at ?? null
|
|
20176
21313
|
};
|
|
20177
21314
|
}
|
|
20178
|
-
function testerMetadata(report, fingerprint2, previous,
|
|
21315
|
+
function testerMetadata(report, fingerprint2, previous, timestamp3) {
|
|
20179
21316
|
const occurrenceCount = typeof previous?.["occurrence_count"] === "number" ? previous["occurrence_count"] + 1 : 1;
|
|
20180
21317
|
const previousRecent = previous?.["recent_reports"];
|
|
20181
21318
|
const recent = Array.isArray(previousRecent) ? previousRecent : [];
|
|
20182
21319
|
return {
|
|
20183
21320
|
schema_version: TESTERS_ISSUE_REPORT_SCHEMA_VERSION,
|
|
20184
21321
|
fingerprint: fingerprint2,
|
|
20185
|
-
first_seen_at: asString3(previous?.["first_seen_at"]) ||
|
|
20186
|
-
last_seen_at:
|
|
21322
|
+
first_seen_at: asString3(previous?.["first_seen_at"]) || timestamp3,
|
|
21323
|
+
last_seen_at: timestamp3,
|
|
20187
21324
|
occurrence_count: occurrenceCount,
|
|
20188
21325
|
latest_report: storedReportSummary(report),
|
|
20189
21326
|
recent_reports: [...recent.slice(-4), storedReportSummary(report)]
|
|
@@ -20223,7 +21360,7 @@ function commandsFor(task2) {
|
|
|
20223
21360
|
`todos dedupe scan --threshold 0.8 --json`
|
|
20224
21361
|
];
|
|
20225
21362
|
}
|
|
20226
|
-
function updateExistingTask(task2, report, fingerprint2, input,
|
|
21363
|
+
function updateExistingTask(task2, report, fingerprint2, input, timestamp3, db) {
|
|
20227
21364
|
if (input.update_existing === false)
|
|
20228
21365
|
return { action: "matched", task: task2 };
|
|
20229
21366
|
const previous = asObject3(task2.metadata["tester_issue_report"]);
|
|
@@ -20243,7 +21380,7 @@ function updateExistingTask(task2, report, fingerprint2, input, timestamp2, db)
|
|
|
20243
21380
|
...sourceMetadata(report),
|
|
20244
21381
|
external_ref: fingerprint2,
|
|
20245
21382
|
tester_issue_fingerprint: fingerprint2,
|
|
20246
|
-
tester_issue_report: testerMetadata(report, fingerprint2, previous,
|
|
21383
|
+
tester_issue_report: testerMetadata(report, fingerprint2, previous, timestamp3)
|
|
20247
21384
|
},
|
|
20248
21385
|
...input.assigned_to !== undefined ? { assigned_to: input.assigned_to } : {},
|
|
20249
21386
|
task_type: task2.task_type || "bug"
|
|
@@ -20252,7 +21389,7 @@ function updateExistingTask(task2, report, fingerprint2, input, timestamp2, db)
|
|
|
20252
21389
|
}
|
|
20253
21390
|
function upsertTesterIssueReport(input, db) {
|
|
20254
21391
|
const d = db || getDatabase();
|
|
20255
|
-
const
|
|
21392
|
+
const timestamp3 = now();
|
|
20256
21393
|
const warnings = [];
|
|
20257
21394
|
const report = normalizeTesterIssueReport(input.report, input.default_priority || "medium");
|
|
20258
21395
|
const fingerprint2 = fingerprintTesterIssueReport(report);
|
|
@@ -20263,7 +21400,7 @@ function upsertTesterIssueReport(input, db) {
|
|
|
20263
21400
|
schema_version: TESTERS_ISSUE_REPORT_RESULT_SCHEMA_VERSION,
|
|
20264
21401
|
local_only: true,
|
|
20265
21402
|
dry_run: true,
|
|
20266
|
-
processed_at:
|
|
21403
|
+
processed_at: timestamp3,
|
|
20267
21404
|
action,
|
|
20268
21405
|
fingerprint: fingerprint2,
|
|
20269
21406
|
report,
|
|
@@ -20273,12 +21410,12 @@ function upsertTesterIssueReport(input, db) {
|
|
|
20273
21410
|
};
|
|
20274
21411
|
}
|
|
20275
21412
|
if (existing) {
|
|
20276
|
-
const updated = updateExistingTask(existing, report, fingerprint2, input,
|
|
21413
|
+
const updated = updateExistingTask(existing, report, fingerprint2, input, timestamp3, d);
|
|
20277
21414
|
return {
|
|
20278
21415
|
schema_version: TESTERS_ISSUE_REPORT_RESULT_SCHEMA_VERSION,
|
|
20279
21416
|
local_only: true,
|
|
20280
21417
|
dry_run: false,
|
|
20281
|
-
processed_at:
|
|
21418
|
+
processed_at: timestamp3,
|
|
20282
21419
|
action: updated.action,
|
|
20283
21420
|
fingerprint: fingerprint2,
|
|
20284
21421
|
report,
|
|
@@ -20298,7 +21435,7 @@ function upsertTesterIssueReport(input, db) {
|
|
|
20298
21435
|
...sourceMetadata(report),
|
|
20299
21436
|
external_ref: fingerprint2,
|
|
20300
21437
|
tester_issue_fingerprint: fingerprint2,
|
|
20301
|
-
tester_issue_report: testerMetadata(report, fingerprint2, null,
|
|
21438
|
+
tester_issue_report: testerMetadata(report, fingerprint2, null, timestamp3),
|
|
20302
21439
|
tester_issue_report_raw: redactValue({
|
|
20303
21440
|
...report,
|
|
20304
21441
|
evidence: report.evidence ? {
|
|
@@ -20317,7 +21454,7 @@ function upsertTesterIssueReport(input, db) {
|
|
|
20317
21454
|
schema_version: TESTERS_ISSUE_REPORT_RESULT_SCHEMA_VERSION,
|
|
20318
21455
|
local_only: true,
|
|
20319
21456
|
dry_run: false,
|
|
20320
|
-
processed_at:
|
|
21457
|
+
processed_at: timestamp3,
|
|
20321
21458
|
action: "created",
|
|
20322
21459
|
fingerprint: fingerprint2,
|
|
20323
21460
|
report,
|
|
@@ -20414,10 +21551,10 @@ function parseQuietHours(value, timezone = "local") {
|
|
|
20414
21551
|
parseClock(end);
|
|
20415
21552
|
return { start, end, timezone };
|
|
20416
21553
|
}
|
|
20417
|
-
function isQuietTime(quiet,
|
|
21554
|
+
function isQuietTime(quiet, timestamp3) {
|
|
20418
21555
|
if (!quiet)
|
|
20419
21556
|
return false;
|
|
20420
|
-
const date = new Date(
|
|
21557
|
+
const date = new Date(timestamp3);
|
|
20421
21558
|
if (Number.isNaN(date.getTime()))
|
|
20422
21559
|
return false;
|
|
20423
21560
|
const minutes = quiet.timezone === "utc" ? date.getUTCHours() * 60 + date.getUTCMinutes() : date.getHours() * 60 + date.getMinutes();
|
|
@@ -20436,7 +21573,7 @@ function normalizeRule2(input, existing) {
|
|
|
20436
21573
|
throw new Error(`unsupported terminal notification severity: ${severity}`);
|
|
20437
21574
|
if (!VALID_FORMATS.has(format))
|
|
20438
21575
|
throw new Error(`unsupported terminal notification format: ${format}`);
|
|
20439
|
-
const
|
|
21576
|
+
const timestamp3 = new Date().toISOString();
|
|
20440
21577
|
return {
|
|
20441
21578
|
...existing,
|
|
20442
21579
|
name: safeName2(input.name),
|
|
@@ -20451,8 +21588,8 @@ function normalizeRule2(input, existing) {
|
|
|
20451
21588
|
project_ids: normalizeList(input.project_ids) ?? existing?.project_ids,
|
|
20452
21589
|
contains: normalizeList(input.contains) ?? existing?.contains,
|
|
20453
21590
|
quiet_hours: input.quiet_hours ?? existing?.quiet_hours,
|
|
20454
|
-
created_at: existing?.created_at ||
|
|
20455
|
-
updated_at:
|
|
21591
|
+
created_at: existing?.created_at || timestamp3,
|
|
21592
|
+
updated_at: timestamp3
|
|
20456
21593
|
};
|
|
20457
21594
|
}
|
|
20458
21595
|
function eventSeverity(eventType) {
|
|
@@ -20513,7 +21650,7 @@ function removeTerminalNotificationRule(name) {
|
|
|
20513
21650
|
return true;
|
|
20514
21651
|
}
|
|
20515
21652
|
function evaluateTerminalWatchRules(input, rules = listTerminalNotificationRules()) {
|
|
20516
|
-
const
|
|
21653
|
+
const timestamp3 = input.timestamp || new Date().toISOString();
|
|
20517
21654
|
const payload = redactValue(input.payload || {});
|
|
20518
21655
|
return rules.map((rule) => {
|
|
20519
21656
|
const skipped = [];
|
|
@@ -20534,7 +21671,7 @@ function evaluateTerminalWatchRules(input, rules = listTerminalNotificationRules
|
|
|
20534
21671
|
skipped.push("project does not match rule");
|
|
20535
21672
|
if (!containsMatches(rule.contains, payload))
|
|
20536
21673
|
skipped.push("payload text does not match rule");
|
|
20537
|
-
if (isQuietTime(rule.quiet_hours,
|
|
21674
|
+
if (isQuietTime(rule.quiet_hours, timestamp3))
|
|
20538
21675
|
skipped.push("quiet hours active");
|
|
20539
21676
|
const matched = skipped.length === 0;
|
|
20540
21677
|
const title = asString4(payload["title"]) || asString4(payload["name"]) || input.type;
|
|
@@ -20545,7 +21682,7 @@ function evaluateTerminalWatchRules(input, rules = listTerminalNotificationRules
|
|
|
20545
21682
|
severity,
|
|
20546
21683
|
title,
|
|
20547
21684
|
message: `${input.type}: ${title}`,
|
|
20548
|
-
timestamp:
|
|
21685
|
+
timestamp: timestamp3,
|
|
20549
21686
|
task_id: taskId,
|
|
20550
21687
|
project_id: asString4(payload["project_id"]),
|
|
20551
21688
|
agent_id: asString4(payload["agent_id"]) || asString4(payload["assigned_to"]),
|
|
@@ -20601,10 +21738,10 @@ function parseClock2(value) {
|
|
|
20601
21738
|
throw new Error("quiet hours must use HH:MM-HH:MM");
|
|
20602
21739
|
return Number(match[1]) * 60 + Number(match[2]);
|
|
20603
21740
|
}
|
|
20604
|
-
function quietActive(quiet,
|
|
21741
|
+
function quietActive(quiet, timestamp3) {
|
|
20605
21742
|
if (!quiet)
|
|
20606
21743
|
return false;
|
|
20607
|
-
const date = new Date(
|
|
21744
|
+
const date = new Date(timestamp3);
|
|
20608
21745
|
if (Number.isNaN(date.getTime()))
|
|
20609
21746
|
return false;
|
|
20610
21747
|
const current = quiet.timezone === "utc" ? date.getUTCHours() * 60 + date.getUTCMinutes() : date.getHours() * 60 + date.getMinutes();
|
|
@@ -21538,6 +22675,7 @@ function renderLocalReportMarkdown(report) {
|
|
|
21538
22675
|
// src/lib/local-encryption.ts
|
|
21539
22676
|
init_config2();
|
|
21540
22677
|
init_redaction();
|
|
22678
|
+
init_prewrite_secrets();
|
|
21541
22679
|
import { createCipheriv, createDecipheriv, createHash as createHash8, randomBytes, scryptSync, timingSafeEqual as timingSafeEqual2 } from "crypto";
|
|
21542
22680
|
var TODOS_ENCRYPTED_VALUE_KIND = "hasna.todos.encrypted-value";
|
|
21543
22681
|
var TODOS_ENCRYPTED_BRIDGE_KIND = "hasna.todos.encrypted-bridge";
|
|
@@ -21597,7 +22735,7 @@ function upsertEncryptionProfile(input) {
|
|
|
21597
22735
|
const name = normalizeProfileName(input.name);
|
|
21598
22736
|
const config = loadConfig();
|
|
21599
22737
|
const existing = config.encryption_profiles?.[name];
|
|
21600
|
-
const
|
|
22738
|
+
const timestamp3 = now3();
|
|
21601
22739
|
const profile = {
|
|
21602
22740
|
name,
|
|
21603
22741
|
algorithm: "aes-256-gcm",
|
|
@@ -21605,8 +22743,8 @@ function upsertEncryptionProfile(input) {
|
|
|
21605
22743
|
key_env: input.key_env?.trim() || existing?.key_env || DEFAULT_ENCRYPTION_KEY_ENV,
|
|
21606
22744
|
salt: input.salt || existing?.salt || randomBase64(16),
|
|
21607
22745
|
description: input.description ?? existing?.description,
|
|
21608
|
-
created_at: existing?.created_at ||
|
|
21609
|
-
updated_at:
|
|
22746
|
+
created_at: existing?.created_at || timestamp3,
|
|
22747
|
+
updated_at: timestamp3
|
|
21610
22748
|
};
|
|
21611
22749
|
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(profile.key_env))
|
|
21612
22750
|
throw new Error("key_env must be a valid environment variable name");
|
|
@@ -21704,6 +22842,9 @@ function looksSensitiveKey(key) {
|
|
|
21704
22842
|
return /api[_-]?key|token|secret|password|credential|evidence|artifact|output|summary|metadata/i.test(key);
|
|
21705
22843
|
}
|
|
21706
22844
|
function encryptSensitiveFields(value, options = {}) {
|
|
22845
|
+
if (typeof value === "string") {
|
|
22846
|
+
return scanPreWriteText(value, "encrypted_export.string").clean ? value : encryptValue(value, options);
|
|
22847
|
+
}
|
|
21707
22848
|
if (!value || typeof value !== "object")
|
|
21708
22849
|
return value;
|
|
21709
22850
|
if (Array.isArray(value))
|
|
@@ -21782,14 +22923,15 @@ function applyExportProfile(data, options = {}) {
|
|
|
21782
22923
|
const warnings = [];
|
|
21783
22924
|
if (profile === "plaintext") {
|
|
21784
22925
|
if (!options.acknowledge_plaintext) {
|
|
21785
|
-
|
|
22926
|
+
throw new Error("Export profile 'plaintext' includes unredacted data; pass acknowledge_plaintext=true to confirm, or use the redacted/encrypted profiles.");
|
|
21786
22927
|
}
|
|
22928
|
+
warnings.push("Export profile 'plaintext' includes unredacted data; prefer encrypted exports for sensitive task evidence.");
|
|
21787
22929
|
return { profile, data, warnings };
|
|
21788
22930
|
}
|
|
21789
22931
|
if (profile === "encrypted") {
|
|
21790
22932
|
return { profile, data: encryptSensitiveFields(data, { profile: DEFAULT_ENCRYPTION_PROFILE, env: options.env }), warnings };
|
|
21791
22933
|
}
|
|
21792
|
-
return { profile, data: redactValue(data), warnings };
|
|
22934
|
+
return { profile, data: sanitizePreWriteValue(redactValue(data), "export.redacted"), warnings };
|
|
21793
22935
|
}
|
|
21794
22936
|
|
|
21795
22937
|
// src/contracts.ts
|