@etiquekit/etq 1.0.11 → 1.0.13
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/AGENTS.md +19 -26
- package/LICENSE +2 -3
- package/NOTICE +6 -4
- package/QuickStart.md +35 -47
- package/README.md +54 -56
- package/bin/etiquette +1 -5
- package/bin/etiquette-core +1 -5
- package/docs/ARCHITECTURE.md +5 -5
- package/docs/CODEX_CLIENT_COMPATIBILITY.md +1 -1
- package/docs/CONCEPTS.md +7 -23
- package/docs/CONCEPT_STATUS.md +66 -0
- package/docs/CORE_PROFILE.md +3 -5
- package/docs/LANE_PROVISIONING.md +7 -2
- package/docs/README.md +4 -4
- package/docs/RELEASE_SURFACE_AUDIT.md +7 -6
- package/docs/SEAT_DISCIPLINE.md +55 -91
- package/docs/SEAT_PROVISIONING.md +19 -22
- package/docs/TEAM_HANDOFF.md +21 -21
- package/docs/WORKTREE_QOL.md +7 -6
- package/docs/contracts/ledger-entry/README.md +11 -11
- package/docs/contracts/ledger-entry/ledger-entry.v0.2.md +3 -3
- package/docs/contracts/ledger-entry/ledger-entry.v0.md +8 -8
- package/package.json +11 -11
- package/packages/control/src/authority/lease.ts +261 -0
- package/packages/control/src/authority/node-delegation.ts +257 -0
- package/packages/control/src/authority/rig-conductor.ts +632 -0
- package/packages/control/src/cli/argv.ts +155 -0
- package/packages/control/src/cli/commands/console.ts +200 -0
- package/packages/control/src/cli/commands/dispatch-core.ts +89 -0
- package/packages/control/src/cli/commands/dispatch.ts +279 -0
- package/packages/control/src/cli/commands/harness.ts +89 -0
- package/packages/control/src/cli/commands/hook.ts +50 -0
- package/packages/control/src/cli/commands/ledger.ts +91 -0
- package/packages/control/src/cli/commands/local-workflow.ts +4686 -0
- package/packages/control/src/cli/commands/memory.ts +445 -0
- package/packages/control/src/cli/commands/release.ts +108 -0
- package/packages/control/src/cli/commands/rubric.ts +103 -0
- package/packages/control/src/cli/commands/seat.ts +179 -0
- package/packages/control/src/cli/commands/session.ts +127 -0
- package/packages/control/src/cli/commands/supervision.ts +246 -0
- package/packages/control/src/cli/commands/sync.ts +119 -0
- package/packages/control/src/cli/commands/workflow.ts +86 -0
- package/packages/control/src/cli/commands/workspace.ts +50 -0
- package/packages/control/src/cli/core-usage.ts +34 -0
- package/packages/control/src/cli/prompt.ts +67 -0
- package/packages/control/src/cli/supervision-deps.ts +44 -0
- package/packages/control/src/cli/usage.ts +241 -0
- package/packages/control/src/cli.ts +207 -0
- package/packages/control/src/core-cli.ts +50 -0
- package/packages/control/src/dispatch/decision.ts +202 -0
- package/packages/control/src/dispatch/projection.ts +293 -0
- package/packages/control/src/dispatch/record.ts +153 -0
- package/packages/control/src/engagement/project.ts +170 -0
- package/packages/control/src/fs.ts +19 -0
- package/packages/control/src/harness/pruning.ts +406 -0
- package/packages/control/src/hooks/dispatcher.ts +117 -0
- package/packages/control/src/hooks/outbox.ts +86 -0
- package/packages/control/src/hooks/sanitize.ts +6 -0
- package/packages/control/src/hooks/types.ts +34 -0
- package/packages/control/src/index.ts +384 -0
- package/packages/control/src/ledger/entry.ts +303 -0
- package/packages/control/src/ledger/indexer.ts +542 -0
- package/packages/control/src/memory/context.ts +149 -0
- package/packages/control/src/memory/drain-import.ts +207 -0
- package/packages/control/src/memory/indexer.ts +284 -0
- package/packages/control/src/memory/query.ts +75 -0
- package/packages/control/src/memory/sanitize.ts +50 -0
- package/packages/control/src/memory/sharded-drain-import.ts +212 -0
- package/packages/control/src/memory/status.ts +211 -0
- package/packages/control/src/memory/store-lifecycle.ts +509 -0
- package/packages/control/src/memory/store.ts +284 -0
- package/packages/control/src/memory/types.ts +146 -0
- package/packages/control/src/parity/surfaces.ts +748 -0
- package/packages/control/src/project.ts +141 -0
- package/packages/control/src/projection/local-ledger-view.ts +373 -0
- package/packages/control/src/projection/return-enforcement.ts +48 -0
- package/packages/control/src/projection/timeline-preview.ts +539 -0
- package/packages/control/src/projection/timeline.ts +708 -0
- package/packages/control/src/release/readiness.ts +831 -0
- package/packages/control/src/rubric/loader.ts +326 -0
- package/packages/control/src/rubric/promotion.ts +54 -0
- package/packages/control/src/rubric/runner.ts +159 -0
- package/packages/control/src/rubric/types.ts +158 -0
- package/packages/control/src/seat/owner-card.ts +388 -0
- package/packages/control/src/seat/readiness.ts +834 -0
- package/packages/control/src/session/runbook.ts +431 -0
- package/packages/control/src/shared/sanitize.ts +49 -0
- package/packages/control/src/supervision/action-classes.ts +192 -0
- package/packages/control/src/supervision/command-apply.ts +378 -0
- package/packages/control/src/supervision/errors.ts +14 -0
- package/packages/control/src/supervision/event-replay.ts +155 -0
- package/packages/control/src/supervision/events.ts +109 -0
- package/packages/control/src/supervision/index.ts +16 -0
- package/packages/control/src/supervision/manifest.ts +127 -0
- package/packages/control/src/supervision/paths.ts +49 -0
- package/packages/control/src/supervision/projection-adapter.ts +274 -0
- package/packages/control/src/supervision/projection.ts +75 -0
- package/packages/control/src/supervision/rebuild.ts +99 -0
- package/packages/control/src/supervision/session-open.ts +131 -0
- package/packages/control/src/supervision/session-read.ts +99 -0
- package/packages/control/src/supervision/sqlite-impl.ts +71 -0
- package/packages/control/src/supervision/sqlite.ts +121 -0
- package/packages/control/src/supervision/store-rows.ts +371 -0
- package/packages/control/src/supervision/turn-close.ts +154 -0
- package/packages/control/src/supervision/turn-open.ts +284 -0
- package/packages/control/src/sync/event-log-merge-driver.ts +263 -0
- package/packages/control/src/sync/join-plan.ts +375 -0
- package/packages/control/src/sync/outbox.ts +492 -0
- package/packages/control/src/workflow/evaluator.ts +140 -0
- package/packages/control/src/workflow/loader.ts +200 -0
- package/packages/control/src/workflow/types.ts +90 -0
- package/packages/control/src/workspace/authority.ts +499 -0
- package/packages/protocol/src/guards.ts +119 -0
- package/packages/protocol/src/huddle-board.ts +198 -0
- package/packages/protocol/src/huddle.ts +295 -0
- package/packages/protocol/src/incident.ts +251 -0
- package/packages/protocol/src/index.ts +8 -0
- package/packages/protocol/src/interfaces.ts +107 -0
- package/packages/protocol/src/packet-profile.ts +195 -0
- package/packages/protocol/src/state.ts +81 -0
- package/packages/protocol/src/types.ts +434 -0
- package/release/lineage.v0.json +14 -0
- package/scripts/release-candidate-verify.sh +175 -0
- package/scripts/release-checksum.sh +25 -0
- package/scripts/release-pack-canary.sh +97 -0
- package/scripts/release-scan.sh +249 -0
- package/scripts/release-sign.sh +34 -0
- package/templates/etiquette-vanilla-v0/README.md +2 -3
- package/templates/etiquette-vanilla-v0/source/control-seat/README.md +0 -6
- package/templates/etiquette-vanilla-v0/source/control-seat/bin/seat-doctor +0 -5
- package/templates/etiquette-vanilla-v0/validate-vanilla.sh +0 -5
- package/templates/seat-packs-v0/README.md +3 -2
- package/templates/seat-packs-v0/source/claude-code-seat/README.md +3 -29
- package/templates/seat-packs-v0/source/codex-seat/README.md +3 -17
- package/templates/seat-packs-v0/source/gemini-seat/README.md +3 -23
- package/templates/seat-packs-v0/source/ollama-seat/README.md +3 -3
- package/templates/seat-packs-v0/source/openrouter-seat/README.md +3 -3
- package/docs/AGENT_SPAWNING.md +0 -78
- package/docs/INTEGRATION_UI.md +0 -215
- package/docs/OPERATOR_PLAYBOOK.md +0 -77
- package/docs/TAG_ROUTE.md +0 -95
- package/lib/etiquette-core.js +0 -315
- package/lib/etiquette.js +0 -1160
- package/templates/DISPATCH_PREMISES.md +0 -39
- package/templates/etiquette-vanilla-v0/source/control-seat/bin/access-assurance-check +0 -55
- package/templates/etiquette-vanilla-v0/source/control-seat/docs/work/access/ACCESS-ASSURANCE.md +0 -39
- package/templates/etiquette-vanilla-v0/source/control-seat/docs/work/access/workspace-secure-profile.v0.json +0 -53
- package/templates/hosted-receiver/README.md +0 -41
- package/templates/hosted-receiver/w1-github-webhook-receiver.mjs +0 -129
|
@@ -0,0 +1,542 @@
|
|
|
1
|
+
import { Database } from 'bun:sqlite';
|
|
2
|
+
import { existsSync, mkdirSync, rmSync, statSync } from 'node:fs';
|
|
3
|
+
import { readFile, readdir } from 'node:fs/promises';
|
|
4
|
+
import { dirname, extname, join, relative, resolve } from 'node:path';
|
|
5
|
+
import {
|
|
6
|
+
LEDGER_STATUS_EFFECTS,
|
|
7
|
+
assertLedgerTopicHints,
|
|
8
|
+
type LedgerAuthorityClass,
|
|
9
|
+
type LedgerEntryRefs,
|
|
10
|
+
type LedgerEntryType,
|
|
11
|
+
type LedgerExportClass,
|
|
12
|
+
type LedgerStatusEffect,
|
|
13
|
+
} from './entry';
|
|
14
|
+
|
|
15
|
+
const DOCUMENT_SCAN_DIRS = [
|
|
16
|
+
'docs/work/ledger',
|
|
17
|
+
'docs/work/receipts',
|
|
18
|
+
'docs/work/topic-snapshots',
|
|
19
|
+
'docs/work/ledger-digests',
|
|
20
|
+
'docs/work/consumer-registry',
|
|
21
|
+
'docs/work/bus-feed',
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
const TEXT_EXTENSIONS = new Set(['.md', '.yaml', '.yml', '.json', '.jsonl', '.ndjson', '.txt']);
|
|
25
|
+
|
|
26
|
+
export interface ParsedLedgerLine {
|
|
27
|
+
entry_id: string;
|
|
28
|
+
schema: 'ledger-entry.v0' | 'ledger-entry.v0.1' | 'ledger-entry.v0.2';
|
|
29
|
+
created_at: string;
|
|
30
|
+
entry_type: LedgerEntryType | string;
|
|
31
|
+
spine_eligible: boolean;
|
|
32
|
+
authority_class: LedgerAuthorityClass | string;
|
|
33
|
+
grants_execution: boolean;
|
|
34
|
+
export_class: LedgerExportClass | string;
|
|
35
|
+
refs: LedgerEntryRefs;
|
|
36
|
+
topic_hints: string[];
|
|
37
|
+
status_effect: LedgerStatusEffect;
|
|
38
|
+
source_path: string;
|
|
39
|
+
line_no: number;
|
|
40
|
+
extra: Record<string, string>;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface LedgerIndexResult {
|
|
44
|
+
dbPath: string;
|
|
45
|
+
ledger_entries: number;
|
|
46
|
+
refs: number;
|
|
47
|
+
topics: number;
|
|
48
|
+
documents: number;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface CompactLedgerSearchResult {
|
|
52
|
+
entry_id: string;
|
|
53
|
+
title: string;
|
|
54
|
+
type: string;
|
|
55
|
+
topic_hints: string[];
|
|
56
|
+
status_effect: LedgerStatusEffect;
|
|
57
|
+
refs: string[];
|
|
58
|
+
confidence: 'metadata';
|
|
59
|
+
is_canonical: false;
|
|
60
|
+
regenerable: true;
|
|
61
|
+
can_authorize: false;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
interface LedgerEntryRow {
|
|
65
|
+
entry_id: string;
|
|
66
|
+
entry_type: string;
|
|
67
|
+
topic_hints_json: string;
|
|
68
|
+
status_effect: LedgerStatusEffect;
|
|
69
|
+
refs_json: string;
|
|
70
|
+
source_path?: string;
|
|
71
|
+
line_no?: number;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export interface LedgerIndexEntryDetail extends CompactLedgerSearchResult {
|
|
75
|
+
source_path: string;
|
|
76
|
+
line_no: number;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function defaultLedgerIndexDbPath(projectRoot: string): string {
|
|
80
|
+
return join(projectRoot, '.etiquette', 'state', 'ledger', 'index.db');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function defaultPrivateVerbatimDbPath(projectRoot: string): string {
|
|
84
|
+
return join(projectRoot, '.etiquette', 'state', 'ledger', 'private-verbatim.db');
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export async function ledgerIndexNeedsRebuild(options: {
|
|
88
|
+
projectRoot: string;
|
|
89
|
+
dbPath?: string;
|
|
90
|
+
}): Promise<boolean> {
|
|
91
|
+
const projectRoot = resolve(options.projectRoot);
|
|
92
|
+
const dbPath = options.dbPath ?? defaultLedgerIndexDbPath(projectRoot);
|
|
93
|
+
if (!existsSync(dbPath)) return true;
|
|
94
|
+
const dbMtime = statSync(dbPath).mtimeMs;
|
|
95
|
+
const batches = await Promise.all(DOCUMENT_SCAN_DIRS.map((dir) => collectDocuments(projectRoot, dir)));
|
|
96
|
+
for (const relPath of new Set(batches.flat())) {
|
|
97
|
+
if (statSync(join(projectRoot, relPath)).mtimeMs > dbMtime) return true;
|
|
98
|
+
}
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function compactWhitespace(value: string): string {
|
|
103
|
+
return value.replace(/\s+/g, ' ').trim();
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function stripMarkdownHeading(value: string): string {
|
|
107
|
+
return compactWhitespace(value.replace(/^#+\s*/, ''));
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function titleFromContent(content: string, fallback: string): string {
|
|
111
|
+
const heading = content.split('\n').find((line) => line.startsWith('# '));
|
|
112
|
+
return heading ? stripMarkdownHeading(heading) : fallback;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function parseKeyValueTokens(line: string): { created_at: string; fields: Record<string, string> } | null {
|
|
116
|
+
const match = /^-\s+(\S+)\s+(.*)$/.exec(line.trim());
|
|
117
|
+
if (!match) return null;
|
|
118
|
+
const [, createdAt, rest] = match;
|
|
119
|
+
const fields: Record<string, string> = {};
|
|
120
|
+
for (const token of rest.split(/\s+/)) {
|
|
121
|
+
const equalsAt = token.indexOf('=');
|
|
122
|
+
if (equalsAt <= 0) continue;
|
|
123
|
+
fields[token.slice(0, equalsAt)] = token.slice(equalsAt + 1);
|
|
124
|
+
}
|
|
125
|
+
return { created_at: createdAt, fields };
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function parseBool(value: string | undefined): boolean {
|
|
129
|
+
return value === 'true';
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function splitList(value: string | undefined): string[] {
|
|
133
|
+
if (!value || value === 'none') return [];
|
|
134
|
+
return value.split(',').map((part) => part.trim()).filter(Boolean);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function parseRefs(value: string | undefined): LedgerEntryRefs {
|
|
138
|
+
const refs: LedgerEntryRefs = {};
|
|
139
|
+
for (const item of splitList(value)) {
|
|
140
|
+
const colonAt = item.indexOf(':');
|
|
141
|
+
if (colonAt <= 0) continue;
|
|
142
|
+
const key = item.slice(0, colonAt) as keyof LedgerEntryRefs;
|
|
143
|
+
const ref = item.slice(colonAt + 1);
|
|
144
|
+
if (!['lanes', 'commits', 'prs', 'bus', 'artifacts', 'receipts'].includes(key)) continue;
|
|
145
|
+
refs[key] = [...(refs[key] ?? []), ref];
|
|
146
|
+
}
|
|
147
|
+
return refs;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function refPairs(refs: LedgerEntryRefs): { type: string; ref: string }[] {
|
|
151
|
+
const pairs: { type: string; ref: string }[] = [];
|
|
152
|
+
for (const key of ['lanes', 'commits', 'prs', 'bus', 'artifacts', 'receipts'] as const) {
|
|
153
|
+
for (const ref of refs[key] ?? []) {
|
|
154
|
+
pairs.push({ type: key, ref });
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return pairs;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function statusEffect(value: string | undefined): LedgerStatusEffect {
|
|
161
|
+
if (!value) return 'none';
|
|
162
|
+
if (LEDGER_STATUS_EFFECTS.includes(value as LedgerStatusEffect)) {
|
|
163
|
+
return value as LedgerStatusEffect;
|
|
164
|
+
}
|
|
165
|
+
throw new Error(`unsupported ledger status_effect: ${value}`);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export function parseLedgerEntryLine(line: string, sourcePath = 'docs/work/ledger/LEDGER.md', lineNo = 0): ParsedLedgerLine | null {
|
|
169
|
+
const parsed = parseKeyValueTokens(line);
|
|
170
|
+
if (!parsed) return null;
|
|
171
|
+
const { created_at: createdAt, fields } = parsed;
|
|
172
|
+
if (fields.schema !== 'ledger-entry.v0' && fields.schema !== 'ledger-entry.v0.1' && fields.schema !== 'ledger-entry.v0.2') return null;
|
|
173
|
+
const entryId = fields.entry_id;
|
|
174
|
+
if (!entryId) return null;
|
|
175
|
+
const topicHints = splitList(fields.topic_hints);
|
|
176
|
+
assertLedgerTopicHints(topicHints);
|
|
177
|
+
const effect = statusEffect(fields.status_effect);
|
|
178
|
+
const known = new Set([
|
|
179
|
+
'schema',
|
|
180
|
+
'entry_id',
|
|
181
|
+
'entry_type',
|
|
182
|
+
'spine_eligible',
|
|
183
|
+
'authority_class',
|
|
184
|
+
'grants_execution',
|
|
185
|
+
'export_class',
|
|
186
|
+
'refs',
|
|
187
|
+
'topic_hints',
|
|
188
|
+
'status_effect',
|
|
189
|
+
]);
|
|
190
|
+
const extra = Object.fromEntries(Object.entries(fields).filter(([key]) => !known.has(key)));
|
|
191
|
+
return {
|
|
192
|
+
entry_id: entryId,
|
|
193
|
+
schema: fields.schema,
|
|
194
|
+
created_at: createdAt,
|
|
195
|
+
entry_type: fields.entry_type ?? 'event',
|
|
196
|
+
spine_eligible: parseBool(fields.spine_eligible),
|
|
197
|
+
authority_class: fields.authority_class ?? 'advisory',
|
|
198
|
+
grants_execution: parseBool(fields.grants_execution),
|
|
199
|
+
export_class: fields.export_class ?? 'local_private',
|
|
200
|
+
refs: parseRefs(fields.refs),
|
|
201
|
+
topic_hints: topicHints,
|
|
202
|
+
status_effect: effect,
|
|
203
|
+
source_path: sourcePath,
|
|
204
|
+
line_no: lineNo,
|
|
205
|
+
extra,
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function resetDbFiles(dbPath: string): void {
|
|
210
|
+
for (const path of [dbPath, `${dbPath}-wal`, `${dbPath}-shm`]) {
|
|
211
|
+
if (existsSync(path)) rmSync(path, { force: true });
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function applyLedgerIndexSchema(db: Database): void {
|
|
216
|
+
db.exec(`
|
|
217
|
+
PRAGMA journal_mode = DELETE;
|
|
218
|
+
PRAGMA busy_timeout = 5000;
|
|
219
|
+
CREATE TABLE IF NOT EXISTS ledger_entries (
|
|
220
|
+
entry_id TEXT PRIMARY KEY,
|
|
221
|
+
schema TEXT NOT NULL,
|
|
222
|
+
created_at TEXT NOT NULL,
|
|
223
|
+
entry_type TEXT NOT NULL,
|
|
224
|
+
spine_eligible INTEGER NOT NULL,
|
|
225
|
+
authority_class TEXT NOT NULL,
|
|
226
|
+
grants_execution INTEGER NOT NULL,
|
|
227
|
+
export_class TEXT NOT NULL,
|
|
228
|
+
summary TEXT NOT NULL,
|
|
229
|
+
topic_hints_json TEXT NOT NULL,
|
|
230
|
+
status_effect TEXT NOT NULL,
|
|
231
|
+
refs_json TEXT NOT NULL,
|
|
232
|
+
source_path TEXT NOT NULL,
|
|
233
|
+
line_no INTEGER NOT NULL,
|
|
234
|
+
extra_json TEXT NOT NULL,
|
|
235
|
+
is_canonical INTEGER NOT NULL DEFAULT 0,
|
|
236
|
+
regenerable INTEGER NOT NULL DEFAULT 1,
|
|
237
|
+
can_authorize INTEGER NOT NULL DEFAULT 0,
|
|
238
|
+
indexed_at TEXT NOT NULL
|
|
239
|
+
);
|
|
240
|
+
CREATE TABLE IF NOT EXISTS refs (
|
|
241
|
+
entry_id TEXT NOT NULL,
|
|
242
|
+
ref_type TEXT NOT NULL,
|
|
243
|
+
ref TEXT NOT NULL,
|
|
244
|
+
FOREIGN KEY(entry_id) REFERENCES ledger_entries(entry_id)
|
|
245
|
+
);
|
|
246
|
+
CREATE TABLE IF NOT EXISTS topics (
|
|
247
|
+
entry_id TEXT NOT NULL,
|
|
248
|
+
topic TEXT NOT NULL,
|
|
249
|
+
source TEXT NOT NULL,
|
|
250
|
+
FOREIGN KEY(entry_id) REFERENCES ledger_entries(entry_id)
|
|
251
|
+
);
|
|
252
|
+
CREATE TABLE IF NOT EXISTS documents (
|
|
253
|
+
document_id TEXT PRIMARY KEY,
|
|
254
|
+
doc_type TEXT NOT NULL,
|
|
255
|
+
path TEXT NOT NULL,
|
|
256
|
+
title TEXT NOT NULL,
|
|
257
|
+
indexed_at TEXT NOT NULL
|
|
258
|
+
);
|
|
259
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS fts_chunks
|
|
260
|
+
USING fts5(chunk_id UNINDEXED, source_type UNINDEXED, source_id UNINDEXED, title, body, ref UNINDEXED);
|
|
261
|
+
`);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function documentType(relPath: string): string {
|
|
265
|
+
if (relPath.includes('/topic-snapshots/')) return 'topic_snapshot';
|
|
266
|
+
if (relPath.includes('/ledger-digests/')) return 'ledger_digest';
|
|
267
|
+
if (relPath.includes('/consumer-registry/')) return 'consumer_registry';
|
|
268
|
+
if (relPath.includes('/bus-feed/')) return 'bus_feed';
|
|
269
|
+
if (relPath.includes('/receipts/')) return 'receipt';
|
|
270
|
+
if (relPath.includes('/ledger/')) return 'ledger';
|
|
271
|
+
return 'document';
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
async function collectDocuments(root: string, relDir: string): Promise<string[]> {
|
|
275
|
+
const absDir = join(root, relDir);
|
|
276
|
+
if (!existsSync(absDir)) return [];
|
|
277
|
+
const out: string[] = [];
|
|
278
|
+
async function walk(dir: string): Promise<void> {
|
|
279
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
280
|
+
for (const entry of entries) {
|
|
281
|
+
const abs = join(dir, entry.name);
|
|
282
|
+
if (entry.isDirectory()) {
|
|
283
|
+
if (entry.name === 'node_modules' || entry.name === '.git' || entry.name === '_worktrees') continue;
|
|
284
|
+
await walk(abs);
|
|
285
|
+
continue;
|
|
286
|
+
}
|
|
287
|
+
if (!entry.isFile() || !TEXT_EXTENSIONS.has(extname(entry.name))) continue;
|
|
288
|
+
out.push(relative(root, abs));
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
await walk(absDir);
|
|
292
|
+
return out.sort();
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
async function insertDocuments(db: Database, projectRoot: string, indexedAt: string): Promise<number> {
|
|
296
|
+
const batches = await Promise.all(DOCUMENT_SCAN_DIRS.map((dir) => collectDocuments(projectRoot, dir)));
|
|
297
|
+
const docs = [...new Set(batches.flat())].sort();
|
|
298
|
+
const insertDoc = db.query(`
|
|
299
|
+
INSERT OR REPLACE INTO documents (document_id, doc_type, path, title, indexed_at)
|
|
300
|
+
VALUES (?, ?, ?, ?, ?)
|
|
301
|
+
`);
|
|
302
|
+
const insertFts = db.query(`
|
|
303
|
+
INSERT INTO fts_chunks (chunk_id, source_type, source_id, title, body, ref)
|
|
304
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
305
|
+
`);
|
|
306
|
+
for (const relPath of docs) {
|
|
307
|
+
const content = await readFile(join(projectRoot, relPath), 'utf-8');
|
|
308
|
+
const title = titleFromContent(content, relPath.split('/').at(-1) ?? relPath);
|
|
309
|
+
const id = `doc:${relPath}`;
|
|
310
|
+
const type = documentType(relPath);
|
|
311
|
+
insertDoc.run(id, type, relPath, title, indexedAt);
|
|
312
|
+
insertFts.run(`doc:${relPath}`, 'document', id, title, `${type} ${title} ${relPath}`, relPath);
|
|
313
|
+
}
|
|
314
|
+
return docs.length;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function compactRefsForSearch(refs: LedgerEntryRefs): string {
|
|
318
|
+
return refPairs(refs).map((pair) => `${pair.type}:${pair.ref}`).join(' ');
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
function insertLedgerEntry(db: Database, entry: ParsedLedgerLine, indexedAt: string): { refs: number; topics: number } {
|
|
322
|
+
const summary = [
|
|
323
|
+
entry.entry_type,
|
|
324
|
+
entry.entry_id,
|
|
325
|
+
entry.status_effect !== 'none' ? entry.status_effect : '',
|
|
326
|
+
entry.topic_hints.join(' '),
|
|
327
|
+
compactRefsForSearch(entry.refs),
|
|
328
|
+
].filter(Boolean).join(' ');
|
|
329
|
+
db.query(`
|
|
330
|
+
INSERT OR REPLACE INTO ledger_entries
|
|
331
|
+
(entry_id, schema, created_at, entry_type, spine_eligible, authority_class,
|
|
332
|
+
grants_execution, export_class, summary, topic_hints_json, status_effect,
|
|
333
|
+
refs_json, source_path, line_no, extra_json, is_canonical, regenerable,
|
|
334
|
+
can_authorize, indexed_at)
|
|
335
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0, 1, 0, ?)
|
|
336
|
+
`).run(
|
|
337
|
+
entry.entry_id,
|
|
338
|
+
entry.schema,
|
|
339
|
+
entry.created_at,
|
|
340
|
+
entry.entry_type,
|
|
341
|
+
entry.spine_eligible ? 1 : 0,
|
|
342
|
+
entry.authority_class,
|
|
343
|
+
entry.grants_execution ? 1 : 0,
|
|
344
|
+
entry.export_class,
|
|
345
|
+
summary,
|
|
346
|
+
JSON.stringify(entry.topic_hints),
|
|
347
|
+
entry.status_effect,
|
|
348
|
+
JSON.stringify(entry.refs),
|
|
349
|
+
entry.source_path,
|
|
350
|
+
entry.line_no,
|
|
351
|
+
JSON.stringify(entry.extra),
|
|
352
|
+
indexedAt,
|
|
353
|
+
);
|
|
354
|
+
db.query(`
|
|
355
|
+
INSERT INTO fts_chunks (chunk_id, source_type, source_id, title, body, ref)
|
|
356
|
+
VALUES (?, 'ledger_entry', ?, ?, ?, ?)
|
|
357
|
+
`).run(
|
|
358
|
+
`ledger:${entry.entry_id}`,
|
|
359
|
+
entry.entry_id,
|
|
360
|
+
entry.entry_id,
|
|
361
|
+
summary,
|
|
362
|
+
`${entry.source_path}:${entry.line_no}`,
|
|
363
|
+
);
|
|
364
|
+
let refs = 0;
|
|
365
|
+
for (const pair of refPairs(entry.refs)) {
|
|
366
|
+
db.query('INSERT INTO refs (entry_id, ref_type, ref) VALUES (?, ?, ?)').run(entry.entry_id, pair.type, pair.ref);
|
|
367
|
+
refs += 1;
|
|
368
|
+
}
|
|
369
|
+
let topics = 0;
|
|
370
|
+
for (const topic of entry.topic_hints) {
|
|
371
|
+
db.query('INSERT INTO topics (entry_id, topic, source) VALUES (?, ?, ?)').run(entry.entry_id, topic, 'topic_hints');
|
|
372
|
+
topics += 1;
|
|
373
|
+
}
|
|
374
|
+
return { refs, topics };
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
export async function rebuildLedgerIndex(options: {
|
|
378
|
+
projectRoot: string;
|
|
379
|
+
dbPath?: string;
|
|
380
|
+
}): Promise<LedgerIndexResult> {
|
|
381
|
+
const projectRoot = resolve(options.projectRoot);
|
|
382
|
+
const dbPath = options.dbPath ?? defaultLedgerIndexDbPath(projectRoot);
|
|
383
|
+
mkdirSync(dirname(dbPath), { recursive: true });
|
|
384
|
+
resetDbFiles(dbPath);
|
|
385
|
+
const db = new Database(dbPath);
|
|
386
|
+
const indexedAt = new Date().toISOString();
|
|
387
|
+
try {
|
|
388
|
+
applyLedgerIndexSchema(db);
|
|
389
|
+
const ledgerPath = join(projectRoot, 'docs', 'work', 'ledger', 'LEDGER.md');
|
|
390
|
+
let ledgerEntries = 0;
|
|
391
|
+
let refs = 0;
|
|
392
|
+
let topics = 0;
|
|
393
|
+
if (existsSync(ledgerPath)) {
|
|
394
|
+
const relLedgerPath = relative(projectRoot, ledgerPath);
|
|
395
|
+
const lines = (await readFile(ledgerPath, 'utf-8')).split('\n');
|
|
396
|
+
for (const [index, line] of lines.entries()) {
|
|
397
|
+
const entry = parseLedgerEntryLine(line, relLedgerPath, index + 1);
|
|
398
|
+
if (!entry) continue;
|
|
399
|
+
const counts = insertLedgerEntry(db, entry, indexedAt);
|
|
400
|
+
ledgerEntries += 1;
|
|
401
|
+
refs += counts.refs;
|
|
402
|
+
topics += counts.topics;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
const documents = await insertDocuments(db, projectRoot, indexedAt);
|
|
406
|
+
return { dbPath, ledger_entries: ledgerEntries, refs, topics, documents };
|
|
407
|
+
} finally {
|
|
408
|
+
db.close();
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
function mapSearchRow(row: LedgerEntryRow): CompactLedgerSearchResult {
|
|
413
|
+
const refs = refPairs(JSON.parse(row.refs_json) as LedgerEntryRefs).map((pair) => `${pair.type}:${pair.ref}`);
|
|
414
|
+
return {
|
|
415
|
+
entry_id: row.entry_id,
|
|
416
|
+
title: row.entry_id,
|
|
417
|
+
type: row.entry_type,
|
|
418
|
+
topic_hints: JSON.parse(row.topic_hints_json) as string[],
|
|
419
|
+
status_effect: row.status_effect,
|
|
420
|
+
refs,
|
|
421
|
+
confidence: 'metadata',
|
|
422
|
+
is_canonical: false,
|
|
423
|
+
regenerable: true,
|
|
424
|
+
can_authorize: false,
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
function mapDetailRow(row: Required<LedgerEntryRow>): LedgerIndexEntryDetail {
|
|
429
|
+
return {
|
|
430
|
+
...mapSearchRow(row),
|
|
431
|
+
source_path: row.source_path,
|
|
432
|
+
line_no: row.line_no,
|
|
433
|
+
};
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
function searchTerms(query: string): string[] {
|
|
437
|
+
return compactWhitespace(query.toLowerCase()).split(/[^a-z0-9]+/).filter(Boolean).slice(0, 8);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
function ftsQuery(terms: string[]): string {
|
|
441
|
+
return terms.map((term) => `"${term.replace(/"/g, '""')}"`).join(' ');
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
function likeSearchRows(db: Database, terms: string[], limit: number): LedgerEntryRow[] {
|
|
445
|
+
const like = `%${terms.join('%')}%`;
|
|
446
|
+
return db.query<LedgerEntryRow, [string, string, string, number]>(`
|
|
447
|
+
SELECT entry_id, entry_type, topic_hints_json, status_effect, refs_json
|
|
448
|
+
FROM ledger_entries
|
|
449
|
+
WHERE lower(summary) LIKE ?
|
|
450
|
+
OR lower(entry_id) LIKE ?
|
|
451
|
+
OR lower(topic_hints_json) LIKE ?
|
|
452
|
+
ORDER BY created_at DESC, entry_id ASC
|
|
453
|
+
LIMIT ?
|
|
454
|
+
`).all(like, like, like, limit);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
export function searchLedgerIndex(options: {
|
|
458
|
+
dbPath: string;
|
|
459
|
+
query: string;
|
|
460
|
+
limit?: number;
|
|
461
|
+
}): CompactLedgerSearchResult[] {
|
|
462
|
+
const db = new Database(options.dbPath, { readonly: true });
|
|
463
|
+
try {
|
|
464
|
+
const terms = searchTerms(options.query);
|
|
465
|
+
if (terms.length === 0) return [];
|
|
466
|
+
const limit = options.limit ?? 10;
|
|
467
|
+
let rows: LedgerEntryRow[];
|
|
468
|
+
try {
|
|
469
|
+
rows = db.query<LedgerEntryRow, [string, number]>(`
|
|
470
|
+
WITH matches AS (
|
|
471
|
+
SELECT source_id, bm25(fts_chunks) AS rank
|
|
472
|
+
FROM fts_chunks
|
|
473
|
+
WHERE fts_chunks MATCH ?
|
|
474
|
+
AND source_type = 'ledger_entry'
|
|
475
|
+
)
|
|
476
|
+
SELECT le.entry_id, le.entry_type, le.topic_hints_json, le.status_effect, le.refs_json
|
|
477
|
+
FROM ledger_entries le
|
|
478
|
+
JOIN matches ON matches.source_id = le.entry_id
|
|
479
|
+
ORDER BY matches.rank ASC, le.created_at DESC, le.entry_id ASC
|
|
480
|
+
LIMIT ?
|
|
481
|
+
`).all(ftsQuery(terms), limit);
|
|
482
|
+
} catch {
|
|
483
|
+
rows = likeSearchRows(db, terms, limit);
|
|
484
|
+
}
|
|
485
|
+
if (rows.length === 0) {
|
|
486
|
+
rows = likeSearchRows(db, terms, limit);
|
|
487
|
+
}
|
|
488
|
+
return rows.map(mapSearchRow);
|
|
489
|
+
} finally {
|
|
490
|
+
db.close();
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
export function showLedgerIndexEntry(options: {
|
|
495
|
+
dbPath: string;
|
|
496
|
+
entryIdOrRef: string;
|
|
497
|
+
}): LedgerIndexEntryDetail | null {
|
|
498
|
+
const db = new Database(options.dbPath, { readonly: true });
|
|
499
|
+
try {
|
|
500
|
+
const row = db.query<Required<LedgerEntryRow>, [string, string]>(`
|
|
501
|
+
SELECT entry_id, entry_type, topic_hints_json, status_effect, refs_json, source_path, line_no
|
|
502
|
+
FROM ledger_entries
|
|
503
|
+
WHERE entry_id = ?
|
|
504
|
+
OR entry_id = (
|
|
505
|
+
SELECT entry_id FROM refs WHERE ref = ? ORDER BY entry_id ASC LIMIT 1
|
|
506
|
+
)
|
|
507
|
+
ORDER BY entry_id ASC
|
|
508
|
+
LIMIT 1
|
|
509
|
+
`).get(options.entryIdOrRef, options.entryIdOrRef);
|
|
510
|
+
return row ? mapDetailRow(row) : null;
|
|
511
|
+
} finally {
|
|
512
|
+
db.close();
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
export function initializePrivateVerbatimStore(options: {
|
|
517
|
+
projectRoot: string;
|
|
518
|
+
dbPath?: string;
|
|
519
|
+
explicitOptIn?: boolean;
|
|
520
|
+
}): string {
|
|
521
|
+
if (!options.explicitOptIn) {
|
|
522
|
+
throw new Error('private verbatim ledger store requires explicit operator opt-in');
|
|
523
|
+
}
|
|
524
|
+
const dbPath = options.dbPath ?? defaultPrivateVerbatimDbPath(resolve(options.projectRoot));
|
|
525
|
+
mkdirSync(dirname(dbPath), { recursive: true });
|
|
526
|
+
const db = new Database(dbPath);
|
|
527
|
+
try {
|
|
528
|
+
db.exec(`
|
|
529
|
+
PRAGMA journal_mode = DELETE;
|
|
530
|
+
CREATE TABLE IF NOT EXISTS verbatim_chunks (
|
|
531
|
+
chunk_id TEXT PRIMARY KEY,
|
|
532
|
+
source_ref TEXT NOT NULL,
|
|
533
|
+
body TEXT NOT NULL,
|
|
534
|
+
export_class TEXT NOT NULL DEFAULT 'local_private',
|
|
535
|
+
captured_at TEXT NOT NULL
|
|
536
|
+
);
|
|
537
|
+
`);
|
|
538
|
+
} finally {
|
|
539
|
+
db.close();
|
|
540
|
+
}
|
|
541
|
+
return dbPath;
|
|
542
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { resolve } from 'node:path';
|
|
2
|
+
import { assertSharedMemoryScopeId, assertSharedMemoryText } from './sanitize';
|
|
3
|
+
import { defaultMemoryDbPath } from './indexer';
|
|
4
|
+
import { openMemoryStore } from './store';
|
|
5
|
+
import type { EvidenceRef, MemoryCategory, MemoryConfidence, RecallCapsule, StalenessStatus } from './types';
|
|
6
|
+
|
|
7
|
+
export interface MemoryContextItem {
|
|
8
|
+
capsule_id: string;
|
|
9
|
+
title: string;
|
|
10
|
+
category: MemoryCategory;
|
|
11
|
+
summary: string;
|
|
12
|
+
why_it_matters: string;
|
|
13
|
+
confidence: MemoryConfidence;
|
|
14
|
+
stale_status: StalenessStatus;
|
|
15
|
+
evidence_refs: EvidenceRef[];
|
|
16
|
+
recommended_action: RecallCapsule['recommended_action'];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface MemoryContextPack {
|
|
20
|
+
schema: 'memory-context.v0';
|
|
21
|
+
generated_at: string;
|
|
22
|
+
query: string;
|
|
23
|
+
scope: {
|
|
24
|
+
org_id?: string;
|
|
25
|
+
workspace_id?: string;
|
|
26
|
+
lane_ref?: string;
|
|
27
|
+
task_ref?: string;
|
|
28
|
+
};
|
|
29
|
+
items: MemoryContextItem[];
|
|
30
|
+
content_boundary: {
|
|
31
|
+
summaries_only: true;
|
|
32
|
+
raw_ledger_body_included: false;
|
|
33
|
+
raw_event_body_included: false;
|
|
34
|
+
raw_prompt_included: false;
|
|
35
|
+
raw_tool_output_included: false;
|
|
36
|
+
source_snippets_included: false;
|
|
37
|
+
local_paths_included: false;
|
|
38
|
+
};
|
|
39
|
+
authority_boundary: {
|
|
40
|
+
can_authorize_work: false;
|
|
41
|
+
can_close_task: false;
|
|
42
|
+
can_replace_receipt: false;
|
|
43
|
+
can_mutate_ledger: false;
|
|
44
|
+
memory_is_execution_truth: false;
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface BuildMemoryContextOptions {
|
|
49
|
+
projectRoot: string;
|
|
50
|
+
dbPath?: string;
|
|
51
|
+
query?: string;
|
|
52
|
+
laneRef?: string;
|
|
53
|
+
taskRef?: string;
|
|
54
|
+
orgId?: string;
|
|
55
|
+
workspaceId?: string;
|
|
56
|
+
limit: number;
|
|
57
|
+
at?: string;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function compactQuery(options: BuildMemoryContextOptions): string {
|
|
61
|
+
if (options.query?.trim()) {
|
|
62
|
+
const query = options.query.trim();
|
|
63
|
+
assertSharedMemoryText(query, 'memory context query');
|
|
64
|
+
return query;
|
|
65
|
+
}
|
|
66
|
+
const parts = [options.laneRef, options.taskRef]
|
|
67
|
+
.map((part) => part?.trim())
|
|
68
|
+
.filter((part): part is string => Boolean(part));
|
|
69
|
+
if (parts.length === 0) {
|
|
70
|
+
throw new Error('memory context requires --lane, --task, or --query');
|
|
71
|
+
}
|
|
72
|
+
const query = parts.join(' ');
|
|
73
|
+
assertSharedMemoryText(query, 'memory context query');
|
|
74
|
+
return query;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function validateScope(options: BuildMemoryContextOptions): MemoryContextPack['scope'] {
|
|
78
|
+
if (options.orgId) assertSharedMemoryScopeId(options.orgId, 'scope.org_id');
|
|
79
|
+
if (options.workspaceId) assertSharedMemoryScopeId(options.workspaceId, 'scope.workspace_id');
|
|
80
|
+
if (options.laneRef) assertSharedMemoryText(options.laneRef, 'scope.lane_ref');
|
|
81
|
+
if (options.taskRef) assertSharedMemoryText(options.taskRef, 'scope.task_ref');
|
|
82
|
+
return {
|
|
83
|
+
...(options.orgId ? { org_id: options.orgId } : {}),
|
|
84
|
+
...(options.workspaceId ? { workspace_id: options.workspaceId } : {}),
|
|
85
|
+
...(options.laneRef ? { lane_ref: options.laneRef } : {}),
|
|
86
|
+
...(options.taskRef ? { task_ref: options.taskRef } : {}),
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function contextItem(capsule: RecallCapsule): MemoryContextItem {
|
|
91
|
+
return {
|
|
92
|
+
capsule_id: capsule.capsule_id,
|
|
93
|
+
title: capsule.title,
|
|
94
|
+
category: capsule.category,
|
|
95
|
+
summary: capsule.summary,
|
|
96
|
+
why_it_matters: capsule.why_it_matters,
|
|
97
|
+
confidence: capsule.confidence,
|
|
98
|
+
stale_status: capsule.staleness.status,
|
|
99
|
+
evidence_refs: capsule.evidence_refs,
|
|
100
|
+
recommended_action: capsule.recommended_action,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function buildMemoryContext(options: BuildMemoryContextOptions): MemoryContextPack {
|
|
105
|
+
if (options.limit < 1 || options.limit > 50) {
|
|
106
|
+
throw new Error('memory context limit must be between 1 and 50');
|
|
107
|
+
}
|
|
108
|
+
const projectRoot = resolve(options.projectRoot);
|
|
109
|
+
const query = compactQuery(options);
|
|
110
|
+
const scope = validateScope(options);
|
|
111
|
+
const generatedAt = options.at ?? new Date().toISOString();
|
|
112
|
+
if (!Number.isFinite(Date.parse(generatedAt))) {
|
|
113
|
+
throw new Error('--at must be an ISO-8601 timestamp');
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const store = openMemoryStore(options.dbPath ?? defaultMemoryDbPath(projectRoot));
|
|
117
|
+
try {
|
|
118
|
+
const hits = store.search(query, options.limit);
|
|
119
|
+
const items = hits
|
|
120
|
+
.map((hit) => store.show(hit.capsule_id))
|
|
121
|
+
.filter((capsule): capsule is RecallCapsule => capsule !== null)
|
|
122
|
+
.map(contextItem);
|
|
123
|
+
return {
|
|
124
|
+
schema: 'memory-context.v0',
|
|
125
|
+
generated_at: new Date(generatedAt).toISOString(),
|
|
126
|
+
query,
|
|
127
|
+
scope,
|
|
128
|
+
items,
|
|
129
|
+
content_boundary: {
|
|
130
|
+
summaries_only: true,
|
|
131
|
+
raw_ledger_body_included: false,
|
|
132
|
+
raw_event_body_included: false,
|
|
133
|
+
raw_prompt_included: false,
|
|
134
|
+
raw_tool_output_included: false,
|
|
135
|
+
source_snippets_included: false,
|
|
136
|
+
local_paths_included: false,
|
|
137
|
+
},
|
|
138
|
+
authority_boundary: {
|
|
139
|
+
can_authorize_work: false,
|
|
140
|
+
can_close_task: false,
|
|
141
|
+
can_replace_receipt: false,
|
|
142
|
+
can_mutate_ledger: false,
|
|
143
|
+
memory_is_execution_truth: false,
|
|
144
|
+
},
|
|
145
|
+
};
|
|
146
|
+
} finally {
|
|
147
|
+
store.close();
|
|
148
|
+
}
|
|
149
|
+
}
|