dsh-kingdom 2.0.0 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/ASSETS_NOTICE.md +22 -22
  2. package/README.md +244 -243
  3. package/THIRD_PARTY_NOTICES.md +92 -92
  4. package/docs/DSH-KINGDOM-3.0-ANNOUNCEMENT.md +13 -0
  5. package/docs/V2.0-RELEASE-NOTES.md +30 -30
  6. package/docs/V2.0-USER-GUIDE.md +74 -74
  7. package/docs/V3.0-RELEASE-NOTES.md +21 -0
  8. package/docs/V3.0-USER-GUIDE.md +33 -0
  9. package/docs/V3.1-RELEASE-NOTES.md +72 -0
  10. package/lib/core/binding.js +2 -0
  11. package/lib/core/binding.js.map +1 -1
  12. package/lib/core/db.js +546 -546
  13. package/lib/core/delivery-ack.js +1356 -0
  14. package/lib/core/delivery-ack.js.map +1 -0
  15. package/lib/core/delivery-change.js +1237 -0
  16. package/lib/core/delivery-change.js.map +1 -0
  17. package/lib/core/owner-window.js +592 -11
  18. package/lib/core/owner-window.js.map +1 -1
  19. package/lib/core/task-service.js +144 -5
  20. package/lib/core/task-service.js.map +1 -1
  21. package/lib/gui/assets/characters/chancellor-idle.svg +67 -67
  22. package/lib/gui/assets/characters/chancellor-sleeping.svg +67 -67
  23. package/lib/gui/assets/characters/chancellor-thinking.svg +67 -67
  24. package/lib/gui/assets/characters/chancellor-working.svg +67 -67
  25. package/lib/gui/assets/characters/knight-redraw-r1-idle.svg +67 -67
  26. package/lib/gui/assets/characters/knight-redraw-r1-sleeping.svg +67 -67
  27. package/lib/gui/assets/characters/knight-redraw-r1-thinking.svg +67 -67
  28. package/lib/gui/assets/characters/knight-redraw-r1-working.svg +67 -67
  29. package/lib/gui/assets/characters/supervisor-idle.svg +67 -67
  30. package/lib/gui/assets/characters/supervisor-sleeping.svg +67 -67
  31. package/lib/gui/assets/characters/supervisor-thinking.svg +67 -67
  32. package/lib/gui/assets/characters/supervisor-working.svg +67 -67
  33. package/lib/gui/console-app.js +7 -1
  34. package/lib/gui/console-app.js.map +1 -1
  35. package/lib/gui/contract.js +6 -0
  36. package/lib/gui/contract.js.map +1 -1
  37. package/lib/gui/owner-control.js +114 -4
  38. package/lib/gui/owner-control.js.map +1 -1
  39. package/lib/gui/owner-ui.js +604 -183
  40. package/lib/gui/owner-ui.js.map +1 -1
  41. package/lib/gui/server.js +109 -109
  42. package/lib/gui/snapshot.js +127 -4
  43. package/lib/gui/snapshot.js.map +1 -1
  44. package/lib/gui/visual-assets.js +37 -21
  45. package/lib/gui/visual-assets.js.map +1 -1
  46. package/lib/gui/visual-system.js +269 -269
  47. package/lib/gui/workbench-ui.js +537 -136
  48. package/lib/gui/workbench-ui.js.map +1 -1
  49. package/lib/gui/workbench.js +123 -0
  50. package/lib/gui/workbench.js.map +1 -1
  51. package/lib/index.js +338 -26
  52. package/lib/index.js.map +1 -1
  53. package/lib/types/core/binding.d.ts +15 -0
  54. package/lib/types/core/delivery-ack.d.ts +688 -0
  55. package/lib/types/core/delivery-change.d.ts +381 -0
  56. package/lib/types/core/owner-window.d.ts +235 -2
  57. package/lib/types/core/task-service.d.ts +22 -0
  58. package/lib/types/gui/contract.d.ts +153 -1
  59. package/lib/types/gui/owner-control.d.ts +61 -1
  60. package/lib/types/gui/snapshot.d.ts +8 -0
  61. package/lib/types/gui/visual-assets.d.ts +16 -0
  62. package/lib/types/gui/workbench.d.ts +47 -1
  63. package/lib/types/index.d.ts +49 -0
  64. package/lib/types/worker/governed-executor.d.ts +15 -0
  65. package/lib/worker/governed-executor.js +21 -0
  66. package/lib/worker/governed-executor.js.map +1 -1
  67. package/package.json +79 -75
package/lib/core/db.js CHANGED
@@ -44,118 +44,118 @@ import { asExecutionState, isTerminalExecutionState, transitionExecution, } from
44
44
  * - v4 DDL 不进 SCHEMA_SQL bootstrap(避免迁移事务语义失效;见 ensureSchemaV4 头注)。
45
45
  */
46
46
  export const SCHEMA_VERSION = 4;
47
- export const SCHEMA_SQL = `
48
- CREATE TABLE IF NOT EXISTS kingdoms (
49
- kingdom_id TEXT PRIMARY KEY,
50
- name TEXT NOT NULL,
51
- created_at TEXT NOT NULL,
52
- owner_id TEXT NOT NULL,
53
- owner_name TEXT NOT NULL,
54
- schema_version INTEGER NOT NULL DEFAULT 1
55
- );
56
-
57
- CREATE TABLE IF NOT EXISTS territories (
58
- territory_id TEXT PRIMARY KEY,
59
- kingdom_id TEXT NOT NULL,
60
- name TEXT NOT NULL,
61
- workspace_path TEXT,
62
- summary TEXT,
63
- supervisor_binding_id TEXT,
64
- status TEXT NOT NULL DEFAULT 'ACTIVE',
65
- created_at TEXT NOT NULL
66
- );
67
-
68
- CREATE TABLE IF NOT EXISTS role_bindings (
69
- binding_id TEXT PRIMARY KEY,
70
- kingdom_id TEXT NOT NULL,
71
- role_type TEXT NOT NULL CHECK(role_type IN ('OWNER','CHANCELLOR','SUPERVISOR','WORKER')),
72
- role_name TEXT NOT NULL,
73
- runtime_type TEXT NOT NULL DEFAULT 'dsh',
74
- session_id TEXT,
75
- model_name TEXT,
76
- agent_name TEXT,
77
- session_meta TEXT,
78
- principal_id TEXT,
79
- created_at TEXT NOT NULL,
80
- updated_at TEXT NOT NULL
81
- );
82
-
83
- CREATE TABLE IF NOT EXISTS tasks (
84
- task_id TEXT PRIMARY KEY,
85
- territory_id TEXT NOT NULL,
86
- parent_task_id TEXT,
87
- title TEXT NOT NULL,
88
- description TEXT,
89
- assigned_binding_id TEXT,
90
- status TEXT NOT NULL DEFAULT 'CREATED',
91
- acceptance_criteria TEXT,
92
- result_summary TEXT,
93
- created_at TEXT NOT NULL,
94
- updated_at TEXT NOT NULL
95
- );
96
-
97
- CREATE TABLE IF NOT EXISTS events (
98
- event_id TEXT PRIMARY KEY,
99
- kingdom_id TEXT NOT NULL,
100
- event_type TEXT NOT NULL,
101
- actor_role TEXT,
102
- actor_id TEXT,
103
- target_type TEXT,
104
- target_id TEXT,
105
- payload_json TEXT NOT NULL DEFAULT '{}',
106
- created_at TEXT NOT NULL
107
- );
108
-
109
- CREATE TABLE IF NOT EXISTS worker_results (
110
- result_id TEXT PRIMARY KEY,
111
- task_id TEXT NOT NULL,
112
- attempt_no INTEGER NOT NULL,
113
- worker_binding_id TEXT,
114
- session_id TEXT,
115
- outcome TEXT NOT NULL,
116
- result_json TEXT NOT NULL DEFAULT '{}',
117
- created_at TEXT NOT NULL,
118
- UNIQUE(task_id, attempt_no)
119
- );
120
-
121
- CREATE UNIQUE INDEX IF NOT EXISTS territories_kingdom_name_uk
122
- ON territories(kingdom_id, name);
123
-
124
- CREATE TABLE IF NOT EXISTS executions (
125
- execution_id TEXT PRIMARY KEY,
126
- task_id TEXT NOT NULL,
127
- attempt_no INTEGER NOT NULL,
128
- worker_binding_id TEXT,
129
- session_id TEXT,
130
- state TEXT NOT NULL,
131
- detail TEXT,
132
- started_at TEXT NOT NULL,
133
- heartbeat_at TEXT,
134
- ended_at TEXT,
135
- pause_requested_at TEXT,
136
- UNIQUE(task_id, attempt_no)
137
- );
138
-
139
- CREATE INDEX IF NOT EXISTS executions_task_idx ON executions(task_id);
140
-
141
- CREATE TABLE IF NOT EXISTS task_assignments (
142
- assignment_id TEXT PRIMARY KEY,
143
- task_id TEXT NOT NULL,
144
- territory_id TEXT NOT NULL,
145
- worker_binding_id TEXT NOT NULL,
146
- assigned_by TEXT NOT NULL,
147
- assigned_at TEXT NOT NULL,
148
- ended_at TEXT,
149
- end_reason TEXT,
150
- previous_assignment_id TEXT,
151
- handoff_reason TEXT,
152
- created_at TEXT NOT NULL
153
- );
154
-
155
- CREATE UNIQUE INDEX IF NOT EXISTS one_active_assignment_per_task
156
- ON task_assignments(task_id) WHERE ended_at IS NULL;
157
-
158
- CREATE INDEX IF NOT EXISTS task_assignments_task_idx ON task_assignments(task_id);
47
+ export const SCHEMA_SQL = `
48
+ CREATE TABLE IF NOT EXISTS kingdoms (
49
+ kingdom_id TEXT PRIMARY KEY,
50
+ name TEXT NOT NULL,
51
+ created_at TEXT NOT NULL,
52
+ owner_id TEXT NOT NULL,
53
+ owner_name TEXT NOT NULL,
54
+ schema_version INTEGER NOT NULL DEFAULT 1
55
+ );
56
+
57
+ CREATE TABLE IF NOT EXISTS territories (
58
+ territory_id TEXT PRIMARY KEY,
59
+ kingdom_id TEXT NOT NULL,
60
+ name TEXT NOT NULL,
61
+ workspace_path TEXT,
62
+ summary TEXT,
63
+ supervisor_binding_id TEXT,
64
+ status TEXT NOT NULL DEFAULT 'ACTIVE',
65
+ created_at TEXT NOT NULL
66
+ );
67
+
68
+ CREATE TABLE IF NOT EXISTS role_bindings (
69
+ binding_id TEXT PRIMARY KEY,
70
+ kingdom_id TEXT NOT NULL,
71
+ role_type TEXT NOT NULL CHECK(role_type IN ('OWNER','CHANCELLOR','SUPERVISOR','WORKER')),
72
+ role_name TEXT NOT NULL,
73
+ runtime_type TEXT NOT NULL DEFAULT 'dsh',
74
+ session_id TEXT,
75
+ model_name TEXT,
76
+ agent_name TEXT,
77
+ session_meta TEXT,
78
+ principal_id TEXT,
79
+ created_at TEXT NOT NULL,
80
+ updated_at TEXT NOT NULL
81
+ );
82
+
83
+ CREATE TABLE IF NOT EXISTS tasks (
84
+ task_id TEXT PRIMARY KEY,
85
+ territory_id TEXT NOT NULL,
86
+ parent_task_id TEXT,
87
+ title TEXT NOT NULL,
88
+ description TEXT,
89
+ assigned_binding_id TEXT,
90
+ status TEXT NOT NULL DEFAULT 'CREATED',
91
+ acceptance_criteria TEXT,
92
+ result_summary TEXT,
93
+ created_at TEXT NOT NULL,
94
+ updated_at TEXT NOT NULL
95
+ );
96
+
97
+ CREATE TABLE IF NOT EXISTS events (
98
+ event_id TEXT PRIMARY KEY,
99
+ kingdom_id TEXT NOT NULL,
100
+ event_type TEXT NOT NULL,
101
+ actor_role TEXT,
102
+ actor_id TEXT,
103
+ target_type TEXT,
104
+ target_id TEXT,
105
+ payload_json TEXT NOT NULL DEFAULT '{}',
106
+ created_at TEXT NOT NULL
107
+ );
108
+
109
+ CREATE TABLE IF NOT EXISTS worker_results (
110
+ result_id TEXT PRIMARY KEY,
111
+ task_id TEXT NOT NULL,
112
+ attempt_no INTEGER NOT NULL,
113
+ worker_binding_id TEXT,
114
+ session_id TEXT,
115
+ outcome TEXT NOT NULL,
116
+ result_json TEXT NOT NULL DEFAULT '{}',
117
+ created_at TEXT NOT NULL,
118
+ UNIQUE(task_id, attempt_no)
119
+ );
120
+
121
+ CREATE UNIQUE INDEX IF NOT EXISTS territories_kingdom_name_uk
122
+ ON territories(kingdom_id, name);
123
+
124
+ CREATE TABLE IF NOT EXISTS executions (
125
+ execution_id TEXT PRIMARY KEY,
126
+ task_id TEXT NOT NULL,
127
+ attempt_no INTEGER NOT NULL,
128
+ worker_binding_id TEXT,
129
+ session_id TEXT,
130
+ state TEXT NOT NULL,
131
+ detail TEXT,
132
+ started_at TEXT NOT NULL,
133
+ heartbeat_at TEXT,
134
+ ended_at TEXT,
135
+ pause_requested_at TEXT,
136
+ UNIQUE(task_id, attempt_no)
137
+ );
138
+
139
+ CREATE INDEX IF NOT EXISTS executions_task_idx ON executions(task_id);
140
+
141
+ CREATE TABLE IF NOT EXISTS task_assignments (
142
+ assignment_id TEXT PRIMARY KEY,
143
+ task_id TEXT NOT NULL,
144
+ territory_id TEXT NOT NULL,
145
+ worker_binding_id TEXT NOT NULL,
146
+ assigned_by TEXT NOT NULL,
147
+ assigned_at TEXT NOT NULL,
148
+ ended_at TEXT,
149
+ end_reason TEXT,
150
+ previous_assignment_id TEXT,
151
+ handoff_reason TEXT,
152
+ created_at TEXT NOT NULL
153
+ );
154
+
155
+ CREATE UNIQUE INDEX IF NOT EXISTS one_active_assignment_per_task
156
+ ON task_assignments(task_id) WHERE ended_at IS NULL;
157
+
158
+ CREATE INDEX IF NOT EXISTS task_assignments_task_idx ON task_assignments(task_id);
159
159
  `;
160
160
  export class KingdomStore {
161
161
  db;
@@ -335,13 +335,13 @@ export class KingdomStore {
335
335
  .prepare(`SELECT COUNT(*) AS n FROM role_bindings WHERE role_type = 'SUPERVISOR' AND status = 'ACTIVE'`)
336
336
  .get();
337
337
  if (supervisorCount.n === 1) {
338
- this.db.exec(`
339
- UPDATE territories
340
- SET supervisor_binding_id = (
341
- SELECT binding_id FROM role_bindings
342
- WHERE role_type = 'SUPERVISOR' AND status = 'ACTIVE' LIMIT 1
343
- )
344
- WHERE supervisor_binding_id IS NULL AND status = 'ACTIVE'
338
+ this.db.exec(`
339
+ UPDATE territories
340
+ SET supervisor_binding_id = (
341
+ SELECT binding_id FROM role_bindings
342
+ WHERE role_type = 'SUPERVISOR' AND status = 'ACTIVE' LIMIT 1
343
+ )
344
+ WHERE supervisor_binding_id IS NULL AND status = 'ACTIVE'
345
345
  `);
346
346
  }
347
347
  // verify:全部预期列/表/索引存在,缺失即失败(fail-loud)
@@ -386,351 +386,351 @@ export class KingdomStore {
386
386
  // 既有 v3 库开库瞬间就被建出 v4 表,ensureSchemaV4 的"单事务迁移 + 失败 ROLLBACK"
387
387
  // 语义失效。因此 v4 全部 DDL 只在 ensureSchemaV4 的 BEGIN IMMEDIATE 事务内落地。
388
388
  /** v4 Ledger 表(affinity/lease/decision)+ 各自索引与 trigger(不含 executions 重建与 dispatch)。 */
389
- static V4_LEDGER_SQL = `
390
- CREATE TABLE IF NOT EXISTS session_territory_affinities (
391
- affinity_id TEXT PRIMARY KEY,
392
- kingdom_id TEXT NOT NULL,
393
- worker_binding_id TEXT NOT NULL,
394
- runtime_type TEXT NOT NULL,
395
- runtime_instance_ref TEXT NOT NULL,
396
- session_ref TEXT NOT NULL,
397
- territory_id TEXT NOT NULL,
398
- established_at TEXT NOT NULL,
399
- retired_at TEXT,
400
- is_current INTEGER NOT NULL DEFAULT 1,
401
- created_at TEXT NOT NULL,
402
- UNIQUE (runtime_type, runtime_instance_ref, session_ref),
403
- CHECK (
404
- (is_current = 1 AND retired_at IS NULL)
405
- OR
406
- (is_current = 0 AND retired_at IS NOT NULL)
407
- )
408
- );
409
- CREATE UNIQUE INDEX IF NOT EXISTS affinity_one_current_per_worker
410
- ON session_territory_affinities(kingdom_id, worker_binding_id)
411
- WHERE is_current = 1;
412
- CREATE TRIGGER IF NOT EXISTS affinity_identity_immutable
413
- BEFORE UPDATE OF kingdom_id, worker_binding_id, runtime_type,
414
- runtime_instance_ref, session_ref, territory_id,
415
- established_at, created_at
416
- ON session_territory_affinities
417
- BEGIN SELECT RAISE(ABORT, 'AFFINITY_IDENTITY_IMMUTABLE'); END;
418
- CREATE TRIGGER IF NOT EXISTS affinity_retire
419
- BEFORE UPDATE OF is_current, retired_at
420
- ON session_territory_affinities
421
- BEGIN
422
- SELECT RAISE(ABORT, 'AFFINITY_RETIRE_ONLY_JOINT')
423
- WHERE NOT (
424
- OLD.is_current = 1 AND OLD.retired_at IS NULL
425
- AND NEW.is_current = 0 AND NEW.retired_at IS NOT NULL
426
- );
427
- SELECT RAISE(ABORT, 'AFFINITY_ALREADY_RETIRED')
428
- WHERE OLD.retired_at IS NOT NULL;
429
- END;
430
- CREATE TRIGGER IF NOT EXISTS affinity_no_delete
431
- BEFORE DELETE ON session_territory_affinities
432
- BEGIN SELECT RAISE(ABORT, 'AFFINITY_NO_DELETE'); END;
433
-
434
- CREATE TABLE IF NOT EXISTS execution_leases (
435
- lease_id TEXT PRIMARY KEY,
436
- kingdom_id TEXT NOT NULL,
437
- worker_binding_id TEXT NOT NULL,
438
- runtime_type TEXT NOT NULL,
439
- runtime_instance_ref TEXT NOT NULL,
440
- session_ref TEXT NOT NULL,
441
- territory_id TEXT NOT NULL,
442
- task_id TEXT NOT NULL,
443
- attempt_no INTEGER NOT NULL,
444
- state TEXT NOT NULL CHECK(state IN
445
- ('ACQUIRED','PREPARING','MATERIALIZING','DISPATCH_READY',
446
- 'EXECUTING','SETTLING','RELEASING','RECOVERING','RELEASED')),
447
- capability_decision_id TEXT,
448
- enforcement_plan_snapshot TEXT,
449
- release_evidence_json TEXT,
450
- release_reason TEXT,
451
- acquired_at TEXT NOT NULL,
452
- released_at TEXT,
453
- updated_at TEXT NOT NULL,
454
- UNIQUE (task_id, attempt_no),
455
- UNIQUE (runtime_type, runtime_instance_ref, session_ref, task_id, attempt_no)
456
- );
457
- CREATE UNIQUE INDEX IF NOT EXISTS lease_one_active_per_session
458
- ON execution_leases(runtime_type, runtime_instance_ref, session_ref)
459
- WHERE state <> 'RELEASED';
460
- CREATE TRIGGER IF NOT EXISTS lease_requires_matching_affinity
461
- BEFORE INSERT ON execution_leases
462
- BEGIN
463
- SELECT RAISE(ABORT, 'LEASE_REQUIRES_MATCHING_CURRENT_AFFINITY')
464
- WHERE NOT EXISTS (
465
- SELECT 1
466
- FROM session_territory_affinities a
467
- JOIN tasks t ON t.task_id = NEW.task_id
468
- WHERE a.kingdom_id = NEW.kingdom_id
469
- AND a.worker_binding_id = NEW.worker_binding_id
470
- AND a.runtime_type = NEW.runtime_type
471
- AND a.runtime_instance_ref = NEW.runtime_instance_ref
472
- AND a.session_ref = NEW.session_ref
473
- AND a.is_current = 1 AND a.retired_at IS NULL
474
- AND a.territory_id = NEW.territory_id
475
- AND t.territory_id = NEW.territory_id
476
- );
477
- END;
478
- CREATE TRIGGER IF NOT EXISTS lease_identity_immutable
479
- BEFORE UPDATE OF kingdom_id, worker_binding_id, runtime_type, runtime_instance_ref,
480
- session_ref, territory_id, task_id, attempt_no, acquired_at
481
- ON execution_leases
482
- BEGIN SELECT RAISE(ABORT, 'LEASE_IDENTITY_IMMUTABLE'); END;
483
- CREATE TRIGGER IF NOT EXISTS lease_plan_once
484
- BEFORE UPDATE OF enforcement_plan_snapshot
485
- ON execution_leases
486
- WHEN OLD.enforcement_plan_snapshot IS NOT NULL
487
- BEGIN SELECT RAISE(ABORT, 'LEASE_PLAN_ALREADY_SET'); END;
488
- CREATE TRIGGER IF NOT EXISTS lease_decision_once
489
- BEFORE UPDATE OF capability_decision_id
490
- ON execution_leases
491
- WHEN OLD.capability_decision_id IS NOT NULL
492
- BEGIN SELECT RAISE(ABORT, 'LEASE_DECISION_ALREADY_BOUND'); END;
493
- CREATE TRIGGER IF NOT EXISTS lease_release_evidence_once
494
- BEFORE UPDATE OF release_evidence_json, release_reason
495
- ON execution_leases
496
- WHEN OLD.release_evidence_json IS NOT NULL
497
- BEGIN SELECT RAISE(ABORT, 'LEASE_RELEASE_EVIDENCE_ALREADY_SET'); END;
498
- CREATE TRIGGER IF NOT EXISTS lease_state_guard
499
- BEFORE UPDATE OF state ON execution_leases
500
- WHEN NEW.state <> OLD.state
501
- BEGIN
502
- SELECT RAISE(ABORT, 'ILLEGAL_LEASE_TRANSITION') WHERE NOT (
503
- (OLD.state='ACQUIRED' AND NEW.state IN ('PREPARING','RECOVERING'))
504
- OR (OLD.state='PREPARING' AND NEW.state IN ('MATERIALIZING','RECOVERING'))
505
- OR (OLD.state='MATERIALIZING' AND NEW.state IN ('DISPATCH_READY','RECOVERING','RELEASED'))
506
- OR (OLD.state='DISPATCH_READY' AND NEW.state IN ('EXECUTING','RECOVERING'))
507
- OR (OLD.state='EXECUTING' AND NEW.state IN ('SETTLING','RECOVERING'))
508
- OR (OLD.state='SETTLING' AND NEW.state IN ('RELEASING','RECOVERING'))
509
- OR (OLD.state='RELEASING' AND NEW.state IN ('RELEASED','RECOVERING'))
510
- OR (OLD.state='RECOVERING' AND NEW.state IN ('RELEASED','RECOVERING'))
511
- );
512
- SELECT RAISE(ABORT, 'LEASE_MATERIALIZING_REQUIRES_PLAN')
513
- WHERE NEW.state='MATERIALIZING' AND (NEW.enforcement_plan_snapshot IS NULL OR json_valid(NEW.enforcement_plan_snapshot)=0);
514
- SELECT RAISE(ABORT, 'LEASE_DISPATCH_READY_REQUIRES_DECISION')
515
- WHERE NEW.state='DISPATCH_READY' AND NOT EXISTS (
516
- SELECT 1 FROM capability_decisions d
517
- WHERE d.decision_id = NEW.capability_decision_id
518
- AND d.decision='GRANTED' AND d.enforcement_status='ENFORCED'
519
- AND d.enforcement_evidence_json IS NOT NULL
520
- );
521
- SELECT RAISE(ABORT, 'LEASE_RELEASED_REQUIRES_EVIDENCE')
522
- WHERE NEW.state='RELEASED' AND (NEW.release_evidence_json IS NULL OR NEW.released_at IS NULL);
523
- END;
524
- CREATE TRIGGER IF NOT EXISTS lease_insert_state_guard
525
- BEFORE INSERT ON execution_leases
526
- WHEN NEW.state <> 'ACQUIRED'
527
- BEGIN SELECT RAISE(ABORT, 'LEASE_INSERT_MUST_BE_ACQUIRED'); END;
528
- CREATE TRIGGER IF NOT EXISTS lease_no_delete
529
- BEFORE DELETE ON execution_leases
530
- BEGIN SELECT RAISE(ABORT, 'LEASE_NO_DELETE'); END;
531
-
532
- CREATE TABLE IF NOT EXISTS capability_decisions (
533
- decision_id TEXT PRIMARY KEY,
534
- kingdom_id TEXT NOT NULL,
535
- task_id TEXT NOT NULL,
536
- worker_binding_id TEXT,
537
- supervisor_binding_id TEXT,
538
- requirement_snapshot TEXT,
539
- ceiling_snapshot TEXT,
540
- proposed_grant_snapshot TEXT,
541
- scope_snapshot TEXT,
542
- effective_snapshot TEXT,
543
- decision TEXT NOT NULL,
544
- enforcement_status TEXT NOT NULL,
545
- enforcement_evidence_json TEXT,
546
- requirement_coverage TEXT NOT NULL DEFAULT 'NONE'
547
- CHECK(requirement_coverage IN ('FULL','PARTIAL','NONE')),
548
- reason_code TEXT,
549
- execution_id TEXT,
550
- created_at TEXT NOT NULL,
551
- CHECK (
552
- (decision = 'GRANTED' AND enforcement_status = 'ENFORCED')
553
- OR
554
- (decision = 'DENIED' AND enforcement_status IN
555
- ('NOT_ATTEMPTED','UNAVAILABLE','FAILED'))
556
- ),
557
- CHECK (decision <> 'GRANTED' OR
558
- (enforcement_evidence_json IS NOT NULL AND json_valid(enforcement_evidence_json)=1))
559
- );
560
- CREATE UNIQUE INDEX IF NOT EXISTS capability_decision_execution_uk
561
- ON capability_decisions(execution_id) WHERE execution_id IS NOT NULL;
562
- CREATE TRIGGER IF NOT EXISTS capability_decision_immutable
563
- BEFORE UPDATE OF kingdom_id, task_id, worker_binding_id, supervisor_binding_id,
564
- requirement_snapshot, ceiling_snapshot, proposed_grant_snapshot,
565
- scope_snapshot, effective_snapshot, decision, enforcement_status,
566
- enforcement_evidence_json, requirement_coverage, reason_code, created_at
567
- ON capability_decisions
568
- BEGIN SELECT RAISE(ABORT, 'CAPABILITY_DECISION_IMMUTABLE'); END;
569
- CREATE TRIGGER IF NOT EXISTS capability_decision_execution_bind
570
- BEFORE UPDATE OF execution_id
571
- ON capability_decisions
572
- WHEN NEW.execution_id IS NOT NULL
573
- BEGIN
574
- SELECT RAISE(ABORT, 'CAPABILITY_DECISION_EXECUTION_ALREADY_BOUND')
575
- WHERE OLD.execution_id IS NOT NULL;
576
- SELECT RAISE(ABORT, 'CAPABILITY_DECISION_BIND_REQUIRES_GRANTED')
577
- WHERE NOT (OLD.decision='GRANTED' AND OLD.enforcement_status='ENFORCED');
578
- END;
579
- CREATE TRIGGER IF NOT EXISTS capability_decision_no_delete
580
- BEFORE DELETE ON capability_decisions
581
- BEGIN SELECT RAISE(ABORT, 'CAPABILITY_DECISION_NO_DELETE'); END;
389
+ static V4_LEDGER_SQL = `
390
+ CREATE TABLE IF NOT EXISTS session_territory_affinities (
391
+ affinity_id TEXT PRIMARY KEY,
392
+ kingdom_id TEXT NOT NULL,
393
+ worker_binding_id TEXT NOT NULL,
394
+ runtime_type TEXT NOT NULL,
395
+ runtime_instance_ref TEXT NOT NULL,
396
+ session_ref TEXT NOT NULL,
397
+ territory_id TEXT NOT NULL,
398
+ established_at TEXT NOT NULL,
399
+ retired_at TEXT,
400
+ is_current INTEGER NOT NULL DEFAULT 1,
401
+ created_at TEXT NOT NULL,
402
+ UNIQUE (runtime_type, runtime_instance_ref, session_ref),
403
+ CHECK (
404
+ (is_current = 1 AND retired_at IS NULL)
405
+ OR
406
+ (is_current = 0 AND retired_at IS NOT NULL)
407
+ )
408
+ );
409
+ CREATE UNIQUE INDEX IF NOT EXISTS affinity_one_current_per_worker
410
+ ON session_territory_affinities(kingdom_id, worker_binding_id)
411
+ WHERE is_current = 1;
412
+ CREATE TRIGGER IF NOT EXISTS affinity_identity_immutable
413
+ BEFORE UPDATE OF kingdom_id, worker_binding_id, runtime_type,
414
+ runtime_instance_ref, session_ref, territory_id,
415
+ established_at, created_at
416
+ ON session_territory_affinities
417
+ BEGIN SELECT RAISE(ABORT, 'AFFINITY_IDENTITY_IMMUTABLE'); END;
418
+ CREATE TRIGGER IF NOT EXISTS affinity_retire
419
+ BEFORE UPDATE OF is_current, retired_at
420
+ ON session_territory_affinities
421
+ BEGIN
422
+ SELECT RAISE(ABORT, 'AFFINITY_RETIRE_ONLY_JOINT')
423
+ WHERE NOT (
424
+ OLD.is_current = 1 AND OLD.retired_at IS NULL
425
+ AND NEW.is_current = 0 AND NEW.retired_at IS NOT NULL
426
+ );
427
+ SELECT RAISE(ABORT, 'AFFINITY_ALREADY_RETIRED')
428
+ WHERE OLD.retired_at IS NOT NULL;
429
+ END;
430
+ CREATE TRIGGER IF NOT EXISTS affinity_no_delete
431
+ BEFORE DELETE ON session_territory_affinities
432
+ BEGIN SELECT RAISE(ABORT, 'AFFINITY_NO_DELETE'); END;
433
+
434
+ CREATE TABLE IF NOT EXISTS execution_leases (
435
+ lease_id TEXT PRIMARY KEY,
436
+ kingdom_id TEXT NOT NULL,
437
+ worker_binding_id TEXT NOT NULL,
438
+ runtime_type TEXT NOT NULL,
439
+ runtime_instance_ref TEXT NOT NULL,
440
+ session_ref TEXT NOT NULL,
441
+ territory_id TEXT NOT NULL,
442
+ task_id TEXT NOT NULL,
443
+ attempt_no INTEGER NOT NULL,
444
+ state TEXT NOT NULL CHECK(state IN
445
+ ('ACQUIRED','PREPARING','MATERIALIZING','DISPATCH_READY',
446
+ 'EXECUTING','SETTLING','RELEASING','RECOVERING','RELEASED')),
447
+ capability_decision_id TEXT,
448
+ enforcement_plan_snapshot TEXT,
449
+ release_evidence_json TEXT,
450
+ release_reason TEXT,
451
+ acquired_at TEXT NOT NULL,
452
+ released_at TEXT,
453
+ updated_at TEXT NOT NULL,
454
+ UNIQUE (task_id, attempt_no),
455
+ UNIQUE (runtime_type, runtime_instance_ref, session_ref, task_id, attempt_no)
456
+ );
457
+ CREATE UNIQUE INDEX IF NOT EXISTS lease_one_active_per_session
458
+ ON execution_leases(runtime_type, runtime_instance_ref, session_ref)
459
+ WHERE state <> 'RELEASED';
460
+ CREATE TRIGGER IF NOT EXISTS lease_requires_matching_affinity
461
+ BEFORE INSERT ON execution_leases
462
+ BEGIN
463
+ SELECT RAISE(ABORT, 'LEASE_REQUIRES_MATCHING_CURRENT_AFFINITY')
464
+ WHERE NOT EXISTS (
465
+ SELECT 1
466
+ FROM session_territory_affinities a
467
+ JOIN tasks t ON t.task_id = NEW.task_id
468
+ WHERE a.kingdom_id = NEW.kingdom_id
469
+ AND a.worker_binding_id = NEW.worker_binding_id
470
+ AND a.runtime_type = NEW.runtime_type
471
+ AND a.runtime_instance_ref = NEW.runtime_instance_ref
472
+ AND a.session_ref = NEW.session_ref
473
+ AND a.is_current = 1 AND a.retired_at IS NULL
474
+ AND a.territory_id = NEW.territory_id
475
+ AND t.territory_id = NEW.territory_id
476
+ );
477
+ END;
478
+ CREATE TRIGGER IF NOT EXISTS lease_identity_immutable
479
+ BEFORE UPDATE OF kingdom_id, worker_binding_id, runtime_type, runtime_instance_ref,
480
+ session_ref, territory_id, task_id, attempt_no, acquired_at
481
+ ON execution_leases
482
+ BEGIN SELECT RAISE(ABORT, 'LEASE_IDENTITY_IMMUTABLE'); END;
483
+ CREATE TRIGGER IF NOT EXISTS lease_plan_once
484
+ BEFORE UPDATE OF enforcement_plan_snapshot
485
+ ON execution_leases
486
+ WHEN OLD.enforcement_plan_snapshot IS NOT NULL
487
+ BEGIN SELECT RAISE(ABORT, 'LEASE_PLAN_ALREADY_SET'); END;
488
+ CREATE TRIGGER IF NOT EXISTS lease_decision_once
489
+ BEFORE UPDATE OF capability_decision_id
490
+ ON execution_leases
491
+ WHEN OLD.capability_decision_id IS NOT NULL
492
+ BEGIN SELECT RAISE(ABORT, 'LEASE_DECISION_ALREADY_BOUND'); END;
493
+ CREATE TRIGGER IF NOT EXISTS lease_release_evidence_once
494
+ BEFORE UPDATE OF release_evidence_json, release_reason
495
+ ON execution_leases
496
+ WHEN OLD.release_evidence_json IS NOT NULL
497
+ BEGIN SELECT RAISE(ABORT, 'LEASE_RELEASE_EVIDENCE_ALREADY_SET'); END;
498
+ CREATE TRIGGER IF NOT EXISTS lease_state_guard
499
+ BEFORE UPDATE OF state ON execution_leases
500
+ WHEN NEW.state <> OLD.state
501
+ BEGIN
502
+ SELECT RAISE(ABORT, 'ILLEGAL_LEASE_TRANSITION') WHERE NOT (
503
+ (OLD.state='ACQUIRED' AND NEW.state IN ('PREPARING','RECOVERING'))
504
+ OR (OLD.state='PREPARING' AND NEW.state IN ('MATERIALIZING','RECOVERING'))
505
+ OR (OLD.state='MATERIALIZING' AND NEW.state IN ('DISPATCH_READY','RECOVERING','RELEASED'))
506
+ OR (OLD.state='DISPATCH_READY' AND NEW.state IN ('EXECUTING','RECOVERING'))
507
+ OR (OLD.state='EXECUTING' AND NEW.state IN ('SETTLING','RECOVERING'))
508
+ OR (OLD.state='SETTLING' AND NEW.state IN ('RELEASING','RECOVERING'))
509
+ OR (OLD.state='RELEASING' AND NEW.state IN ('RELEASED','RECOVERING'))
510
+ OR (OLD.state='RECOVERING' AND NEW.state IN ('RELEASED','RECOVERING'))
511
+ );
512
+ SELECT RAISE(ABORT, 'LEASE_MATERIALIZING_REQUIRES_PLAN')
513
+ WHERE NEW.state='MATERIALIZING' AND (NEW.enforcement_plan_snapshot IS NULL OR json_valid(NEW.enforcement_plan_snapshot)=0);
514
+ SELECT RAISE(ABORT, 'LEASE_DISPATCH_READY_REQUIRES_DECISION')
515
+ WHERE NEW.state='DISPATCH_READY' AND NOT EXISTS (
516
+ SELECT 1 FROM capability_decisions d
517
+ WHERE d.decision_id = NEW.capability_decision_id
518
+ AND d.decision='GRANTED' AND d.enforcement_status='ENFORCED'
519
+ AND d.enforcement_evidence_json IS NOT NULL
520
+ );
521
+ SELECT RAISE(ABORT, 'LEASE_RELEASED_REQUIRES_EVIDENCE')
522
+ WHERE NEW.state='RELEASED' AND (NEW.release_evidence_json IS NULL OR NEW.released_at IS NULL);
523
+ END;
524
+ CREATE TRIGGER IF NOT EXISTS lease_insert_state_guard
525
+ BEFORE INSERT ON execution_leases
526
+ WHEN NEW.state <> 'ACQUIRED'
527
+ BEGIN SELECT RAISE(ABORT, 'LEASE_INSERT_MUST_BE_ACQUIRED'); END;
528
+ CREATE TRIGGER IF NOT EXISTS lease_no_delete
529
+ BEFORE DELETE ON execution_leases
530
+ BEGIN SELECT RAISE(ABORT, 'LEASE_NO_DELETE'); END;
531
+
532
+ CREATE TABLE IF NOT EXISTS capability_decisions (
533
+ decision_id TEXT PRIMARY KEY,
534
+ kingdom_id TEXT NOT NULL,
535
+ task_id TEXT NOT NULL,
536
+ worker_binding_id TEXT,
537
+ supervisor_binding_id TEXT,
538
+ requirement_snapshot TEXT,
539
+ ceiling_snapshot TEXT,
540
+ proposed_grant_snapshot TEXT,
541
+ scope_snapshot TEXT,
542
+ effective_snapshot TEXT,
543
+ decision TEXT NOT NULL,
544
+ enforcement_status TEXT NOT NULL,
545
+ enforcement_evidence_json TEXT,
546
+ requirement_coverage TEXT NOT NULL DEFAULT 'NONE'
547
+ CHECK(requirement_coverage IN ('FULL','PARTIAL','NONE')),
548
+ reason_code TEXT,
549
+ execution_id TEXT,
550
+ created_at TEXT NOT NULL,
551
+ CHECK (
552
+ (decision = 'GRANTED' AND enforcement_status = 'ENFORCED')
553
+ OR
554
+ (decision = 'DENIED' AND enforcement_status IN
555
+ ('NOT_ATTEMPTED','UNAVAILABLE','FAILED'))
556
+ ),
557
+ CHECK (decision <> 'GRANTED' OR
558
+ (enforcement_evidence_json IS NOT NULL AND json_valid(enforcement_evidence_json)=1))
559
+ );
560
+ CREATE UNIQUE INDEX IF NOT EXISTS capability_decision_execution_uk
561
+ ON capability_decisions(execution_id) WHERE execution_id IS NOT NULL;
562
+ CREATE TRIGGER IF NOT EXISTS capability_decision_immutable
563
+ BEFORE UPDATE OF kingdom_id, task_id, worker_binding_id, supervisor_binding_id,
564
+ requirement_snapshot, ceiling_snapshot, proposed_grant_snapshot,
565
+ scope_snapshot, effective_snapshot, decision, enforcement_status,
566
+ enforcement_evidence_json, requirement_coverage, reason_code, created_at
567
+ ON capability_decisions
568
+ BEGIN SELECT RAISE(ABORT, 'CAPABILITY_DECISION_IMMUTABLE'); END;
569
+ CREATE TRIGGER IF NOT EXISTS capability_decision_execution_bind
570
+ BEFORE UPDATE OF execution_id
571
+ ON capability_decisions
572
+ WHEN NEW.execution_id IS NOT NULL
573
+ BEGIN
574
+ SELECT RAISE(ABORT, 'CAPABILITY_DECISION_EXECUTION_ALREADY_BOUND')
575
+ WHERE OLD.execution_id IS NOT NULL;
576
+ SELECT RAISE(ABORT, 'CAPABILITY_DECISION_BIND_REQUIRES_GRANTED')
577
+ WHERE NOT (OLD.decision='GRANTED' AND OLD.enforcement_status='ENFORCED');
578
+ END;
579
+ CREATE TRIGGER IF NOT EXISTS capability_decision_no_delete
580
+ BEFORE DELETE ON capability_decisions
581
+ BEGIN SELECT RAISE(ABORT, 'CAPABILITY_DECISION_NO_DELETE'); END;
582
582
  `;
583
583
  /**
584
584
  * executions 重建形态(暂存名 executions_v4,迁移后 RENAME 回 executions)。
585
585
  * = 旧 executions 全部列(含 v2 证据列)+ 新增 3 列(v6 §8.1)。
586
586
  */
587
- static V4_EXECUTIONS_STAGING_SQL = `
588
- CREATE TABLE IF NOT EXISTS executions_v4 (
589
- execution_id TEXT PRIMARY KEY,
590
- task_id TEXT NOT NULL,
591
- attempt_no INTEGER NOT NULL,
592
- worker_binding_id TEXT,
593
- session_id TEXT,
594
- state TEXT NOT NULL CHECK(state IN
595
- ('STARTING','RUNNING','PAUSED','RECOVERING','COMPLETED','FAILED','ABORTED')),
596
- detail TEXT,
597
- started_at TEXT NOT NULL,
598
- heartbeat_at TEXT,
599
- ended_at TEXT,
600
- pause_requested_at TEXT,
601
- executor_kind TEXT,
602
- provider TEXT,
603
- provider_source TEXT,
604
- requested_model TEXT,
605
- resolved_model TEXT,
606
- model_source TEXT,
607
- execution_profile_json TEXT,
608
- execution_contract TEXT NOT NULL DEFAULT 'LEGACY_COMPAT'
609
- CHECK(execution_contract IN ('LEGACY_COMPAT','GOVERNED_PERSISTENT')),
610
- lease_id TEXT,
611
- capability_decision_id TEXT,
612
- UNIQUE (task_id, attempt_no),
613
- CHECK (
614
- execution_contract = 'LEGACY_COMPAT'
615
- OR (
616
- execution_contract = 'GOVERNED_PERSISTENT'
617
- AND lease_id IS NOT NULL
618
- AND capability_decision_id IS NOT NULL
619
- )
620
- ),
621
- FOREIGN KEY (lease_id) REFERENCES execution_leases(lease_id),
622
- FOREIGN KEY (capability_decision_id) REFERENCES capability_decisions(decision_id)
623
- );
587
+ static V4_EXECUTIONS_STAGING_SQL = `
588
+ CREATE TABLE IF NOT EXISTS executions_v4 (
589
+ execution_id TEXT PRIMARY KEY,
590
+ task_id TEXT NOT NULL,
591
+ attempt_no INTEGER NOT NULL,
592
+ worker_binding_id TEXT,
593
+ session_id TEXT,
594
+ state TEXT NOT NULL CHECK(state IN
595
+ ('STARTING','RUNNING','PAUSED','RECOVERING','COMPLETED','FAILED','ABORTED')),
596
+ detail TEXT,
597
+ started_at TEXT NOT NULL,
598
+ heartbeat_at TEXT,
599
+ ended_at TEXT,
600
+ pause_requested_at TEXT,
601
+ executor_kind TEXT,
602
+ provider TEXT,
603
+ provider_source TEXT,
604
+ requested_model TEXT,
605
+ resolved_model TEXT,
606
+ model_source TEXT,
607
+ execution_profile_json TEXT,
608
+ execution_contract TEXT NOT NULL DEFAULT 'LEGACY_COMPAT'
609
+ CHECK(execution_contract IN ('LEGACY_COMPAT','GOVERNED_PERSISTENT')),
610
+ lease_id TEXT,
611
+ capability_decision_id TEXT,
612
+ UNIQUE (task_id, attempt_no),
613
+ CHECK (
614
+ execution_contract = 'LEGACY_COMPAT'
615
+ OR (
616
+ execution_contract = 'GOVERNED_PERSISTENT'
617
+ AND lease_id IS NOT NULL
618
+ AND capability_decision_id IS NOT NULL
619
+ )
620
+ ),
621
+ FOREIGN KEY (lease_id) REFERENCES execution_leases(lease_id),
622
+ FOREIGN KEY (capability_decision_id) REFERENCES capability_decisions(decision_id)
623
+ );
624
624
  `;
625
625
  /** executions 重建后的 trigger(RENAME 回 executions 之后创建;不依赖 RENAME 自动改写)。 */
626
- static V4_EXECUTIONS_TRIGGERS_SQL = `
627
- CREATE TRIGGER IF NOT EXISTS execution_governed_consistency
628
- BEFORE INSERT ON executions
629
- WHEN NEW.execution_contract = 'GOVERNED_PERSISTENT'
630
- BEGIN
631
- SELECT RAISE(ABORT, 'EXECUTION_GOVERNED_REQUIRES_MATCHING_LEASE')
632
- WHERE NOT EXISTS (
633
- SELECT 1
634
- FROM execution_leases l
635
- JOIN capability_decisions d ON d.decision_id = NEW.capability_decision_id
636
- WHERE l.lease_id = NEW.lease_id
637
- AND l.task_id = NEW.task_id
638
- AND l.attempt_no = NEW.attempt_no
639
- AND l.capability_decision_id = NEW.capability_decision_id
640
- AND l.kingdom_id = d.kingdom_id
641
- AND d.task_id = NEW.task_id
642
- AND d.decision='GRANTED' AND d.enforcement_status='ENFORCED'
643
- AND (d.execution_id IS NULL OR d.execution_id = NEW.execution_id)
644
- );
645
- END;
646
- CREATE TRIGGER IF NOT EXISTS execution_contract_immutable
647
- BEFORE UPDATE OF execution_contract ON executions
648
- BEGIN SELECT RAISE(ABORT, 'EXECUTION_CONTRACT_IMMUTABLE'); END;
626
+ static V4_EXECUTIONS_TRIGGERS_SQL = `
627
+ CREATE TRIGGER IF NOT EXISTS execution_governed_consistency
628
+ BEFORE INSERT ON executions
629
+ WHEN NEW.execution_contract = 'GOVERNED_PERSISTENT'
630
+ BEGIN
631
+ SELECT RAISE(ABORT, 'EXECUTION_GOVERNED_REQUIRES_MATCHING_LEASE')
632
+ WHERE NOT EXISTS (
633
+ SELECT 1
634
+ FROM execution_leases l
635
+ JOIN capability_decisions d ON d.decision_id = NEW.capability_decision_id
636
+ WHERE l.lease_id = NEW.lease_id
637
+ AND l.task_id = NEW.task_id
638
+ AND l.attempt_no = NEW.attempt_no
639
+ AND l.capability_decision_id = NEW.capability_decision_id
640
+ AND l.kingdom_id = d.kingdom_id
641
+ AND d.task_id = NEW.task_id
642
+ AND d.decision='GRANTED' AND d.enforcement_status='ENFORCED'
643
+ AND (d.execution_id IS NULL OR d.execution_id = NEW.execution_id)
644
+ );
645
+ END;
646
+ CREATE TRIGGER IF NOT EXISTS execution_contract_immutable
647
+ BEFORE UPDATE OF execution_contract ON executions
648
+ BEGIN SELECT RAISE(ABORT, 'EXECUTION_CONTRACT_IMMUTABLE'); END;
649
649
  `;
650
650
  /** Dispatch Ledger(executions 重建完成后创建;FK 直接指向最终名 executions)。 */
651
- static V4_DISPATCH_SQL = `
652
- CREATE TABLE IF NOT EXISTS dispatch_records (
653
- dispatch_id TEXT PRIMARY KEY,
654
- kingdom_id TEXT NOT NULL,
655
- lease_id TEXT NOT NULL,
656
- execution_id TEXT NOT NULL,
657
- task_id TEXT NOT NULL,
658
- attempt_no INTEGER NOT NULL,
659
- runtime_type TEXT NOT NULL,
660
- runtime_instance_ref TEXT NOT NULL,
661
- session_ref TEXT NOT NULL,
662
- state TEXT NOT NULL CHECK(state IN
663
- ('INTENDED','DISPATCHED','RECEIVED','CORRELATED','TERMINAL','FAILED','RECOVERING')),
664
- dispatch_request_snapshot TEXT NOT NULL,
665
- dispatch_input_ref_json TEXT NOT NULL,
666
- dispatch_payload_hash TEXT NOT NULL,
667
- runtime_dispatch_ref TEXT,
668
- runtime_execution_ref TEXT,
669
- receipt_json TEXT,
670
- terminal_evidence_json TEXT,
671
- output_ref_json TEXT,
672
- dispatched_at TEXT,
673
- receipt_at TEXT,
674
- terminal_at TEXT,
675
- created_at TEXT NOT NULL,
676
- updated_at TEXT NOT NULL,
677
- FOREIGN KEY (lease_id) REFERENCES execution_leases(lease_id),
678
- FOREIGN KEY (execution_id) REFERENCES executions(execution_id),
679
- UNIQUE (lease_id),
680
- UNIQUE (execution_id)
681
- );
682
- CREATE TRIGGER IF NOT EXISTS dispatch_request_immutable
683
- BEFORE UPDATE OF dispatch_id, kingdom_id, lease_id, execution_id, task_id, attempt_no,
684
- runtime_type, runtime_instance_ref, session_ref,
685
- dispatch_request_snapshot, dispatch_input_ref_json, dispatch_payload_hash,
686
- created_at
687
- ON dispatch_records
688
- BEGIN SELECT RAISE(ABORT, 'DISPATCH_REQUEST_IMMUTABLE'); END;
689
- CREATE TRIGGER IF NOT EXISTS dispatch_requires_ready_lease
690
- BEFORE INSERT ON dispatch_records
691
- BEGIN
692
- SELECT RAISE(ABORT, 'DISPATCH_REQUIRES_MATCHING_READY_LEASE')
693
- WHERE NOT EXISTS (
694
- SELECT 1
695
- FROM execution_leases l
696
- JOIN executions e ON e.execution_id = NEW.execution_id
697
- WHERE l.lease_id = NEW.lease_id
698
- AND l.state = 'DISPATCH_READY'
699
- AND l.kingdom_id = NEW.kingdom_id
700
- AND l.task_id = NEW.task_id
701
- AND l.attempt_no = NEW.attempt_no
702
- AND l.runtime_type = NEW.runtime_type
703
- AND l.runtime_instance_ref = NEW.runtime_instance_ref
704
- AND l.session_ref = NEW.session_ref
705
- AND e.task_id = NEW.task_id
706
- AND e.attempt_no = NEW.attempt_no
707
- );
708
- END;
709
- CREATE TRIGGER IF NOT EXISTS dispatch_state_guard
710
- BEFORE UPDATE OF state ON dispatch_records
711
- WHEN NEW.state <> OLD.state
712
- BEGIN
713
- SELECT RAISE(ABORT, 'ILLEGAL_DISPATCH_TRANSITION') WHERE NOT (
714
- (OLD.state='INTENDED' AND NEW.state IN ('DISPATCHED','FAILED','RECOVERING'))
715
- OR (OLD.state='DISPATCHED' AND NEW.state IN ('RECEIVED','RECOVERING'))
716
- OR (OLD.state='RECEIVED' AND NEW.state IN ('CORRELATED','RECOVERING'))
717
- OR (OLD.state='CORRELATED' AND NEW.state IN ('TERMINAL','RECOVERING'))
718
- OR (OLD.state='RECOVERING' AND NEW.state IN ('TERMINAL','FAILED','RECOVERING'))
719
- );
720
- SELECT RAISE(ABORT, 'DISPATCH_RECEIVED_REQUIRES_REF_RECEIPT')
721
- WHERE NEW.state='RECEIVED' AND (NEW.runtime_dispatch_ref IS NULL OR NEW.receipt_json IS NULL OR NEW.receipt_at IS NULL);
722
- SELECT RAISE(ABORT, 'DISPATCH_CORRELATED_REQUIRES_EXEC_REF')
723
- WHERE NEW.state='CORRELATED' AND NEW.runtime_execution_ref IS NULL;
724
- SELECT RAISE(ABORT, 'DISPATCH_TERMINAL_REQUIRES_EVIDENCE')
725
- WHERE NEW.state='TERMINAL' AND (NEW.terminal_evidence_json IS NULL OR NEW.terminal_at IS NULL);
726
- END;
727
- CREATE TRIGGER IF NOT EXISTS dispatch_insert_state_guard
728
- BEFORE INSERT ON dispatch_records
729
- WHEN NEW.state <> 'INTENDED'
730
- BEGIN SELECT RAISE(ABORT, 'DISPATCH_INSERT_MUST_BE_INTENDED'); END;
731
- CREATE TRIGGER IF NOT EXISTS dispatch_no_delete
732
- BEFORE DELETE ON dispatch_records
733
- BEGIN SELECT RAISE(ABORT, 'DISPATCH_NO_DELETE'); END;
651
+ static V4_DISPATCH_SQL = `
652
+ CREATE TABLE IF NOT EXISTS dispatch_records (
653
+ dispatch_id TEXT PRIMARY KEY,
654
+ kingdom_id TEXT NOT NULL,
655
+ lease_id TEXT NOT NULL,
656
+ execution_id TEXT NOT NULL,
657
+ task_id TEXT NOT NULL,
658
+ attempt_no INTEGER NOT NULL,
659
+ runtime_type TEXT NOT NULL,
660
+ runtime_instance_ref TEXT NOT NULL,
661
+ session_ref TEXT NOT NULL,
662
+ state TEXT NOT NULL CHECK(state IN
663
+ ('INTENDED','DISPATCHED','RECEIVED','CORRELATED','TERMINAL','FAILED','RECOVERING')),
664
+ dispatch_request_snapshot TEXT NOT NULL,
665
+ dispatch_input_ref_json TEXT NOT NULL,
666
+ dispatch_payload_hash TEXT NOT NULL,
667
+ runtime_dispatch_ref TEXT,
668
+ runtime_execution_ref TEXT,
669
+ receipt_json TEXT,
670
+ terminal_evidence_json TEXT,
671
+ output_ref_json TEXT,
672
+ dispatched_at TEXT,
673
+ receipt_at TEXT,
674
+ terminal_at TEXT,
675
+ created_at TEXT NOT NULL,
676
+ updated_at TEXT NOT NULL,
677
+ FOREIGN KEY (lease_id) REFERENCES execution_leases(lease_id),
678
+ FOREIGN KEY (execution_id) REFERENCES executions(execution_id),
679
+ UNIQUE (lease_id),
680
+ UNIQUE (execution_id)
681
+ );
682
+ CREATE TRIGGER IF NOT EXISTS dispatch_request_immutable
683
+ BEFORE UPDATE OF dispatch_id, kingdom_id, lease_id, execution_id, task_id, attempt_no,
684
+ runtime_type, runtime_instance_ref, session_ref,
685
+ dispatch_request_snapshot, dispatch_input_ref_json, dispatch_payload_hash,
686
+ created_at
687
+ ON dispatch_records
688
+ BEGIN SELECT RAISE(ABORT, 'DISPATCH_REQUEST_IMMUTABLE'); END;
689
+ CREATE TRIGGER IF NOT EXISTS dispatch_requires_ready_lease
690
+ BEFORE INSERT ON dispatch_records
691
+ BEGIN
692
+ SELECT RAISE(ABORT, 'DISPATCH_REQUIRES_MATCHING_READY_LEASE')
693
+ WHERE NOT EXISTS (
694
+ SELECT 1
695
+ FROM execution_leases l
696
+ JOIN executions e ON e.execution_id = NEW.execution_id
697
+ WHERE l.lease_id = NEW.lease_id
698
+ AND l.state = 'DISPATCH_READY'
699
+ AND l.kingdom_id = NEW.kingdom_id
700
+ AND l.task_id = NEW.task_id
701
+ AND l.attempt_no = NEW.attempt_no
702
+ AND l.runtime_type = NEW.runtime_type
703
+ AND l.runtime_instance_ref = NEW.runtime_instance_ref
704
+ AND l.session_ref = NEW.session_ref
705
+ AND e.task_id = NEW.task_id
706
+ AND e.attempt_no = NEW.attempt_no
707
+ );
708
+ END;
709
+ CREATE TRIGGER IF NOT EXISTS dispatch_state_guard
710
+ BEFORE UPDATE OF state ON dispatch_records
711
+ WHEN NEW.state <> OLD.state
712
+ BEGIN
713
+ SELECT RAISE(ABORT, 'ILLEGAL_DISPATCH_TRANSITION') WHERE NOT (
714
+ (OLD.state='INTENDED' AND NEW.state IN ('DISPATCHED','FAILED','RECOVERING'))
715
+ OR (OLD.state='DISPATCHED' AND NEW.state IN ('RECEIVED','RECOVERING'))
716
+ OR (OLD.state='RECEIVED' AND NEW.state IN ('CORRELATED','RECOVERING'))
717
+ OR (OLD.state='CORRELATED' AND NEW.state IN ('TERMINAL','RECOVERING'))
718
+ OR (OLD.state='RECOVERING' AND NEW.state IN ('TERMINAL','FAILED','RECOVERING'))
719
+ );
720
+ SELECT RAISE(ABORT, 'DISPATCH_RECEIVED_REQUIRES_REF_RECEIPT')
721
+ WHERE NEW.state='RECEIVED' AND (NEW.runtime_dispatch_ref IS NULL OR NEW.receipt_json IS NULL OR NEW.receipt_at IS NULL);
722
+ SELECT RAISE(ABORT, 'DISPATCH_CORRELATED_REQUIRES_EXEC_REF')
723
+ WHERE NEW.state='CORRELATED' AND NEW.runtime_execution_ref IS NULL;
724
+ SELECT RAISE(ABORT, 'DISPATCH_TERMINAL_REQUIRES_EVIDENCE')
725
+ WHERE NEW.state='TERMINAL' AND (NEW.terminal_evidence_json IS NULL OR NEW.terminal_at IS NULL);
726
+ END;
727
+ CREATE TRIGGER IF NOT EXISTS dispatch_insert_state_guard
728
+ BEFORE INSERT ON dispatch_records
729
+ WHEN NEW.state <> 'INTENDED'
730
+ BEGIN SELECT RAISE(ABORT, 'DISPATCH_INSERT_MUST_BE_INTENDED'); END;
731
+ CREATE TRIGGER IF NOT EXISTS dispatch_no_delete
732
+ BEFORE DELETE ON dispatch_records
733
+ BEGIN SELECT RAISE(ABORT, 'DISPATCH_NO_DELETE'); END;
734
734
  `;
735
735
  /** v4 对象是否已存在(execution_leases 表是 v4 迁移完成的地标)。 */
736
736
  hasV4Objects() {
@@ -771,20 +771,20 @@ BEGIN SELECT RAISE(ABORT, 'DISPATCH_NO_DELETE'); END;
771
771
  this.db.exec(KingdomStore.V4_LEDGER_SQL);
772
772
  // c. executions 重建:暂存表 → 复制(LEGACY_COMPAT backfill)→ 行数校验
773
773
  this.db.exec(KingdomStore.V4_EXECUTIONS_STAGING_SQL);
774
- this.db.exec(`
775
- INSERT INTO executions_v4
776
- (execution_id, task_id, attempt_no, worker_binding_id, session_id, state,
777
- detail, started_at, heartbeat_at, ended_at, pause_requested_at,
778
- executor_kind, provider, provider_source, requested_model, resolved_model,
779
- model_source, execution_profile_json,
780
- execution_contract, lease_id, capability_decision_id)
781
- SELECT
782
- execution_id, task_id, attempt_no, worker_binding_id, session_id, state,
783
- detail, started_at, heartbeat_at, ended_at, pause_requested_at,
784
- executor_kind, provider, provider_source, requested_model, resolved_model,
785
- model_source, execution_profile_json,
786
- 'LEGACY_COMPAT', NULL, NULL
787
- FROM executions
774
+ this.db.exec(`
775
+ INSERT INTO executions_v4
776
+ (execution_id, task_id, attempt_no, worker_binding_id, session_id, state,
777
+ detail, started_at, heartbeat_at, ended_at, pause_requested_at,
778
+ executor_kind, provider, provider_source, requested_model, resolved_model,
779
+ model_source, execution_profile_json,
780
+ execution_contract, lease_id, capability_decision_id)
781
+ SELECT
782
+ execution_id, task_id, attempt_no, worker_binding_id, session_id, state,
783
+ detail, started_at, heartbeat_at, ended_at, pause_requested_at,
784
+ executor_kind, provider, provider_source, requested_model, resolved_model,
785
+ model_source, execution_profile_json,
786
+ 'LEGACY_COMPAT', NULL, NULL
787
+ FROM executions
788
788
  `);
789
789
  const oldCount = this.db.prepare('SELECT COUNT(*) AS n FROM executions').get();
790
790
  const newCount = this.db.prepare('SELECT COUNT(*) AS n FROM executions_v4').get();
@@ -895,7 +895,7 @@ BEGIN SELECT RAISE(ABORT, 'DISPATCH_NO_DELETE'); END;
895
895
  ...row,
896
896
  };
897
897
  this.db
898
- .prepare(`INSERT INTO kingdoms (kingdom_id, name, created_at, owner_id, owner_name, schema_version)
898
+ .prepare(`INSERT INTO kingdoms (kingdom_id, name, created_at, owner_id, owner_name, schema_version)
899
899
  VALUES (?, ?, ?, ?, ?, ?)`)
900
900
  .run(full.kingdom_id, full.name, full.created_at, full.owner_id, full.owner_name, full.schema_version);
901
901
  return full;
@@ -921,8 +921,8 @@ BEGIN SELECT RAISE(ABORT, 'DISPATCH_NO_DELETE'); END;
921
921
  }
922
922
  insertTerritory(row) {
923
923
  this.db
924
- .prepare(`INSERT INTO territories
925
- (territory_id, kingdom_id, name, workspace_path, summary, supervisor_binding_id, status, created_at)
924
+ .prepare(`INSERT INTO territories
925
+ (territory_id, kingdom_id, name, workspace_path, summary, supervisor_binding_id, status, created_at)
926
926
  VALUES (?, ?, ?, ?, ?, ?, ?, ?)`)
927
927
  .run(row.territory_id, row.kingdom_id, row.name, row.workspace_path, row.summary, row.supervisor_binding_id, row.status, row.created_at);
928
928
  return row;
@@ -958,16 +958,16 @@ BEGIN SELECT RAISE(ABORT, 'DISPATCH_NO_DELETE'); END;
958
958
  }
959
959
  /** Owner topology guards include RECOVERING and unknown future nonterminal states. */
960
960
  listUnsettledOwnerExecutions(kingdomId) {
961
- return this.db.prepare(`SELECT e.* FROM executions e JOIN tasks t ON t.task_id = e.task_id
962
- JOIN territories te ON te.territory_id = t.territory_id WHERE te.kingdom_id = ?
961
+ return this.db.prepare(`SELECT e.* FROM executions e JOIN tasks t ON t.task_id = e.task_id
962
+ JOIN territories te ON te.territory_id = t.territory_id WHERE te.kingdom_id = ?
963
963
  AND e.state NOT IN ('COMPLETED', 'FAILED', 'ABORTED') ORDER BY e.started_at`).all(kingdomId);
964
964
  }
965
965
  // ── task_assignments(v0.7.0 M2-B Assignment Ledger)──────────
966
966
  insertTaskAssignment(row) {
967
967
  this.db
968
- .prepare(`INSERT INTO task_assignments
969
- (assignment_id, task_id, territory_id, worker_binding_id, assigned_by, assigned_at,
970
- ended_at, end_reason, previous_assignment_id, handoff_reason, created_at)
968
+ .prepare(`INSERT INTO task_assignments
969
+ (assignment_id, task_id, territory_id, worker_binding_id, assigned_by, assigned_at,
970
+ ended_at, end_reason, previous_assignment_id, handoff_reason, created_at)
971
971
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
972
972
  .run(row.assignment_id, row.task_id, row.territory_id, row.worker_binding_id, row.assigned_by, row.assigned_at, row.ended_at, row.end_reason, row.previous_assignment_id, row.handoff_reason, row.created_at);
973
973
  return row;
@@ -1036,10 +1036,10 @@ BEGIN SELECT RAISE(ABORT, 'DISPATCH_NO_DELETE'); END;
1036
1036
  }
1037
1037
  insertBinding(row) {
1038
1038
  this.db
1039
- .prepare(`INSERT INTO role_bindings
1040
- (binding_id, kingdom_id, role_type, role_name, runtime_type,
1041
- session_id, model_name, agent_name, session_meta, execution_profile_json,
1042
- status, retired_at, retired_reason, principal_id, created_at, updated_at)
1039
+ .prepare(`INSERT INTO role_bindings
1040
+ (binding_id, kingdom_id, role_type, role_name, runtime_type,
1041
+ session_id, model_name, agent_name, session_meta, execution_profile_json,
1042
+ status, retired_at, retired_reason, principal_id, created_at, updated_at)
1043
1043
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
1044
1044
  .run(row.binding_id, row.kingdom_id, row.role_type, row.role_name, row.runtime_type, row.session_id, row.model_name ?? null, row.agent_name ?? null, row.session_meta ?? null, row.execution_profile_json ?? null, row.status ?? 'ACTIVE', row.retired_at ?? null, row.retired_reason ?? null, row.principal_id, row.created_at, row.updated_at);
1045
1045
  return row;
@@ -1108,8 +1108,8 @@ BEGIN SELECT RAISE(ABORT, 'DISPATCH_NO_DELETE'); END;
1108
1108
  */
1109
1109
  listDispatchesForTaskAttempt(taskId, attemptNo) {
1110
1110
  return this.db
1111
- .prepare(`SELECT * FROM dispatch_records
1112
- WHERE task_id = ? AND attempt_no = ?
1111
+ .prepare(`SELECT * FROM dispatch_records
1112
+ WHERE task_id = ? AND attempt_no = ?
1113
1113
  ORDER BY created_at ASC`)
1114
1114
  .all(taskId, attemptNo);
1115
1115
  }
@@ -1129,11 +1129,11 @@ BEGIN SELECT RAISE(ABORT, 'DISPATCH_NO_DELETE'); END;
1129
1129
  || dispatch.attempt_no !== attemptNo)
1130
1130
  return null;
1131
1131
  const rows = this.db
1132
- .prepare(`SELECT * FROM events
1133
- WHERE kingdom_id = ?
1134
- AND event_type = 'DISPATCH_TERMINAL_INTEGRITY_INCIDENT'
1135
- AND target_type = 'dispatch'
1136
- AND target_id = ?
1132
+ .prepare(`SELECT * FROM events
1133
+ WHERE kingdom_id = ?
1134
+ AND event_type = 'DISPATCH_TERMINAL_INTEGRITY_INCIDENT'
1135
+ AND target_type = 'dispatch'
1136
+ AND target_id = ?
1137
1137
  ORDER BY seq ASC`)
1138
1138
  .all(kingdomId, dispatchId);
1139
1139
  for (const row of rows) {
@@ -1165,13 +1165,13 @@ BEGIN SELECT RAISE(ABORT, 'DISPATCH_NO_DELETE'); END;
1165
1165
  }
1166
1166
  /** Observations and future admission/window policy cannot change an in-flight Runner relation. */
1167
1167
  runnerContextRevision(kingdomId, scope) {
1168
- const rows = this.db.prepare(`SELECT * FROM events
1169
- WHERE kingdom_id = ? AND event_type NOT IN (
1170
- 'RUNTIME_ROLE_USAGE_OBSERVED', 'RUNTIME_PROMPT_COST_OBSERVED', 'DISPATCH_USAGE_OBSERVED',
1171
- 'BUDGET_POLICY_UPDATED', 'BUDGET_ADMISSION_RESERVED', 'BUDGET_ADMISSION_BOUND', 'BUDGET_ADMISSION_CANCELLED',
1172
- 'OWNER_DECISION_CREATED', 'OWNER_DECISION_REVOKED', 'OWNER_DECISION_CONSUMED'
1173
- ) AND NOT (event_type = 'OWNER_OPERATION_APPLIED' AND COALESCE(
1174
- CASE WHEN json_valid(payload_json) THEN json_extract(payload_json, '$.action') = 'budget.policy' ELSE 0 END, 0
1168
+ const rows = this.db.prepare(`SELECT * FROM events
1169
+ WHERE kingdom_id = ? AND event_type NOT IN (
1170
+ 'RUNTIME_ROLE_USAGE_OBSERVED', 'RUNTIME_PROMPT_COST_OBSERVED', 'DISPATCH_USAGE_OBSERVED',
1171
+ 'BUDGET_POLICY_UPDATED', 'BUDGET_ADMISSION_RESERVED', 'BUDGET_ADMISSION_BOUND', 'BUDGET_ADMISSION_CANCELLED',
1172
+ 'OWNER_DECISION_CREATED', 'OWNER_DECISION_REVOKED', 'OWNER_DECISION_CONSUMED'
1173
+ ) AND NOT (event_type = 'OWNER_OPERATION_APPLIED' AND COALESCE(
1174
+ CASE WHEN json_valid(payload_json) THEN json_extract(payload_json, '$.action') = 'budget.policy' ELSE 0 END, 0
1175
1175
  )) ORDER BY seq DESC`).all(kingdomId);
1176
1176
  if (!scope)
1177
1177
  return rows[0]?.seq ?? 0;
@@ -1269,8 +1269,8 @@ BEGIN SELECT RAISE(ABORT, 'DISPATCH_NO_DELETE'); END;
1269
1269
  .get();
1270
1270
  const seq = (next?.n ?? 0) + 1;
1271
1271
  this.db
1272
- .prepare(`INSERT INTO events
1273
- (event_id, kingdom_id, event_type, actor_role, actor_id, target_type, target_id, payload_json, created_at, seq)
1272
+ .prepare(`INSERT INTO events
1273
+ (event_id, kingdom_id, event_type, actor_role, actor_id, target_type, target_id, payload_json, created_at, seq)
1274
1274
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
1275
1275
  .run(row.event_id, row.kingdom_id, row.event_type, row.actor_role, row.actor_id, row.target_type, row.target_id, row.payload_json, row.created_at, seq);
1276
1276
  if (!outer)
@@ -1306,9 +1306,9 @@ BEGIN SELECT RAISE(ABORT, 'DISPATCH_NO_DELETE'); END;
1306
1306
  params.push(filter.status);
1307
1307
  }
1308
1308
  return this.db
1309
- .prepare(`SELECT t.* FROM tasks t
1310
- JOIN territories te ON te.territory_id = t.territory_id
1311
- WHERE ${clauses.join(' AND ')}
1309
+ .prepare(`SELECT t.* FROM tasks t
1310
+ JOIN territories te ON te.territory_id = t.territory_id
1311
+ WHERE ${clauses.join(' AND ')}
1312
1312
  ORDER BY t.created_at`)
1313
1313
  .all(...params);
1314
1314
  }
@@ -1323,9 +1323,9 @@ BEGIN SELECT RAISE(ABORT, 'DISPATCH_NO_DELETE'); END;
1323
1323
  const columns = hasRequirementColumn ? ', capability_requirement_json' : '';
1324
1324
  const values = hasRequirementColumn ? ', ?' : '';
1325
1325
  this.db
1326
- .prepare(`INSERT INTO tasks
1327
- (task_id, territory_id, parent_task_id, title, description, assigned_binding_id,
1328
- status, acceptance_criteria, result_summary, created_at, updated_at${columns})
1326
+ .prepare(`INSERT INTO tasks
1327
+ (task_id, territory_id, parent_task_id, title, description, assigned_binding_id,
1328
+ status, acceptance_criteria, result_summary, created_at, updated_at${columns})
1329
1329
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?${values})`)
1330
1330
  .run(row.task_id, row.territory_id, row.parent_task_id, row.title, row.description, row.assigned_binding_id, row.status, row.acceptance_criteria, row.result_summary, row.created_at, row.updated_at, ...(hasRequirementColumn ? [row.capability_requirement_json ?? null] : []));
1331
1331
  return row;
@@ -1351,7 +1351,7 @@ BEGIN SELECT RAISE(ABORT, 'DISPATCH_NO_DELETE'); END;
1351
1351
  : patch.assigned_binding_id;
1352
1352
  const summary = patch.result_summary === undefined ? task.result_summary : patch.result_summary;
1353
1353
  this.db
1354
- .prepare(`UPDATE tasks SET status = ?, assigned_binding_id = ?, result_summary = ?, updated_at = ?
1354
+ .prepare(`UPDATE tasks SET status = ?, assigned_binding_id = ?, result_summary = ?, updated_at = ?
1355
1355
  WHERE task_id = ?`)
1356
1356
  .run(next, assigned, summary, now, task.task_id);
1357
1357
  return { ...task, status: next, assigned_binding_id: assigned, result_summary: summary, updated_at: now };
@@ -1387,17 +1387,17 @@ BEGIN SELECT RAISE(ABORT, 'DISPATCH_NO_DELETE'); END;
1387
1387
  }
1388
1388
  /** Complete exact budget ledger; display event windows must never be used as accounting history. */
1389
1389
  listBudgetEvents(kingdomId) {
1390
- return this.db.prepare(`SELECT * FROM events WHERE kingdom_id = ? AND event_type IN
1391
- ('BUDGET_POLICY_UPDATED', 'BUDGET_ADMISSION_RESERVED', 'BUDGET_ADMISSION_BOUND', 'BUDGET_ADMISSION_CANCELLED', 'DISPATCH_INTENDED')
1390
+ return this.db.prepare(`SELECT * FROM events WHERE kingdom_id = ? AND event_type IN
1391
+ ('BUDGET_POLICY_UPDATED', 'BUDGET_ADMISSION_RESERVED', 'BUDGET_ADMISSION_BOUND', 'BUDGET_ADMISSION_CANCELLED', 'DISPATCH_INTENDED')
1392
1392
  ORDER BY seq ASC`).all(kingdomId);
1393
1393
  }
1394
1394
  listWorkspaceAdmissionEvents(kingdomId) {
1395
- return this.db.prepare(`SELECT * FROM events WHERE kingdom_id = ? AND event_type IN
1396
- ('WORKSPACE_ADMISSION_RESERVED', 'WORKSPACE_ADMISSION_BOUND', 'WORKSPACE_ADMISSION_CANCELLED')
1395
+ return this.db.prepare(`SELECT * FROM events WHERE kingdom_id = ? AND event_type IN
1396
+ ('WORKSPACE_ADMISSION_RESERVED', 'WORKSPACE_ADMISSION_BOUND', 'WORKSPACE_ADMISSION_CANCELLED')
1397
1397
  ORDER BY seq ASC`).all(kingdomId);
1398
1398
  }
1399
1399
  listRuntimeCostEvents(kingdomId) {
1400
- return this.db.prepare(`SELECT * FROM events WHERE kingdom_id = ? AND event_type IN
1400
+ return this.db.prepare(`SELECT * FROM events WHERE kingdom_id = ? AND event_type IN
1401
1401
  ('RUNTIME_ROLE_USAGE_OBSERVED', 'RUNTIME_PROMPT_COST_OBSERVED') ORDER BY seq ASC`).all(kingdomId);
1402
1402
  }
1403
1403
  listActorEventsSince(kingdomId, actorId, afterSeq) {
@@ -1435,8 +1435,8 @@ BEGIN SELECT RAISE(ABORT, 'DISPATCH_NO_DELETE'); END;
1435
1435
  }
1436
1436
  insertWorkerResult(row) {
1437
1437
  this.db
1438
- .prepare(`INSERT INTO worker_results
1439
- (result_id, task_id, attempt_no, worker_binding_id, session_id, outcome, result_json, created_at)
1438
+ .prepare(`INSERT INTO worker_results
1439
+ (result_id, task_id, attempt_no, worker_binding_id, session_id, outcome, result_json, created_at)
1440
1440
  VALUES (?, ?, ?, ?, ?, ?, ?, ?)`)
1441
1441
  .run(row.result_id, row.task_id, row.attempt_no, row.worker_binding_id, row.session_id, row.outcome, row.result_json, row.created_at);
1442
1442
  return row;
@@ -1462,21 +1462,21 @@ BEGIN SELECT RAISE(ABORT, 'DISPATCH_NO_DELETE'); END;
1462
1462
  /** 王国内所有未终结的 Execution(人物应当在场的那些)。 */
1463
1463
  listLiveExecutions(kingdomId) {
1464
1464
  return this.db
1465
- .prepare(`SELECT e.* FROM executions e
1466
- JOIN tasks t ON t.task_id = e.task_id
1467
- JOIN territories te ON te.territory_id = t.territory_id
1468
- WHERE te.kingdom_id = ?
1469
- AND e.state IN ('STARTING', 'RUNNING', 'PAUSED')
1465
+ .prepare(`SELECT e.* FROM executions e
1466
+ JOIN tasks t ON t.task_id = e.task_id
1467
+ JOIN territories te ON te.territory_id = t.territory_id
1468
+ WHERE te.kingdom_id = ?
1469
+ AND e.state IN ('STARTING', 'RUNNING', 'PAUSED')
1470
1470
  ORDER BY e.started_at`)
1471
1471
  .all(kingdomId);
1472
1472
  }
1473
1473
  insertExecution(row) {
1474
1474
  this.db
1475
- .prepare(`INSERT INTO executions
1476
- (execution_id, task_id, attempt_no, worker_binding_id, session_id, state, detail,
1477
- started_at, heartbeat_at, ended_at, pause_requested_at,
1478
- executor_kind, provider, provider_source, requested_model, resolved_model, model_source,
1479
- execution_profile_json, execution_contract, lease_id, capability_decision_id)
1475
+ .prepare(`INSERT INTO executions
1476
+ (execution_id, task_id, attempt_no, worker_binding_id, session_id, state, detail,
1477
+ started_at, heartbeat_at, ended_at, pause_requested_at,
1478
+ executor_kind, provider, provider_source, requested_model, resolved_model, model_source,
1479
+ execution_profile_json, execution_contract, lease_id, capability_decision_id)
1480
1480
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
1481
1481
  .run(row.execution_id, row.task_id, row.attempt_no, row.worker_binding_id, row.session_id, row.state, row.detail, row.started_at, row.heartbeat_at, row.ended_at, row.pause_requested_at, row.executor_kind, row.provider, row.provider_source, row.requested_model, row.resolved_model, row.model_source, row.execution_profile_json, row.execution_contract ?? 'LEGACY_COMPAT', row.lease_id ?? null, row.capability_decision_id ?? null);
1482
1482
  return row;
@@ -1509,8 +1509,8 @@ BEGIN SELECT RAISE(ABORT, 'DISPATCH_NO_DELETE'); END;
1509
1509
  ? execution.pause_requested_at
1510
1510
  : patch.pauseRequestedAt;
1511
1511
  this.db
1512
- .prepare(`UPDATE executions
1513
- SET state = ?, detail = ?, session_id = ?, heartbeat_at = ?, ended_at = ?, pause_requested_at = ?
1512
+ .prepare(`UPDATE executions
1513
+ SET state = ?, detail = ?, session_id = ?, heartbeat_at = ?, ended_at = ?, pause_requested_at = ?
1514
1514
  WHERE execution_id = ?`)
1515
1515
  .run(next, detail, sessionId, now, ended, pauseRequestedAt, execution.execution_id);
1516
1516
  return {
@@ -1533,9 +1533,9 @@ BEGIN SELECT RAISE(ABORT, 'DISPATCH_NO_DELETE'); END;
1533
1533
  // session_territory_affinities ────────────────────────────────
1534
1534
  insertAffinity(row) {
1535
1535
  this.db
1536
- .prepare(`INSERT INTO session_territory_affinities
1537
- (affinity_id, kingdom_id, worker_binding_id, runtime_type, runtime_instance_ref,
1538
- session_ref, territory_id, established_at, retired_at, is_current, created_at)
1536
+ .prepare(`INSERT INTO session_territory_affinities
1537
+ (affinity_id, kingdom_id, worker_binding_id, runtime_type, runtime_instance_ref,
1538
+ session_ref, territory_id, established_at, retired_at, is_current, created_at)
1539
1539
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
1540
1540
  .run(row.affinity_id, row.kingdom_id, row.worker_binding_id, row.runtime_type, row.runtime_instance_ref, row.session_ref, row.territory_id, row.established_at, row.retired_at ?? null, row.is_current ?? 1, row.created_at);
1541
1541
  return row;
@@ -1546,7 +1546,7 @@ BEGIN SELECT RAISE(ABORT, 'DISPATCH_NO_DELETE'); END;
1546
1546
  }
1547
1547
  getAffinityBySession(session) {
1548
1548
  const rows = this.db
1549
- .prepare(`SELECT * FROM session_territory_affinities
1549
+ .prepare(`SELECT * FROM session_territory_affinities
1550
1550
  WHERE runtime_type = ? AND runtime_instance_ref = ? AND session_ref = ?`)
1551
1551
  .all(session.runtimeType, session.runtimeInstanceRef, session.sessionRef);
1552
1552
  return rows[0] ?? null;
@@ -1559,7 +1559,7 @@ BEGIN SELECT RAISE(ABORT, 'DISPATCH_NO_DELETE'); END;
1559
1559
  /** Worker 当前(未退役)Affinity;DB 部分唯一索引保证至多一条。 */
1560
1560
  getCurrentAffinityForWorker(kingdomId, workerBindingId) {
1561
1561
  const rows = this.db
1562
- .prepare(`SELECT * FROM session_territory_affinities
1562
+ .prepare(`SELECT * FROM session_territory_affinities
1563
1563
  WHERE kingdom_id = ? AND worker_binding_id = ? AND is_current = 1 LIMIT 1`)
1564
1564
  .all(kingdomId, workerBindingId);
1565
1565
  return rows[0] ?? null;
@@ -1577,11 +1577,11 @@ BEGIN SELECT RAISE(ABORT, 'DISPATCH_NO_DELETE'); END;
1577
1577
  // execution_leases ────────────────────────────────────────────
1578
1578
  insertLease(row) {
1579
1579
  this.db
1580
- .prepare(`INSERT INTO execution_leases
1581
- (lease_id, kingdom_id, worker_binding_id, runtime_type, runtime_instance_ref,
1582
- session_ref, territory_id, task_id, attempt_no, state,
1583
- capability_decision_id, enforcement_plan_snapshot, release_evidence_json, release_reason,
1584
- acquired_at, released_at, updated_at)
1580
+ .prepare(`INSERT INTO execution_leases
1581
+ (lease_id, kingdom_id, worker_binding_id, runtime_type, runtime_instance_ref,
1582
+ session_ref, territory_id, task_id, attempt_no, state,
1583
+ capability_decision_id, enforcement_plan_snapshot, release_evidence_json, release_reason,
1584
+ acquired_at, released_at, updated_at)
1585
1585
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
1586
1586
  .run(row.lease_id, row.kingdom_id, row.worker_binding_id, row.runtime_type, row.runtime_instance_ref, row.session_ref, row.territory_id, row.task_id, row.attempt_no, row.state, row.capability_decision_id ?? null, row.enforcement_plan_snapshot ?? null, row.release_evidence_json ?? null, row.release_reason ?? null, row.acquired_at, row.released_at ?? null, row.updated_at);
1587
1587
  return row;
@@ -1604,8 +1604,8 @@ BEGIN SELECT RAISE(ABORT, 'DISPATCH_NO_DELETE'); END;
1604
1604
  /** 某 Session 当前未释放的 Lease(部分唯一索引保证至多一条非 RELEASED)。 */
1605
1605
  getActiveLeaseForSession(session) {
1606
1606
  const rows = this.db
1607
- .prepare(`SELECT * FROM execution_leases
1608
- WHERE runtime_type = ? AND runtime_instance_ref = ? AND session_ref = ? AND state <> 'RELEASED'
1607
+ .prepare(`SELECT * FROM execution_leases
1608
+ WHERE runtime_type = ? AND runtime_instance_ref = ? AND session_ref = ? AND state <> 'RELEASED'
1609
1609
  LIMIT 1`)
1610
1610
  .all(session.runtimeType, session.runtimeInstanceRef, session.sessionRef);
1611
1611
  return rows[0] ?? null;
@@ -1653,11 +1653,11 @@ BEGIN SELECT RAISE(ABORT, 'DISPATCH_NO_DELETE'); END;
1653
1653
  // capability_decisions ────────────────────────────────────────
1654
1654
  insertCapabilityDecision(row) {
1655
1655
  this.db
1656
- .prepare(`INSERT INTO capability_decisions
1657
- (decision_id, kingdom_id, task_id, worker_binding_id, supervisor_binding_id,
1658
- requirement_snapshot, ceiling_snapshot, proposed_grant_snapshot, scope_snapshot, effective_snapshot,
1659
- decision, enforcement_status, enforcement_evidence_json, requirement_coverage,
1660
- reason_code, execution_id, created_at)
1656
+ .prepare(`INSERT INTO capability_decisions
1657
+ (decision_id, kingdom_id, task_id, worker_binding_id, supervisor_binding_id,
1658
+ requirement_snapshot, ceiling_snapshot, proposed_grant_snapshot, scope_snapshot, effective_snapshot,
1659
+ decision, enforcement_status, enforcement_evidence_json, requirement_coverage,
1660
+ reason_code, execution_id, created_at)
1661
1661
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
1662
1662
  .run(row.decision_id, row.kingdom_id, row.task_id, row.worker_binding_id ?? null, row.supervisor_binding_id ?? null, row.requirement_snapshot ?? null, row.ceiling_snapshot ?? null, row.proposed_grant_snapshot ?? null, row.scope_snapshot ?? null, row.effective_snapshot ?? null, row.decision, row.enforcement_status, row.enforcement_evidence_json ?? null, row.requirement_coverage ?? 'NONE', row.reason_code ?? null, row.execution_id ?? null, row.created_at);
1663
1663
  return row;
@@ -1679,12 +1679,12 @@ BEGIN SELECT RAISE(ABORT, 'DISPATCH_NO_DELETE'); END;
1679
1679
  // dispatch_records ────────────────────────────────────────────
1680
1680
  insertDispatchIntent(row) {
1681
1681
  this.db
1682
- .prepare(`INSERT INTO dispatch_records
1683
- (dispatch_id, kingdom_id, lease_id, execution_id, task_id, attempt_no,
1684
- runtime_type, runtime_instance_ref, session_ref, state,
1685
- dispatch_request_snapshot, dispatch_input_ref_json, dispatch_payload_hash,
1686
- runtime_dispatch_ref, runtime_execution_ref, receipt_json, terminal_evidence_json,
1687
- output_ref_json, dispatched_at, receipt_at, terminal_at, created_at, updated_at)
1682
+ .prepare(`INSERT INTO dispatch_records
1683
+ (dispatch_id, kingdom_id, lease_id, execution_id, task_id, attempt_no,
1684
+ runtime_type, runtime_instance_ref, session_ref, state,
1685
+ dispatch_request_snapshot, dispatch_input_ref_json, dispatch_payload_hash,
1686
+ runtime_dispatch_ref, runtime_execution_ref, receipt_json, terminal_evidence_json,
1687
+ output_ref_json, dispatched_at, receipt_at, terminal_at, created_at, updated_at)
1688
1688
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
1689
1689
  .run(row.dispatch_id, row.kingdom_id, row.lease_id, row.execution_id, row.task_id, row.attempt_no, row.runtime_type, row.runtime_instance_ref, row.session_ref, row.state, row.dispatch_request_snapshot, row.dispatch_input_ref_json, row.dispatch_payload_hash, row.runtime_dispatch_ref ?? null, row.runtime_execution_ref ?? null, row.receipt_json ?? null, row.terminal_evidence_json ?? null, row.output_ref_json ?? null, row.dispatched_at ?? null, row.receipt_at ?? null, row.terminal_at ?? null, row.created_at, row.updated_at);
1690
1690
  return row;