@aiwg/cli 2026.10.5 → 2026.10.6
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/agentic/code/addons/composition-engine/lib/runtime.mjs +1401 -0
- package/agentic/code/addons/composition-engine/lib/validator.mjs +513 -0
- package/agentic/code/addons/composition-engine/schemas/flow-graph.schema.json +372 -0
- package/agentic/code/providers/model-capabilities.v1.json +74 -50
- package/bin/aiwg.mjs +3 -1
- package/dist/src/artifacts/browser-export.js +1 -0
- package/dist/src/artifacts/citation-parser.js +8 -1
- package/dist/src/artifacts/cli.js +57 -6
- package/dist/src/artifacts/fortemi-core-query-adapter.js +2 -0
- package/dist/src/artifacts/fortemi-core-sync.js +26 -8
- package/dist/src/artifacts/graph-query.js +18 -8
- package/dist/src/artifacts/index-adapters.js +339 -0
- package/dist/src/artifacts/index-builder.js +143 -14
- package/dist/src/artifacts/index-files.js +15 -4
- package/dist/src/artifacts/index-reader.js +44 -0
- package/dist/src/artifacts/index-refresh.js +114 -55
- package/dist/src/artifacts/index-status.js +20 -5
- package/dist/src/artifacts/memory-maintenance.js +503 -0
- package/dist/src/artifacts/prebuilt-relations.js +3 -1
- package/dist/src/artifacts/query-engine.js +263 -108
- package/dist/src/artifacts/relations-builder.js +2 -2
- package/dist/src/artifacts/source-graph.js +2 -2
- package/dist/src/artifacts/types.js +9 -1
- package/dist/src/cli/handlers/decision.js +19 -1
- package/dist/src/cli/handlers/guided-setup.js +470 -0
- package/dist/src/cli/handlers/help.js +2 -2
- package/dist/src/cli/handlers/housekeeper.js +1110 -0
- package/dist/src/cli/handlers/index.js +4 -2
- package/dist/src/cli/handlers/init.js +41 -6
- package/dist/src/cli/handlers/local-executor.js +5 -3
- package/dist/src/cli/handlers/mc.js +509 -53
- package/dist/src/cli/handlers/models.js +32 -2
- package/dist/src/cli/handlers/ralph-launcher.js +76 -4
- package/dist/src/cli/handlers/rules.js +1 -1
- package/dist/src/cli/handlers/script-runner.js +73 -2
- package/dist/src/cli/handlers/serve.js +5 -3
- package/dist/src/cli/handlers/sessions.js +1 -0
- package/dist/src/cli/handlers/setup.js +428 -1
- package/dist/src/cli/handlers/skill-lint.js +1 -1
- package/dist/src/cli/handlers/subcommands.js +118 -8
- package/dist/src/cli/handlers/use.js +279 -93
- package/dist/src/cli/handlers/workspace.js +3 -1
- package/dist/src/cli/prompt-utils.js +8 -0
- package/dist/src/cli/router.js +16 -1
- package/dist/src/config/aiwg-config.js +42 -0
- package/dist/src/config/cli.js +7 -3
- package/dist/src/config/decision-config.js +14 -1
- package/dist/src/config/native-sandbox-warnings.js +25 -0
- package/dist/src/contributors/validation.js +6 -5
- package/dist/src/decision/admission.js +7 -3
- package/dist/src/decision/benchmark.js +469 -0
- package/dist/src/decision/ledger.js +22 -3
- package/dist/src/decision/pricing.js +147 -0
- package/dist/src/decision/qualification/gate-evidence.js +18 -15
- package/dist/src/decision/qualification/load-staged.js +539 -0
- package/dist/src/decision/qualification/load.js +109 -33
- package/dist/src/decision/qualification/runner.js +5 -2
- package/dist/src/decision/review/service.js +6 -4
- package/dist/src/decision/review/validate.js +23 -2
- package/dist/src/decision/savings.js +8 -3
- package/dist/src/discovery/index.js +492 -0
- package/dist/src/discovery/interview.js +233 -0
- package/dist/src/discovery/plan-apply.js +571 -0
- package/dist/src/discovery/plan-store.js +171 -0
- package/dist/src/discovery/plan-types.js +2 -0
- package/dist/src/discovery/plan-validation.js +139 -0
- package/dist/src/discovery/types.js +2 -0
- package/dist/src/extensions/commands/definitions.js +49 -9
- package/dist/src/extensions/manifest.js +71 -44
- package/dist/src/extensions/project-local-discovery.js +1 -1
- package/dist/src/extensions/project-quickref.js +18 -17
- package/dist/src/extensions/validation.js +32 -29
- package/dist/src/housekeeper/daemon-config.js +148 -0
- package/dist/src/housekeeper/ipc.js +84 -0
- package/dist/src/housekeeper/lifecycle.js +148 -0
- package/dist/src/housekeeper/platform.js +35 -0
- package/dist/src/housekeeper/status.js +70 -0
- package/dist/src/housekeeper/units.js +114 -0
- package/dist/src/jobs/flow.js +22 -21
- package/dist/src/lint/rule-activation.js +20 -5
- package/dist/src/lint/runner.js +1 -1
- package/dist/src/mcp/cli.mjs +5 -6
- package/dist/src/mcp/config-file.mjs +6 -2
- package/dist/src/mcp/config-layers.mjs +23 -7
- package/dist/src/mcp/profiles.js +18 -31
- package/dist/src/mcp/profiles.mjs +18 -32
- package/dist/src/mcp/registry.js +7 -27
- package/dist/src/mcp/registry.mjs +7 -28
- package/dist/src/mcp/server.mjs +34 -1
- package/dist/src/mcp/tools/agentic-sandbox.mjs +8 -7
- package/dist/src/mcp/tools/command-run.mjs +3 -2
- package/dist/src/mcp/tools/decision.mjs +49 -48
- package/dist/src/mcp/tools/discovery.mjs +46 -21
- package/dist/src/mcp/tools/orchestration.mjs +22 -22
- package/dist/src/mcp/tools/subsystems.mjs +9 -8
- package/dist/src/mcp/tools/worker-invoke.mjs +17 -4
- package/dist/src/mcp/tools/writer-profiles.mjs +1 -1
- package/dist/src/memory/audit.js +719 -0
- package/dist/src/memory/canonical-context.js +392 -13
- package/dist/src/memory/canonical-maintenance.js +95 -0
- package/dist/src/memory/cli-result.js +22 -0
- package/dist/src/memory/cli.js +176 -0
- package/dist/src/memory/context-pack.js +57 -6
- package/dist/src/memory/cost.js +512 -0
- package/dist/src/memory/firewall-classification.d.mts +7 -0
- package/dist/src/memory/firewall-classification.mjs +32 -0
- package/dist/src/memory/index.js +8 -0
- package/dist/src/memory/line-maintenance.js +68 -0
- package/dist/src/memory/maintenance-cli.js +286 -0
- package/dist/src/memory/maintenance-contract.js +136 -0
- package/dist/src/memory/maintenance-index.js +18 -0
- package/dist/src/memory/maintenance-owner.js +2 -0
- package/dist/src/memory/maintenance-record.js +138 -0
- package/dist/src/memory/maintenance.js +254 -0
- package/dist/src/memory/provider-memory-adapter.js +154 -0
- package/dist/src/memory/recommend-cli.js +83 -0
- package/dist/src/memory/recommend.js +269 -0
- package/dist/src/memory/scheduled.js +81 -0
- package/dist/src/memory/taxonomy.js +44 -0
- package/dist/src/mission-protocol/codecs.js +19 -9
- package/dist/src/models/config-loader.js +11 -1
- package/dist/src/models/model-capabilities.v1.json +74 -50
- package/dist/src/models/provider-policy.js +125 -62
- package/dist/src/models/resolver.js +45 -8
- package/dist/src/models/worker-installation.d.mts +7 -0
- package/dist/src/models/worker-installation.mjs +169 -37
- package/dist/src/models/wrapper-route.js +1 -1
- package/dist/src/output-modes/registry.js +8 -7
- package/dist/src/plugin/metadata-validator.js +1 -1
- package/dist/src/providers/deploy-version.d.mts +1 -0
- package/dist/src/providers/deploy-version.mjs +17 -0
- package/dist/src/providers/provider-definitions.js +19 -16
- package/dist/src/providers/skill-ownership.d.mts +11 -2
- package/dist/src/providers/skill-ownership.mjs +35 -17
- package/dist/src/rules/activation.d.mts +11 -2
- package/dist/src/rules/activation.mjs +82 -6
- package/dist/src/rules/inventory.js +1 -1
- package/dist/src/rules/rule-frontmatter.schema.json +2 -0
- package/dist/src/schema/zod-validation.d.mts +6 -0
- package/dist/src/schema/zod-validation.mjs +50 -0
- package/dist/src/serve/executor-registry.js +297 -20
- package/dist/src/serve/executor-token.d.mts +1 -0
- package/dist/src/serve/executor-token.mjs +8 -0
- package/dist/src/serve/shared-host-scheduler.js +1102 -31
- package/dist/src/serve/worker-invocation.js +114 -15
- package/dist/src/sessions/adapters/claude.js +19 -18
- package/dist/src/sessions/adapters/codex.js +10 -9
- package/dist/src/sessions/adapters/copilot.js +14 -13
- package/dist/src/sessions/adapters/cursor.js +19 -18
- package/dist/src/sessions/adapters/deepseek-harness.js +9 -8
- package/dist/src/sessions/adapters/factory.js +11 -10
- package/dist/src/sessions/adapters/generic.js +32 -23
- package/dist/src/sessions/adapters/grokbot.js +35 -35
- package/dist/src/sessions/adapters/hermes.js +20 -19
- package/dist/src/sessions/adapters/muse.js +50 -49
- package/dist/src/sessions/adapters/omp.js +5 -4
- package/dist/src/sessions/adapters/openclaw.js +30 -29
- package/dist/src/sessions/adapters/opencode.js +26 -26
- package/dist/src/sessions/adapters/openhuman.js +24 -23
- package/dist/src/sessions/adapters/pi.js +5 -4
- package/dist/src/sessions/adapters/windsurf.js +8 -8
- package/dist/src/sessions/analytics.js +10 -9
- package/dist/src/sessions/candidates.js +7 -6
- package/dist/src/sessions/contracts.js +63 -53
- package/dist/src/sessions/output-registration.js +11 -10
- package/dist/src/sessions/workspace-discovery.js +3 -4
- package/dist/src/smiths/context-pipeline/aiwg-md.js +18 -1
- package/dist/src/smiths/context-pipeline/claude-hook.js +42 -6
- package/dist/src/smiths/context-pipeline/parallelism-section.js +72 -5
- package/dist/src/smiths/context-pipeline/workspace-context.js +106 -10
- package/dist/src/update/checker.d.mts +4 -0
- package/dist/src/update/checker.mjs +14 -0
- package/dist/src/writing/exemplar-selection.js +6 -6
- package/dist/src/writing/voice-evaluation.js +24 -23
- package/dist/src/writing/writer-migration.js +14 -13
- package/dist/src/writing/writer-profile.js +19 -18
- package/dist/src/writing/writing-brief.js +18 -17
- package/dist/src/writing/writing-receipt.js +47 -46
- package/package.json +5 -2
- package/schemas/decision/DecisionReview.schema.json +78 -1
- package/schemas/mission-v1.schema.json +3 -0
- package/tools/agents/deploy-agents.mjs +67 -39
- package/tools/agents/providers/antigravity.mjs +6 -1
- package/tools/agents/providers/base.mjs +19 -13
- package/tools/agents/providers/claude.mjs +1 -1
- package/tools/agents/providers/codex.mjs +50 -56
- package/tools/agents/providers/copilot.mjs +8 -2
- package/tools/agents/providers/cursor.mjs +1 -1
- package/tools/agents/providers/deepseek-harness.mjs +1 -1
- package/tools/agents/providers/factory.mjs +7 -1
- package/tools/agents/providers/grok-build.mjs +18 -2
- package/tools/agents/providers/grokbot.mjs +1 -1
- package/tools/agents/providers/hermes.mjs +7 -13
- package/tools/agents/providers/muse-hooks.mjs +21 -14
- package/tools/agents/providers/muse.mjs +7 -2
- package/tools/agents/providers/omp.mjs +28 -4
- package/tools/agents/providers/opencode.mjs +14 -5
- package/tools/agents/providers/openhuman.mjs +1 -1
- package/tools/agents/providers/pi.mjs +1 -1
- package/tools/agents/providers/provider-prepare-coordinator.mjs +29 -9
- package/tools/agents/providers/rule-delivery.mjs +263 -71
- package/tools/agents/providers/windsurf.mjs +7 -0
- package/tools/cli/workspace-status.mjs +5 -0
- package/tools/commands/deploy-prompts-codex.mjs +11 -8
- package/tools/ralph-external/lib/codex-adapter.mjs +6 -2
- package/tools/ralph-external/lib/muse-adapter.mjs +12 -2
- package/tools/ralph-external/lib/worker-health.mjs +1 -1
- package/tools/ralph-external/lib/worker-isolation.mjs +65 -9
- package/tools/ralph-external/process-monitor.mjs +7 -4
- package/tools/ralph-external/session-launcher.mjs +280 -32
- package/tools/skills/deploy-skills-codex.mjs +11 -7
- package/vscode-extension/schemas/aiwg.config.v1.json +11 -1
|
@@ -0,0 +1,503 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { randomUUID } from 'node:crypto';
|
|
4
|
+
import { isDeepStrictEqual } from 'node:util';
|
|
5
|
+
import { dump } from 'js-yaml';
|
|
6
|
+
import { resolveProjectAiwgDir } from '../config/project-artifacts.js';
|
|
7
|
+
import { sha256 } from '../memory/context-pack.js';
|
|
8
|
+
import { indexMemoryAliases, loadMemoryRecord, memoryRecordId, parseMemoryMarkdown } from '../memory/maintenance-record.js';
|
|
9
|
+
import { validateMaintenancePlan } from '../memory/maintenance-contract.js';
|
|
10
|
+
import { getProjectIndexRoot } from './types.js';
|
|
11
|
+
import { acquireDirectoryLock } from './prebuilt-build-lock.js';
|
|
12
|
+
/** Bound source frontmatter while retaining a verifiable chain of older revisions. */
|
|
13
|
+
export const ARTIFACT_MEMORY_HISTORY_LIMIT = 16;
|
|
14
|
+
function boundedHistory(journal, previous) {
|
|
15
|
+
const history = [...(journal.history ?? [])];
|
|
16
|
+
if (previous && !history.some(item => item.id === previous.id && item.version === previous.version))
|
|
17
|
+
history.push(previous);
|
|
18
|
+
const trimmed = history.splice(0, Math.max(0, history.length - ARTIFACT_MEMORY_HISTORY_LIMIT));
|
|
19
|
+
const trimmedHistory = trimmed.length ? {
|
|
20
|
+
count: (journal.trimmedHistory?.count ?? 0) + trimmed.length,
|
|
21
|
+
digest: sha256(JSON.stringify({ previous: journal.trimmedHistory ?? null, records: trimmed })),
|
|
22
|
+
} : journal.trimmedHistory;
|
|
23
|
+
return { history, ...(trimmedHistory ? { trimmedHistory } : {}) };
|
|
24
|
+
}
|
|
25
|
+
function ownerMetadata(data) {
|
|
26
|
+
if (data.memoryMaintenance === undefined)
|
|
27
|
+
return { receipts: [] };
|
|
28
|
+
const value = data.memoryMaintenance;
|
|
29
|
+
if (!value || !Array.isArray(value.receipts))
|
|
30
|
+
throw new Error('Invalid artifact maintenance journal');
|
|
31
|
+
return value;
|
|
32
|
+
}
|
|
33
|
+
function atomicSource(file, data, body) {
|
|
34
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
35
|
+
const temporary = `${file}.${randomUUID()}.tmp`;
|
|
36
|
+
try {
|
|
37
|
+
fs.writeFileSync(temporary, `---\n${dump(data, { noRefs: true, lineWidth: -1 })}---\n${body}`, { mode: 0o600 });
|
|
38
|
+
fs.renameSync(temporary, file);
|
|
39
|
+
}
|
|
40
|
+
finally {
|
|
41
|
+
fs.rmSync(temporary, { force: true });
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
function sourceMetadataDigest(data) {
|
|
45
|
+
const { memoryRecord: _record, memoryMaintenance: _journal, memoryCategory: _category, ...source } = data;
|
|
46
|
+
return sha256(JSON.stringify(source));
|
|
47
|
+
}
|
|
48
|
+
/** Semantic memory maintenance edits canonical sources, then republishes the existing artifact index. */
|
|
49
|
+
export class ArtifactMaintenanceOwner {
|
|
50
|
+
name = 'semantic-memory';
|
|
51
|
+
root;
|
|
52
|
+
options;
|
|
53
|
+
constructor(projectRoot, options = {}) {
|
|
54
|
+
this.root = path.resolve(projectRoot);
|
|
55
|
+
this.options = options;
|
|
56
|
+
}
|
|
57
|
+
indexes() {
|
|
58
|
+
const directory = getProjectIndexRoot(this.root);
|
|
59
|
+
if (!fs.existsSync(directory))
|
|
60
|
+
return [];
|
|
61
|
+
const files = [{ graph: '', file: path.join(directory, 'metadata.json') },
|
|
62
|
+
...fs.readdirSync(directory, { withFileTypes: true }).filter(item => item.isDirectory() && !item.name.startsWith('.'))
|
|
63
|
+
.map(item => ({ graph: item.name, file: path.join(directory, item.name, 'metadata.json') }))];
|
|
64
|
+
return files.filter(item => fs.existsSync(item.file)).map(item => ({ ...item,
|
|
65
|
+
index: JSON.parse(fs.readFileSync(item.file, 'utf8')) }));
|
|
66
|
+
}
|
|
67
|
+
resolveSource(sourcePath) {
|
|
68
|
+
if (!sourcePath || /[\r\n\0]/.test(sourcePath))
|
|
69
|
+
throw new Error('Invalid semantic-memory source path');
|
|
70
|
+
const full = path.resolve(this.root, sourcePath);
|
|
71
|
+
const artifactRoot = resolveProjectAiwgDir(this.root);
|
|
72
|
+
if (!full.startsWith(`${artifactRoot}${path.sep}`))
|
|
73
|
+
throw new Error('Semantic-memory changes require canonical project artifact sources');
|
|
74
|
+
// Reject symlink escapes, including a replacement's nearest existing parent.
|
|
75
|
+
let existing = full;
|
|
76
|
+
while (!fs.existsSync(existing))
|
|
77
|
+
existing = path.dirname(existing);
|
|
78
|
+
const real = fs.realpathSync(existing);
|
|
79
|
+
if (real !== artifactRoot && !real.startsWith(`${fs.realpathSync(artifactRoot)}${path.sep}`)) {
|
|
80
|
+
throw new Error('Semantic-memory source escapes the canonical artifact root');
|
|
81
|
+
}
|
|
82
|
+
if (!full.endsWith('.md'))
|
|
83
|
+
throw new Error('Semantic-memory maintenance requires Markdown canonical sources');
|
|
84
|
+
const logical = path.relative(artifactRoot, full).replace(/\\/g, '/');
|
|
85
|
+
if (/^(?:context\/compound-memory|memory\/line-memory)(?:\/|\.)/.test(logical)) {
|
|
86
|
+
throw new Error('Another lifecycle owner controls this memory source');
|
|
87
|
+
}
|
|
88
|
+
return full;
|
|
89
|
+
}
|
|
90
|
+
sourceFor(entry) {
|
|
91
|
+
let file;
|
|
92
|
+
try {
|
|
93
|
+
const full = entry.path.startsWith('.aiwg/')
|
|
94
|
+
? path.join(resolveProjectAiwgDir(this.root), entry.path.slice('.aiwg/'.length))
|
|
95
|
+
: path.resolve(this.root, entry.path);
|
|
96
|
+
file = this.resolveSource(path.relative(this.root, full));
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
return undefined;
|
|
100
|
+
}
|
|
101
|
+
if (!fs.existsSync(file))
|
|
102
|
+
return undefined;
|
|
103
|
+
const sourcePath = path.relative(this.root, file).replace(/\\/g, '/');
|
|
104
|
+
const { data, body } = parseMemoryMarkdown(fs.readFileSync(file, 'utf8'));
|
|
105
|
+
const digest = sha256(body);
|
|
106
|
+
let stamped;
|
|
107
|
+
if (data.memoryRecord !== undefined) {
|
|
108
|
+
try {
|
|
109
|
+
stamped = loadMemoryRecord(data.memoryRecord);
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
console.warn(`Semantic-memory: ${sourcePath}: invalid memoryRecord metadata quarantined; source is not a maintenance target`);
|
|
113
|
+
return undefined;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
const record = stamped ?? loadMemoryRecord({
|
|
117
|
+
schemaVersion: 'aiwg.memory-record.v1', id: memoryRecordId(sourcePath, digest), sourcePath, digest,
|
|
118
|
+
version: 1, aliases: [...new Set([sourcePath, entry.path])], supersedes: [], mergedFrom: [], splitFrom: [],
|
|
119
|
+
scope: 'project', placement: 'indexed', lifecycle: 'active',
|
|
120
|
+
authority: { owner: this.name, source: 'import', protection: data.pinned === true ? 'pinned' : 'unprotected' },
|
|
121
|
+
observation: { status: 'on-disk', evidence: { source: sourcePath,
|
|
122
|
+
at: fs.statSync(file).mtime.toISOString(), session: null, provider: null, version: null } },
|
|
123
|
+
});
|
|
124
|
+
if (record.sourcePath !== sourcePath || record.digest !== digest || record.authority.owner !== this.name) {
|
|
125
|
+
throw new Error('Canonical semantic-memory source identity or digest has changed');
|
|
126
|
+
}
|
|
127
|
+
return { file, sourcePath, entry, data, body, record };
|
|
128
|
+
}
|
|
129
|
+
sources() {
|
|
130
|
+
const sources = new Map();
|
|
131
|
+
for (const { index } of this.indexes()) {
|
|
132
|
+
for (const entry of Object.values(index.entries)) {
|
|
133
|
+
const source = this.sourceFor(entry);
|
|
134
|
+
if (source)
|
|
135
|
+
sources.set(source.sourcePath, source);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return [...sources.values()].sort((a, b) => a.sourcePath.localeCompare(b.sourcePath));
|
|
139
|
+
}
|
|
140
|
+
async records() { return this.sources().map(source => source.record); }
|
|
141
|
+
async snapshot() {
|
|
142
|
+
return sha256(JSON.stringify({ indexes: this.indexes().map(item => [item.graph, item.index]),
|
|
143
|
+
sources: this.sources().map(source => [source.sourcePath, fs.readFileSync(source.file, 'utf8')]) }));
|
|
144
|
+
}
|
|
145
|
+
target(operation) {
|
|
146
|
+
const source = this.sources().find(item => item.record.id === operation.target.id || item.record.aliases.includes(operation.target.id)
|
|
147
|
+
|| item.sourcePath === operation.owner?.handle);
|
|
148
|
+
if (!source)
|
|
149
|
+
throw new Error('Semantic-memory maintenance target is missing');
|
|
150
|
+
if (operation.owner?.handle !== source.sourcePath || operation.owner.name !== this.name) {
|
|
151
|
+
throw new Error('Semantic-memory owner binding does not match the exact source');
|
|
152
|
+
}
|
|
153
|
+
if (source.record.id !== operation.target.id && !source.record.aliases.includes(operation.target.id)) {
|
|
154
|
+
throw new Error('Stale semantic-memory source digest');
|
|
155
|
+
}
|
|
156
|
+
return source;
|
|
157
|
+
}
|
|
158
|
+
async validate(operation) {
|
|
159
|
+
const source = this.target(operation);
|
|
160
|
+
for (const item of this.sources()) {
|
|
161
|
+
const journal = ownerMetadata(item.data).pending;
|
|
162
|
+
if (journal && journal.operationDigest !== sha256(JSON.stringify(operation))) {
|
|
163
|
+
throw new Error('Another semantic-memory transaction is pending');
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
const pending = ownerMetadata(source.data).pending;
|
|
167
|
+
if (pending) {
|
|
168
|
+
if (pending.operationDigest !== sha256(JSON.stringify(operation)))
|
|
169
|
+
throw new Error('Another semantic-memory transaction is pending');
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
if (operation.owner?.snapshot !== await this.snapshot())
|
|
173
|
+
throw new Error('Stale semantic-memory owner snapshot');
|
|
174
|
+
const records = await this.records();
|
|
175
|
+
validateMaintenancePlan({ schemaVersion: 'aiwg.maintenance-plan.v1', operations: [operation] }, records, { retirementBasis: operation.review?.retirementBasis ?? 'age' });
|
|
176
|
+
if (!operation.review?.reviewer)
|
|
177
|
+
throw new Error('Semantic-memory operations require an exact-plan reviewer');
|
|
178
|
+
if (operation.op === 'recategorize' && !operation.category)
|
|
179
|
+
throw new Error('Recategorize requires an explicit category');
|
|
180
|
+
if (['merge', 'split', 'supersede'].includes(operation.op) && !operation.replacements?.length) {
|
|
181
|
+
throw new Error('Semantic-memory lineage operations require explicit replacements');
|
|
182
|
+
}
|
|
183
|
+
if (operation.op === 'split' && operation.replacements.length < 2)
|
|
184
|
+
throw new Error('Split requires at least two replacements');
|
|
185
|
+
if (operation.op === 'merge' && !operation.related?.length)
|
|
186
|
+
throw new Error('Merge requires reviewed input revisions');
|
|
187
|
+
if (['merge', 'split', 'supersede'].includes(operation.op) && source.record.lifecycle !== 'active') {
|
|
188
|
+
throw new Error('Lineage operations require active originals');
|
|
189
|
+
}
|
|
190
|
+
const seen = new Set([source.record.id]);
|
|
191
|
+
for (const related of operation.related ?? []) {
|
|
192
|
+
const record = records.find(item => item.id === related.id);
|
|
193
|
+
if (!record || seen.has(record.id) || record.digest !== related.digest || record.version !== related.version) {
|
|
194
|
+
throw new Error('Stale or duplicate merge input revision');
|
|
195
|
+
}
|
|
196
|
+
if (record.scope !== source.record.scope || !isDeepStrictEqual(record.authority, source.record.authority)) {
|
|
197
|
+
throw new Error('Merge cannot implicitly change scope or authority');
|
|
198
|
+
}
|
|
199
|
+
if (record.placement !== source.record.placement || record.lifecycle !== source.record.lifecycle) {
|
|
200
|
+
throw new Error('Merge inputs require the same reviewed placement and lifecycle');
|
|
201
|
+
}
|
|
202
|
+
seen.add(record.id);
|
|
203
|
+
}
|
|
204
|
+
const paths = new Set();
|
|
205
|
+
const keys = new Set();
|
|
206
|
+
for (const replacement of operation.replacements ?? []) {
|
|
207
|
+
if (keys.has(replacement.key))
|
|
208
|
+
throw new Error('Replacement keys must be unique');
|
|
209
|
+
keys.add(replacement.key);
|
|
210
|
+
if (!replacement.sourcePath)
|
|
211
|
+
throw new Error('Semantic-memory replacements require an explicit canonical sourcePath');
|
|
212
|
+
const file = this.resolveSource(replacement.sourcePath);
|
|
213
|
+
if (fs.existsSync(file) || paths.has(file))
|
|
214
|
+
throw new Error('Replacement cannot overwrite an original source');
|
|
215
|
+
paths.add(file);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
async verifyRetrieval(operation) {
|
|
219
|
+
const candidates = this.indexes().flatMap(({ index }) => Object.values(index.entries).map(entry => this.sourceFor(entry)))
|
|
220
|
+
.filter((source) => !!source && source.record.lifecycle === 'active' && source.record.placement !== 'archived');
|
|
221
|
+
for (const target of [operation.target, ...(operation.related ?? [])]) {
|
|
222
|
+
const found = candidates.some(source => {
|
|
223
|
+
const referenced = operation.impactedLinks.includes(source.sourcePath) || operation.impactedLinks.includes(source.entry.path)
|
|
224
|
+
|| operation.impactedLinks.includes(source.record.id);
|
|
225
|
+
const retainedActive = operation.to.lifecycle === 'active' && source.record.id === target.id;
|
|
226
|
+
if ((!referenced && !retainedActive) || source.record.digest !== target.digest)
|
|
227
|
+
return false;
|
|
228
|
+
// Compare the published index revision and source body, not merely an existing locator.
|
|
229
|
+
if (source.entry.memoryRecord && (source.entry.memoryRecord.digest !== source.record.digest
|
|
230
|
+
|| source.entry.memoryRecord.version !== source.record.version || source.entry.memoryRecord.lifecycle !== 'active'))
|
|
231
|
+
return false;
|
|
232
|
+
if (!source.entry.memoryRecord && source.entry.checksum !== sha256(fs.readFileSync(source.file, 'utf8')).slice(7, 23))
|
|
233
|
+
return false;
|
|
234
|
+
if (operation.to.lifecycle !== 'active' && [operation.target.id, ...(operation.related ?? []).map(item => item.id)].includes(source.record.id))
|
|
235
|
+
return false;
|
|
236
|
+
return true;
|
|
237
|
+
});
|
|
238
|
+
if (!found)
|
|
239
|
+
return false;
|
|
240
|
+
}
|
|
241
|
+
return true;
|
|
242
|
+
}
|
|
243
|
+
transaction(operation, key, source) {
|
|
244
|
+
const at = (this.options.now?.() ?? new Date()).toISOString();
|
|
245
|
+
const lineage = [source.record.id, ...(operation.related ?? []).map(item => item.id)];
|
|
246
|
+
const replacements = (operation.replacements ?? []).map(replacement => {
|
|
247
|
+
const sourcePath = path.relative(this.root, this.resolveSource(replacement.sourcePath)).replace(/\\/g, '/');
|
|
248
|
+
const digest = sha256(replacement.value);
|
|
249
|
+
return { sourcePath, replacementKey: replacement.key, category: replacement.category ?? operation.category,
|
|
250
|
+
sourceMetadataDigest: sourceMetadataDigest({}),
|
|
251
|
+
record: loadMemoryRecord({ ...source.record, id: memoryRecordId(sourcePath, digest), sourcePath, digest, version: 1,
|
|
252
|
+
placement: source.record.placement, lifecycle: 'active', aliases: [sourcePath, replacement.key], tombstone: undefined,
|
|
253
|
+
supersedes: operation.op === 'supersede' ? lineage : [], mergedFrom: operation.op === 'merge' ? lineage : [],
|
|
254
|
+
splitFrom: operation.op === 'split' ? lineage : [],
|
|
255
|
+
observation: { status: 'on-disk', evidence: { source: sourcePath, at, session: null, provider: null, version: null } },
|
|
256
|
+
}) };
|
|
257
|
+
});
|
|
258
|
+
const originals = [source, ...this.sources().filter(item => (operation.related ?? []).some(related => related.id === item.record.id))];
|
|
259
|
+
const updates = originals.map(original => {
|
|
260
|
+
const lifecycle = ['merge', 'split', 'supersede'].includes(operation.op) ? 'superseded' : operation.to.lifecycle;
|
|
261
|
+
const record = { ...original.record, version: original.record.version + 1,
|
|
262
|
+
placement: operation.to.placement, lifecycle,
|
|
263
|
+
...(lifecycle === 'active' ? { tombstone: undefined } : { tombstone: {
|
|
264
|
+
id: original.record.id, digest: original.record.digest, disposition: lifecycle,
|
|
265
|
+
reason: operation.rationale, reviewer: operation.review.reviewer, at,
|
|
266
|
+
...(replacements[0] ? { successor: replacements[0].record.id } : {}),
|
|
267
|
+
} }),
|
|
268
|
+
};
|
|
269
|
+
return { sourcePath: original.sourcePath, record: loadMemoryRecord(record), previous: original.record, category: operation.category,
|
|
270
|
+
sourceMetadataDigest: sourceMetadataDigest(original.data), hadRecord: original.data.memoryRecord !== undefined,
|
|
271
|
+
previousCategory: original.data.memoryCategory };
|
|
272
|
+
});
|
|
273
|
+
updates.push(...replacements);
|
|
274
|
+
const changed = new Set(updates.map(update => update.record.id));
|
|
275
|
+
indexMemoryAliases([...this.sources().map(item => item.record).filter(record => !changed.has(record.id)),
|
|
276
|
+
...updates.map(update => update.record)]);
|
|
277
|
+
return { key, operationDigest: sha256(JSON.stringify(operation)), updates,
|
|
278
|
+
receipt: { owner: this.name, key, operationDigest: sha256(JSON.stringify(operation)),
|
|
279
|
+
records: updates.map(({ record }) => ({ id: record.id, sourcePath: record.sourcePath, digest: record.digest, version: record.version })),
|
|
280
|
+
sessionEffect: operation.sessionEffect } };
|
|
281
|
+
}
|
|
282
|
+
/** Publish dispositions before a potentially failing rebuild; queries only read metadata. */
|
|
283
|
+
publishDispositions(updates) {
|
|
284
|
+
for (const { file, index } of this.indexes()) {
|
|
285
|
+
let changed = false;
|
|
286
|
+
for (const entry of Object.values(index.entries)) {
|
|
287
|
+
const entryFile = entry.path.startsWith('.aiwg/')
|
|
288
|
+
? path.join(resolveProjectAiwgDir(this.root), entry.path.slice('.aiwg/'.length))
|
|
289
|
+
: path.resolve(this.root, entry.path);
|
|
290
|
+
const update = updates.find(item => path.resolve(this.root, item.sourcePath) === entryFile);
|
|
291
|
+
if (!update)
|
|
292
|
+
continue;
|
|
293
|
+
entry.memoryRecord = update.record;
|
|
294
|
+
changed = true;
|
|
295
|
+
}
|
|
296
|
+
if (!changed)
|
|
297
|
+
continue;
|
|
298
|
+
// Fortemi cache freshness is bound to builtAt. Disposition publication is
|
|
299
|
+
// a new metadata revision even if the subsequent rebuild fails.
|
|
300
|
+
const previousTime = Date.parse(index.builtAt);
|
|
301
|
+
index.builtAt = new Date(Math.max(Date.now(), Number.isFinite(previousTime) ? previousTime + 1 : 0)).toISOString();
|
|
302
|
+
const temporary = `${file}.${randomUUID()}.tmp`;
|
|
303
|
+
try {
|
|
304
|
+
fs.writeFileSync(temporary, `${JSON.stringify(index)}\n`, { flag: 'wx', mode: 0o600 });
|
|
305
|
+
fs.renameSync(temporary, file);
|
|
306
|
+
}
|
|
307
|
+
finally {
|
|
308
|
+
fs.rmSync(temporary, { force: true });
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
async apply(operation, key) {
|
|
313
|
+
const release = await acquireDirectoryLock(path.join(getProjectIndexRoot(this.root), '.semantic-maintenance.lock'), { timeoutMs: 0, reclaimDeadOwner: true });
|
|
314
|
+
try {
|
|
315
|
+
const existing = await this.receipt(key);
|
|
316
|
+
if (existing) {
|
|
317
|
+
if (existing.operationDigest !== sha256(JSON.stringify(operation)))
|
|
318
|
+
throw new Error('Receipt key belongs to a different operation');
|
|
319
|
+
return existing;
|
|
320
|
+
}
|
|
321
|
+
await this.validate(operation);
|
|
322
|
+
const source = this.target(operation);
|
|
323
|
+
const metadata = ownerMetadata(source.data);
|
|
324
|
+
const pending = metadata.pending ?? this.transaction(operation, key, source);
|
|
325
|
+
if (pending.key !== key || pending.operationDigest !== sha256(JSON.stringify(operation))) {
|
|
326
|
+
throw new Error('Pending semantic-memory transaction belongs to a different exact plan');
|
|
327
|
+
}
|
|
328
|
+
if (!metadata.pending) {
|
|
329
|
+
if (operation.from.placement === 'resident' && (operation.to.placement !== 'resident' || operation.to.lifecycle !== 'active')
|
|
330
|
+
&& !await this.verifyRetrieval(operation)) {
|
|
331
|
+
throw new Error('Indexed retrieval must be verified before resident removal');
|
|
332
|
+
}
|
|
333
|
+
const current = parseMemoryMarkdown(fs.readFileSync(source.file, 'utf8'));
|
|
334
|
+
if (sha256(current.body) !== source.record.digest || JSON.stringify(current.data) !== JSON.stringify(source.data)) {
|
|
335
|
+
throw new Error('Canonical source changed before the owner transaction started');
|
|
336
|
+
}
|
|
337
|
+
atomicSource(source.file, { ...source.data, memoryMaintenance: { ...metadata, pending } }, source.body);
|
|
338
|
+
}
|
|
339
|
+
for (const update of pending.updates) {
|
|
340
|
+
const file = this.resolveSource(update.sourcePath);
|
|
341
|
+
const replacement = operation.replacements?.find(item => item.key === update.replacementKey);
|
|
342
|
+
const current = fs.existsSync(file) ? parseMemoryMarkdown(fs.readFileSync(file, 'utf8')) : undefined;
|
|
343
|
+
const body = current?.body ?? replacement?.value;
|
|
344
|
+
if (body === undefined || sha256(body) !== update.record.digest)
|
|
345
|
+
throw new Error('Source changed during pending semantic-memory transaction');
|
|
346
|
+
if (sourceMetadataDigest(current?.data ?? {}) !== update.sourceMetadataDigest) {
|
|
347
|
+
throw new Error('Source metadata changed during pending semantic-memory transaction');
|
|
348
|
+
}
|
|
349
|
+
const currentRecord = current?.data.memoryRecord === undefined ? undefined : loadMemoryRecord(current.data.memoryRecord);
|
|
350
|
+
const before = !update.hadRecord && currentRecord === undefined
|
|
351
|
+
|| update.previous && JSON.stringify(currentRecord) === JSON.stringify(update.previous);
|
|
352
|
+
const after = JSON.stringify(currentRecord) === JSON.stringify(update.record);
|
|
353
|
+
if (!before && !after)
|
|
354
|
+
throw new Error('Source authority or revision changed during pending semantic-memory transaction');
|
|
355
|
+
const currentCategory = current?.data.memoryCategory;
|
|
356
|
+
if (JSON.stringify(currentCategory) !== JSON.stringify(update.previousCategory)
|
|
357
|
+
&& JSON.stringify(currentCategory) !== JSON.stringify(update.category ?? update.previousCategory)) {
|
|
358
|
+
throw new Error('Source category changed during pending semantic-memory transaction');
|
|
359
|
+
}
|
|
360
|
+
const journal = ownerMetadata(current?.data ?? {});
|
|
361
|
+
atomicSource(file, { ...(current?.data ?? {}), memoryRecord: update.record,
|
|
362
|
+
memoryMaintenance: { ...journal, ...boundedHistory(journal, update.previous) },
|
|
363
|
+
...(update.category === undefined ? {} : { memoryCategory: update.category }) }, body);
|
|
364
|
+
}
|
|
365
|
+
this.publishDispositions(pending.updates);
|
|
366
|
+
const graphs = this.indexes().map(item => item.graph);
|
|
367
|
+
if (this.options.rebuild)
|
|
368
|
+
await this.options.rebuild(this.root, graphs);
|
|
369
|
+
else {
|
|
370
|
+
const { buildIndex } = await import('./index-builder.js');
|
|
371
|
+
for (const graph of graphs)
|
|
372
|
+
await buildIndex(this.root, { ...(graph ? { graph } : {}), force: true, quiet: true });
|
|
373
|
+
}
|
|
374
|
+
for (const update of pending.updates) {
|
|
375
|
+
const current = parseMemoryMarkdown(fs.readFileSync(this.resolveSource(update.sourcePath), 'utf8'));
|
|
376
|
+
if (sha256(current.body) !== update.record.digest || sourceMetadataDigest(current.data) !== update.sourceMetadataDigest
|
|
377
|
+
|| JSON.stringify(loadMemoryRecord(current.data.memoryRecord)) !== JSON.stringify(update.record)) {
|
|
378
|
+
throw new Error('Canonical source changed during index publication; owner receipt remains pending');
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
const anchor = parseMemoryMarkdown(fs.readFileSync(source.file, 'utf8'));
|
|
382
|
+
atomicSource(source.file, { ...anchor.data, memoryMaintenance: {
|
|
383
|
+
...boundedHistory(ownerMetadata(anchor.data)),
|
|
384
|
+
receipts: [...metadata.receipts, pending.receipt],
|
|
385
|
+
} }, anchor.body);
|
|
386
|
+
return pending.receipt;
|
|
387
|
+
}
|
|
388
|
+
finally {
|
|
389
|
+
await release();
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
async receipt(key, options = {}) {
|
|
393
|
+
if (options.readOnly) {
|
|
394
|
+
for (const { index } of this.indexes()) {
|
|
395
|
+
for (const entry of Object.values(index.entries)) {
|
|
396
|
+
const full = entry.path.startsWith('.aiwg/')
|
|
397
|
+
? path.join(resolveProjectAiwgDir(this.root), entry.path.slice('.aiwg/'.length)) : path.resolve(this.root, entry.path);
|
|
398
|
+
let file;
|
|
399
|
+
try {
|
|
400
|
+
file = this.resolveSource(path.relative(this.root, full));
|
|
401
|
+
}
|
|
402
|
+
catch {
|
|
403
|
+
continue;
|
|
404
|
+
}
|
|
405
|
+
if (!fs.existsSync(file))
|
|
406
|
+
continue;
|
|
407
|
+
const { data } = parseMemoryMarkdown(fs.readFileSync(file, 'utf8'));
|
|
408
|
+
const receipt = ownerMetadata(data).receipts.find(item => item.key === key);
|
|
409
|
+
if (receipt)
|
|
410
|
+
return receipt;
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
return undefined;
|
|
414
|
+
}
|
|
415
|
+
for (const source of this.sources()) {
|
|
416
|
+
const receipt = ownerMetadata(source.data).receipts.find(item => item.key === key);
|
|
417
|
+
if (receipt)
|
|
418
|
+
return receipt;
|
|
419
|
+
}
|
|
420
|
+
return undefined;
|
|
421
|
+
}
|
|
422
|
+
async verifyEndState(operation, key) {
|
|
423
|
+
const findings = [];
|
|
424
|
+
const anchorFile = this.resolveSource(operation.owner.handle);
|
|
425
|
+
if (!fs.existsSync(anchorFile))
|
|
426
|
+
return ['Retained owner receipt source is missing'];
|
|
427
|
+
const anchor = parseMemoryMarkdown(fs.readFileSync(anchorFile, 'utf8'));
|
|
428
|
+
const receipt = ownerMetadata(anchor.data).receipts.find(item => item.key === key);
|
|
429
|
+
if (receipt && (receipt.owner !== this.name || receipt.operationDigest !== sha256(JSON.stringify(operation)))) {
|
|
430
|
+
findings.push('Owner receipt does not bind the exact reviewed operation');
|
|
431
|
+
}
|
|
432
|
+
const paths = new Set([operation.owner.handle, ...(receipt?.records ?? []).map(item => item.sourcePath),
|
|
433
|
+
...(operation.replacements ?? []).map(item => item.sourcePath)]);
|
|
434
|
+
const sources = [];
|
|
435
|
+
for (const sourcePath of paths) {
|
|
436
|
+
const file = this.resolveSource(sourcePath);
|
|
437
|
+
if (!fs.existsSync(file)) {
|
|
438
|
+
findings.push(`Retained source is missing: ${sourcePath}`);
|
|
439
|
+
continue;
|
|
440
|
+
}
|
|
441
|
+
const { data, body } = parseMemoryMarkdown(fs.readFileSync(file, 'utf8'));
|
|
442
|
+
if (data.memoryRecord === undefined) {
|
|
443
|
+
findings.push(`Committed source metadata is missing: ${sourcePath}`);
|
|
444
|
+
continue;
|
|
445
|
+
}
|
|
446
|
+
const record = loadMemoryRecord(data.memoryRecord);
|
|
447
|
+
const journal = ownerMetadata(data);
|
|
448
|
+
if (journal.history !== undefined && !Array.isArray(journal.history))
|
|
449
|
+
throw new Error('Invalid artifact maintenance history');
|
|
450
|
+
if (record.sourcePath !== sourcePath || record.digest !== sha256(body))
|
|
451
|
+
findings.push(`Retained source body differs: ${sourcePath}`);
|
|
452
|
+
const committed = receipt?.records.find(item => item.sourcePath === sourcePath);
|
|
453
|
+
if (committed && (record.id !== committed.id || record.digest !== committed.digest || record.version !== committed.version)) {
|
|
454
|
+
findings.push(`Source differs from its exact owner receipt: ${sourcePath}`);
|
|
455
|
+
}
|
|
456
|
+
const target = [operation.target, ...(operation.related ?? [])].find(item => item.id === record.id);
|
|
457
|
+
if (target) {
|
|
458
|
+
const prior = journal.history?.find(item => item.id === target.id && item.version === target.version
|
|
459
|
+
&& item.digest === target.digest && item.placement === operation.from.placement && item.lifecycle === operation.from.lifecycle);
|
|
460
|
+
if (!prior)
|
|
461
|
+
findings.push(`Reviewed source history is missing: ${target.id}`);
|
|
462
|
+
if (prior && !isDeepStrictEqual(prior.authority, record.authority))
|
|
463
|
+
findings.push(`Source authority changed: ${target.id}`);
|
|
464
|
+
if (operation.op === 'restore' && !prior?.tombstone)
|
|
465
|
+
findings.push(`Recovery tombstone history is missing: ${target.id}`);
|
|
466
|
+
if (operation.category && data.memoryCategory !== operation.category)
|
|
467
|
+
findings.push(`Reviewed category differs: ${target.id}`);
|
|
468
|
+
}
|
|
469
|
+
const replacement = operation.replacements?.find(item => item.sourcePath === sourcePath);
|
|
470
|
+
const expectedCategory = replacement?.category ?? (replacement ? operation.category : undefined);
|
|
471
|
+
if (expectedCategory && data.memoryCategory !== expectedCategory)
|
|
472
|
+
findings.push(`Replacement category differs: ${sourcePath}`);
|
|
473
|
+
sources.push({ file, sourcePath, data, record });
|
|
474
|
+
}
|
|
475
|
+
// Inspect every published graph containing an affected source. The normal query path
|
|
476
|
+
// consumes this exact metadata, including inactive dispositions and category changes.
|
|
477
|
+
const indexes = this.indexes();
|
|
478
|
+
for (const source of sources) {
|
|
479
|
+
let published = false;
|
|
480
|
+
for (const { graph, index } of indexes) {
|
|
481
|
+
const entries = Object.values(index.entries).filter(entry => {
|
|
482
|
+
const full = entry.path.startsWith('.aiwg/')
|
|
483
|
+
? path.join(resolveProjectAiwgDir(this.root), entry.path.slice('.aiwg/'.length)) : path.resolve(this.root, entry.path);
|
|
484
|
+
return full === source.file;
|
|
485
|
+
});
|
|
486
|
+
for (const entry of entries) {
|
|
487
|
+
published = true;
|
|
488
|
+
if (!isDeepStrictEqual(entry.memoryRecord, source.record)
|
|
489
|
+
|| entry.memoryCategory !== source.data.memoryCategory) {
|
|
490
|
+
findings.push(`Published index revision differs: ${source.sourcePath} (${graph || 'default'})`);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
if (!published)
|
|
495
|
+
findings.push(`Indexed source is unavailable: ${source.sourcePath}`);
|
|
496
|
+
}
|
|
497
|
+
return findings;
|
|
498
|
+
}
|
|
499
|
+
async pending(operation) {
|
|
500
|
+
return ownerMetadata(this.target(operation).data).pending?.operationDigest === sha256(JSON.stringify(operation));
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
//# sourceMappingURL=memory-maintenance.js.map
|
|
@@ -21,7 +21,9 @@ export function buildResolvedBindings(root, relations) {
|
|
|
21
21
|
targets[target.type].push(target.id);
|
|
22
22
|
table.rules[id] = { activation, tagged, summary: parsed.rule?.summary ?? rule.summary, targets };
|
|
23
23
|
for (const [provider, projection] of Object.entries(table.providers)) {
|
|
24
|
-
if (activation === '
|
|
24
|
+
if (activation === 'core')
|
|
25
|
+
projection.kernel.push(id); // core bodies load at startup like kernels
|
|
26
|
+
else if (activation === 'kernel') {
|
|
25
27
|
const hermesDeferred = provider === 'hermes' && tagged && String(parsed.rule?.enforcement ?? '').toLowerCase() !== 'critical';
|
|
26
28
|
projection[hermesDeferred ? 'summary' : 'kernel'].push(id);
|
|
27
29
|
}
|