scene-capability-engine 3.6.5 → 3.6.8

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 CHANGED
@@ -7,6 +7,44 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [3.6.8] - 2026-03-05
11
+
12
+ ### Added
13
+ - New one-shot state command:
14
+ - `sce state reconcile --all --apply --json`
15
+ - runs `doctor -> migrate -> doctor` with before/after drift summary in one payload
16
+ - Release workflow now runs state reconcile before reconciliation gate and uploads:
17
+ - `state-reconcile-<tag>.json`
18
+
19
+ ### Changed
20
+ - Release workflow state reconciliation gate is now enforce-by-default (`KSE_RELEASE_STATE_MIGRATION_ENFORCE` defaults to `true`).
21
+ - Release workflow Node runtime updated to `22.x` so sqlite-backed reconciliation can run in release pipeline.
22
+
23
+ ## [3.6.7] - 2026-03-05
24
+
25
+ ### Fixed
26
+ - `state-migration-reconciliation-gate` now treats `blocking` checks as advisory by default and only fails when `--fail-on-blocking` is explicitly set.
27
+ - Release workflow reconciliation step now adds `--fail-on-blocking` only in enforce mode, preventing advisory mode from failing npm publish.
28
+
29
+ ## [3.6.6] - 2026-03-05
30
+
31
+ ### Added
32
+ - Stage-4 SQLite migration components for release evidence indexes:
33
+ - `release.evidence-runs-index` (`.sce/reports/release-evidence/handoff-runs.json`)
34
+ - `release.gate-history-index` (`.sce/reports/release-evidence/release-gate-history.json`)
35
+ - New SQLite index tables in state store:
36
+ - `release_evidence_run_registry`
37
+ - `release_gate_history_registry`
38
+ - New `SceStateStore` APIs:
39
+ - `upsert/listReleaseEvidenceRunRecords`
40
+ - `upsert/listReleaseGateHistoryRecords`
41
+ - Release workflow gate integration:
42
+ - new step `state_migration_reconciliation`
43
+ - new release asset `.sce/reports/release-evidence/state-migration-reconciliation-<tag>.json`
44
+
45
+ ### Changed
46
+ - `sce state plan/doctor/migrate/export` now includes release-evidence components in migration scope and parity summaries.
47
+
10
48
  ## [3.6.5] - 2026-03-05
11
49
 
12
50
  ### Added
package/README.md CHANGED
@@ -149,11 +149,13 @@ Studio task-stream output contract (default):
149
149
  - `sce state plan --json`
150
150
  - `sce state doctor --json`
151
151
  - `sce state migrate --all --apply --json`
152
+ - `sce state reconcile --all --apply --json` (doctor -> migrate -> doctor one-shot)
152
153
  - `sce state export --out .sce/reports/state-migration/state-export.latest.json --json`
153
154
  - reconciliation gate: `npm run gate:state-migration-reconciliation`
155
+ - release workflow defaults to enforce mode for state reconciliation gate and runs reconcile before publish
154
156
  - runtime reads now prefer sqlite indexes for timeline/scene-session views when indexed data exists
155
157
  - `state doctor` now emits `summary` and runtime diagnostics (`runtime.timeline`, `runtime.scene_session`) with read-source and consistency status
156
- - migratable components now include errorbook + spec-governance indexes (`errorbook.entry-index`, `errorbook.incident-index`, `governance.spec-scene-overrides`, `governance.scene-index`) in addition to runtime registries
158
+ - migratable components now include runtime + errorbook + spec-governance + release evidence indexes (`errorbook.entry-index`, `errorbook.incident-index`, `governance.spec-scene-overrides`, `governance.scene-index`, `release.evidence-runs-index`, `release.gate-history-index`)
157
159
  - Write authorization lease model (SQLite-backed):
158
160
  - policy file: `.sce/config/authorization-policy.json`
159
161
  - grant lease: `sce auth grant --scope studio:* --reason "<reason>" --auth-password <password> --json`
package/README.zh.md CHANGED
@@ -149,11 +149,13 @@ Studio 任务流输出契约(默认):
149
149
  - `sce state plan --json`
150
150
  - `sce state doctor --json`
151
151
  - `sce state migrate --all --apply --json`
152
+ - `sce state reconcile --all --apply --json`(一键执行 doctor -> migrate -> doctor)
152
153
  - `sce state export --out .sce/reports/state-migration/state-export.latest.json --json`
153
154
  - 对账门禁:`npm run gate:state-migration-reconciliation`
155
+ - 发布工作流默认对 state reconciliation 启用 enforce,并在发布前执行 reconcile
154
156
  - 运行时读取在存在索引数据时优先使用 SQLite(timeline/scene-session 视图)
155
157
  - `state doctor` 新增 `summary` 与运行时诊断(`runtime.timeline`、`runtime.scene_session`),可直接读取读源与一致性状态
156
- - 可迁移组件扩展到 errorbook + spec-governance 索引(`errorbook.entry-index`、`errorbook.incident-index`、`governance.spec-scene-overrides`、`governance.scene-index`)
158
+ - 可迁移组件扩展到 runtime + errorbook + spec-governance + release evidence 索引(`errorbook.entry-index`、`errorbook.incident-index`、`governance.spec-scene-overrides`、`governance.scene-index`、`release.evidence-runs-index`、`release.gate-history-index`)
157
159
  - 写入授权租约模型(SQLite 持久化):
158
160
  - 策略文件:`.sce/config/authorization-policy.json`
159
161
  - 申请租约:`sce auth grant --scope studio:* --reason "<原因>" --auth-password <密码> --json`
@@ -719,6 +719,9 @@ sce state migrate --all --json
719
719
  # apply migration writes into sqlite index tables
720
720
  sce state migrate --all --apply --json
721
721
 
722
+ # reconcile in one flow (doctor -> migrate -> doctor)
723
+ sce state reconcile --all --apply --json
724
+
722
725
  # migrate specific components
723
726
  sce state migrate --component collab.agent-registry --component runtime.timeline-index --apply --json
724
727
 
@@ -727,7 +730,7 @@ sce state export --out .sce/reports/state-migration/state-export.latest.json --j
727
730
 
728
731
  # reconciliation gate (non-blocking by default; choose strict flags as needed)
729
732
  npm run gate:state-migration-reconciliation
730
- node scripts/state-migration-reconciliation-gate.js --fail-on-alert --fail-on-pending --json
733
+ node scripts/state-migration-reconciliation-gate.js --fail-on-blocking --fail-on-alert --fail-on-pending --json
731
734
  ```
732
735
 
733
736
  Current migratable components:
@@ -738,6 +741,8 @@ Current migratable components:
738
741
  - `errorbook.incident-index` (`.sce/errorbook/staging/index.json`)
739
742
  - `governance.spec-scene-overrides` (`.sce/spec-governance/spec-scene-overrides.json`)
740
743
  - `governance.scene-index` (`.sce/spec-governance/scene-index.json`)
744
+ - `release.evidence-runs-index` (`.sce/reports/release-evidence/handoff-runs.json`)
745
+ - `release.gate-history-index` (`.sce/reports/release-evidence/release-gate-history.json`)
741
746
 
742
747
  SQLite index tables introduced for gradual migration:
743
748
  - `agent_runtime_registry`
@@ -747,11 +752,14 @@ SQLite index tables introduced for gradual migration:
747
752
  - `errorbook_incident_index_registry`
748
753
  - `governance_spec_scene_override_registry`
749
754
  - `governance_scene_index_registry`
755
+ - `release_evidence_run_registry`
756
+ - `release_gate_history_registry`
750
757
  - `state_migration_registry`
751
758
 
752
759
  Runtime read preference:
753
760
  - timeline/session runtime views now prefer SQLite indexes when indexed rows exist.
754
761
  - file artifacts remain source-of-truth for content payload and recovery operations.
762
+ - release workflow runs `state reconcile --all --apply` before reconciliation gate.
755
763
  - `sce state doctor --json` now includes:
756
764
  - `summary` aggregate (`pending_components`, `total_record_drift`, `blocking_count`, `alert_count`)
757
765
  - runtime read diagnostics (`runtime.timeline`, `runtime.scene_session`) with read-source/read-preference and consistency status
@@ -8,6 +8,7 @@ const {
8
8
  runStateDoctor,
9
9
  runStateExport
10
10
  } = require('../state/state-migration-manager');
11
+ const { getSceStateStore } = require('../state/sce-state-store');
11
12
 
12
13
  function normalizeString(value) {
13
14
  if (typeof value !== 'string') {
@@ -147,6 +148,85 @@ async function runStateExportCommand(options = {}, dependencies = {}) {
147
148
  return payload;
148
149
  }
149
150
 
151
+ async function runStateReconcileCommand(options = {}, dependencies = {}) {
152
+ const projectPath = dependencies.projectPath || process.cwd();
153
+ const fileSystem = dependencies.fileSystem || fs;
154
+ const env = dependencies.env || process.env;
155
+ const components = normalizeComponentInput(options.component);
156
+ const componentIds = options.all === true ? [] : components;
157
+ const apply = options.apply === true;
158
+ const stateStore = dependencies.stateStore || getSceStateStore(projectPath, {
159
+ fileSystem,
160
+ env
161
+ });
162
+
163
+ const before = await runStateDoctor({}, {
164
+ projectPath,
165
+ fileSystem,
166
+ env,
167
+ stateStore
168
+ });
169
+
170
+ const migration = await runStateMigration({
171
+ apply,
172
+ all: options.all === true,
173
+ componentIds
174
+ }, {
175
+ projectPath,
176
+ fileSystem,
177
+ env,
178
+ stateStore
179
+ });
180
+
181
+ const after = await runStateDoctor({}, {
182
+ projectPath,
183
+ fileSystem,
184
+ env,
185
+ stateStore
186
+ });
187
+
188
+ const beforePending = Number(before && before.summary && before.summary.pending_components) || 0;
189
+ const afterPending = Number(after && after.summary && after.summary.pending_components) || 0;
190
+ const beforeBlocking = Number(before && before.summary && before.summary.blocking_count) || 0;
191
+ const afterBlocking = Number(after && after.summary && after.summary.blocking_count) || 0;
192
+ const pendingReduced = Math.max(0, beforePending - afterPending);
193
+ const blockingReduced = Math.max(0, beforeBlocking - afterBlocking);
194
+
195
+ const payload = {
196
+ mode: 'state-reconcile',
197
+ success: Boolean(migration && migration.success) && afterBlocking === 0,
198
+ apply,
199
+ generated_at: new Date().toISOString(),
200
+ store_path: after && after.store_path ? after.store_path : null,
201
+ sqlite: after && after.sqlite ? after.sqlite : null,
202
+ migration,
203
+ before: {
204
+ summary: before && before.summary ? before.summary : null,
205
+ blocking: Array.isArray(before && before.blocking) ? before.blocking : [],
206
+ alerts: Array.isArray(before && before.alerts) ? before.alerts : []
207
+ },
208
+ after: {
209
+ summary: after && after.summary ? after.summary : null,
210
+ blocking: Array.isArray(after && after.blocking) ? after.blocking : [],
211
+ alerts: Array.isArray(after && after.alerts) ? after.alerts : []
212
+ },
213
+ summary: {
214
+ apply,
215
+ migrated_components: Number(migration && migration.summary && migration.summary.migrated_components) || 0,
216
+ migrated_records: Number(migration && migration.summary && migration.summary.migrated_records) || 0,
217
+ before_pending_components: beforePending,
218
+ after_pending_components: afterPending,
219
+ pending_components_reduced: pendingReduced,
220
+ before_blocking_count: beforeBlocking,
221
+ after_blocking_count: afterBlocking,
222
+ blocking_reduced: blockingReduced
223
+ }
224
+ };
225
+
226
+ printPayload(payload, options, 'State Reconcile');
227
+ return payload;
228
+ }
229
+
150
230
  async function safeRun(handler, options = {}, dependencies = {}, title = 'state command') {
151
231
  try {
152
232
  await handler(options, dependencies);
@@ -199,6 +279,15 @@ function registerStateCommands(program) {
199
279
  .option('--out <path>', 'Output file path', '.sce/reports/state-migration/state-export.latest.json')
200
280
  .option('--json', 'Print machine-readable JSON output')
201
281
  .action(async (options) => safeRun(runStateExportCommand, options, {}, 'state export'));
282
+
283
+ state
284
+ .command('reconcile')
285
+ .description('Run doctor + migrate + doctor in one flow (dry-run by default)')
286
+ .option('--component <id>', `Component id (repeatable): ${knownIds}`, collectOptionValue, [])
287
+ .option('--all', 'Reconcile all known components')
288
+ .option('--apply', 'Apply migration writes (default is dry-run)')
289
+ .option('--json', 'Print machine-readable JSON output')
290
+ .action(async (options) => safeRun(runStateReconcileCommand, options, {}, 'state reconcile'));
202
291
  }
203
292
 
204
293
  module.exports = {
@@ -206,5 +295,6 @@ module.exports = {
206
295
  runStateDoctorCommand,
207
296
  runStateMigrateCommand,
208
297
  runStateExportCommand,
298
+ runStateReconcileCommand,
209
299
  registerStateCommands
210
300
  };
@@ -137,6 +137,8 @@ class SceStateStore {
137
137
  errorbook_incident_index: {},
138
138
  governance_spec_scene_override: {},
139
139
  governance_scene_index: {},
140
+ release_evidence_run: {},
141
+ release_gate_history: {},
140
142
  migration_records: {},
141
143
  auth_leases: {},
142
144
  auth_events: [],
@@ -407,6 +409,54 @@ class SceStateStore {
407
409
 
408
410
  CREATE INDEX IF NOT EXISTS idx_governance_scene_index_registry_counts
409
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);
410
460
  `);
411
461
  }
412
462
 
@@ -678,6 +728,66 @@ class SceStateStore {
678
728
  };
679
729
  }
680
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
+
681
791
  async resolveOrCreateTaskRef(options = {}) {
682
792
  const sceneId = normalizeString(options.sceneId);
683
793
  const specId = normalizeString(options.specId);
@@ -2165,6 +2275,322 @@ class SceStateStore {
2165
2275
  .filter(Boolean);
2166
2276
  }
2167
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
+
2168
2594
  _resolveOrCreateTaskRefInMemory(options = {}) {
2169
2595
  const sceneId = normalizeString(options.sceneId);
2170
2596
  const specId = normalizeString(options.specId);
@@ -11,6 +11,8 @@ const COMPONENT_ERRORBOOK_ENTRY_INDEX = 'errorbook.entry-index';
11
11
  const COMPONENT_ERRORBOOK_INCIDENT_INDEX = 'errorbook.incident-index';
12
12
  const COMPONENT_SPEC_SCENE_OVERRIDES = 'governance.spec-scene-overrides';
13
13
  const COMPONENT_SPEC_SCENE_INDEX = 'governance.scene-index';
14
+ const COMPONENT_RELEASE_EVIDENCE_RUNS = 'release.evidence-runs-index';
15
+ const COMPONENT_RELEASE_GATE_HISTORY = 'release.gate-history-index';
14
16
  const DEFAULT_STATE_EXPORT_PATH = '.sce/reports/state-migration/state-export.latest.json';
15
17
 
16
18
  const COMPONENT_DEFINITIONS = Object.freeze([
@@ -41,6 +43,14 @@ const COMPONENT_DEFINITIONS = Object.freeze([
41
43
  {
42
44
  id: COMPONENT_SPEC_SCENE_INDEX,
43
45
  source_path: '.sce/spec-governance/scene-index.json'
46
+ },
47
+ {
48
+ id: COMPONENT_RELEASE_EVIDENCE_RUNS,
49
+ source_path: '.sce/reports/release-evidence/handoff-runs.json'
50
+ },
51
+ {
52
+ id: COMPONENT_RELEASE_GATE_HISTORY,
53
+ source_path: '.sce/reports/release-evidence/release-gate-history.json'
44
54
  }
45
55
  ]);
46
56
 
@@ -280,6 +290,106 @@ function mapSpecSceneIndexPayload(payload = {}) {
280
290
  .filter((item) => item.scene_id);
281
291
  }
282
292
 
293
+ function mapReleaseEvidenceRunsPayload(payload = {}) {
294
+ if (!payload || typeof payload !== 'object' || !Array.isArray(payload.sessions)) {
295
+ return [];
296
+ }
297
+ const sourceUpdatedAt = normalizeString(payload.updated_at) || normalizeString(payload.generated_at);
298
+ return payload.sessions
299
+ .map((session) => {
300
+ const gate = session && session.gate && typeof session.gate === 'object' ? session.gate : {};
301
+ const gateActual = gate && gate.actual && typeof gate.actual === 'object' ? gate.actual : {};
302
+ const coverage = session && session.capability_coverage && typeof session.capability_coverage === 'object'
303
+ ? session.capability_coverage
304
+ : {};
305
+ const coverageSummary = coverage && coverage.summary && typeof coverage.summary === 'object'
306
+ ? coverage.summary
307
+ : {};
308
+ const scenePackageBatch = session && session.scene_package_batch && typeof session.scene_package_batch === 'object'
309
+ ? session.scene_package_batch
310
+ : {};
311
+ const scenePackageBatchSummary = scenePackageBatch && scenePackageBatch.summary && typeof scenePackageBatch.summary === 'object'
312
+ ? scenePackageBatch.summary
313
+ : {};
314
+ const batchSummary = session && session.batch_summary && typeof session.batch_summary === 'object'
315
+ ? session.batch_summary
316
+ : {};
317
+ const preflight = session && session.release_gate_preflight && typeof session.release_gate_preflight === 'object'
318
+ ? session.release_gate_preflight
319
+ : {};
320
+ return {
321
+ session_id: normalizeString(session && session.session_id),
322
+ merged_at: normalizeString(session && session.merged_at) || normalizeString(session && session.generated_at),
323
+ status: normalizeString(session && session.status),
324
+ gate_passed: gate && gate.passed === true,
325
+ spec_success_rate_percent: Number.isFinite(Number(gateActual.spec_success_rate_percent))
326
+ ? Number(gateActual.spec_success_rate_percent)
327
+ : null,
328
+ risk_level: normalizeString(gateActual.risk_level),
329
+ ontology_quality_score: Number.isFinite(Number(gateActual.ontology_quality_score))
330
+ ? Number(gateActual.ontology_quality_score)
331
+ : null,
332
+ capability_coverage_percent: Number.isFinite(Number(coverageSummary.coverage_percent))
333
+ ? Number(coverageSummary.coverage_percent)
334
+ : null,
335
+ capability_coverage_passed: coverageSummary.passed === true,
336
+ scene_package_batch_passed: scenePackageBatchSummary.batch_gate_passed === true,
337
+ scene_package_batch_failure_count: Number.isFinite(Number(scenePackageBatchSummary.batch_gate_failure_count))
338
+ ? Number.parseInt(`${scenePackageBatchSummary.batch_gate_failure_count}`, 10)
339
+ : (
340
+ Number.isFinite(Number(scenePackageBatchSummary.failed))
341
+ ? Number.parseInt(`${scenePackageBatchSummary.failed}`, 10)
342
+ : 0
343
+ ),
344
+ failed_goals: Number.isFinite(Number(batchSummary.failed_goals))
345
+ ? Number.parseInt(`${batchSummary.failed_goals}`, 10)
346
+ : 0,
347
+ release_gate_preflight_available: preflight && preflight.available === true,
348
+ release_gate_preflight_blocked: preflight && preflight.blocked === true,
349
+ source_updated_at: sourceUpdatedAt
350
+ };
351
+ })
352
+ .filter((item) => item.session_id);
353
+ }
354
+
355
+ function mapReleaseGateHistoryPayload(payload = {}) {
356
+ if (!payload || typeof payload !== 'object' || !Array.isArray(payload.entries)) {
357
+ return [];
358
+ }
359
+ const sourceUpdatedAt = normalizeString(payload.updated_at) || normalizeString(payload.generated_at);
360
+ return payload.entries
361
+ .map((entry) => ({
362
+ tag: normalizeString(entry && entry.tag),
363
+ evaluated_at: normalizeString(entry && entry.evaluated_at),
364
+ gate_passed: entry && entry.gate_passed === true,
365
+ enforce: entry && entry.enforce === true,
366
+ risk_level: normalizeString(entry && entry.risk_level),
367
+ spec_success_rate_percent: Number.isFinite(Number(entry && entry.spec_success_rate_percent))
368
+ ? Number(entry.spec_success_rate_percent)
369
+ : null,
370
+ scene_package_batch_passed: entry && entry.scene_package_batch_passed === true,
371
+ scene_package_batch_failure_count: Number.isFinite(Number(entry && entry.scene_package_batch_failure_count))
372
+ ? Number.parseInt(`${entry.scene_package_batch_failure_count}`, 10)
373
+ : 0,
374
+ capability_expected_unknown_count: Number.isFinite(Number(entry && entry.capability_expected_unknown_count))
375
+ ? Number.parseInt(`${entry.capability_expected_unknown_count}`, 10)
376
+ : 0,
377
+ capability_provided_unknown_count: Number.isFinite(Number(entry && entry.capability_provided_unknown_count))
378
+ ? Number.parseInt(`${entry.capability_provided_unknown_count}`, 10)
379
+ : 0,
380
+ release_gate_preflight_available: entry && entry.release_gate_preflight_available === true,
381
+ release_gate_preflight_blocked: entry && entry.release_gate_preflight_blocked === true,
382
+ require_release_gate_preflight: entry && entry.require_release_gate_preflight === true,
383
+ drift_alert_count: Number.isFinite(Number(entry && entry.drift_alert_count))
384
+ ? Number.parseInt(`${entry.drift_alert_count}`, 10)
385
+ : 0,
386
+ drift_blocked: entry && entry.drift_blocked === true,
387
+ weekly_ops_blocked: entry && entry.weekly_ops_blocked === true,
388
+ source_updated_at: sourceUpdatedAt
389
+ }))
390
+ .filter((item) => item.tag);
391
+ }
392
+
283
393
  async function readJsonSource(absolutePath, fileSystem = fs) {
284
394
  if (!await fileSystem.pathExists(absolutePath)) {
285
395
  return {
@@ -325,6 +435,10 @@ async function readComponentSnapshot(component = {}, projectPath = process.cwd()
325
435
  records = mapSpecSceneOverridesPayload(source.payload);
326
436
  } else if (component.id === COMPONENT_SPEC_SCENE_INDEX) {
327
437
  records = mapSpecSceneIndexPayload(source.payload);
438
+ } else if (component.id === COMPONENT_RELEASE_EVIDENCE_RUNS) {
439
+ records = mapReleaseEvidenceRunsPayload(source.payload);
440
+ } else if (component.id === COMPONENT_RELEASE_GATE_HISTORY) {
441
+ records = mapReleaseGateHistoryPayload(source.payload);
328
442
  }
329
443
  }
330
444
 
@@ -434,6 +548,16 @@ async function writeComponentToStateStore(componentSnapshot = {}, stateStore, co
434
548
  source: componentId
435
549
  });
436
550
  }
551
+ if (componentId === COMPONENT_RELEASE_EVIDENCE_RUNS) {
552
+ return stateStore.upsertReleaseEvidenceRunRecords(componentSnapshot.records, {
553
+ source: componentId
554
+ });
555
+ }
556
+ if (componentId === COMPONENT_RELEASE_GATE_HISTORY) {
557
+ return stateStore.upsertReleaseGateHistoryRecords(componentSnapshot.records, {
558
+ source: componentId
559
+ });
560
+ }
437
561
  return null;
438
562
  }
439
563
 
@@ -561,6 +685,8 @@ async function runStateDoctor(options = {}, dependencies = {}) {
561
685
  errorbookIncidentRows,
562
686
  governanceOverrideRows,
563
687
  governanceSceneRows,
688
+ releaseEvidenceRows,
689
+ releaseGateHistoryRows,
564
690
  migrations
565
691
  ] = await Promise.all([
566
692
  stateStore.listAgentRuntimeRecords({ limit: 0 }),
@@ -570,6 +696,8 @@ async function runStateDoctor(options = {}, dependencies = {}) {
570
696
  stateStore.listErrorbookIncidentIndexRecords({ limit: 0 }),
571
697
  stateStore.listGovernanceSpecSceneOverrideRecords({ limit: 0 }),
572
698
  stateStore.listGovernanceSceneIndexRecords({ limit: 0 }),
699
+ stateStore.listReleaseEvidenceRunRecords({ limit: 0 }),
700
+ stateStore.listReleaseGateHistoryRecords({ limit: 0 }),
573
701
  stateStore.listStateMigrations({ limit: 20 })
574
702
  ]);
575
703
 
@@ -580,7 +708,9 @@ async function runStateDoctor(options = {}, dependencies = {}) {
580
708
  [COMPONENT_ERRORBOOK_ENTRY_INDEX, Array.isArray(errorbookEntryRows) ? errorbookEntryRows.length : 0],
581
709
  [COMPONENT_ERRORBOOK_INCIDENT_INDEX, Array.isArray(errorbookIncidentRows) ? errorbookIncidentRows.length : 0],
582
710
  [COMPONENT_SPEC_SCENE_OVERRIDES, Array.isArray(governanceOverrideRows) ? governanceOverrideRows.length : 0],
583
- [COMPONENT_SPEC_SCENE_INDEX, Array.isArray(governanceSceneRows) ? governanceSceneRows.length : 0]
711
+ [COMPONENT_SPEC_SCENE_INDEX, Array.isArray(governanceSceneRows) ? governanceSceneRows.length : 0],
712
+ [COMPONENT_RELEASE_EVIDENCE_RUNS, Array.isArray(releaseEvidenceRows) ? releaseEvidenceRows.length : 0],
713
+ [COMPONENT_RELEASE_GATE_HISTORY, Array.isArray(releaseGateHistoryRows) ? releaseGateHistoryRows.length : 0]
584
714
  ]);
585
715
 
586
716
  const checks = plan.components.map((component) => {
@@ -788,6 +918,8 @@ async function runStateExport(options = {}, dependencies = {}) {
788
918
  errorbookIncidentRows,
789
919
  governanceOverrideRows,
790
920
  governanceSceneRows,
921
+ releaseEvidenceRows,
922
+ releaseGateHistoryRows,
791
923
  migrations
792
924
  ] = await Promise.all([
793
925
  stateStore.listAgentRuntimeRecords({ limit: 0 }),
@@ -797,6 +929,8 @@ async function runStateExport(options = {}, dependencies = {}) {
797
929
  stateStore.listErrorbookIncidentIndexRecords({ limit: 0 }),
798
930
  stateStore.listGovernanceSpecSceneOverrideRecords({ limit: 0 }),
799
931
  stateStore.listGovernanceSceneIndexRecords({ limit: 0 }),
932
+ stateStore.listReleaseEvidenceRunRecords({ limit: 0 }),
933
+ stateStore.listReleaseGateHistoryRecords({ limit: 0 }),
800
934
  stateStore.listStateMigrations({ limit: 0 })
801
935
  ]);
802
936
 
@@ -813,6 +947,8 @@ async function runStateExport(options = {}, dependencies = {}) {
813
947
  errorbook_incident_index_registry: Array.isArray(errorbookIncidentRows) ? errorbookIncidentRows : [],
814
948
  governance_spec_scene_override_registry: Array.isArray(governanceOverrideRows) ? governanceOverrideRows : [],
815
949
  governance_scene_index_registry: Array.isArray(governanceSceneRows) ? governanceSceneRows : [],
950
+ release_evidence_run_registry: Array.isArray(releaseEvidenceRows) ? releaseEvidenceRows : [],
951
+ release_gate_history_registry: Array.isArray(releaseGateHistoryRows) ? releaseGateHistoryRows : [],
816
952
  state_migration_registry: Array.isArray(migrations) ? migrations : []
817
953
  },
818
954
  summary: {
@@ -823,6 +959,8 @@ async function runStateExport(options = {}, dependencies = {}) {
823
959
  errorbook_incident_index_registry: Array.isArray(errorbookIncidentRows) ? errorbookIncidentRows.length : 0,
824
960
  governance_spec_scene_override_registry: Array.isArray(governanceOverrideRows) ? governanceOverrideRows.length : 0,
825
961
  governance_scene_index_registry: Array.isArray(governanceSceneRows) ? governanceSceneRows.length : 0,
962
+ release_evidence_run_registry: Array.isArray(releaseEvidenceRows) ? releaseEvidenceRows.length : 0,
963
+ release_gate_history_registry: Array.isArray(releaseGateHistoryRows) ? releaseGateHistoryRows.length : 0,
826
964
  state_migration_registry: Array.isArray(migrations) ? migrations.length : 0
827
965
  },
828
966
  out_file: path.relative(projectPath, outPath).replace(/\\/g, '/')
@@ -841,6 +979,8 @@ module.exports = {
841
979
  COMPONENT_ERRORBOOK_INCIDENT_INDEX,
842
980
  COMPONENT_SPEC_SCENE_OVERRIDES,
843
981
  COMPONENT_SPEC_SCENE_INDEX,
982
+ COMPONENT_RELEASE_EVIDENCE_RUNS,
983
+ COMPONENT_RELEASE_GATE_HISTORY,
844
984
  COMPONENT_DEFINITIONS,
845
985
  DEFAULT_STATE_EXPORT_PATH,
846
986
  buildStateMigrationPlan,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "scene-capability-engine",
3
- "version": "3.6.5",
3
+ "version": "3.6.8",
4
4
  "description": "SCE (Scene Capability Engine) - A CLI tool and npm package for spec-driven development with AI coding assistants.",
5
5
  "main": "index.js",
6
6
  "bin": {