scene-capability-engine 3.6.37 → 3.6.39

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/docs/command-reference.md +27 -0
  3. package/docs/document-governance.md +22 -2
  4. package/docs/releases/README.md +2 -0
  5. package/docs/releases/v3.6.38.md +19 -0
  6. package/docs/releases/v3.6.39.md +24 -0
  7. package/docs/state-migration-reconciliation-runbook.md +76 -0
  8. package/docs/state-storage-tiering.md +104 -0
  9. package/docs/steering-governance.md +112 -0
  10. package/docs/zh/releases/README.md +2 -0
  11. package/docs/zh/releases/v3.6.38.md +19 -0
  12. package/docs/zh/releases/v3.6.39.md +24 -0
  13. package/lib/commands/docs.js +8 -2
  14. package/lib/commands/scene.js +78 -18
  15. package/lib/commands/watch.js +10 -1
  16. package/lib/governance/config-manager.js +16 -0
  17. package/lib/governance/diagnostic-engine.js +2 -1
  18. package/lib/governance/validation-engine.js +3 -2
  19. package/lib/runtime/session-store.js +8 -0
  20. package/lib/state/sce-state-store.js +265 -0
  21. package/lib/state/state-migration-manager.js +27 -2
  22. package/lib/state/state-storage-policy.js +179 -0
  23. package/lib/watch/action-executor.js +10 -1
  24. package/lib/watch/event-debouncer.js +3 -0
  25. package/lib/watch/file-watcher.js +51 -10
  26. package/lib/watch/watch-manager.js +10 -1
  27. package/lib/workspace/takeover-baseline.js +11 -0
  28. package/package.json +8 -2
  29. package/template/.sce/config/state-storage-policy.json +165 -0
  30. package/template/.sce/steering/CORE_PRINCIPLES.md +21 -211
  31. package/template/.sce/steering/CURRENT_CONTEXT.md +9 -27
  32. package/template/.sce/steering/ENVIRONMENT.md +18 -32
  33. package/template/.sce/steering/RULES_GUIDE.md +16 -44
  34. package/template/.sce/steering/manifest.yaml +50 -0
@@ -0,0 +1,179 @@
1
+ 'use strict';
2
+
3
+ const DEFAULT_STATE_STORAGE_POLICY = Object.freeze({
4
+ schema_version: '1.0',
5
+ strategy: 'selective-sqlite-advancement',
6
+ tiers: {
7
+ 'file-source': {
8
+ description: 'Canonical file-backed storage for low-cardinality config, raw evidence, audit streams, and recovery-oriented payloads.'
9
+ },
10
+ 'sqlite-index': {
11
+ description: 'SQLite index/registry layer for file-backed resources that need high-frequency filtering, sorting, and cross-run aggregation.'
12
+ },
13
+ 'derived-sqlite-projection': {
14
+ description: 'Disposable SQLite projection rebuilt from canonical files for append-only streams with query pressure.'
15
+ }
16
+ },
17
+ admission: {
18
+ required_signals: [
19
+ 'cross-run or cross-session query pressure is proven',
20
+ 'file scans are materially weaker than indexed filtering/sorting',
21
+ 'sqlite content remains rebuildable from a canonical source',
22
+ 'operator diagnostics and reconcile path are defined before rollout'
23
+ ],
24
+ deny_if_any: [
25
+ 'resource is the only copy of raw audit or evidence payload',
26
+ 'resource is low-cardinality personal workspace or preference state',
27
+ 'human-readable diff and manual recovery are more valuable than query speed',
28
+ 'migration would introduce silent source-of-truth cutover'
29
+ ],
30
+ future_candidate_checklist: [
31
+ 'identify canonical source path or stream',
32
+ 'document expected query patterns and consumers',
33
+ 'define rebuild and reconcile semantics',
34
+ 'define release gate or audit behavior for drift states',
35
+ 'document why existing file-only storage is insufficient'
36
+ ]
37
+ },
38
+ component_scope: [
39
+ {
40
+ component_id: 'collab.agent-registry',
41
+ tier: 'sqlite-index',
42
+ canonical_source: 'file',
43
+ source_path: '.sce/config/agent-registry.json',
44
+ sqlite_tables: ['agent_runtime_registry'],
45
+ rationale: 'Registry-style lookup with repeated status and capability queries.'
46
+ },
47
+ {
48
+ component_id: 'runtime.timeline-index',
49
+ tier: 'sqlite-index',
50
+ canonical_source: 'file',
51
+ source_path: '.sce/timeline/index.json',
52
+ sqlite_tables: ['timeline_snapshot_registry'],
53
+ rationale: 'Timeline index benefits from filtered and cross-session reads while file snapshots remain recoverable source artifacts.'
54
+ },
55
+ {
56
+ component_id: 'runtime.scene-session-index',
57
+ tier: 'sqlite-index',
58
+ canonical_source: 'file',
59
+ source_path: '.sce/session-governance/scene-index.json',
60
+ sqlite_tables: ['scene_session_cycle_registry'],
61
+ rationale: 'Scene/session lookups have query pressure and consistency checks but still rely on file session payloads.'
62
+ },
63
+ {
64
+ component_id: 'errorbook.entry-index',
65
+ tier: 'sqlite-index',
66
+ canonical_source: 'file',
67
+ source_path: '.sce/errorbook/index.json',
68
+ sqlite_tables: ['errorbook_entry_index_registry'],
69
+ rationale: 'Promoted errorbook registry queries benefit from indexed status and quality filtering.'
70
+ },
71
+ {
72
+ component_id: 'errorbook.incident-index',
73
+ tier: 'sqlite-index',
74
+ canonical_source: 'file',
75
+ source_path: '.sce/errorbook/staging/index.json',
76
+ sqlite_tables: ['errorbook_incident_index_registry'],
77
+ rationale: 'Incident staging state requires queryable triage views without replacing raw incident artifacts.'
78
+ },
79
+ {
80
+ component_id: 'governance.spec-scene-overrides',
81
+ tier: 'sqlite-index',
82
+ canonical_source: 'file',
83
+ source_path: '.sce/spec-governance/spec-scene-overrides.json',
84
+ sqlite_tables: ['governance_spec_scene_override_registry'],
85
+ rationale: 'Override lookups are registry-like and join naturally with other governance indexes.'
86
+ },
87
+ {
88
+ component_id: 'governance.scene-index',
89
+ tier: 'sqlite-index',
90
+ canonical_source: 'file',
91
+ source_path: '.sce/spec-governance/scene-index.json',
92
+ sqlite_tables: ['governance_scene_index_registry'],
93
+ rationale: 'Scene governance summaries are better served by indexed counts and status filters.'
94
+ },
95
+ {
96
+ component_id: 'release.evidence-runs-index',
97
+ tier: 'sqlite-index',
98
+ canonical_source: 'file',
99
+ source_path: '.sce/reports/release-evidence/handoff-runs.json',
100
+ sqlite_tables: ['release_evidence_run_registry'],
101
+ rationale: 'Release evidence run summaries need fast historical querying while release assets remain file-backed.'
102
+ },
103
+ {
104
+ component_id: 'release.gate-history-index',
105
+ tier: 'sqlite-index',
106
+ canonical_source: 'file',
107
+ source_path: '.sce/reports/release-evidence/release-gate-history.json',
108
+ sqlite_tables: ['release_gate_history_registry'],
109
+ rationale: 'Gate history is registry-shaped and queried by tag, pass/fail, and drift metrics.'
110
+ }
111
+ ],
112
+ resource_rules: [
113
+ {
114
+ rule_id: 'workspace-personal-state',
115
+ tier: 'file-source',
116
+ explicit_paths: ['~/.sce/workspace-state.json'],
117
+ derived_projection_allowed: false,
118
+ source_replacement_allowed: false,
119
+ rationale: 'Personal workspace selection and preferences are low-cardinality, atomic, and not worth migrating into SQLite.'
120
+ },
121
+ {
122
+ rule_id: 'append-only-report-streams',
123
+ tier: 'file-source',
124
+ path_patterns: ['.sce/reports/**/*.jsonl'],
125
+ derived_projection_allowed: true,
126
+ source_replacement_allowed: false,
127
+ rationale: 'Raw governance and evidence streams must stay append-only files; projection is allowed only for query acceleration.'
128
+ },
129
+ {
130
+ rule_id: 'append-only-audit-streams',
131
+ tier: 'file-source',
132
+ path_patterns: ['.sce/audit/**/*.jsonl'],
133
+ derived_projection_allowed: true,
134
+ source_replacement_allowed: false,
135
+ rationale: 'Audit streams remain canonical evidence and should never become SQLite-only write paths.'
136
+ },
137
+ {
138
+ rule_id: 'timeline-snapshot-payloads',
139
+ tier: 'file-source',
140
+ path_patterns: ['.sce/timeline/snapshots/**'],
141
+ derived_projection_allowed: false,
142
+ source_replacement_allowed: false,
143
+ rationale: 'Timeline snapshots are recovery-oriented payload artifacts, not registry data.'
144
+ },
145
+ {
146
+ rule_id: 'session-payload-artifacts',
147
+ tier: 'file-source',
148
+ path_patterns: ['.sce/session-governance/sessions/**'],
149
+ derived_projection_allowed: false,
150
+ source_replacement_allowed: false,
151
+ rationale: 'Session payloads must stay file-backed for recovery, archive, and manual debugging.'
152
+ },
153
+ {
154
+ rule_id: 'release-evidence-assets',
155
+ tier: 'file-source',
156
+ path_patterns: [
157
+ '.sce/reports/release-evidence/**/*.json',
158
+ '.sce/reports/release-evidence/**/*.md',
159
+ '.sce/reports/release-evidence/**/*.jsonl',
160
+ '.sce/reports/release-evidence/**/*.lines'
161
+ ],
162
+ derived_projection_allowed: true,
163
+ source_replacement_allowed: false,
164
+ rationale: 'Release evidence assets remain portable files even when selected summary indexes are mirrored into SQLite.'
165
+ }
166
+ ]
167
+ });
168
+
169
+ const REQUIRED_COMPONENT_IDS = Object.freeze(DEFAULT_STATE_STORAGE_POLICY.component_scope.map((item) => item.component_id));
170
+
171
+ function cloneStateStoragePolicyDefaults() {
172
+ return JSON.parse(JSON.stringify(DEFAULT_STATE_STORAGE_POLICY));
173
+ }
174
+
175
+ module.exports = {
176
+ DEFAULT_STATE_STORAGE_POLICY,
177
+ REQUIRED_COMPONENT_IDS,
178
+ cloneStateStoragePolicyDefaults
179
+ };
@@ -4,6 +4,15 @@ const { promisify } = require('util');
4
4
 
5
5
  const execAsync = promisify(exec);
6
6
 
7
+ function sleep(ms) {
8
+ return new Promise(resolve => {
9
+ const timer = setTimeout(resolve, ms);
10
+ if (typeof timer.unref === 'function') {
11
+ timer.unref();
12
+ }
13
+ });
14
+ }
15
+
7
16
  /**
8
17
  * ActionExecutor - 动作执行器
9
18
  *
@@ -203,7 +212,7 @@ class ActionExecutor extends EventEmitter {
203
212
  });
204
213
 
205
214
  // 等待
206
- await new Promise(resolve => setTimeout(resolve, delay));
215
+ await sleep(delay);
207
216
 
208
217
  try {
209
218
  // 重新执行
@@ -71,6 +71,9 @@ class EventDebouncer extends EventEmitter {
71
71
  this.emit('error', { error, key, type: 'debounce' });
72
72
  }
73
73
  }, actualDelay);
74
+ if (typeof timer.unref === 'function') {
75
+ timer.unref();
76
+ }
74
77
 
75
78
  this.debounceTimers.set(key, timer);
76
79
  }
@@ -41,6 +41,9 @@ class FileWatcher extends EventEmitter {
41
41
  };
42
42
  this.retryCount = 0;
43
43
  this.lastError = null;
44
+ this.basePath = process.cwd();
45
+ this.initializationTimer = null;
46
+ this.recoveryTimer = null;
44
47
  }
45
48
 
46
49
  /**
@@ -93,6 +96,8 @@ class FileWatcher extends EventEmitter {
93
96
  }
94
97
 
95
98
  try {
99
+ this.basePath = basePath;
100
+
96
101
  // 将相对路径转换为绝对路径
97
102
  const absolutePatterns = this.config.patterns.map(pattern => {
98
103
  if (path.isAbsolute(pattern)) {
@@ -120,19 +125,22 @@ class FileWatcher extends EventEmitter {
120
125
 
121
126
  // 等待 watcher 准备就绪
122
127
  await new Promise((resolve, reject) => {
123
- const timeout = setTimeout(() => {
128
+ this._clearInitializationTimer();
129
+ this.initializationTimer = setTimeout(() => {
130
+ this.initializationTimer = null;
124
131
  reject(new Error('FileWatcher initialization timeout'));
125
132
  }, 10000);
133
+ this._unrefTimer(this.initializationTimer);
126
134
 
127
135
  this.watcher.once('ready', () => {
128
- clearTimeout(timeout);
136
+ this._clearInitializationTimer();
129
137
  this.isWatching = true;
130
138
  this.stats.startedAt = new Date();
131
139
  resolve();
132
140
  });
133
141
 
134
142
  this.watcher.once('error', (error) => {
135
- clearTimeout(timeout);
143
+ this._clearInitializationTimer();
136
144
  reject(error);
137
145
  });
138
146
  });
@@ -140,6 +148,7 @@ class FileWatcher extends EventEmitter {
140
148
  this.emit('started', { patterns: this.config.patterns });
141
149
  } catch (error) {
142
150
  this.isWatching = false;
151
+ await this._disposeWatcher();
143
152
  throw error;
144
153
  }
145
154
  }
@@ -150,15 +159,14 @@ class FileWatcher extends EventEmitter {
150
159
  * @returns {Promise<void>}
151
160
  */
152
161
  async stop() {
153
- if (!this.isWatching) {
162
+ if (!this.isWatching && !this.watcher && !this.initializationTimer && !this.recoveryTimer) {
154
163
  return;
155
164
  }
156
165
 
157
166
  try {
158
- if (this.watcher) {
159
- await this.watcher.close();
160
- this.watcher = null;
161
- }
167
+ this._clearInitializationTimer();
168
+ this._clearRecoveryTimer();
169
+ await this._disposeWatcher();
162
170
 
163
171
  this.isWatching = false;
164
172
  this.watchedFiles.clear();
@@ -300,10 +308,12 @@ class FileWatcher extends EventEmitter {
300
308
  });
301
309
 
302
310
  // 延迟后重试
303
- setTimeout(async () => {
311
+ this._clearRecoveryTimer();
312
+ this.recoveryTimer = setTimeout(async () => {
313
+ this.recoveryTimer = null;
304
314
  try {
305
315
  // 重启 watcher
306
- const basePath = this.watcher ? this.watcher.options.cwd : process.cwd();
316
+ const basePath = this.basePath || process.cwd();
307
317
 
308
318
  await this.stop();
309
319
  await this.start(basePath);
@@ -326,6 +336,7 @@ class FileWatcher extends EventEmitter {
326
336
  await this._attemptRecovery(recoveryError);
327
337
  }
328
338
  }, this.config.retryDelay);
339
+ this._unrefTimer(this.recoveryTimer);
329
340
  }
330
341
 
331
342
  /**
@@ -494,6 +505,36 @@ class FileWatcher extends EventEmitter {
494
505
  lastError: this.lastError ? this.lastError.message : null
495
506
  };
496
507
  }
508
+
509
+ _unrefTimer(timer) {
510
+ if (timer && typeof timer.unref === 'function') {
511
+ timer.unref();
512
+ }
513
+ }
514
+
515
+ _clearInitializationTimer() {
516
+ if (this.initializationTimer) {
517
+ clearTimeout(this.initializationTimer);
518
+ this.initializationTimer = null;
519
+ }
520
+ }
521
+
522
+ _clearRecoveryTimer() {
523
+ if (this.recoveryTimer) {
524
+ clearTimeout(this.recoveryTimer);
525
+ this.recoveryTimer = null;
526
+ }
527
+ }
528
+
529
+ async _disposeWatcher() {
530
+ if (!this.watcher) {
531
+ return;
532
+ }
533
+
534
+ const watcher = this.watcher;
535
+ this.watcher = null;
536
+ await watcher.close();
537
+ }
497
538
  }
498
539
 
499
540
  module.exports = FileWatcher;
@@ -6,6 +6,15 @@ const EventDebouncer = require('./event-debouncer');
6
6
  const ActionExecutor = require('./action-executor');
7
7
  const ExecutionLogger = require('./execution-logger');
8
8
 
9
+ function sleep(ms) {
10
+ return new Promise(resolve => {
11
+ const timer = setTimeout(resolve, ms);
12
+ if (typeof timer.unref === 'function') {
13
+ timer.unref();
14
+ }
15
+ });
16
+ }
17
+
9
18
  /**
10
19
  * WatchManager - Watch 模式管理器
11
20
  *
@@ -137,7 +146,7 @@ class WatchManager extends EventEmitter {
137
146
  */
138
147
  async restart() {
139
148
  await this.stop();
140
- await new Promise(resolve => setTimeout(resolve, 1000));
149
+ await sleep(1000);
141
150
  await this.start();
142
151
  }
143
152
 
@@ -7,6 +7,9 @@ const {
7
7
  MANIFEST_FILENAME,
8
8
  SCE_STEERING_DIR,
9
9
  } = require('../runtime/steering-contract');
10
+ const {
11
+ cloneStateStoragePolicyDefaults
12
+ } = require('../state/state-storage-policy');
10
13
 
11
14
  const TAKEOVER_BASELINE_SCHEMA_VERSION = '1.0';
12
15
 
@@ -479,6 +482,7 @@ async function applyTakeoverBaseline(projectPath = process.cwd(), options = {})
479
482
  const problemEvalPolicyPath = path.join(sceRoot, 'config', 'problem-eval-policy.json');
480
483
  const problemClosurePolicyPath = path.join(sceRoot, 'config', 'problem-closure-policy.json');
481
484
  const studioIntakePolicyPath = path.join(sceRoot, 'config', 'studio-intake-policy.json');
485
+ const stateStoragePolicyPath = path.join(sceRoot, 'config', 'state-storage-policy.json');
482
486
  const reportPath = path.join(sceRoot, 'reports', 'takeover-baseline-latest.json');
483
487
 
484
488
  const existingAdoption = await _readJsonSafe(adoptionPath, fileSystem);
@@ -489,6 +493,7 @@ async function applyTakeoverBaseline(projectPath = process.cwd(), options = {})
489
493
  const existingProblemEvalPolicy = await _readJsonSafe(problemEvalPolicyPath, fileSystem);
490
494
  const existingProblemClosurePolicy = await _readJsonSafe(problemClosurePolicyPath, fileSystem);
491
495
  const existingStudioIntakePolicy = await _readJsonSafe(studioIntakePolicyPath, fileSystem);
496
+ const existingStateStoragePolicy = await _readJsonSafe(stateStoragePolicyPath, fileSystem);
492
497
 
493
498
  const desiredAdoption = _buildAdoptionConfig(existingAdoption, nowIso, sceVersion);
494
499
  const desiredAutoConfig = _buildAutoConfig(existingAuto);
@@ -498,6 +503,7 @@ async function applyTakeoverBaseline(projectPath = process.cwd(), options = {})
498
503
  const desiredProblemEvalPolicy = _deepMerge(existingProblemEvalPolicy || {}, PROBLEM_EVAL_POLICY_DEFAULTS);
499
504
  const desiredProblemClosurePolicy = _deepMerge(existingProblemClosurePolicy || {}, PROBLEM_CLOSURE_POLICY_DEFAULTS);
500
505
  const desiredStudioIntakePolicy = _deepMerge(existingStudioIntakePolicy || {}, STUDIO_INTAKE_POLICY_DEFAULTS);
506
+ const desiredStateStoragePolicy = _deepMerge(existingStateStoragePolicy || {}, cloneStateStoragePolicyDefaults());
501
507
 
502
508
  const fileResults = [];
503
509
  fileResults.push(await _reconcileJsonFile(adoptionPath, desiredAdoption, {
@@ -540,6 +546,11 @@ async function applyTakeoverBaseline(projectPath = process.cwd(), options = {})
540
546
  apply,
541
547
  fileSystem
542
548
  }));
549
+ fileResults.push(await _reconcileJsonFile(stateStoragePolicyPath, desiredStateStoragePolicy, {
550
+ projectPath,
551
+ apply,
552
+ fileSystem
553
+ }));
543
554
  fileResults.push(await _reconcileSteeringContract(projectPath, {
544
555
  apply,
545
556
  fileSystem
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "scene-capability-engine",
3
- "version": "3.6.37",
3
+ "version": "3.6.39",
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": {
@@ -38,6 +38,12 @@
38
38
  "test:skip-audit": "node scripts/check-skip-allowlist.js",
39
39
  "test:sce-tracking": "node scripts/check-sce-tracking.js",
40
40
  "test:brand-consistency": "node scripts/check-branding-consistency.js",
41
+ "audit:steering": "node scripts/steering-content-audit.js --fail-on-error",
42
+ "audit:state-storage": "node scripts/state-storage-tiering-audit.js",
43
+ "report:steering-audit": "node scripts/steering-content-audit.js --json",
44
+ "report:state-storage": "node scripts/state-storage-tiering-audit.js --json",
45
+ "report:interactive-approval-projection": "node scripts/interactive-approval-event-projection.js --action doctor --json",
46
+ "audit:interactive-approval-projection": "node scripts/interactive-approval-event-projection.js --action doctor --fail-on-drift --fail-on-parse-error",
41
47
  "test:watch": "npx jest --watch",
42
48
  "coverage": "npx jest --coverage",
43
49
  "report:moqui-metadata": "node scripts/moqui-metadata-extract.js --json",
@@ -76,7 +82,7 @@
76
82
  "gate:release-asset-integrity": "node scripts/release-asset-integrity-check.js",
77
83
  "report:release-risk-remediation": "node scripts/release-risk-remediation-bundle.js --json",
78
84
  "report:moqui-core-regression": "node scripts/moqui-core-regression-suite.js --json",
79
- "prepublishOnly": "npm run test:release && npm run test:skip-audit && npm run test:sce-tracking && npm run test:brand-consistency && npm run gate:git-managed && npm run gate:errorbook-registry-health && npm run gate:errorbook-release && npm run report:interactive-governance -- --fail-on-alert",
85
+ "prepublishOnly": "npm run test:release && npm run test:skip-audit && npm run test:sce-tracking && npm run test:brand-consistency && npm run audit:steering && npm run gate:git-managed && npm run gate:errorbook-registry-health && npm run gate:errorbook-release && npm run report:interactive-governance -- --fail-on-alert",
80
86
  "publish:manual": "npm publish --access public",
81
87
  "install-global": "npm install -g .",
82
88
  "uninstall-global": "npm uninstall -g scene-capability-engine"
@@ -0,0 +1,165 @@
1
+ {
2
+ "schema_version": "1.0",
3
+ "strategy": "selective-sqlite-advancement",
4
+ "tiers": {
5
+ "file-source": {
6
+ "description": "Canonical file-backed storage for low-cardinality config, raw evidence, audit streams, and recovery-oriented payloads."
7
+ },
8
+ "sqlite-index": {
9
+ "description": "SQLite index/registry layer for file-backed resources that need high-frequency filtering, sorting, and cross-run aggregation."
10
+ },
11
+ "derived-sqlite-projection": {
12
+ "description": "Disposable SQLite projection rebuilt from canonical files for append-only streams with query pressure."
13
+ }
14
+ },
15
+ "admission": {
16
+ "required_signals": [
17
+ "cross-run or cross-session query pressure is proven",
18
+ "file scans are materially weaker than indexed filtering/sorting",
19
+ "sqlite content remains rebuildable from a canonical source",
20
+ "operator diagnostics and reconcile path are defined before rollout"
21
+ ],
22
+ "deny_if_any": [
23
+ "resource is the only copy of raw audit or evidence payload",
24
+ "resource is low-cardinality personal workspace or preference state",
25
+ "human-readable diff and manual recovery are more valuable than query speed",
26
+ "migration would introduce silent source-of-truth cutover"
27
+ ],
28
+ "future_candidate_checklist": [
29
+ "identify canonical source path or stream",
30
+ "document expected query patterns and consumers",
31
+ "define rebuild and reconcile semantics",
32
+ "define release gate or audit behavior for drift states",
33
+ "document why existing file-only storage is insufficient"
34
+ ]
35
+ },
36
+ "component_scope": [
37
+ {
38
+ "component_id": "collab.agent-registry",
39
+ "tier": "sqlite-index",
40
+ "canonical_source": "file",
41
+ "source_path": ".sce/config/agent-registry.json",
42
+ "sqlite_tables": ["agent_runtime_registry"],
43
+ "rationale": "Registry-style lookup with repeated status and capability queries."
44
+ },
45
+ {
46
+ "component_id": "runtime.timeline-index",
47
+ "tier": "sqlite-index",
48
+ "canonical_source": "file",
49
+ "source_path": ".sce/timeline/index.json",
50
+ "sqlite_tables": ["timeline_snapshot_registry"],
51
+ "rationale": "Timeline index benefits from filtered and cross-session reads while file snapshots remain recoverable source artifacts."
52
+ },
53
+ {
54
+ "component_id": "runtime.scene-session-index",
55
+ "tier": "sqlite-index",
56
+ "canonical_source": "file",
57
+ "source_path": ".sce/session-governance/scene-index.json",
58
+ "sqlite_tables": ["scene_session_cycle_registry"],
59
+ "rationale": "Scene/session lookups have query pressure and consistency checks but still rely on file session payloads."
60
+ },
61
+ {
62
+ "component_id": "errorbook.entry-index",
63
+ "tier": "sqlite-index",
64
+ "canonical_source": "file",
65
+ "source_path": ".sce/errorbook/index.json",
66
+ "sqlite_tables": ["errorbook_entry_index_registry"],
67
+ "rationale": "Promoted errorbook registry queries benefit from indexed status and quality filtering."
68
+ },
69
+ {
70
+ "component_id": "errorbook.incident-index",
71
+ "tier": "sqlite-index",
72
+ "canonical_source": "file",
73
+ "source_path": ".sce/errorbook/staging/index.json",
74
+ "sqlite_tables": ["errorbook_incident_index_registry"],
75
+ "rationale": "Incident staging state requires queryable triage views without replacing raw incident artifacts."
76
+ },
77
+ {
78
+ "component_id": "governance.spec-scene-overrides",
79
+ "tier": "sqlite-index",
80
+ "canonical_source": "file",
81
+ "source_path": ".sce/spec-governance/spec-scene-overrides.json",
82
+ "sqlite_tables": ["governance_spec_scene_override_registry"],
83
+ "rationale": "Override lookups are registry-like and join naturally with other governance indexes."
84
+ },
85
+ {
86
+ "component_id": "governance.scene-index",
87
+ "tier": "sqlite-index",
88
+ "canonical_source": "file",
89
+ "source_path": ".sce/spec-governance/scene-index.json",
90
+ "sqlite_tables": ["governance_scene_index_registry"],
91
+ "rationale": "Scene governance summaries are better served by indexed counts and status filters."
92
+ },
93
+ {
94
+ "component_id": "release.evidence-runs-index",
95
+ "tier": "sqlite-index",
96
+ "canonical_source": "file",
97
+ "source_path": ".sce/reports/release-evidence/handoff-runs.json",
98
+ "sqlite_tables": ["release_evidence_run_registry"],
99
+ "rationale": "Release evidence run summaries need fast historical querying while release assets remain file-backed."
100
+ },
101
+ {
102
+ "component_id": "release.gate-history-index",
103
+ "tier": "sqlite-index",
104
+ "canonical_source": "file",
105
+ "source_path": ".sce/reports/release-evidence/release-gate-history.json",
106
+ "sqlite_tables": ["release_gate_history_registry"],
107
+ "rationale": "Gate history is registry-shaped and queried by tag, pass/fail, and drift metrics."
108
+ }
109
+ ],
110
+ "resource_rules": [
111
+ {
112
+ "rule_id": "workspace-personal-state",
113
+ "tier": "file-source",
114
+ "explicit_paths": ["~/.sce/workspace-state.json"],
115
+ "derived_projection_allowed": false,
116
+ "source_replacement_allowed": false,
117
+ "rationale": "Personal workspace selection and preferences are low-cardinality, atomic, and not worth migrating into SQLite."
118
+ },
119
+ {
120
+ "rule_id": "append-only-report-streams",
121
+ "tier": "file-source",
122
+ "path_patterns": [".sce/reports/**/*.jsonl"],
123
+ "derived_projection_allowed": true,
124
+ "source_replacement_allowed": false,
125
+ "rationale": "Raw governance and evidence streams must stay append-only files; projection is allowed only for query acceleration."
126
+ },
127
+ {
128
+ "rule_id": "append-only-audit-streams",
129
+ "tier": "file-source",
130
+ "path_patterns": [".sce/audit/**/*.jsonl"],
131
+ "derived_projection_allowed": true,
132
+ "source_replacement_allowed": false,
133
+ "rationale": "Audit streams remain canonical evidence and should never become SQLite-only write paths."
134
+ },
135
+ {
136
+ "rule_id": "timeline-snapshot-payloads",
137
+ "tier": "file-source",
138
+ "path_patterns": [".sce/timeline/snapshots/**"],
139
+ "derived_projection_allowed": false,
140
+ "source_replacement_allowed": false,
141
+ "rationale": "Timeline snapshots are recovery-oriented payload artifacts, not registry data."
142
+ },
143
+ {
144
+ "rule_id": "session-payload-artifacts",
145
+ "tier": "file-source",
146
+ "path_patterns": [".sce/session-governance/sessions/**"],
147
+ "derived_projection_allowed": false,
148
+ "source_replacement_allowed": false,
149
+ "rationale": "Session payloads must stay file-backed for recovery, archive, and manual debugging."
150
+ },
151
+ {
152
+ "rule_id": "release-evidence-assets",
153
+ "tier": "file-source",
154
+ "path_patterns": [
155
+ ".sce/reports/release-evidence/**/*.json",
156
+ ".sce/reports/release-evidence/**/*.md",
157
+ ".sce/reports/release-evidence/**/*.jsonl",
158
+ ".sce/reports/release-evidence/**/*.lines"
159
+ ],
160
+ "derived_projection_allowed": true,
161
+ "source_replacement_allowed": false,
162
+ "rationale": "Release evidence assets remain portable files even when selected summary indexes are mirrored into SQLite."
163
+ }
164
+ ]
165
+ }