scene-capability-engine 3.6.4 → 3.6.7
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/CHANGELOG.md +49 -0
- package/README.md +1 -0
- package/README.zh.md +1 -0
- package/docs/command-reference.md +13 -1
- package/lib/gitignore/gitignore-detector.js +2 -1
- package/lib/gitignore/layered-rules-template.js +1 -0
- package/lib/state/sce-state-store.js +1077 -0
- package/lib/state/state-migration-manager.js +334 -3
- package/package.json +1 -1
|
@@ -21,6 +21,23 @@ function normalizeInteger(value, fallback = 0) {
|
|
|
21
21
|
return parsed;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
function normalizeBooleanValue(value, fallback = false) {
|
|
25
|
+
if (typeof value === 'boolean') {
|
|
26
|
+
return value;
|
|
27
|
+
}
|
|
28
|
+
const normalized = normalizeString(`${value || ''}`).toLowerCase();
|
|
29
|
+
if (!normalized) {
|
|
30
|
+
return fallback;
|
|
31
|
+
}
|
|
32
|
+
if (['1', 'true', 'yes', 'y', 'on'].includes(normalized)) {
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
if (['0', 'false', 'no', 'n', 'off'].includes(normalized)) {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
return fallback;
|
|
39
|
+
}
|
|
40
|
+
|
|
24
41
|
function normalizeNonNegativeInteger(value, fallback = 0) {
|
|
25
42
|
const parsed = Number.parseInt(`${value}`, 10);
|
|
26
43
|
if (!Number.isFinite(parsed) || parsed < 0) {
|
|
@@ -116,6 +133,12 @@ class SceStateStore {
|
|
|
116
133
|
timeline_snapshots: {},
|
|
117
134
|
scene_session_cycles: {},
|
|
118
135
|
agent_runtime: {},
|
|
136
|
+
errorbook_entry_index: {},
|
|
137
|
+
errorbook_incident_index: {},
|
|
138
|
+
governance_spec_scene_override: {},
|
|
139
|
+
governance_scene_index: {},
|
|
140
|
+
release_evidence_run: {},
|
|
141
|
+
release_gate_history: {},
|
|
119
142
|
migration_records: {},
|
|
120
143
|
auth_leases: {},
|
|
121
144
|
auth_events: [],
|
|
@@ -317,6 +340,123 @@ class SceStateStore {
|
|
|
317
340
|
|
|
318
341
|
CREATE INDEX IF NOT EXISTS idx_state_migration_registry_component_started
|
|
319
342
|
ON state_migration_registry(component_id, started_at DESC);
|
|
343
|
+
|
|
344
|
+
CREATE TABLE IF NOT EXISTS errorbook_entry_index_registry (
|
|
345
|
+
entry_id TEXT PRIMARY KEY,
|
|
346
|
+
fingerprint TEXT,
|
|
347
|
+
title TEXT,
|
|
348
|
+
status TEXT,
|
|
349
|
+
quality_score INTEGER,
|
|
350
|
+
tags_json TEXT,
|
|
351
|
+
ontology_tags_json TEXT,
|
|
352
|
+
temporary_mitigation_active INTEGER,
|
|
353
|
+
temporary_mitigation_deadline_at TEXT,
|
|
354
|
+
occurrences INTEGER,
|
|
355
|
+
created_at TEXT,
|
|
356
|
+
updated_at TEXT,
|
|
357
|
+
source TEXT,
|
|
358
|
+
indexed_at TEXT NOT NULL
|
|
359
|
+
);
|
|
360
|
+
|
|
361
|
+
CREATE INDEX IF NOT EXISTS idx_errorbook_entry_index_registry_status_updated
|
|
362
|
+
ON errorbook_entry_index_registry(status, updated_at DESC);
|
|
363
|
+
|
|
364
|
+
CREATE TABLE IF NOT EXISTS errorbook_incident_index_registry (
|
|
365
|
+
incident_id TEXT PRIMARY KEY,
|
|
366
|
+
fingerprint TEXT,
|
|
367
|
+
title TEXT,
|
|
368
|
+
symptom TEXT,
|
|
369
|
+
state TEXT,
|
|
370
|
+
attempt_count INTEGER,
|
|
371
|
+
created_at TEXT,
|
|
372
|
+
updated_at TEXT,
|
|
373
|
+
last_attempt_at TEXT,
|
|
374
|
+
resolved_at TEXT,
|
|
375
|
+
linked_entry_id TEXT,
|
|
376
|
+
source TEXT,
|
|
377
|
+
indexed_at TEXT NOT NULL
|
|
378
|
+
);
|
|
379
|
+
|
|
380
|
+
CREATE INDEX IF NOT EXISTS idx_errorbook_incident_index_registry_state_updated
|
|
381
|
+
ON errorbook_incident_index_registry(state, updated_at DESC);
|
|
382
|
+
|
|
383
|
+
CREATE TABLE IF NOT EXISTS governance_spec_scene_override_registry (
|
|
384
|
+
spec_id TEXT PRIMARY KEY,
|
|
385
|
+
scene_id TEXT NOT NULL,
|
|
386
|
+
source TEXT,
|
|
387
|
+
rule_id TEXT,
|
|
388
|
+
updated_at TEXT,
|
|
389
|
+
indexed_at TEXT NOT NULL
|
|
390
|
+
);
|
|
391
|
+
|
|
392
|
+
CREATE INDEX IF NOT EXISTS idx_governance_spec_scene_override_registry_scene
|
|
393
|
+
ON governance_spec_scene_override_registry(scene_id, updated_at DESC);
|
|
394
|
+
|
|
395
|
+
CREATE TABLE IF NOT EXISTS governance_scene_index_registry (
|
|
396
|
+
scene_id TEXT PRIMARY KEY,
|
|
397
|
+
total_specs INTEGER,
|
|
398
|
+
active_specs INTEGER,
|
|
399
|
+
completed_specs INTEGER,
|
|
400
|
+
stale_specs INTEGER,
|
|
401
|
+
spec_ids_json TEXT,
|
|
402
|
+
active_spec_ids_json TEXT,
|
|
403
|
+
stale_spec_ids_json TEXT,
|
|
404
|
+
generated_at TEXT,
|
|
405
|
+
scene_filter TEXT,
|
|
406
|
+
source TEXT,
|
|
407
|
+
indexed_at TEXT NOT NULL
|
|
408
|
+
);
|
|
409
|
+
|
|
410
|
+
CREATE INDEX IF NOT EXISTS idx_governance_scene_index_registry_counts
|
|
411
|
+
ON governance_scene_index_registry(total_specs DESC, active_specs DESC);
|
|
412
|
+
|
|
413
|
+
CREATE TABLE IF NOT EXISTS release_evidence_run_registry (
|
|
414
|
+
session_id TEXT PRIMARY KEY,
|
|
415
|
+
merged_at TEXT,
|
|
416
|
+
status TEXT,
|
|
417
|
+
gate_passed INTEGER,
|
|
418
|
+
spec_success_rate_percent REAL,
|
|
419
|
+
risk_level TEXT,
|
|
420
|
+
ontology_quality_score REAL,
|
|
421
|
+
capability_coverage_percent REAL,
|
|
422
|
+
capability_coverage_passed INTEGER,
|
|
423
|
+
scene_package_batch_passed INTEGER,
|
|
424
|
+
scene_package_batch_failure_count INTEGER,
|
|
425
|
+
failed_goals INTEGER,
|
|
426
|
+
release_gate_preflight_available INTEGER,
|
|
427
|
+
release_gate_preflight_blocked INTEGER,
|
|
428
|
+
source_updated_at TEXT,
|
|
429
|
+
source TEXT,
|
|
430
|
+
indexed_at TEXT NOT NULL
|
|
431
|
+
);
|
|
432
|
+
|
|
433
|
+
CREATE INDEX IF NOT EXISTS idx_release_evidence_run_registry_status_merged
|
|
434
|
+
ON release_evidence_run_registry(status, merged_at DESC);
|
|
435
|
+
|
|
436
|
+
CREATE TABLE IF NOT EXISTS release_gate_history_registry (
|
|
437
|
+
tag TEXT PRIMARY KEY,
|
|
438
|
+
evaluated_at TEXT,
|
|
439
|
+
gate_passed INTEGER,
|
|
440
|
+
enforce INTEGER,
|
|
441
|
+
risk_level TEXT,
|
|
442
|
+
spec_success_rate_percent REAL,
|
|
443
|
+
scene_package_batch_passed INTEGER,
|
|
444
|
+
scene_package_batch_failure_count INTEGER,
|
|
445
|
+
capability_expected_unknown_count INTEGER,
|
|
446
|
+
capability_provided_unknown_count INTEGER,
|
|
447
|
+
release_gate_preflight_available INTEGER,
|
|
448
|
+
release_gate_preflight_blocked INTEGER,
|
|
449
|
+
require_release_gate_preflight INTEGER,
|
|
450
|
+
drift_alert_count INTEGER,
|
|
451
|
+
drift_blocked INTEGER,
|
|
452
|
+
weekly_ops_blocked INTEGER,
|
|
453
|
+
source_updated_at TEXT,
|
|
454
|
+
source TEXT,
|
|
455
|
+
indexed_at TEXT NOT NULL
|
|
456
|
+
);
|
|
457
|
+
|
|
458
|
+
CREATE INDEX IF NOT EXISTS idx_release_gate_history_registry_eval
|
|
459
|
+
ON release_gate_history_registry(evaluated_at DESC);
|
|
320
460
|
`);
|
|
321
461
|
}
|
|
322
462
|
|
|
@@ -511,6 +651,143 @@ class SceStateStore {
|
|
|
511
651
|
};
|
|
512
652
|
}
|
|
513
653
|
|
|
654
|
+
_mapErrorbookEntryIndexRow(row) {
|
|
655
|
+
if (!row) {
|
|
656
|
+
return null;
|
|
657
|
+
}
|
|
658
|
+
return {
|
|
659
|
+
entry_id: normalizeString(row.entry_id),
|
|
660
|
+
fingerprint: normalizeString(row.fingerprint) || null,
|
|
661
|
+
title: normalizeString(row.title) || null,
|
|
662
|
+
status: normalizeString(row.status) || null,
|
|
663
|
+
quality_score: normalizeNonNegativeInteger(row.quality_score, 0),
|
|
664
|
+
tags: normalizeStringArray(parseJsonSafe(row.tags_json, []), []),
|
|
665
|
+
ontology_tags: normalizeStringArray(parseJsonSafe(row.ontology_tags_json, []), []),
|
|
666
|
+
temporary_mitigation_active: Number(row.temporary_mitigation_active) === 1,
|
|
667
|
+
temporary_mitigation_deadline_at: normalizeIsoTimestamp(row.temporary_mitigation_deadline_at, '') || null,
|
|
668
|
+
occurrences: normalizeNonNegativeInteger(row.occurrences, 0),
|
|
669
|
+
created_at: normalizeIsoTimestamp(row.created_at, '') || null,
|
|
670
|
+
updated_at: normalizeIsoTimestamp(row.updated_at, '') || null,
|
|
671
|
+
source: normalizeString(row.source) || null,
|
|
672
|
+
indexed_at: normalizeIsoTimestamp(row.indexed_at, '') || null
|
|
673
|
+
};
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
_mapErrorbookIncidentIndexRow(row) {
|
|
677
|
+
if (!row) {
|
|
678
|
+
return null;
|
|
679
|
+
}
|
|
680
|
+
return {
|
|
681
|
+
incident_id: normalizeString(row.incident_id),
|
|
682
|
+
fingerprint: normalizeString(row.fingerprint) || null,
|
|
683
|
+
title: normalizeString(row.title) || null,
|
|
684
|
+
symptom: normalizeString(row.symptom) || null,
|
|
685
|
+
state: normalizeString(row.state) || null,
|
|
686
|
+
attempt_count: normalizeNonNegativeInteger(row.attempt_count, 0),
|
|
687
|
+
created_at: normalizeIsoTimestamp(row.created_at, '') || null,
|
|
688
|
+
updated_at: normalizeIsoTimestamp(row.updated_at, '') || null,
|
|
689
|
+
last_attempt_at: normalizeIsoTimestamp(row.last_attempt_at, '') || null,
|
|
690
|
+
resolved_at: normalizeIsoTimestamp(row.resolved_at, '') || null,
|
|
691
|
+
linked_entry_id: normalizeString(row.linked_entry_id) || null,
|
|
692
|
+
source: normalizeString(row.source) || null,
|
|
693
|
+
indexed_at: normalizeIsoTimestamp(row.indexed_at, '') || null
|
|
694
|
+
};
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
_mapGovernanceSpecSceneOverrideRow(row) {
|
|
698
|
+
if (!row) {
|
|
699
|
+
return null;
|
|
700
|
+
}
|
|
701
|
+
return {
|
|
702
|
+
spec_id: normalizeString(row.spec_id),
|
|
703
|
+
scene_id: normalizeString(row.scene_id),
|
|
704
|
+
source: normalizeString(row.source) || null,
|
|
705
|
+
rule_id: normalizeString(row.rule_id) || null,
|
|
706
|
+
updated_at: normalizeIsoTimestamp(row.updated_at, '') || null,
|
|
707
|
+
indexed_at: normalizeIsoTimestamp(row.indexed_at, '') || null
|
|
708
|
+
};
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
_mapGovernanceSceneIndexRow(row) {
|
|
712
|
+
if (!row) {
|
|
713
|
+
return null;
|
|
714
|
+
}
|
|
715
|
+
return {
|
|
716
|
+
scene_id: normalizeString(row.scene_id),
|
|
717
|
+
total_specs: normalizeNonNegativeInteger(row.total_specs, 0),
|
|
718
|
+
active_specs: normalizeNonNegativeInteger(row.active_specs, 0),
|
|
719
|
+
completed_specs: normalizeNonNegativeInteger(row.completed_specs, 0),
|
|
720
|
+
stale_specs: normalizeNonNegativeInteger(row.stale_specs, 0),
|
|
721
|
+
spec_ids: normalizeStringArray(parseJsonSafe(row.spec_ids_json, []), []),
|
|
722
|
+
active_spec_ids: normalizeStringArray(parseJsonSafe(row.active_spec_ids_json, []), []),
|
|
723
|
+
stale_spec_ids: normalizeStringArray(parseJsonSafe(row.stale_spec_ids_json, []), []),
|
|
724
|
+
generated_at: normalizeIsoTimestamp(row.generated_at, '') || null,
|
|
725
|
+
scene_filter: normalizeString(row.scene_filter) || null,
|
|
726
|
+
source: normalizeString(row.source) || null,
|
|
727
|
+
indexed_at: normalizeIsoTimestamp(row.indexed_at, '') || null
|
|
728
|
+
};
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
_mapReleaseEvidenceRunRow(row) {
|
|
732
|
+
if (!row) {
|
|
733
|
+
return null;
|
|
734
|
+
}
|
|
735
|
+
return {
|
|
736
|
+
session_id: normalizeString(row.session_id),
|
|
737
|
+
merged_at: normalizeIsoTimestamp(row.merged_at, '') || null,
|
|
738
|
+
status: normalizeString(row.status) || null,
|
|
739
|
+
gate_passed: normalizeBooleanValue(row.gate_passed, false),
|
|
740
|
+
spec_success_rate_percent: Number.isFinite(Number(row.spec_success_rate_percent))
|
|
741
|
+
? Number(row.spec_success_rate_percent)
|
|
742
|
+
: null,
|
|
743
|
+
risk_level: normalizeString(row.risk_level) || null,
|
|
744
|
+
ontology_quality_score: Number.isFinite(Number(row.ontology_quality_score))
|
|
745
|
+
? Number(row.ontology_quality_score)
|
|
746
|
+
: null,
|
|
747
|
+
capability_coverage_percent: Number.isFinite(Number(row.capability_coverage_percent))
|
|
748
|
+
? Number(row.capability_coverage_percent)
|
|
749
|
+
: null,
|
|
750
|
+
capability_coverage_passed: normalizeBooleanValue(row.capability_coverage_passed, false),
|
|
751
|
+
scene_package_batch_passed: normalizeBooleanValue(row.scene_package_batch_passed, false),
|
|
752
|
+
scene_package_batch_failure_count: normalizeNonNegativeInteger(row.scene_package_batch_failure_count, 0),
|
|
753
|
+
failed_goals: normalizeNonNegativeInteger(row.failed_goals, 0),
|
|
754
|
+
release_gate_preflight_available: normalizeBooleanValue(row.release_gate_preflight_available, false),
|
|
755
|
+
release_gate_preflight_blocked: normalizeBooleanValue(row.release_gate_preflight_blocked, false),
|
|
756
|
+
source_updated_at: normalizeIsoTimestamp(row.source_updated_at, '') || null,
|
|
757
|
+
source: normalizeString(row.source) || null,
|
|
758
|
+
indexed_at: normalizeIsoTimestamp(row.indexed_at, '') || null
|
|
759
|
+
};
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
_mapReleaseGateHistoryRow(row) {
|
|
763
|
+
if (!row) {
|
|
764
|
+
return null;
|
|
765
|
+
}
|
|
766
|
+
return {
|
|
767
|
+
tag: normalizeString(row.tag),
|
|
768
|
+
evaluated_at: normalizeIsoTimestamp(row.evaluated_at, '') || null,
|
|
769
|
+
gate_passed: normalizeBooleanValue(row.gate_passed, false),
|
|
770
|
+
enforce: normalizeBooleanValue(row.enforce, false),
|
|
771
|
+
risk_level: normalizeString(row.risk_level) || null,
|
|
772
|
+
spec_success_rate_percent: Number.isFinite(Number(row.spec_success_rate_percent))
|
|
773
|
+
? Number(row.spec_success_rate_percent)
|
|
774
|
+
: null,
|
|
775
|
+
scene_package_batch_passed: normalizeBooleanValue(row.scene_package_batch_passed, false),
|
|
776
|
+
scene_package_batch_failure_count: normalizeNonNegativeInteger(row.scene_package_batch_failure_count, 0),
|
|
777
|
+
capability_expected_unknown_count: normalizeNonNegativeInteger(row.capability_expected_unknown_count, 0),
|
|
778
|
+
capability_provided_unknown_count: normalizeNonNegativeInteger(row.capability_provided_unknown_count, 0),
|
|
779
|
+
release_gate_preflight_available: normalizeBooleanValue(row.release_gate_preflight_available, false),
|
|
780
|
+
release_gate_preflight_blocked: normalizeBooleanValue(row.release_gate_preflight_blocked, false),
|
|
781
|
+
require_release_gate_preflight: normalizeBooleanValue(row.require_release_gate_preflight, false),
|
|
782
|
+
drift_alert_count: normalizeNonNegativeInteger(row.drift_alert_count, 0),
|
|
783
|
+
drift_blocked: normalizeBooleanValue(row.drift_blocked, false),
|
|
784
|
+
weekly_ops_blocked: normalizeBooleanValue(row.weekly_ops_blocked, false),
|
|
785
|
+
source_updated_at: normalizeIsoTimestamp(row.source_updated_at, '') || null,
|
|
786
|
+
source: normalizeString(row.source) || null,
|
|
787
|
+
indexed_at: normalizeIsoTimestamp(row.indexed_at, '') || null
|
|
788
|
+
};
|
|
789
|
+
}
|
|
790
|
+
|
|
514
791
|
async resolveOrCreateTaskRef(options = {}) {
|
|
515
792
|
const sceneId = normalizeString(options.sceneId);
|
|
516
793
|
const specId = normalizeString(options.specId);
|
|
@@ -1514,6 +1791,806 @@ class SceStateStore {
|
|
|
1514
1791
|
.filter(Boolean);
|
|
1515
1792
|
}
|
|
1516
1793
|
|
|
1794
|
+
async upsertErrorbookEntryIndexRecords(records = [], options = {}) {
|
|
1795
|
+
const source = normalizeString(options.source) || 'file.errorbook.index';
|
|
1796
|
+
const nowIso = this.now();
|
|
1797
|
+
const normalizedRecords = Array.isArray(records)
|
|
1798
|
+
? records.map((item) => ({
|
|
1799
|
+
entry_id: normalizeString(item && (item.entry_id || item.id)),
|
|
1800
|
+
fingerprint: normalizeString(item && item.fingerprint) || null,
|
|
1801
|
+
title: normalizeString(item && item.title) || null,
|
|
1802
|
+
status: normalizeString(item && item.status) || null,
|
|
1803
|
+
quality_score: normalizeNonNegativeInteger(item && item.quality_score, 0),
|
|
1804
|
+
tags: normalizeStringArray(item && item.tags, []),
|
|
1805
|
+
ontology_tags: normalizeStringArray(item && item.ontology_tags, []),
|
|
1806
|
+
temporary_mitigation_active: normalizeBooleanValue(item && item.temporary_mitigation_active, false),
|
|
1807
|
+
temporary_mitigation_deadline_at: normalizeIsoTimestamp(item && item.temporary_mitigation_deadline_at, '') || null,
|
|
1808
|
+
occurrences: normalizeNonNegativeInteger(item && item.occurrences, 0),
|
|
1809
|
+
created_at: normalizeIsoTimestamp(item && item.created_at, '') || null,
|
|
1810
|
+
updated_at: normalizeIsoTimestamp(item && item.updated_at, nowIso) || nowIso,
|
|
1811
|
+
source,
|
|
1812
|
+
indexed_at: nowIso
|
|
1813
|
+
}))
|
|
1814
|
+
.filter((item) => item.entry_id)
|
|
1815
|
+
: [];
|
|
1816
|
+
|
|
1817
|
+
if (this._useMemoryBackend()) {
|
|
1818
|
+
for (const item of normalizedRecords) {
|
|
1819
|
+
this._memory.errorbook_entry_index[item.entry_id] = { ...item };
|
|
1820
|
+
}
|
|
1821
|
+
return {
|
|
1822
|
+
success: true,
|
|
1823
|
+
written: normalizedRecords.length,
|
|
1824
|
+
total: Object.keys(this._memory.errorbook_entry_index || {}).length
|
|
1825
|
+
};
|
|
1826
|
+
}
|
|
1827
|
+
|
|
1828
|
+
if (!await this.ensureReady()) {
|
|
1829
|
+
return null;
|
|
1830
|
+
}
|
|
1831
|
+
|
|
1832
|
+
const statement = this._db.prepare(`
|
|
1833
|
+
INSERT OR REPLACE INTO errorbook_entry_index_registry(
|
|
1834
|
+
entry_id, fingerprint, title, status, quality_score, tags_json, ontology_tags_json,
|
|
1835
|
+
temporary_mitigation_active, temporary_mitigation_deadline_at, occurrences, created_at, updated_at, source, indexed_at
|
|
1836
|
+
)
|
|
1837
|
+
VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1838
|
+
`);
|
|
1839
|
+
|
|
1840
|
+
this._withTransaction(() => {
|
|
1841
|
+
for (const item of normalizedRecords) {
|
|
1842
|
+
statement.run(
|
|
1843
|
+
item.entry_id,
|
|
1844
|
+
item.fingerprint,
|
|
1845
|
+
item.title,
|
|
1846
|
+
item.status,
|
|
1847
|
+
item.quality_score,
|
|
1848
|
+
JSON.stringify(item.tags || []),
|
|
1849
|
+
JSON.stringify(item.ontology_tags || []),
|
|
1850
|
+
item.temporary_mitigation_active ? 1 : 0,
|
|
1851
|
+
item.temporary_mitigation_deadline_at,
|
|
1852
|
+
item.occurrences,
|
|
1853
|
+
item.created_at,
|
|
1854
|
+
item.updated_at,
|
|
1855
|
+
item.source,
|
|
1856
|
+
item.indexed_at
|
|
1857
|
+
);
|
|
1858
|
+
}
|
|
1859
|
+
});
|
|
1860
|
+
|
|
1861
|
+
const totalRow = this._db
|
|
1862
|
+
.prepare('SELECT COUNT(*) AS total FROM errorbook_entry_index_registry')
|
|
1863
|
+
.get();
|
|
1864
|
+
|
|
1865
|
+
return {
|
|
1866
|
+
success: true,
|
|
1867
|
+
written: normalizedRecords.length,
|
|
1868
|
+
total: normalizeNonNegativeInteger(totalRow && totalRow.total, 0)
|
|
1869
|
+
};
|
|
1870
|
+
}
|
|
1871
|
+
|
|
1872
|
+
async listErrorbookEntryIndexRecords(options = {}) {
|
|
1873
|
+
const limit = normalizeInteger(options.limit, 100);
|
|
1874
|
+
const status = normalizeString(options.status);
|
|
1875
|
+
|
|
1876
|
+
if (this._useMemoryBackend()) {
|
|
1877
|
+
let rows = Object.values(this._memory.errorbook_entry_index || {}).map((item) => ({ ...item }));
|
|
1878
|
+
if (status) {
|
|
1879
|
+
rows = rows.filter((item) => normalizeString(item.status) === status);
|
|
1880
|
+
}
|
|
1881
|
+
rows.sort((left, right) => (Date.parse(right.updated_at || '') || 0) - (Date.parse(left.updated_at || '') || 0));
|
|
1882
|
+
if (limit > 0) {
|
|
1883
|
+
rows = rows.slice(0, limit);
|
|
1884
|
+
}
|
|
1885
|
+
return rows.map((row) => this._mapErrorbookEntryIndexRow({
|
|
1886
|
+
...row,
|
|
1887
|
+
tags_json: JSON.stringify(row.tags || []),
|
|
1888
|
+
ontology_tags_json: JSON.stringify(row.ontology_tags || [])
|
|
1889
|
+
}));
|
|
1890
|
+
}
|
|
1891
|
+
|
|
1892
|
+
if (!await this.ensureReady()) {
|
|
1893
|
+
return null;
|
|
1894
|
+
}
|
|
1895
|
+
|
|
1896
|
+
let query = `
|
|
1897
|
+
SELECT entry_id, fingerprint, title, status, quality_score, tags_json, ontology_tags_json,
|
|
1898
|
+
temporary_mitigation_active, temporary_mitigation_deadline_at, occurrences, created_at, updated_at, source, indexed_at
|
|
1899
|
+
FROM errorbook_entry_index_registry
|
|
1900
|
+
`;
|
|
1901
|
+
const params = [];
|
|
1902
|
+
if (status) {
|
|
1903
|
+
query += ' WHERE status = ?';
|
|
1904
|
+
params.push(status);
|
|
1905
|
+
}
|
|
1906
|
+
query += ' ORDER BY updated_at DESC';
|
|
1907
|
+
if (limit > 0) {
|
|
1908
|
+
query += ' LIMIT ?';
|
|
1909
|
+
params.push(limit);
|
|
1910
|
+
}
|
|
1911
|
+
|
|
1912
|
+
const rows = this._db.prepare(query).all(...params);
|
|
1913
|
+
return rows
|
|
1914
|
+
.map((row) => this._mapErrorbookEntryIndexRow(row))
|
|
1915
|
+
.filter(Boolean);
|
|
1916
|
+
}
|
|
1917
|
+
|
|
1918
|
+
async upsertErrorbookIncidentIndexRecords(records = [], options = {}) {
|
|
1919
|
+
const source = normalizeString(options.source) || 'file.errorbook.incident-index';
|
|
1920
|
+
const nowIso = this.now();
|
|
1921
|
+
const normalizedRecords = Array.isArray(records)
|
|
1922
|
+
? records.map((item) => ({
|
|
1923
|
+
incident_id: normalizeString(item && (item.incident_id || item.id)),
|
|
1924
|
+
fingerprint: normalizeString(item && item.fingerprint) || null,
|
|
1925
|
+
title: normalizeString(item && item.title) || null,
|
|
1926
|
+
symptom: normalizeString(item && item.symptom) || null,
|
|
1927
|
+
state: normalizeString(item && item.state) || null,
|
|
1928
|
+
attempt_count: normalizeNonNegativeInteger(item && item.attempt_count, 0),
|
|
1929
|
+
created_at: normalizeIsoTimestamp(item && item.created_at, '') || null,
|
|
1930
|
+
updated_at: normalizeIsoTimestamp(item && item.updated_at, nowIso) || nowIso,
|
|
1931
|
+
last_attempt_at: normalizeIsoTimestamp(item && item.last_attempt_at, '') || null,
|
|
1932
|
+
resolved_at: normalizeIsoTimestamp(item && item.resolved_at, '') || null,
|
|
1933
|
+
linked_entry_id: normalizeString(item && item.linked_entry_id) || null,
|
|
1934
|
+
source,
|
|
1935
|
+
indexed_at: nowIso
|
|
1936
|
+
}))
|
|
1937
|
+
.filter((item) => item.incident_id)
|
|
1938
|
+
: [];
|
|
1939
|
+
|
|
1940
|
+
if (this._useMemoryBackend()) {
|
|
1941
|
+
for (const item of normalizedRecords) {
|
|
1942
|
+
this._memory.errorbook_incident_index[item.incident_id] = { ...item };
|
|
1943
|
+
}
|
|
1944
|
+
return {
|
|
1945
|
+
success: true,
|
|
1946
|
+
written: normalizedRecords.length,
|
|
1947
|
+
total: Object.keys(this._memory.errorbook_incident_index || {}).length
|
|
1948
|
+
};
|
|
1949
|
+
}
|
|
1950
|
+
|
|
1951
|
+
if (!await this.ensureReady()) {
|
|
1952
|
+
return null;
|
|
1953
|
+
}
|
|
1954
|
+
|
|
1955
|
+
const statement = this._db.prepare(`
|
|
1956
|
+
INSERT OR REPLACE INTO errorbook_incident_index_registry(
|
|
1957
|
+
incident_id, fingerprint, title, symptom, state, attempt_count,
|
|
1958
|
+
created_at, updated_at, last_attempt_at, resolved_at, linked_entry_id, source, indexed_at
|
|
1959
|
+
)
|
|
1960
|
+
VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1961
|
+
`);
|
|
1962
|
+
|
|
1963
|
+
this._withTransaction(() => {
|
|
1964
|
+
for (const item of normalizedRecords) {
|
|
1965
|
+
statement.run(
|
|
1966
|
+
item.incident_id,
|
|
1967
|
+
item.fingerprint,
|
|
1968
|
+
item.title,
|
|
1969
|
+
item.symptom,
|
|
1970
|
+
item.state,
|
|
1971
|
+
item.attempt_count,
|
|
1972
|
+
item.created_at,
|
|
1973
|
+
item.updated_at,
|
|
1974
|
+
item.last_attempt_at,
|
|
1975
|
+
item.resolved_at,
|
|
1976
|
+
item.linked_entry_id,
|
|
1977
|
+
item.source,
|
|
1978
|
+
item.indexed_at
|
|
1979
|
+
);
|
|
1980
|
+
}
|
|
1981
|
+
});
|
|
1982
|
+
|
|
1983
|
+
const totalRow = this._db
|
|
1984
|
+
.prepare('SELECT COUNT(*) AS total FROM errorbook_incident_index_registry')
|
|
1985
|
+
.get();
|
|
1986
|
+
|
|
1987
|
+
return {
|
|
1988
|
+
success: true,
|
|
1989
|
+
written: normalizedRecords.length,
|
|
1990
|
+
total: normalizeNonNegativeInteger(totalRow && totalRow.total, 0)
|
|
1991
|
+
};
|
|
1992
|
+
}
|
|
1993
|
+
|
|
1994
|
+
async listErrorbookIncidentIndexRecords(options = {}) {
|
|
1995
|
+
const limit = normalizeInteger(options.limit, 100);
|
|
1996
|
+
const state = normalizeString(options.state);
|
|
1997
|
+
|
|
1998
|
+
if (this._useMemoryBackend()) {
|
|
1999
|
+
let rows = Object.values(this._memory.errorbook_incident_index || {}).map((item) => ({ ...item }));
|
|
2000
|
+
if (state) {
|
|
2001
|
+
rows = rows.filter((item) => normalizeString(item.state) === state);
|
|
2002
|
+
}
|
|
2003
|
+
rows.sort((left, right) => (Date.parse(right.updated_at || '') || 0) - (Date.parse(left.updated_at || '') || 0));
|
|
2004
|
+
if (limit > 0) {
|
|
2005
|
+
rows = rows.slice(0, limit);
|
|
2006
|
+
}
|
|
2007
|
+
return rows.map((row) => this._mapErrorbookIncidentIndexRow(row));
|
|
2008
|
+
}
|
|
2009
|
+
|
|
2010
|
+
if (!await this.ensureReady()) {
|
|
2011
|
+
return null;
|
|
2012
|
+
}
|
|
2013
|
+
|
|
2014
|
+
let query = `
|
|
2015
|
+
SELECT incident_id, fingerprint, title, symptom, state, attempt_count,
|
|
2016
|
+
created_at, updated_at, last_attempt_at, resolved_at, linked_entry_id, source, indexed_at
|
|
2017
|
+
FROM errorbook_incident_index_registry
|
|
2018
|
+
`;
|
|
2019
|
+
const params = [];
|
|
2020
|
+
if (state) {
|
|
2021
|
+
query += ' WHERE state = ?';
|
|
2022
|
+
params.push(state);
|
|
2023
|
+
}
|
|
2024
|
+
query += ' ORDER BY updated_at DESC';
|
|
2025
|
+
if (limit > 0) {
|
|
2026
|
+
query += ' LIMIT ?';
|
|
2027
|
+
params.push(limit);
|
|
2028
|
+
}
|
|
2029
|
+
|
|
2030
|
+
const rows = this._db.prepare(query).all(...params);
|
|
2031
|
+
return rows
|
|
2032
|
+
.map((row) => this._mapErrorbookIncidentIndexRow(row))
|
|
2033
|
+
.filter(Boolean);
|
|
2034
|
+
}
|
|
2035
|
+
|
|
2036
|
+
async upsertGovernanceSpecSceneOverrideRecords(records = [], options = {}) {
|
|
2037
|
+
const source = normalizeString(options.source) || 'file.spec-governance.spec-scene-overrides';
|
|
2038
|
+
const nowIso = this.now();
|
|
2039
|
+
const normalizedRecords = Array.isArray(records)
|
|
2040
|
+
? records.map((item) => ({
|
|
2041
|
+
spec_id: normalizeString(item && item.spec_id),
|
|
2042
|
+
scene_id: normalizeString(item && item.scene_id),
|
|
2043
|
+
source: normalizeString(item && item.source) || source,
|
|
2044
|
+
rule_id: normalizeString(item && item.rule_id) || null,
|
|
2045
|
+
updated_at: normalizeIsoTimestamp(item && item.updated_at, nowIso) || nowIso,
|
|
2046
|
+
indexed_at: nowIso
|
|
2047
|
+
}))
|
|
2048
|
+
.filter((item) => item.spec_id && item.scene_id)
|
|
2049
|
+
: [];
|
|
2050
|
+
|
|
2051
|
+
if (this._useMemoryBackend()) {
|
|
2052
|
+
for (const item of normalizedRecords) {
|
|
2053
|
+
this._memory.governance_spec_scene_override[item.spec_id] = { ...item };
|
|
2054
|
+
}
|
|
2055
|
+
return {
|
|
2056
|
+
success: true,
|
|
2057
|
+
written: normalizedRecords.length,
|
|
2058
|
+
total: Object.keys(this._memory.governance_spec_scene_override || {}).length
|
|
2059
|
+
};
|
|
2060
|
+
}
|
|
2061
|
+
|
|
2062
|
+
if (!await this.ensureReady()) {
|
|
2063
|
+
return null;
|
|
2064
|
+
}
|
|
2065
|
+
|
|
2066
|
+
const statement = this._db.prepare(`
|
|
2067
|
+
INSERT OR REPLACE INTO governance_spec_scene_override_registry(
|
|
2068
|
+
spec_id, scene_id, source, rule_id, updated_at, indexed_at
|
|
2069
|
+
)
|
|
2070
|
+
VALUES(?, ?, ?, ?, ?, ?)
|
|
2071
|
+
`);
|
|
2072
|
+
|
|
2073
|
+
this._withTransaction(() => {
|
|
2074
|
+
for (const item of normalizedRecords) {
|
|
2075
|
+
statement.run(
|
|
2076
|
+
item.spec_id,
|
|
2077
|
+
item.scene_id,
|
|
2078
|
+
item.source,
|
|
2079
|
+
item.rule_id,
|
|
2080
|
+
item.updated_at,
|
|
2081
|
+
item.indexed_at
|
|
2082
|
+
);
|
|
2083
|
+
}
|
|
2084
|
+
});
|
|
2085
|
+
|
|
2086
|
+
const totalRow = this._db
|
|
2087
|
+
.prepare('SELECT COUNT(*) AS total FROM governance_spec_scene_override_registry')
|
|
2088
|
+
.get();
|
|
2089
|
+
|
|
2090
|
+
return {
|
|
2091
|
+
success: true,
|
|
2092
|
+
written: normalizedRecords.length,
|
|
2093
|
+
total: normalizeNonNegativeInteger(totalRow && totalRow.total, 0)
|
|
2094
|
+
};
|
|
2095
|
+
}
|
|
2096
|
+
|
|
2097
|
+
async listGovernanceSpecSceneOverrideRecords(options = {}) {
|
|
2098
|
+
const limit = normalizeInteger(options.limit, 100);
|
|
2099
|
+
const sceneId = normalizeString(options.sceneId);
|
|
2100
|
+
const specId = normalizeString(options.specId);
|
|
2101
|
+
|
|
2102
|
+
if (this._useMemoryBackend()) {
|
|
2103
|
+
let rows = Object.values(this._memory.governance_spec_scene_override || {}).map((item) => ({ ...item }));
|
|
2104
|
+
if (sceneId) {
|
|
2105
|
+
rows = rows.filter((item) => normalizeString(item.scene_id) === sceneId);
|
|
2106
|
+
}
|
|
2107
|
+
if (specId) {
|
|
2108
|
+
rows = rows.filter((item) => normalizeString(item.spec_id) === specId);
|
|
2109
|
+
}
|
|
2110
|
+
rows.sort((left, right) => (Date.parse(right.updated_at || '') || 0) - (Date.parse(left.updated_at || '') || 0));
|
|
2111
|
+
if (limit > 0) {
|
|
2112
|
+
rows = rows.slice(0, limit);
|
|
2113
|
+
}
|
|
2114
|
+
return rows.map((row) => this._mapGovernanceSpecSceneOverrideRow(row));
|
|
2115
|
+
}
|
|
2116
|
+
|
|
2117
|
+
if (!await this.ensureReady()) {
|
|
2118
|
+
return null;
|
|
2119
|
+
}
|
|
2120
|
+
|
|
2121
|
+
let query = `
|
|
2122
|
+
SELECT spec_id, scene_id, source, rule_id, updated_at, indexed_at
|
|
2123
|
+
FROM governance_spec_scene_override_registry
|
|
2124
|
+
`;
|
|
2125
|
+
const clauses = [];
|
|
2126
|
+
const params = [];
|
|
2127
|
+
if (sceneId) {
|
|
2128
|
+
clauses.push('scene_id = ?');
|
|
2129
|
+
params.push(sceneId);
|
|
2130
|
+
}
|
|
2131
|
+
if (specId) {
|
|
2132
|
+
clauses.push('spec_id = ?');
|
|
2133
|
+
params.push(specId);
|
|
2134
|
+
}
|
|
2135
|
+
if (clauses.length > 0) {
|
|
2136
|
+
query += ` WHERE ${clauses.join(' AND ')}`;
|
|
2137
|
+
}
|
|
2138
|
+
query += ' ORDER BY updated_at DESC, spec_id ASC';
|
|
2139
|
+
if (limit > 0) {
|
|
2140
|
+
query += ' LIMIT ?';
|
|
2141
|
+
params.push(limit);
|
|
2142
|
+
}
|
|
2143
|
+
|
|
2144
|
+
const rows = this._db.prepare(query).all(...params);
|
|
2145
|
+
return rows
|
|
2146
|
+
.map((row) => this._mapGovernanceSpecSceneOverrideRow(row))
|
|
2147
|
+
.filter(Boolean);
|
|
2148
|
+
}
|
|
2149
|
+
|
|
2150
|
+
async upsertGovernanceSceneIndexRecords(records = [], options = {}) {
|
|
2151
|
+
const source = normalizeString(options.source) || 'file.spec-governance.scene-index';
|
|
2152
|
+
const nowIso = this.now();
|
|
2153
|
+
const normalizedRecords = Array.isArray(records)
|
|
2154
|
+
? records.map((item) => ({
|
|
2155
|
+
scene_id: normalizeString(item && item.scene_id),
|
|
2156
|
+
total_specs: normalizeNonNegativeInteger(item && item.total_specs, 0),
|
|
2157
|
+
active_specs: normalizeNonNegativeInteger(item && item.active_specs, 0),
|
|
2158
|
+
completed_specs: normalizeNonNegativeInteger(item && item.completed_specs, 0),
|
|
2159
|
+
stale_specs: normalizeNonNegativeInteger(item && item.stale_specs, 0),
|
|
2160
|
+
spec_ids: normalizeStringArray(item && item.spec_ids, []),
|
|
2161
|
+
active_spec_ids: normalizeStringArray(item && item.active_spec_ids, []),
|
|
2162
|
+
stale_spec_ids: normalizeStringArray(item && item.stale_spec_ids, []),
|
|
2163
|
+
generated_at: normalizeIsoTimestamp(item && item.generated_at, nowIso) || nowIso,
|
|
2164
|
+
scene_filter: normalizeString(item && item.scene_filter) || null,
|
|
2165
|
+
source: normalizeString(item && item.source) || source,
|
|
2166
|
+
indexed_at: nowIso
|
|
2167
|
+
}))
|
|
2168
|
+
.filter((item) => item.scene_id)
|
|
2169
|
+
: [];
|
|
2170
|
+
|
|
2171
|
+
if (this._useMemoryBackend()) {
|
|
2172
|
+
for (const item of normalizedRecords) {
|
|
2173
|
+
this._memory.governance_scene_index[item.scene_id] = { ...item };
|
|
2174
|
+
}
|
|
2175
|
+
return {
|
|
2176
|
+
success: true,
|
|
2177
|
+
written: normalizedRecords.length,
|
|
2178
|
+
total: Object.keys(this._memory.governance_scene_index || {}).length
|
|
2179
|
+
};
|
|
2180
|
+
}
|
|
2181
|
+
|
|
2182
|
+
if (!await this.ensureReady()) {
|
|
2183
|
+
return null;
|
|
2184
|
+
}
|
|
2185
|
+
|
|
2186
|
+
const statement = this._db.prepare(`
|
|
2187
|
+
INSERT OR REPLACE INTO governance_scene_index_registry(
|
|
2188
|
+
scene_id, total_specs, active_specs, completed_specs, stale_specs,
|
|
2189
|
+
spec_ids_json, active_spec_ids_json, stale_spec_ids_json, generated_at,
|
|
2190
|
+
scene_filter, source, indexed_at
|
|
2191
|
+
)
|
|
2192
|
+
VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
2193
|
+
`);
|
|
2194
|
+
|
|
2195
|
+
this._withTransaction(() => {
|
|
2196
|
+
for (const item of normalizedRecords) {
|
|
2197
|
+
statement.run(
|
|
2198
|
+
item.scene_id,
|
|
2199
|
+
item.total_specs,
|
|
2200
|
+
item.active_specs,
|
|
2201
|
+
item.completed_specs,
|
|
2202
|
+
item.stale_specs,
|
|
2203
|
+
JSON.stringify(item.spec_ids || []),
|
|
2204
|
+
JSON.stringify(item.active_spec_ids || []),
|
|
2205
|
+
JSON.stringify(item.stale_spec_ids || []),
|
|
2206
|
+
item.generated_at,
|
|
2207
|
+
item.scene_filter,
|
|
2208
|
+
item.source,
|
|
2209
|
+
item.indexed_at
|
|
2210
|
+
);
|
|
2211
|
+
}
|
|
2212
|
+
});
|
|
2213
|
+
|
|
2214
|
+
const totalRow = this._db
|
|
2215
|
+
.prepare('SELECT COUNT(*) AS total FROM governance_scene_index_registry')
|
|
2216
|
+
.get();
|
|
2217
|
+
|
|
2218
|
+
return {
|
|
2219
|
+
success: true,
|
|
2220
|
+
written: normalizedRecords.length,
|
|
2221
|
+
total: normalizeNonNegativeInteger(totalRow && totalRow.total, 0)
|
|
2222
|
+
};
|
|
2223
|
+
}
|
|
2224
|
+
|
|
2225
|
+
async listGovernanceSceneIndexRecords(options = {}) {
|
|
2226
|
+
const limit = normalizeInteger(options.limit, 100);
|
|
2227
|
+
const sceneId = normalizeString(options.sceneId);
|
|
2228
|
+
|
|
2229
|
+
if (this._useMemoryBackend()) {
|
|
2230
|
+
let rows = Object.values(this._memory.governance_scene_index || {}).map((item) => ({ ...item }));
|
|
2231
|
+
if (sceneId) {
|
|
2232
|
+
rows = rows.filter((item) => normalizeString(item.scene_id) === sceneId);
|
|
2233
|
+
}
|
|
2234
|
+
rows.sort((left, right) => {
|
|
2235
|
+
if (right.total_specs !== left.total_specs) {
|
|
2236
|
+
return right.total_specs - left.total_specs;
|
|
2237
|
+
}
|
|
2238
|
+
return `${left.scene_id}`.localeCompare(`${right.scene_id}`);
|
|
2239
|
+
});
|
|
2240
|
+
if (limit > 0) {
|
|
2241
|
+
rows = rows.slice(0, limit);
|
|
2242
|
+
}
|
|
2243
|
+
return rows.map((row) => this._mapGovernanceSceneIndexRow({
|
|
2244
|
+
...row,
|
|
2245
|
+
spec_ids_json: JSON.stringify(row.spec_ids || []),
|
|
2246
|
+
active_spec_ids_json: JSON.stringify(row.active_spec_ids || []),
|
|
2247
|
+
stale_spec_ids_json: JSON.stringify(row.stale_spec_ids || [])
|
|
2248
|
+
}));
|
|
2249
|
+
}
|
|
2250
|
+
|
|
2251
|
+
if (!await this.ensureReady()) {
|
|
2252
|
+
return null;
|
|
2253
|
+
}
|
|
2254
|
+
|
|
2255
|
+
let query = `
|
|
2256
|
+
SELECT scene_id, total_specs, active_specs, completed_specs, stale_specs,
|
|
2257
|
+
spec_ids_json, active_spec_ids_json, stale_spec_ids_json, generated_at,
|
|
2258
|
+
scene_filter, source, indexed_at
|
|
2259
|
+
FROM governance_scene_index_registry
|
|
2260
|
+
`;
|
|
2261
|
+
const params = [];
|
|
2262
|
+
if (sceneId) {
|
|
2263
|
+
query += ' WHERE scene_id = ?';
|
|
2264
|
+
params.push(sceneId);
|
|
2265
|
+
}
|
|
2266
|
+
query += ' ORDER BY total_specs DESC, active_specs DESC, scene_id ASC';
|
|
2267
|
+
if (limit > 0) {
|
|
2268
|
+
query += ' LIMIT ?';
|
|
2269
|
+
params.push(limit);
|
|
2270
|
+
}
|
|
2271
|
+
|
|
2272
|
+
const rows = this._db.prepare(query).all(...params);
|
|
2273
|
+
return rows
|
|
2274
|
+
.map((row) => this._mapGovernanceSceneIndexRow(row))
|
|
2275
|
+
.filter(Boolean);
|
|
2276
|
+
}
|
|
2277
|
+
|
|
2278
|
+
async upsertReleaseEvidenceRunRecords(records = [], options = {}) {
|
|
2279
|
+
const source = normalizeString(options.source) || 'file.release-evidence.handoff-runs';
|
|
2280
|
+
const nowIso = this.now();
|
|
2281
|
+
const normalizedRecords = Array.isArray(records)
|
|
2282
|
+
? records.map((item) => ({
|
|
2283
|
+
session_id: normalizeString(item && item.session_id),
|
|
2284
|
+
merged_at: normalizeIsoTimestamp(item && item.merged_at, '') || null,
|
|
2285
|
+
status: normalizeString(item && item.status) || null,
|
|
2286
|
+
gate_passed: normalizeBooleanValue(item && item.gate_passed, false),
|
|
2287
|
+
spec_success_rate_percent: Number.isFinite(Number(item && item.spec_success_rate_percent))
|
|
2288
|
+
? Number(item.spec_success_rate_percent)
|
|
2289
|
+
: null,
|
|
2290
|
+
risk_level: normalizeString(item && item.risk_level) || null,
|
|
2291
|
+
ontology_quality_score: Number.isFinite(Number(item && item.ontology_quality_score))
|
|
2292
|
+
? Number(item.ontology_quality_score)
|
|
2293
|
+
: null,
|
|
2294
|
+
capability_coverage_percent: Number.isFinite(Number(item && item.capability_coverage_percent))
|
|
2295
|
+
? Number(item.capability_coverage_percent)
|
|
2296
|
+
: null,
|
|
2297
|
+
capability_coverage_passed: normalizeBooleanValue(item && item.capability_coverage_passed, false),
|
|
2298
|
+
scene_package_batch_passed: normalizeBooleanValue(item && item.scene_package_batch_passed, false),
|
|
2299
|
+
scene_package_batch_failure_count: normalizeNonNegativeInteger(item && item.scene_package_batch_failure_count, 0),
|
|
2300
|
+
failed_goals: normalizeNonNegativeInteger(item && item.failed_goals, 0),
|
|
2301
|
+
release_gate_preflight_available: normalizeBooleanValue(item && item.release_gate_preflight_available, false),
|
|
2302
|
+
release_gate_preflight_blocked: normalizeBooleanValue(item && item.release_gate_preflight_blocked, false),
|
|
2303
|
+
source_updated_at: normalizeIsoTimestamp(item && item.source_updated_at, '') || null,
|
|
2304
|
+
source: normalizeString(item && item.source) || source,
|
|
2305
|
+
indexed_at: nowIso
|
|
2306
|
+
}))
|
|
2307
|
+
.filter((item) => item.session_id)
|
|
2308
|
+
: [];
|
|
2309
|
+
|
|
2310
|
+
if (this._useMemoryBackend()) {
|
|
2311
|
+
for (const item of normalizedRecords) {
|
|
2312
|
+
this._memory.release_evidence_run[item.session_id] = { ...item };
|
|
2313
|
+
}
|
|
2314
|
+
return {
|
|
2315
|
+
success: true,
|
|
2316
|
+
written: normalizedRecords.length,
|
|
2317
|
+
total: Object.keys(this._memory.release_evidence_run || {}).length
|
|
2318
|
+
};
|
|
2319
|
+
}
|
|
2320
|
+
|
|
2321
|
+
if (!await this.ensureReady()) {
|
|
2322
|
+
return null;
|
|
2323
|
+
}
|
|
2324
|
+
|
|
2325
|
+
const statement = this._db.prepare(`
|
|
2326
|
+
INSERT OR REPLACE INTO release_evidence_run_registry(
|
|
2327
|
+
session_id, merged_at, status, gate_passed, spec_success_rate_percent, risk_level,
|
|
2328
|
+
ontology_quality_score, capability_coverage_percent, capability_coverage_passed,
|
|
2329
|
+
scene_package_batch_passed, scene_package_batch_failure_count, failed_goals,
|
|
2330
|
+
release_gate_preflight_available, release_gate_preflight_blocked,
|
|
2331
|
+
source_updated_at, source, indexed_at
|
|
2332
|
+
)
|
|
2333
|
+
VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
2334
|
+
`);
|
|
2335
|
+
|
|
2336
|
+
this._withTransaction(() => {
|
|
2337
|
+
for (const item of normalizedRecords) {
|
|
2338
|
+
statement.run(
|
|
2339
|
+
item.session_id,
|
|
2340
|
+
item.merged_at,
|
|
2341
|
+
item.status,
|
|
2342
|
+
item.gate_passed ? 1 : 0,
|
|
2343
|
+
item.spec_success_rate_percent,
|
|
2344
|
+
item.risk_level,
|
|
2345
|
+
item.ontology_quality_score,
|
|
2346
|
+
item.capability_coverage_percent,
|
|
2347
|
+
item.capability_coverage_passed ? 1 : 0,
|
|
2348
|
+
item.scene_package_batch_passed ? 1 : 0,
|
|
2349
|
+
item.scene_package_batch_failure_count,
|
|
2350
|
+
item.failed_goals,
|
|
2351
|
+
item.release_gate_preflight_available ? 1 : 0,
|
|
2352
|
+
item.release_gate_preflight_blocked ? 1 : 0,
|
|
2353
|
+
item.source_updated_at,
|
|
2354
|
+
item.source,
|
|
2355
|
+
item.indexed_at
|
|
2356
|
+
);
|
|
2357
|
+
}
|
|
2358
|
+
});
|
|
2359
|
+
|
|
2360
|
+
const totalRow = this._db
|
|
2361
|
+
.prepare('SELECT COUNT(*) AS total FROM release_evidence_run_registry')
|
|
2362
|
+
.get();
|
|
2363
|
+
|
|
2364
|
+
return {
|
|
2365
|
+
success: true,
|
|
2366
|
+
written: normalizedRecords.length,
|
|
2367
|
+
total: normalizeNonNegativeInteger(totalRow && totalRow.total, 0)
|
|
2368
|
+
};
|
|
2369
|
+
}
|
|
2370
|
+
|
|
2371
|
+
async listReleaseEvidenceRunRecords(options = {}) {
|
|
2372
|
+
const limit = normalizeInteger(options.limit, 100);
|
|
2373
|
+
const status = normalizeString(options.status);
|
|
2374
|
+
const riskLevel = normalizeString(options.riskLevel || options.risk_level);
|
|
2375
|
+
|
|
2376
|
+
if (this._useMemoryBackend()) {
|
|
2377
|
+
let rows = Object.values(this._memory.release_evidence_run || {}).map((item) => ({ ...item }));
|
|
2378
|
+
if (status) {
|
|
2379
|
+
rows = rows.filter((item) => normalizeString(item.status) === status);
|
|
2380
|
+
}
|
|
2381
|
+
if (riskLevel) {
|
|
2382
|
+
rows = rows.filter((item) => normalizeString(item.risk_level) === riskLevel);
|
|
2383
|
+
}
|
|
2384
|
+
rows.sort((left, right) => {
|
|
2385
|
+
const leftTs = Date.parse(left.merged_at || '') || 0;
|
|
2386
|
+
const rightTs = Date.parse(right.merged_at || '') || 0;
|
|
2387
|
+
if (rightTs !== leftTs) {
|
|
2388
|
+
return rightTs - leftTs;
|
|
2389
|
+
}
|
|
2390
|
+
return `${right.session_id}`.localeCompare(`${left.session_id}`);
|
|
2391
|
+
});
|
|
2392
|
+
if (limit > 0) {
|
|
2393
|
+
rows = rows.slice(0, limit);
|
|
2394
|
+
}
|
|
2395
|
+
return rows.map((row) => this._mapReleaseEvidenceRunRow(row));
|
|
2396
|
+
}
|
|
2397
|
+
|
|
2398
|
+
if (!await this.ensureReady()) {
|
|
2399
|
+
return null;
|
|
2400
|
+
}
|
|
2401
|
+
|
|
2402
|
+
let query = `
|
|
2403
|
+
SELECT session_id, merged_at, status, gate_passed, spec_success_rate_percent, risk_level,
|
|
2404
|
+
ontology_quality_score, capability_coverage_percent, capability_coverage_passed,
|
|
2405
|
+
scene_package_batch_passed, scene_package_batch_failure_count, failed_goals,
|
|
2406
|
+
release_gate_preflight_available, release_gate_preflight_blocked, source_updated_at,
|
|
2407
|
+
source, indexed_at
|
|
2408
|
+
FROM release_evidence_run_registry
|
|
2409
|
+
`;
|
|
2410
|
+
const clauses = [];
|
|
2411
|
+
const params = [];
|
|
2412
|
+
if (status) {
|
|
2413
|
+
clauses.push('status = ?');
|
|
2414
|
+
params.push(status);
|
|
2415
|
+
}
|
|
2416
|
+
if (riskLevel) {
|
|
2417
|
+
clauses.push('risk_level = ?');
|
|
2418
|
+
params.push(riskLevel);
|
|
2419
|
+
}
|
|
2420
|
+
if (clauses.length > 0) {
|
|
2421
|
+
query += ` WHERE ${clauses.join(' AND ')}`;
|
|
2422
|
+
}
|
|
2423
|
+
query += ' ORDER BY merged_at DESC, session_id DESC';
|
|
2424
|
+
if (limit > 0) {
|
|
2425
|
+
query += ' LIMIT ?';
|
|
2426
|
+
params.push(limit);
|
|
2427
|
+
}
|
|
2428
|
+
|
|
2429
|
+
const rows = this._db.prepare(query).all(...params);
|
|
2430
|
+
return rows
|
|
2431
|
+
.map((row) => this._mapReleaseEvidenceRunRow(row))
|
|
2432
|
+
.filter(Boolean);
|
|
2433
|
+
}
|
|
2434
|
+
|
|
2435
|
+
async upsertReleaseGateHistoryRecords(records = [], options = {}) {
|
|
2436
|
+
const source = normalizeString(options.source) || 'file.release-evidence.gate-history';
|
|
2437
|
+
const nowIso = this.now();
|
|
2438
|
+
const normalizedRecords = Array.isArray(records)
|
|
2439
|
+
? records.map((item) => ({
|
|
2440
|
+
tag: normalizeString(item && item.tag),
|
|
2441
|
+
evaluated_at: normalizeIsoTimestamp(item && item.evaluated_at, '') || null,
|
|
2442
|
+
gate_passed: normalizeBooleanValue(item && item.gate_passed, false),
|
|
2443
|
+
enforce: normalizeBooleanValue(item && item.enforce, false),
|
|
2444
|
+
risk_level: normalizeString(item && item.risk_level) || null,
|
|
2445
|
+
spec_success_rate_percent: Number.isFinite(Number(item && item.spec_success_rate_percent))
|
|
2446
|
+
? Number(item.spec_success_rate_percent)
|
|
2447
|
+
: null,
|
|
2448
|
+
scene_package_batch_passed: normalizeBooleanValue(item && item.scene_package_batch_passed, false),
|
|
2449
|
+
scene_package_batch_failure_count: normalizeNonNegativeInteger(item && item.scene_package_batch_failure_count, 0),
|
|
2450
|
+
capability_expected_unknown_count: normalizeNonNegativeInteger(item && item.capability_expected_unknown_count, 0),
|
|
2451
|
+
capability_provided_unknown_count: normalizeNonNegativeInteger(item && item.capability_provided_unknown_count, 0),
|
|
2452
|
+
release_gate_preflight_available: normalizeBooleanValue(item && item.release_gate_preflight_available, false),
|
|
2453
|
+
release_gate_preflight_blocked: normalizeBooleanValue(item && item.release_gate_preflight_blocked, false),
|
|
2454
|
+
require_release_gate_preflight: normalizeBooleanValue(item && item.require_release_gate_preflight, false),
|
|
2455
|
+
drift_alert_count: normalizeNonNegativeInteger(item && item.drift_alert_count, 0),
|
|
2456
|
+
drift_blocked: normalizeBooleanValue(item && item.drift_blocked, false),
|
|
2457
|
+
weekly_ops_blocked: normalizeBooleanValue(item && item.weekly_ops_blocked, false),
|
|
2458
|
+
source_updated_at: normalizeIsoTimestamp(item && item.source_updated_at, '') || null,
|
|
2459
|
+
source: normalizeString(item && item.source) || source,
|
|
2460
|
+
indexed_at: nowIso
|
|
2461
|
+
}))
|
|
2462
|
+
.filter((item) => item.tag)
|
|
2463
|
+
: [];
|
|
2464
|
+
|
|
2465
|
+
if (this._useMemoryBackend()) {
|
|
2466
|
+
for (const item of normalizedRecords) {
|
|
2467
|
+
this._memory.release_gate_history[item.tag] = { ...item };
|
|
2468
|
+
}
|
|
2469
|
+
return {
|
|
2470
|
+
success: true,
|
|
2471
|
+
written: normalizedRecords.length,
|
|
2472
|
+
total: Object.keys(this._memory.release_gate_history || {}).length
|
|
2473
|
+
};
|
|
2474
|
+
}
|
|
2475
|
+
|
|
2476
|
+
if (!await this.ensureReady()) {
|
|
2477
|
+
return null;
|
|
2478
|
+
}
|
|
2479
|
+
|
|
2480
|
+
const statement = this._db.prepare(`
|
|
2481
|
+
INSERT OR REPLACE INTO release_gate_history_registry(
|
|
2482
|
+
tag, evaluated_at, gate_passed, enforce, risk_level, spec_success_rate_percent,
|
|
2483
|
+
scene_package_batch_passed, scene_package_batch_failure_count,
|
|
2484
|
+
capability_expected_unknown_count, capability_provided_unknown_count,
|
|
2485
|
+
release_gate_preflight_available, release_gate_preflight_blocked,
|
|
2486
|
+
require_release_gate_preflight, drift_alert_count, drift_blocked,
|
|
2487
|
+
weekly_ops_blocked, source_updated_at, source, indexed_at
|
|
2488
|
+
)
|
|
2489
|
+
VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
2490
|
+
`);
|
|
2491
|
+
|
|
2492
|
+
this._withTransaction(() => {
|
|
2493
|
+
for (const item of normalizedRecords) {
|
|
2494
|
+
statement.run(
|
|
2495
|
+
item.tag,
|
|
2496
|
+
item.evaluated_at,
|
|
2497
|
+
item.gate_passed ? 1 : 0,
|
|
2498
|
+
item.enforce ? 1 : 0,
|
|
2499
|
+
item.risk_level,
|
|
2500
|
+
item.spec_success_rate_percent,
|
|
2501
|
+
item.scene_package_batch_passed ? 1 : 0,
|
|
2502
|
+
item.scene_package_batch_failure_count,
|
|
2503
|
+
item.capability_expected_unknown_count,
|
|
2504
|
+
item.capability_provided_unknown_count,
|
|
2505
|
+
item.release_gate_preflight_available ? 1 : 0,
|
|
2506
|
+
item.release_gate_preflight_blocked ? 1 : 0,
|
|
2507
|
+
item.require_release_gate_preflight ? 1 : 0,
|
|
2508
|
+
item.drift_alert_count,
|
|
2509
|
+
item.drift_blocked ? 1 : 0,
|
|
2510
|
+
item.weekly_ops_blocked ? 1 : 0,
|
|
2511
|
+
item.source_updated_at,
|
|
2512
|
+
item.source,
|
|
2513
|
+
item.indexed_at
|
|
2514
|
+
);
|
|
2515
|
+
}
|
|
2516
|
+
});
|
|
2517
|
+
|
|
2518
|
+
const totalRow = this._db
|
|
2519
|
+
.prepare('SELECT COUNT(*) AS total FROM release_gate_history_registry')
|
|
2520
|
+
.get();
|
|
2521
|
+
|
|
2522
|
+
return {
|
|
2523
|
+
success: true,
|
|
2524
|
+
written: normalizedRecords.length,
|
|
2525
|
+
total: normalizeNonNegativeInteger(totalRow && totalRow.total, 0)
|
|
2526
|
+
};
|
|
2527
|
+
}
|
|
2528
|
+
|
|
2529
|
+
async listReleaseGateHistoryRecords(options = {}) {
|
|
2530
|
+
const limit = normalizeInteger(options.limit, 100);
|
|
2531
|
+
const tag = normalizeString(options.tag);
|
|
2532
|
+
const riskLevel = normalizeString(options.riskLevel || options.risk_level);
|
|
2533
|
+
|
|
2534
|
+
if (this._useMemoryBackend()) {
|
|
2535
|
+
let rows = Object.values(this._memory.release_gate_history || {}).map((item) => ({ ...item }));
|
|
2536
|
+
if (tag) {
|
|
2537
|
+
rows = rows.filter((item) => normalizeString(item.tag) === tag);
|
|
2538
|
+
}
|
|
2539
|
+
if (riskLevel) {
|
|
2540
|
+
rows = rows.filter((item) => normalizeString(item.risk_level) === riskLevel);
|
|
2541
|
+
}
|
|
2542
|
+
rows.sort((left, right) => {
|
|
2543
|
+
const leftTs = Date.parse(left.evaluated_at || '') || 0;
|
|
2544
|
+
const rightTs = Date.parse(right.evaluated_at || '') || 0;
|
|
2545
|
+
if (rightTs !== leftTs) {
|
|
2546
|
+
return rightTs - leftTs;
|
|
2547
|
+
}
|
|
2548
|
+
return `${right.tag}`.localeCompare(`${left.tag}`);
|
|
2549
|
+
});
|
|
2550
|
+
if (limit > 0) {
|
|
2551
|
+
rows = rows.slice(0, limit);
|
|
2552
|
+
}
|
|
2553
|
+
return rows.map((row) => this._mapReleaseGateHistoryRow(row));
|
|
2554
|
+
}
|
|
2555
|
+
|
|
2556
|
+
if (!await this.ensureReady()) {
|
|
2557
|
+
return null;
|
|
2558
|
+
}
|
|
2559
|
+
|
|
2560
|
+
let query = `
|
|
2561
|
+
SELECT tag, evaluated_at, gate_passed, enforce, risk_level, spec_success_rate_percent,
|
|
2562
|
+
scene_package_batch_passed, scene_package_batch_failure_count,
|
|
2563
|
+
capability_expected_unknown_count, capability_provided_unknown_count,
|
|
2564
|
+
release_gate_preflight_available, release_gate_preflight_blocked,
|
|
2565
|
+
require_release_gate_preflight, drift_alert_count, drift_blocked,
|
|
2566
|
+
weekly_ops_blocked, source_updated_at, source, indexed_at
|
|
2567
|
+
FROM release_gate_history_registry
|
|
2568
|
+
`;
|
|
2569
|
+
const clauses = [];
|
|
2570
|
+
const params = [];
|
|
2571
|
+
if (tag) {
|
|
2572
|
+
clauses.push('tag = ?');
|
|
2573
|
+
params.push(tag);
|
|
2574
|
+
}
|
|
2575
|
+
if (riskLevel) {
|
|
2576
|
+
clauses.push('risk_level = ?');
|
|
2577
|
+
params.push(riskLevel);
|
|
2578
|
+
}
|
|
2579
|
+
if (clauses.length > 0) {
|
|
2580
|
+
query += ` WHERE ${clauses.join(' AND ')}`;
|
|
2581
|
+
}
|
|
2582
|
+
query += ' ORDER BY evaluated_at DESC, tag DESC';
|
|
2583
|
+
if (limit > 0) {
|
|
2584
|
+
query += ' LIMIT ?';
|
|
2585
|
+
params.push(limit);
|
|
2586
|
+
}
|
|
2587
|
+
|
|
2588
|
+
const rows = this._db.prepare(query).all(...params);
|
|
2589
|
+
return rows
|
|
2590
|
+
.map((row) => this._mapReleaseGateHistoryRow(row))
|
|
2591
|
+
.filter(Boolean);
|
|
2592
|
+
}
|
|
2593
|
+
|
|
1517
2594
|
_resolveOrCreateTaskRefInMemory(options = {}) {
|
|
1518
2595
|
const sceneId = normalizeString(options.sceneId);
|
|
1519
2596
|
const specId = normalizeString(options.specId);
|