@graphorin/cli 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +39 -0
- package/README.md +8 -8
- package/dist/bin/graphorin.js +15 -7
- package/dist/bin/graphorin.js.map +1 -1
- package/dist/commands/audit.d.ts +2 -2
- package/dist/commands/audit.js +6 -6
- package/dist/commands/audit.js.map +1 -1
- package/dist/commands/auth.d.ts +1 -1
- package/dist/commands/auth.js +3 -3
- package/dist/commands/auth.js.map +1 -1
- package/dist/commands/consolidator.js +2 -2
- package/dist/commands/consolidator.js.map +1 -1
- package/dist/commands/doctor.d.ts +1 -1
- package/dist/commands/doctor.js +5 -5
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/guard.js +3 -3
- package/dist/commands/guard.js.map +1 -1
- package/dist/commands/index.d.ts +2 -2
- package/dist/commands/index.js +2 -2
- package/dist/commands/init.d.ts +2 -2
- package/dist/commands/init.js +3 -3
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/memory.d.ts +5 -5
- package/dist/commands/memory.d.ts.map +1 -1
- package/dist/commands/memory.js +11 -11
- package/dist/commands/memory.js.map +1 -1
- package/dist/commands/migrate-config.js +2 -2
- package/dist/commands/migrate-config.js.map +1 -1
- package/dist/commands/migrate-export.d.ts +1 -1
- package/dist/commands/migrate-export.js +2 -2
- package/dist/commands/migrate-export.js.map +1 -1
- package/dist/commands/migrate.d.ts +1 -1
- package/dist/commands/migrate.js +1 -1
- package/dist/commands/migrate.js.map +1 -1
- package/dist/commands/pricing.d.ts +1 -1
- package/dist/commands/pricing.js +6 -6
- package/dist/commands/pricing.js.map +1 -1
- package/dist/commands/secrets.d.ts +1 -1
- package/dist/commands/secrets.js +5 -5
- package/dist/commands/secrets.js.map +1 -1
- package/dist/commands/skills.js +5 -5
- package/dist/commands/skills.js.map +1 -1
- package/dist/commands/start.d.ts +1 -1
- package/dist/commands/start.js +1 -1
- package/dist/commands/start.js.map +1 -1
- package/dist/commands/storage.d.ts +26 -2
- package/dist/commands/storage.d.ts.map +1 -1
- package/dist/commands/storage.js +51 -11
- package/dist/commands/storage.js.map +1 -1
- package/dist/commands/telemetry.js +6 -6
- package/dist/commands/telemetry.js.map +1 -1
- package/dist/commands/token.d.ts +2 -2
- package/dist/commands/token.js +4 -4
- package/dist/commands/token.js.map +1 -1
- package/dist/commands/tools-lint.d.ts +1 -1
- package/dist/commands/tools-lint.js +6 -6
- package/dist/commands/tools-lint.js.map +1 -1
- package/dist/commands/traces.js +5 -5
- package/dist/commands/traces.js.map +1 -1
- package/dist/commands/triggers.d.ts +1 -1
- package/dist/commands/triggers.js +1 -1
- package/dist/commands/triggers.js.map +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.js +4 -4
- package/dist/index.js.map +1 -1
- package/dist/internal/exit.js.map +1 -1
- package/dist/internal/load-config.js +5 -5
- package/dist/internal/load-config.js.map +1 -1
- package/dist/internal/offline.d.ts +1 -1
- package/dist/internal/offline.js +2 -2
- package/dist/internal/offline.js.map +1 -1
- package/dist/internal/output.d.ts +6 -6
- package/dist/internal/output.js +6 -6
- package/dist/internal/output.js.map +1 -1
- package/dist/internal/store-context.js.map +1 -1
- package/package.json +12 -12
package/dist/commands/audit.js
CHANGED
|
@@ -9,15 +9,15 @@ import { ensureStoreAuditBinding, parseServerConfig } from "@graphorin/server";
|
|
|
9
9
|
|
|
10
10
|
//#region src/commands/audit.ts
|
|
11
11
|
/**
|
|
12
|
-
* `graphorin audit`
|
|
12
|
+
* `graphorin audit` - operate on the tamper-evident audit log
|
|
13
13
|
* (`audit.db`).
|
|
14
14
|
*
|
|
15
15
|
* Surface (per Phase 15 § Audit):
|
|
16
16
|
*
|
|
17
|
-
* - `graphorin audit verify`
|
|
18
|
-
* - `graphorin audit prune --before <date>`
|
|
17
|
+
* - `graphorin audit verify` - full hash chain integrity check.
|
|
18
|
+
* - `graphorin audit prune --before <date>` - drop entries older than
|
|
19
19
|
* a cutoff while preserving the surviving suffix's chain integrity.
|
|
20
|
-
* - `graphorin audit export --to <file>`
|
|
20
|
+
* - `graphorin audit export --to <file>` - stream every entry as JSONL.
|
|
21
21
|
*
|
|
22
22
|
* The CLI opens `audit.db` through the framework default binding
|
|
23
23
|
* registered by `@graphorin/server` (`ensureStoreAuditBinding()`); it
|
|
@@ -26,7 +26,7 @@ import { ensureStoreAuditBinding, parseServerConfig } from "@graphorin/server";
|
|
|
26
26
|
* @packageDocumentation
|
|
27
27
|
*/
|
|
28
28
|
/**
|
|
29
|
-
* `graphorin audit verify`
|
|
29
|
+
* `graphorin audit verify` - replay the chain and report the first
|
|
30
30
|
* broken link (if any).
|
|
31
31
|
*
|
|
32
32
|
* @stable
|
|
@@ -88,7 +88,7 @@ async function runAuditPrune(options) {
|
|
|
88
88
|
}
|
|
89
89
|
}
|
|
90
90
|
/**
|
|
91
|
-
* `graphorin audit export --to <file>`
|
|
91
|
+
* `graphorin audit export --to <file>` - stream every entry as JSONL.
|
|
92
92
|
*
|
|
93
93
|
* @stable
|
|
94
94
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"audit.js","names":["result: AuditChainVerifyResult","out: AuditVerifyResult","lines: string[]","out: AuditExportResult","passphrase: SecretValue"],"sources":["../../src/commands/audit.ts"],"sourcesContent":["/**\n * `graphorin audit` — operate on the tamper-evident audit log\n * (`audit.db`).\n *\n * Surface (per Phase 15 § Audit):\n *\n * - `graphorin audit verify` — full hash chain integrity check.\n * - `graphorin audit prune --before <date>` — drop entries older than\n * a cutoff while preserving the surviving suffix's chain integrity.\n * - `graphorin audit export --to <file>` — stream every entry as JSONL.\n *\n * The CLI opens `audit.db` through the framework default binding\n * registered by `@graphorin/server` (`ensureStoreAuditBinding()`); it\n * never re-implements the cipher path.\n *\n * @packageDocumentation\n */\n\nimport { writeFile } from 'node:fs/promises';\nimport { dirname, isAbsolute, resolve } from 'node:path';\nimport process from 'node:process';\n\nimport {\n type AuditChainVerifyResult,\n exportAudit,\n openAuditDb,\n type PruneAuditResult,\n pruneAudit,\n resolveSecret,\n type SecretValue,\n type StoredAuditEntry,\n verifyAuditChain,\n} from '@graphorin/security';\nimport { ensureStoreAuditBinding, parseServerConfig } from '@graphorin/server';\n\nimport { EXIT_CODES } from '../internal/exit.js';\nimport { loadConfig } from '../internal/load-config.js';\nimport {\n brand,\n type CommonOutputOptions,\n defaultPrintSink,\n emitReport,\n statusMarker,\n} from '../internal/output.js';\n\n/** @stable */\nexport interface AuditCommonOptions extends CommonOutputOptions {\n readonly config?: string;\n}\n\n/** @stable */\nexport interface AuditVerifyResult {\n readonly ok: boolean;\n readonly path: string;\n readonly entries: number;\n readonly broken?: { readonly seq: number; readonly expected: string; readonly actual: string };\n}\n\n/**\n * `graphorin audit verify` — replay the chain and report the first\n * broken link (if any).\n *\n * @stable\n */\nexport async function runAuditVerify(options: AuditCommonOptions = {}): Promise<AuditVerifyResult> {\n const ctx = await openAuditContext(options);\n try {\n const result: AuditChainVerifyResult = await verifyAuditChain(ctx.auditDb);\n const out: AuditVerifyResult = result.ok\n ? Object.freeze({ ok: true, path: ctx.auditDb.path, entries: result.count })\n : Object.freeze({\n ok: false,\n path: ctx.auditDb.path,\n entries: 0,\n broken: Object.freeze({\n seq: result.brokenAt,\n expected: result.expected,\n actual: result.actual,\n }),\n });\n emitReport(options, out, () => {\n const print = options.print ?? defaultPrintSink;\n if (out.ok) {\n print(\n brand(`audit chain ${statusMarker('ok')} (${out.entries} entries verified, ${out.path})`),\n );\n return;\n }\n print(brand(`audit chain ${statusMarker('fail')} broken at seq ${out.broken?.seq}`));\n print(` expected prevHash=${out.broken?.expected}`);\n print(` actual prevHash=${out.broken?.actual}`);\n process.exitCode = EXIT_CODES.RECOVERABLE_FAILURE;\n });\n return out;\n } finally {\n await ctx.close();\n }\n}\n\n/** @stable */\nexport interface AuditPruneOptions extends AuditCommonOptions {\n /**\n * ISO-8601 date / `YYYY-MM-DD` / millis-since-epoch. The helper\n * drops entries older than this cutoff.\n */\n readonly before: string;\n /** Minimum number of entries that must survive. Default `1`. */\n readonly retain?: number;\n}\n\n/** @stable */\nexport async function runAuditPrune(options: AuditPruneOptions): Promise<PruneAuditResult> {\n const cutoff = parseCutoff(options.before);\n const ctx = await openAuditContext(options);\n try {\n const result = await pruneAudit(ctx.auditDb, {\n before: cutoff,\n ...(options.retain !== undefined ? { retain: options.retain } : {}),\n });\n emitReport(options, result, () => {\n const print = options.print ?? defaultPrintSink;\n if (result.deleted === 0) {\n print(\n brand(`no audit entries qualified for prune (cutoff=${new Date(cutoff).toISOString()}).`),\n );\n return;\n }\n print(\n brand(\n `pruned ${result.deleted} audit entries (cutoff=${new Date(cutoff).toISOString()}, surviving from seq=${result.firstSurvivingSeq ?? '<empty>'})`,\n ),\n );\n });\n return result;\n } finally {\n await ctx.close();\n }\n}\n\n/** @stable */\nexport interface AuditExportOptions extends AuditCommonOptions {\n readonly to: string;\n readonly fromSeq?: number;\n readonly toSeq?: number;\n}\n\n/** @stable */\nexport interface AuditExportResult {\n readonly path: string;\n readonly rows: number;\n}\n\n/**\n * `graphorin audit export --to <file>` — stream every entry as JSONL.\n *\n * @stable\n */\nexport async function runAuditExport(options: AuditExportOptions): Promise<AuditExportResult> {\n const cwd = process.cwd();\n const targetPath = isAbsolute(options.to) ? options.to : resolve(cwd, options.to);\n const ctx = await openAuditContext(options);\n try {\n const lines: string[] = [];\n const { rows } = await exportAudit(ctx.auditDb, {\n writer: { write: (line: string) => void lines.push(line) },\n ...(options.fromSeq !== undefined ? { fromSeq: options.fromSeq } : {}),\n ...(options.toSeq !== undefined ? { toSeq: options.toSeq } : {}),\n });\n await writeFile(targetPath, lines.join(''), { mode: 0o600 });\n const out: AuditExportResult = Object.freeze({ path: targetPath, rows });\n emitReport(options, out, () => {\n const print = options.print ?? defaultPrintSink;\n print(brand(`exported ${rows} audit entries to ${targetPath} (mode 0600).`));\n });\n return out;\n } finally {\n await ctx.close();\n }\n}\n\ninterface AuditContext {\n readonly auditDb: Awaited<ReturnType<typeof openAuditDb>>;\n readonly path: string;\n readonly close: () => Promise<void>;\n}\n\nasync function openAuditContext(options: AuditCommonOptions): Promise<AuditContext> {\n const loaded = await loadConfig(options.config);\n const config = parseServerConfig(loaded.config);\n if (!config.audit.enabled) {\n throw new Error(\n '[graphorin/cli] this command requires audit.enabled = true in the resolved config.',\n );\n }\n const passphraseRef = config.audit.passphraseRef ?? config.storage.encryption.passphraseRef;\n if (passphraseRef === undefined) {\n throw new Error(\n `[graphorin/cli] audit.enabled is true but no audit.passphraseRef (or storage.encryption.passphraseRef) is configured. audit.db is mandatory-encrypted by DEC-124.`,\n );\n }\n let passphrase: SecretValue;\n try {\n passphrase = await resolveSecret(passphraseRef);\n } catch (err) {\n throw new Error(\n `[graphorin/cli] failed to resolve audit passphrase '${passphraseRef}': ${(err as Error).message}`,\n { cause: err },\n );\n }\n const auditPath = config.audit.path ?? deriveAuditPath(config.storage.path);\n ensureStoreAuditBinding();\n const auditDb = await openAuditDb({\n path: auditPath,\n passphrase,\n ...(config.audit.cipher !== undefined ? { cipher: config.audit.cipher } : {}),\n });\n return Object.freeze({\n auditDb,\n path: auditPath,\n close: () => auditDb.close(),\n });\n}\n\nfunction deriveAuditPath(storagePath: string): string {\n const dir = dirname(storagePath);\n return resolve(dir, 'audit.db');\n}\n\nfunction parseCutoff(input: string): number {\n const numeric = Number(input);\n if (Number.isFinite(numeric) && numeric > 0) return numeric;\n const ms = Date.parse(input);\n if (!Number.isFinite(ms)) {\n throw new Error(\n `[graphorin/cli] --before '${input}' is not a valid ISO date or epoch-ms value.`,\n );\n }\n return ms;\n}\n\n// Internal type-only consumers — keep in scope so tree-shaking\n// preserves the entry point.\nvoid ((_e?: StoredAuditEntry) => undefined);\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgEA,eAAsB,eAAe,UAA8B,EAAE,EAA8B;CACjG,MAAM,MAAM,MAAM,iBAAiB,QAAQ;AAC3C,KAAI;EACF,MAAMA,SAAiC,MAAM,iBAAiB,IAAI,QAAQ;EAC1E,MAAMC,MAAyB,OAAO,KAClC,OAAO,OAAO;GAAE,IAAI;GAAM,MAAM,IAAI,QAAQ;GAAM,SAAS,OAAO;GAAO,CAAC,GAC1E,OAAO,OAAO;GACZ,IAAI;GACJ,MAAM,IAAI,QAAQ;GAClB,SAAS;GACT,QAAQ,OAAO,OAAO;IACpB,KAAK,OAAO;IACZ,UAAU,OAAO;IACjB,QAAQ,OAAO;IAChB,CAAC;GACH,CAAC;AACN,aAAW,SAAS,WAAW;GAC7B,MAAM,QAAQ,QAAQ,SAAS;AAC/B,OAAI,IAAI,IAAI;AACV,UACE,MAAM,eAAe,aAAa,KAAK,CAAC,IAAI,IAAI,QAAQ,qBAAqB,IAAI,KAAK,GAAG,CAC1F;AACD;;AAEF,SAAM,MAAM,eAAe,aAAa,OAAO,CAAC,iBAAiB,IAAI,QAAQ,MAAM,CAAC;AACpF,SAAM,uBAAuB,IAAI,QAAQ,WAAW;AACpD,SAAM,qBAAqB,IAAI,QAAQ,SAAS;AAChD,WAAQ,WAAW,WAAW;IAC9B;AACF,SAAO;WACC;AACR,QAAM,IAAI,OAAO;;;;AAgBrB,eAAsB,cAAc,SAAuD;CACzF,MAAM,SAAS,YAAY,QAAQ,OAAO;CAC1C,MAAM,MAAM,MAAM,iBAAiB,QAAQ;AAC3C,KAAI;EACF,MAAM,SAAS,MAAM,WAAW,IAAI,SAAS;GAC3C,QAAQ;GACR,GAAI,QAAQ,WAAW,SAAY,EAAE,QAAQ,QAAQ,QAAQ,GAAG,EAAE;GACnE,CAAC;AACF,aAAW,SAAS,cAAc;GAChC,MAAM,QAAQ,QAAQ,SAAS;AAC/B,OAAI,OAAO,YAAY,GAAG;AACxB,UACE,MAAM,gDAAgD,IAAI,KAAK,OAAO,CAAC,aAAa,CAAC,IAAI,CAC1F;AACD;;AAEF,SACE,MACE,UAAU,OAAO,QAAQ,yBAAyB,IAAI,KAAK,OAAO,CAAC,aAAa,CAAC,uBAAuB,OAAO,qBAAqB,UAAU,GAC/I,CACF;IACD;AACF,SAAO;WACC;AACR,QAAM,IAAI,OAAO;;;;;;;;AAsBrB,eAAsB,eAAe,SAAyD;CAC5F,MAAM,MAAM,QAAQ,KAAK;CACzB,MAAM,aAAa,WAAW,QAAQ,GAAG,GAAG,QAAQ,KAAK,QAAQ,KAAK,QAAQ,GAAG;CACjF,MAAM,MAAM,MAAM,iBAAiB,QAAQ;AAC3C,KAAI;EACF,MAAMC,QAAkB,EAAE;EAC1B,MAAM,EAAE,SAAS,MAAM,YAAY,IAAI,SAAS;GAC9C,QAAQ,EAAE,QAAQ,SAAiB,KAAK,MAAM,KAAK,KAAK,EAAE;GAC1D,GAAI,QAAQ,YAAY,SAAY,EAAE,SAAS,QAAQ,SAAS,GAAG,EAAE;GACrE,GAAI,QAAQ,UAAU,SAAY,EAAE,OAAO,QAAQ,OAAO,GAAG,EAAE;GAChE,CAAC;AACF,QAAM,UAAU,YAAY,MAAM,KAAK,GAAG,EAAE,EAAE,MAAM,KAAO,CAAC;EAC5D,MAAMC,MAAyB,OAAO,OAAO;GAAE,MAAM;GAAY;GAAM,CAAC;AACxE,aAAW,SAAS,WAAW;AAE7B,IADc,QAAQ,SAAS,kBACzB,MAAM,YAAY,KAAK,oBAAoB,WAAW,eAAe,CAAC;IAC5E;AACF,SAAO;WACC;AACR,QAAM,IAAI,OAAO;;;AAUrB,eAAe,iBAAiB,SAAoD;CAElF,MAAM,SAAS,mBADA,MAAM,WAAW,QAAQ,OAAO,EACP,OAAO;AAC/C,KAAI,CAAC,OAAO,MAAM,QAChB,OAAM,IAAI,MACR,qFACD;CAEH,MAAM,gBAAgB,OAAO,MAAM,iBAAiB,OAAO,QAAQ,WAAW;AAC9E,KAAI,kBAAkB,OACpB,OAAM,IAAI,MACR,oKACD;CAEH,IAAIC;AACJ,KAAI;AACF,eAAa,MAAM,cAAc,cAAc;UACxC,KAAK;AACZ,QAAM,IAAI,MACR,uDAAuD,cAAc,KAAM,IAAc,WACzF,EAAE,OAAO,KAAK,CACf;;CAEH,MAAM,YAAY,OAAO,MAAM,QAAQ,gBAAgB,OAAO,QAAQ,KAAK;AAC3E,0BAAyB;CACzB,MAAM,UAAU,MAAM,YAAY;EAChC,MAAM;EACN;EACA,GAAI,OAAO,MAAM,WAAW,SAAY,EAAE,QAAQ,OAAO,MAAM,QAAQ,GAAG,EAAE;EAC7E,CAAC;AACF,QAAO,OAAO,OAAO;EACnB;EACA,MAAM;EACN,aAAa,QAAQ,OAAO;EAC7B,CAAC;;AAGJ,SAAS,gBAAgB,aAA6B;AAEpD,QAAO,QADK,QAAQ,YAAY,EACZ,WAAW;;AAGjC,SAAS,YAAY,OAAuB;CAC1C,MAAM,UAAU,OAAO,MAAM;AAC7B,KAAI,OAAO,SAAS,QAAQ,IAAI,UAAU,EAAG,QAAO;CACpD,MAAM,KAAK,KAAK,MAAM,MAAM;AAC5B,KAAI,CAAC,OAAO,SAAS,GAAG,CACtB,OAAM,IAAI,MACR,6BAA6B,MAAM,8CACpC;AAEH,QAAO"}
|
|
1
|
+
{"version":3,"file":"audit.js","names":["result: AuditChainVerifyResult","out: AuditVerifyResult","lines: string[]","out: AuditExportResult","passphrase: SecretValue"],"sources":["../../src/commands/audit.ts"],"sourcesContent":["/**\n * `graphorin audit` - operate on the tamper-evident audit log\n * (`audit.db`).\n *\n * Surface (per Phase 15 § Audit):\n *\n * - `graphorin audit verify` - full hash chain integrity check.\n * - `graphorin audit prune --before <date>` - drop entries older than\n * a cutoff while preserving the surviving suffix's chain integrity.\n * - `graphorin audit export --to <file>` - stream every entry as JSONL.\n *\n * The CLI opens `audit.db` through the framework default binding\n * registered by `@graphorin/server` (`ensureStoreAuditBinding()`); it\n * never re-implements the cipher path.\n *\n * @packageDocumentation\n */\n\nimport { writeFile } from 'node:fs/promises';\nimport { dirname, isAbsolute, resolve } from 'node:path';\nimport process from 'node:process';\n\nimport {\n type AuditChainVerifyResult,\n exportAudit,\n openAuditDb,\n type PruneAuditResult,\n pruneAudit,\n resolveSecret,\n type SecretValue,\n type StoredAuditEntry,\n verifyAuditChain,\n} from '@graphorin/security';\nimport { ensureStoreAuditBinding, parseServerConfig } from '@graphorin/server';\n\nimport { EXIT_CODES } from '../internal/exit.js';\nimport { loadConfig } from '../internal/load-config.js';\nimport {\n brand,\n type CommonOutputOptions,\n defaultPrintSink,\n emitReport,\n statusMarker,\n} from '../internal/output.js';\n\n/** @stable */\nexport interface AuditCommonOptions extends CommonOutputOptions {\n readonly config?: string;\n}\n\n/** @stable */\nexport interface AuditVerifyResult {\n readonly ok: boolean;\n readonly path: string;\n readonly entries: number;\n readonly broken?: { readonly seq: number; readonly expected: string; readonly actual: string };\n}\n\n/**\n * `graphorin audit verify` - replay the chain and report the first\n * broken link (if any).\n *\n * @stable\n */\nexport async function runAuditVerify(options: AuditCommonOptions = {}): Promise<AuditVerifyResult> {\n const ctx = await openAuditContext(options);\n try {\n const result: AuditChainVerifyResult = await verifyAuditChain(ctx.auditDb);\n const out: AuditVerifyResult = result.ok\n ? Object.freeze({ ok: true, path: ctx.auditDb.path, entries: result.count })\n : Object.freeze({\n ok: false,\n path: ctx.auditDb.path,\n entries: 0,\n broken: Object.freeze({\n seq: result.brokenAt,\n expected: result.expected,\n actual: result.actual,\n }),\n });\n emitReport(options, out, () => {\n const print = options.print ?? defaultPrintSink;\n if (out.ok) {\n print(\n brand(`audit chain ${statusMarker('ok')} (${out.entries} entries verified, ${out.path})`),\n );\n return;\n }\n print(brand(`audit chain ${statusMarker('fail')} broken at seq ${out.broken?.seq}`));\n print(` expected prevHash=${out.broken?.expected}`);\n print(` actual prevHash=${out.broken?.actual}`);\n process.exitCode = EXIT_CODES.RECOVERABLE_FAILURE;\n });\n return out;\n } finally {\n await ctx.close();\n }\n}\n\n/** @stable */\nexport interface AuditPruneOptions extends AuditCommonOptions {\n /**\n * ISO-8601 date / `YYYY-MM-DD` / millis-since-epoch. The helper\n * drops entries older than this cutoff.\n */\n readonly before: string;\n /** Minimum number of entries that must survive. Default `1`. */\n readonly retain?: number;\n}\n\n/** @stable */\nexport async function runAuditPrune(options: AuditPruneOptions): Promise<PruneAuditResult> {\n const cutoff = parseCutoff(options.before);\n const ctx = await openAuditContext(options);\n try {\n const result = await pruneAudit(ctx.auditDb, {\n before: cutoff,\n ...(options.retain !== undefined ? { retain: options.retain } : {}),\n });\n emitReport(options, result, () => {\n const print = options.print ?? defaultPrintSink;\n if (result.deleted === 0) {\n print(\n brand(`no audit entries qualified for prune (cutoff=${new Date(cutoff).toISOString()}).`),\n );\n return;\n }\n print(\n brand(\n `pruned ${result.deleted} audit entries (cutoff=${new Date(cutoff).toISOString()}, surviving from seq=${result.firstSurvivingSeq ?? '<empty>'})`,\n ),\n );\n });\n return result;\n } finally {\n await ctx.close();\n }\n}\n\n/** @stable */\nexport interface AuditExportOptions extends AuditCommonOptions {\n readonly to: string;\n readonly fromSeq?: number;\n readonly toSeq?: number;\n}\n\n/** @stable */\nexport interface AuditExportResult {\n readonly path: string;\n readonly rows: number;\n}\n\n/**\n * `graphorin audit export --to <file>` - stream every entry as JSONL.\n *\n * @stable\n */\nexport async function runAuditExport(options: AuditExportOptions): Promise<AuditExportResult> {\n const cwd = process.cwd();\n const targetPath = isAbsolute(options.to) ? options.to : resolve(cwd, options.to);\n const ctx = await openAuditContext(options);\n try {\n const lines: string[] = [];\n const { rows } = await exportAudit(ctx.auditDb, {\n writer: { write: (line: string) => void lines.push(line) },\n ...(options.fromSeq !== undefined ? { fromSeq: options.fromSeq } : {}),\n ...(options.toSeq !== undefined ? { toSeq: options.toSeq } : {}),\n });\n await writeFile(targetPath, lines.join(''), { mode: 0o600 });\n const out: AuditExportResult = Object.freeze({ path: targetPath, rows });\n emitReport(options, out, () => {\n const print = options.print ?? defaultPrintSink;\n print(brand(`exported ${rows} audit entries to ${targetPath} (mode 0600).`));\n });\n return out;\n } finally {\n await ctx.close();\n }\n}\n\ninterface AuditContext {\n readonly auditDb: Awaited<ReturnType<typeof openAuditDb>>;\n readonly path: string;\n readonly close: () => Promise<void>;\n}\n\nasync function openAuditContext(options: AuditCommonOptions): Promise<AuditContext> {\n const loaded = await loadConfig(options.config);\n const config = parseServerConfig(loaded.config);\n if (!config.audit.enabled) {\n throw new Error(\n '[graphorin/cli] this command requires audit.enabled = true in the resolved config.',\n );\n }\n const passphraseRef = config.audit.passphraseRef ?? config.storage.encryption.passphraseRef;\n if (passphraseRef === undefined) {\n throw new Error(\n `[graphorin/cli] audit.enabled is true but no audit.passphraseRef (or storage.encryption.passphraseRef) is configured. audit.db is mandatory-encrypted by DEC-124.`,\n );\n }\n let passphrase: SecretValue;\n try {\n passphrase = await resolveSecret(passphraseRef);\n } catch (err) {\n throw new Error(\n `[graphorin/cli] failed to resolve audit passphrase '${passphraseRef}': ${(err as Error).message}`,\n { cause: err },\n );\n }\n const auditPath = config.audit.path ?? deriveAuditPath(config.storage.path);\n ensureStoreAuditBinding();\n const auditDb = await openAuditDb({\n path: auditPath,\n passphrase,\n ...(config.audit.cipher !== undefined ? { cipher: config.audit.cipher } : {}),\n });\n return Object.freeze({\n auditDb,\n path: auditPath,\n close: () => auditDb.close(),\n });\n}\n\nfunction deriveAuditPath(storagePath: string): string {\n const dir = dirname(storagePath);\n return resolve(dir, 'audit.db');\n}\n\nfunction parseCutoff(input: string): number {\n const numeric = Number(input);\n if (Number.isFinite(numeric) && numeric > 0) return numeric;\n const ms = Date.parse(input);\n if (!Number.isFinite(ms)) {\n throw new Error(\n `[graphorin/cli] --before '${input}' is not a valid ISO date or epoch-ms value.`,\n );\n }\n return ms;\n}\n\n// Internal type-only consumers - keep in scope so tree-shaking\n// preserves the entry point.\nvoid ((_e?: StoredAuditEntry) => undefined);\n"],"mappings":"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"}
|
package/dist/commands/auth.d.ts
CHANGED
|
@@ -15,7 +15,7 @@ interface AuthLoginOptions extends AuthCommonOptions {
|
|
|
15
15
|
readonly scope?: string;
|
|
16
16
|
readonly deviceFlow?: boolean;
|
|
17
17
|
/**
|
|
18
|
-
* Optional pre-existing client identifier
|
|
18
|
+
* Optional pre-existing client identifier - skips Dynamic Client
|
|
19
19
|
* Registration when supplied.
|
|
20
20
|
*/
|
|
21
21
|
readonly clientId?: string;
|
package/dist/commands/auth.js
CHANGED
|
@@ -7,7 +7,7 @@ import { createSecretsStore as createSecretsStore$1, getActiveSecretsStore as ge
|
|
|
7
7
|
|
|
8
8
|
//#region src/commands/auth.ts
|
|
9
9
|
/**
|
|
10
|
-
* `graphorin auth`
|
|
10
|
+
* `graphorin auth` - outbound OAuth subsystem (e.g. for MCP servers).
|
|
11
11
|
*
|
|
12
12
|
* Surface (per Phase 15 § Auth):
|
|
13
13
|
*
|
|
@@ -17,7 +17,7 @@ import { createSecretsStore as createSecretsStore$1, getActiveSecretsStore as ge
|
|
|
17
17
|
* - `graphorin auth revoke <id>`
|
|
18
18
|
* - `graphorin auth status`
|
|
19
19
|
*
|
|
20
|
-
* Honours `GRAPHORIN_OFFLINE=1`
|
|
20
|
+
* Honours `GRAPHORIN_OFFLINE=1` - when set, every subcommand short-
|
|
21
21
|
* circuits with an explanatory message and exits `1` so CI pipelines
|
|
22
22
|
* see the failure.
|
|
23
23
|
*
|
|
@@ -26,7 +26,7 @@ import { createSecretsStore as createSecretsStore$1, getActiveSecretsStore as ge
|
|
|
26
26
|
/**
|
|
27
27
|
* Resolve the CLI's secrets store for OAuth token persistence (SPL-1):
|
|
28
28
|
* the already-active store when one exists, else the default auto
|
|
29
|
-
* chain (keyring → encrypted-file → env). Best-effort
|
|
29
|
+
* chain (keyring → encrypted-file → env). Best-effort - a store
|
|
30
30
|
* failure degrades to the in-memory-only pre-SPL-1 behavior instead of
|
|
31
31
|
* blocking the auth command.
|
|
32
32
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.js","names":["getActiveSecretsStore","createSecretsStore"],"sources":["../../src/commands/auth.ts"],"sourcesContent":["/**\n * `graphorin auth` — outbound OAuth subsystem (e.g. for MCP servers).\n *\n * Surface (per Phase 15 § Auth):\n *\n * - `graphorin auth login --server <url> [--device-flow]`\n * - `graphorin auth list`\n * - `graphorin auth refresh <id>`\n * - `graphorin auth revoke <id>`\n * - `graphorin auth status`\n *\n * Honours `GRAPHORIN_OFFLINE=1` — when set, every subcommand short-\n * circuits with an explanatory message and exits `1` so CI pipelines\n * see the failure.\n *\n * @packageDocumentation\n */\n\nimport {\n getOAuthStatus,\n type LoginInteractiveResult,\n listOAuthSessions,\n loginInteractive,\n refreshOAuthSession,\n revokeOAuthSession,\n} from '@graphorin/security';\nimport { createSecretsStore, getActiveSecretsStore } from '@graphorin/security/secrets';\nimport { EXIT_CODES } from '../internal/exit.js';\nimport { checkOfflineModeBlocked } from '../internal/offline.js';\nimport {\n brand,\n type CommonOutputOptions,\n defaultPrintSink,\n emitReport,\n statusMarker,\n} from '../internal/output.js';\nimport { openStoreContext } from '../internal/store-context.js';\n\n/**\n * Resolve the CLI's secrets store for OAuth token persistence (SPL-1):\n * the already-active store when one exists, else the default auto\n * chain (keyring → encrypted-file → env). Best-effort — a store\n * failure degrades to the in-memory-only pre-SPL-1 behavior instead of\n * blocking the auth command.\n */\nasync function resolveAuthSecretsStore(): Promise<\n import('@graphorin/core/contracts').SecretsStore | undefined\n> {\n try {\n return getActiveSecretsStore() ?? (await createSecretsStore({}));\n } catch {\n return undefined;\n }\n}\n\n/** @stable */\nexport interface AuthCommonOptions extends CommonOutputOptions {\n readonly config?: string;\n}\n\n/** @stable */\nexport interface AuthLoginOptions extends AuthCommonOptions {\n readonly serverUrl: string;\n readonly serverId?: string;\n readonly scope?: string;\n readonly deviceFlow?: boolean;\n /**\n * Optional pre-existing client identifier — skips Dynamic Client\n * Registration when supplied.\n */\n readonly clientId?: string;\n}\n\n/** @stable */\nexport async function runAuthLogin(options: AuthLoginOptions): Promise<LoginInteractiveResult> {\n if (\n !checkOfflineModeBlocked('auth login', {\n ...(options.print !== undefined ? { print: options.print } : {}),\n })\n ) {\n process.exit(EXIT_CODES.RECOVERABLE_FAILURE);\n }\n const ctx = await openStoreContext({\n ...(options.config !== undefined ? { config: options.config } : {}),\n });\n try {\n const serverId = options.serverId ?? deriveServerId(options.serverUrl);\n const secretsStore = await resolveAuthSecretsStore();\n const result = await loginInteractive({\n serverId,\n serverUrl: options.serverUrl,\n storage: ctx.store.oauthServers,\n ...(secretsStore === undefined ? {} : { secretsStore }),\n ...(options.deviceFlow !== undefined ? { deviceFlow: options.deviceFlow } : {}),\n ...(options.scope !== undefined ? { scope: options.scope } : {}),\n ...(options.clientId !== undefined ? { clientId: options.clientId } : {}),\n });\n emitReport(options, result.status, () => {\n const print = options.print ?? defaultPrintSink;\n print(brand(`oauth login ${statusMarker('ok')} (server=${result.status.serverId})`));\n print(` status: ${result.status.status}`);\n print(` expiresAt: ${result.status.expiresAt ?? '-'}`);\n });\n return result;\n } finally {\n await ctx.close();\n }\n}\n\n/** @stable */\nexport interface AuthListOptions extends AuthCommonOptions {}\n\n/** @stable */\nexport async function runAuthList(options: AuthListOptions = {}) {\n const ctx = await openStoreContext({\n ...(options.config !== undefined ? { config: options.config } : {}),\n });\n try {\n const secretsStore = await resolveAuthSecretsStore();\n const list = await listOAuthSessions(ctx.store.oauthServers, {\n ...(secretsStore === undefined ? {} : { secretsStore }),\n });\n emitReport(options, list, () => {\n const print = options.print ?? defaultPrintSink;\n if (list.length === 0) {\n print(brand('no OAuth sessions persisted.'));\n return;\n }\n print(brand(`${list.length} OAuth session(s):`));\n for (const s of list) {\n const mark = s.status === 'expired' ? statusMarker('warn') : statusMarker('ok');\n print(` ${mark} ${s.serverId} (status=${s.status}, scope=${s.scope ?? '-'})`);\n }\n });\n return list;\n } finally {\n await ctx.close();\n }\n}\n\n/** @stable */\nexport interface AuthRefreshOptions extends AuthCommonOptions {\n readonly id: string;\n}\n\n/** @stable */\nexport async function runAuthRefresh(options: AuthRefreshOptions) {\n if (\n !checkOfflineModeBlocked('auth refresh', {\n ...(options.print !== undefined ? { print: options.print } : {}),\n })\n ) {\n process.exit(EXIT_CODES.RECOVERABLE_FAILURE);\n }\n const ctx = await openStoreContext({\n ...(options.config !== undefined ? { config: options.config } : {}),\n });\n try {\n const secretsStore = await resolveAuthSecretsStore();\n const session = await refreshOAuthSession(ctx.store.oauthServers, options.id, {\n ...(secretsStore === undefined ? {} : { secretsStore }),\n });\n emitReport(options, { ok: true, id: options.id }, () => {\n const print = options.print ?? defaultPrintSink;\n print(\n brand(\n `oauth session '${options.id}' refreshed (expiresAt=${new Date(session.expiresAt ?? Date.now()).toISOString()})`,\n ),\n );\n });\n return session;\n } finally {\n await ctx.close();\n }\n}\n\n/** @stable */\nexport interface AuthRevokeOptions extends AuthCommonOptions {\n readonly id: string;\n readonly reason?: string;\n}\n\n/** @stable */\nexport async function runAuthRevoke(options: AuthRevokeOptions): Promise<{ readonly ok: true }> {\n const ctx = await openStoreContext({\n ...(options.config !== undefined ? { config: options.config } : {}),\n });\n try {\n const secretsStore = await resolveAuthSecretsStore();\n await revokeOAuthSession(ctx.store.oauthServers, options.id, {\n ...(secretsStore === undefined ? {} : { secretsStore }),\n ...(options.reason !== undefined ? { reason: options.reason } : {}),\n });\n emitReport(options, { ok: true } as const, () => {\n const print = options.print ?? defaultPrintSink;\n print(brand(`oauth session '${options.id}' revoked.`));\n });\n return { ok: true };\n } finally {\n await ctx.close();\n }\n}\n\n/** @stable */\nexport async function runAuthStatus(options: AuthCommonOptions = {}) {\n const ctx = await openStoreContext({\n ...(options.config !== undefined ? { config: options.config } : {}),\n });\n try {\n const secretsStore = await resolveAuthSecretsStore();\n const status = await getOAuthStatus(ctx.store.oauthServers, {\n ...(secretsStore === undefined ? {} : { secretsStore }),\n });\n emitReport(options, status, () => {\n const print = options.print ?? defaultPrintSink;\n print(\n brand(\n `oauth subsystem status: ${status.sessions.length} session(s), ${status.providers.length} strategy(ies)`,\n ),\n );\n if (status.defaultStrategy !== null) {\n print(brand(`default strategy: ${status.defaultStrategy.id}`));\n }\n });\n return status;\n } finally {\n await ctx.close();\n }\n}\n\nfunction deriveServerId(serverUrl: string): string {\n try {\n const u = new URL(serverUrl);\n return u.host;\n } catch {\n return serverUrl;\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"auth.js","names":["getActiveSecretsStore","createSecretsStore"],"sources":["../../src/commands/auth.ts"],"sourcesContent":["/**\n * `graphorin auth` - outbound OAuth subsystem (e.g. for MCP servers).\n *\n * Surface (per Phase 15 § Auth):\n *\n * - `graphorin auth login --server <url> [--device-flow]`\n * - `graphorin auth list`\n * - `graphorin auth refresh <id>`\n * - `graphorin auth revoke <id>`\n * - `graphorin auth status`\n *\n * Honours `GRAPHORIN_OFFLINE=1` - when set, every subcommand short-\n * circuits with an explanatory message and exits `1` so CI pipelines\n * see the failure.\n *\n * @packageDocumentation\n */\n\nimport {\n getOAuthStatus,\n type LoginInteractiveResult,\n listOAuthSessions,\n loginInteractive,\n refreshOAuthSession,\n revokeOAuthSession,\n} from '@graphorin/security';\nimport { createSecretsStore, getActiveSecretsStore } from '@graphorin/security/secrets';\nimport { EXIT_CODES } from '../internal/exit.js';\nimport { checkOfflineModeBlocked } from '../internal/offline.js';\nimport {\n brand,\n type CommonOutputOptions,\n defaultPrintSink,\n emitReport,\n statusMarker,\n} from '../internal/output.js';\nimport { openStoreContext } from '../internal/store-context.js';\n\n/**\n * Resolve the CLI's secrets store for OAuth token persistence (SPL-1):\n * the already-active store when one exists, else the default auto\n * chain (keyring → encrypted-file → env). Best-effort - a store\n * failure degrades to the in-memory-only pre-SPL-1 behavior instead of\n * blocking the auth command.\n */\nasync function resolveAuthSecretsStore(): Promise<\n import('@graphorin/core/contracts').SecretsStore | undefined\n> {\n try {\n return getActiveSecretsStore() ?? (await createSecretsStore({}));\n } catch {\n return undefined;\n }\n}\n\n/** @stable */\nexport interface AuthCommonOptions extends CommonOutputOptions {\n readonly config?: string;\n}\n\n/** @stable */\nexport interface AuthLoginOptions extends AuthCommonOptions {\n readonly serverUrl: string;\n readonly serverId?: string;\n readonly scope?: string;\n readonly deviceFlow?: boolean;\n /**\n * Optional pre-existing client identifier - skips Dynamic Client\n * Registration when supplied.\n */\n readonly clientId?: string;\n}\n\n/** @stable */\nexport async function runAuthLogin(options: AuthLoginOptions): Promise<LoginInteractiveResult> {\n if (\n !checkOfflineModeBlocked('auth login', {\n ...(options.print !== undefined ? { print: options.print } : {}),\n })\n ) {\n process.exit(EXIT_CODES.RECOVERABLE_FAILURE);\n }\n const ctx = await openStoreContext({\n ...(options.config !== undefined ? { config: options.config } : {}),\n });\n try {\n const serverId = options.serverId ?? deriveServerId(options.serverUrl);\n const secretsStore = await resolveAuthSecretsStore();\n const result = await loginInteractive({\n serverId,\n serverUrl: options.serverUrl,\n storage: ctx.store.oauthServers,\n ...(secretsStore === undefined ? {} : { secretsStore }),\n ...(options.deviceFlow !== undefined ? { deviceFlow: options.deviceFlow } : {}),\n ...(options.scope !== undefined ? { scope: options.scope } : {}),\n ...(options.clientId !== undefined ? { clientId: options.clientId } : {}),\n });\n emitReport(options, result.status, () => {\n const print = options.print ?? defaultPrintSink;\n print(brand(`oauth login ${statusMarker('ok')} (server=${result.status.serverId})`));\n print(` status: ${result.status.status}`);\n print(` expiresAt: ${result.status.expiresAt ?? '-'}`);\n });\n return result;\n } finally {\n await ctx.close();\n }\n}\n\n/** @stable */\nexport interface AuthListOptions extends AuthCommonOptions {}\n\n/** @stable */\nexport async function runAuthList(options: AuthListOptions = {}) {\n const ctx = await openStoreContext({\n ...(options.config !== undefined ? { config: options.config } : {}),\n });\n try {\n const secretsStore = await resolveAuthSecretsStore();\n const list = await listOAuthSessions(ctx.store.oauthServers, {\n ...(secretsStore === undefined ? {} : { secretsStore }),\n });\n emitReport(options, list, () => {\n const print = options.print ?? defaultPrintSink;\n if (list.length === 0) {\n print(brand('no OAuth sessions persisted.'));\n return;\n }\n print(brand(`${list.length} OAuth session(s):`));\n for (const s of list) {\n const mark = s.status === 'expired' ? statusMarker('warn') : statusMarker('ok');\n print(` ${mark} ${s.serverId} (status=${s.status}, scope=${s.scope ?? '-'})`);\n }\n });\n return list;\n } finally {\n await ctx.close();\n }\n}\n\n/** @stable */\nexport interface AuthRefreshOptions extends AuthCommonOptions {\n readonly id: string;\n}\n\n/** @stable */\nexport async function runAuthRefresh(options: AuthRefreshOptions) {\n if (\n !checkOfflineModeBlocked('auth refresh', {\n ...(options.print !== undefined ? { print: options.print } : {}),\n })\n ) {\n process.exit(EXIT_CODES.RECOVERABLE_FAILURE);\n }\n const ctx = await openStoreContext({\n ...(options.config !== undefined ? { config: options.config } : {}),\n });\n try {\n const secretsStore = await resolveAuthSecretsStore();\n const session = await refreshOAuthSession(ctx.store.oauthServers, options.id, {\n ...(secretsStore === undefined ? {} : { secretsStore }),\n });\n emitReport(options, { ok: true, id: options.id }, () => {\n const print = options.print ?? defaultPrintSink;\n print(\n brand(\n `oauth session '${options.id}' refreshed (expiresAt=${new Date(session.expiresAt ?? Date.now()).toISOString()})`,\n ),\n );\n });\n return session;\n } finally {\n await ctx.close();\n }\n}\n\n/** @stable */\nexport interface AuthRevokeOptions extends AuthCommonOptions {\n readonly id: string;\n readonly reason?: string;\n}\n\n/** @stable */\nexport async function runAuthRevoke(options: AuthRevokeOptions): Promise<{ readonly ok: true }> {\n const ctx = await openStoreContext({\n ...(options.config !== undefined ? { config: options.config } : {}),\n });\n try {\n const secretsStore = await resolveAuthSecretsStore();\n await revokeOAuthSession(ctx.store.oauthServers, options.id, {\n ...(secretsStore === undefined ? {} : { secretsStore }),\n ...(options.reason !== undefined ? { reason: options.reason } : {}),\n });\n emitReport(options, { ok: true } as const, () => {\n const print = options.print ?? defaultPrintSink;\n print(brand(`oauth session '${options.id}' revoked.`));\n });\n return { ok: true };\n } finally {\n await ctx.close();\n }\n}\n\n/** @stable */\nexport async function runAuthStatus(options: AuthCommonOptions = {}) {\n const ctx = await openStoreContext({\n ...(options.config !== undefined ? { config: options.config } : {}),\n });\n try {\n const secretsStore = await resolveAuthSecretsStore();\n const status = await getOAuthStatus(ctx.store.oauthServers, {\n ...(secretsStore === undefined ? {} : { secretsStore }),\n });\n emitReport(options, status, () => {\n const print = options.print ?? defaultPrintSink;\n print(\n brand(\n `oauth subsystem status: ${status.sessions.length} session(s), ${status.providers.length} strategy(ies)`,\n ),\n );\n if (status.defaultStrategy !== null) {\n print(brand(`default strategy: ${status.defaultStrategy.id}`));\n }\n });\n return status;\n } finally {\n await ctx.close();\n }\n}\n\nfunction deriveServerId(serverUrl: string): string {\n try {\n const u = new URL(serverUrl);\n return u.host;\n } catch {\n return serverUrl;\n }\n}\n"],"mappings":"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"}
|
|
@@ -54,7 +54,7 @@ async function runConsolidatorSetTier(options) {
|
|
|
54
54
|
unsupported: true
|
|
55
55
|
};
|
|
56
56
|
emitReport(options, result, () => {
|
|
57
|
-
(options.print ?? defaultPrintSink)(brand(`runtime tier switching is not wired yet
|
|
57
|
+
(options.print ?? defaultPrintSink)(brand(`runtime tier switching is not wired yet - set the tier in the server config (consolidator.tier: '${options.tier}') and restart, or use the server API when available.`));
|
|
58
58
|
});
|
|
59
59
|
process.exitCode = EXIT_CODES.UNSUPPORTED;
|
|
60
60
|
return result;
|
|
@@ -72,7 +72,7 @@ async function runConsolidatorStop(options = {}) {
|
|
|
72
72
|
};
|
|
73
73
|
emitReport(options, result, () => {
|
|
74
74
|
const print = options.print ?? defaultPrintSink;
|
|
75
|
-
print(brand("a CLI stop channel is not wired yet
|
|
75
|
+
print(brand("a CLI stop channel is not wired yet - to stop consolidation NOW, stop the server process (the consolidator runs inside it)."));
|
|
76
76
|
print(brand("To stay stopped across restarts set consolidator.tier to a zero-budget tier in the config."));
|
|
77
77
|
});
|
|
78
78
|
process.exitCode = EXIT_CODES.UNSUPPORTED;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"consolidator.js","names":["VALID_TIERS: ReadonlyArray<ConsolidatorTier>","out: ConsolidatorStatusResult"],"sources":["../../src/commands/consolidator.ts"],"sourcesContent":["/**\n * `graphorin consolidator` — inspect and steer the background memory\n * consolidation pipeline.\n *\n * Surface (per Phase 15 § Consolidator):\n *\n * - `graphorin consolidator status` — current tier + pending CONFLICT-\n * CHECK runs + DLQ size + recent run history.\n * - `graphorin consolidator set-tier <free|cheap|standard|enterprise>`\n * — persist the requested tier so the next process startup picks it\n * up.\n * - `graphorin consolidator stop` — pause the consolidator until the\n * operator resumes it (next `graphorin start`).\n *\n * The consolidator runs **inside** the server process, so the CLI\n * cannot direct-control a live in-memory instance from a different\n * process. `status` reads the SQLite tables the running consolidator\n * writes to (`consolidator_state`, `consolidator_runs`,\n * `consolidator_failed_batches`, `conflict_check_pending`); `set-tier`\n * and `stop` honestly report UNSUPPORTED until a daemon-side control\n * channel exists (IP-4) — nothing polls an admin table.\n *\n * @packageDocumentation\n */\n\nimport type { ConsolidatorTier } from '@graphorin/memory';\n\nimport { EXIT_CODES } from '../internal/exit.js';\nimport {\n brand,\n type CommonOutputOptions,\n defaultPrintSink,\n emitReport,\n statusMarker,\n} from '../internal/output.js';\nimport { openStoreContext } from '../internal/store-context.js';\n\nconst VALID_TIERS: ReadonlyArray<ConsolidatorTier> = Object.freeze([\n 'free',\n 'cheap',\n 'standard',\n 'full',\n 'custom',\n] as const);\n\n/** @stable */\nexport interface ConsolidatorCommonOptions extends CommonOutputOptions {\n readonly config?: string;\n}\n\n/** @stable */\nexport interface ConsolidatorStatusResult {\n readonly tierHint?: ConsolidatorTier;\n readonly recentRuns: number;\n readonly successfulRuns: number;\n readonly failedRuns: number;\n readonly dlqSize: number;\n readonly pendingConflicts: number;\n readonly lastRunAt?: string;\n readonly lastRunStatus?: string;\n}\n\n/** @stable */\nexport async function runConsolidatorStatus(\n options: ConsolidatorCommonOptions = {},\n): Promise<ConsolidatorStatusResult> {\n const ctx = await openStoreContext({\n ...(options.config !== undefined ? { config: options.config } : {}),\n });\n try {\n const conn = ctx.store.connection;\n const recent = conn.get<{ n: number }>(\n 'SELECT COUNT(*) AS n FROM consolidator_runs WHERE started_at > ?',\n [Date.now() - 24 * 60 * 60_000],\n );\n // MCON-5: the store writes 'running' | 'completed' | 'failed' |\n // 'partial' | 'deferred' (consolidator-store.ts) — the old queries\n // asked for 'success'/'error'/'pending' and always returned 0.\n const success = conn.get<{ n: number }>(\n \"SELECT COUNT(*) AS n FROM consolidator_runs WHERE status = 'completed' AND started_at > ?\",\n [Date.now() - 24 * 60 * 60_000],\n );\n const failed = conn.get<{ n: number }>(\n \"SELECT COUNT(*) AS n FROM consolidator_runs WHERE status = 'failed' AND started_at > ?\",\n [Date.now() - 24 * 60 * 60_000],\n );\n const dlq = conn.get<{ n: number }>('SELECT COUNT(*) AS n FROM consolidator_failed_batches');\n // Pending conflict work lives in conflict_check_pending, not in\n // consolidator_runs ('pending' was never a run status).\n const pending = conn.get<{ n: number }>(\n 'SELECT COUNT(*) AS n FROM conflict_check_pending WHERE resolved_at IS NULL',\n );\n const last = conn.get<{ started_at: number; status: string }>(\n 'SELECT started_at, status FROM consolidator_runs ORDER BY started_at DESC LIMIT 1',\n );\n const out: ConsolidatorStatusResult = Object.freeze({\n recentRuns: recent?.n ?? 0,\n successfulRuns: success?.n ?? 0,\n failedRuns: failed?.n ?? 0,\n dlqSize: dlq?.n ?? 0,\n pendingConflicts: pending?.n ?? 0,\n ...(last !== undefined ? { lastRunAt: new Date(last.started_at).toISOString() } : {}),\n ...(last !== undefined ? { lastRunStatus: last.status } : {}),\n });\n emitReport(options, out, () => {\n const print = options.print ?? defaultPrintSink;\n const tier = out.tierHint ?? '<from running config>';\n print(brand(`consolidator status (tier hint: ${tier})`));\n print(\n ` ${statusMarker('ok')} runs in last 24h: ${out.recentRuns} (${out.successfulRuns} success, ${out.failedRuns} error)`,\n );\n print(` ${statusMarker(out.dlqSize > 0 ? 'warn' : 'ok')} dead-letter queue: ${out.dlqSize}`);\n print(\n ` ${statusMarker(out.pendingConflicts > 0 ? 'warn' : 'ok')} pending CONFLICT-CHECK runs: ${out.pendingConflicts}`,\n );\n if (out.lastRunAt !== undefined) {\n print(` last run: ${out.lastRunAt} (${out.lastRunStatus})`);\n }\n });\n return out;\n } finally {\n await ctx.close();\n }\n}\n\n/** @stable */\nexport interface ConsolidatorSetTierOptions extends ConsolidatorCommonOptions {\n readonly tier: ConsolidatorTier;\n}\n\n/** @stable */\nexport async function runConsolidatorSetTier(options: ConsolidatorSetTierOptions): Promise<{\n readonly tier: ConsolidatorTier;\n readonly applied: false;\n readonly unsupported: true;\n}> {\n if (!isTier(options.tier)) {\n throw new Error(\n `[graphorin/cli] invalid tier '${String(options.tier)}'. Allowed: ${VALID_TIERS.join(' | ')}.`,\n );\n }\n const ctx = await openStoreContext({\n ...(options.config !== undefined ? { config: options.config } : {}),\n });\n try {\n // IP-4: the old implementation upserted a `consolidator_admin` row\n // NOTHING polls (neither the daemon nor process startup reads it —\n // createConsolidator takes the tier from opts) and reported\n // success. Honest answer until a daemon-side control channel\n // exists: UNSUPPORTED with the working alternative.\n const result = { tier: options.tier, applied: false, unsupported: true } as const;\n emitReport(options, result, () => {\n const print = options.print ?? defaultPrintSink;\n print(\n brand(\n `runtime tier switching is not wired yet — set the tier in the server config (consolidator.tier: '${options.tier}') and restart, or use the server API when available.`,\n ),\n );\n });\n process.exitCode = EXIT_CODES.UNSUPPORTED;\n return result;\n } finally {\n await ctx.close();\n }\n}\n\n/** @stable */\nexport interface ConsolidatorStopOptions extends ConsolidatorCommonOptions {}\n\n/** @stable */\nexport async function runConsolidatorStop(\n options: ConsolidatorStopOptions = {},\n): Promise<{ readonly stopped: false; readonly unsupported: true }> {\n const ctx = await openStoreContext({\n ...(options.config !== undefined ? { config: options.config } : {}),\n });\n try {\n // IP-4: the old implementation persisted a pause flag NOTHING\n // honoured and told the operator the daemon would stop — the worst\n // possible lie when someone is trying to stop runaway LLM spend.\n const result = { stopped: false, unsupported: true } as const;\n emitReport(options, result, () => {\n const print = options.print ?? defaultPrintSink;\n print(\n brand(\n 'a CLI stop channel is not wired yet — to stop consolidation NOW, stop the server process (the consolidator runs inside it).',\n ),\n );\n print(\n brand(\n 'To stay stopped across restarts set consolidator.tier to a zero-budget tier in the config.',\n ),\n );\n });\n process.exitCode = EXIT_CODES.UNSUPPORTED;\n return result;\n } finally {\n await ctx.close();\n }\n}\n\nfunction isTier(value: unknown): value is ConsolidatorTier {\n return typeof value === 'string' && (VALID_TIERS as ReadonlyArray<string>).includes(value);\n}\n\n/**\n * Exit code emitted when a tier value fails validation. Re-exported\n * so the binary can surface the same code to downstream callers.\n *\n * @stable\n */\nexport const CONSOLIDATOR_INVALID_TIER_EXIT = EXIT_CODES.RECOVERABLE_FAILURE;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"consolidator.js","names":["VALID_TIERS: ReadonlyArray<ConsolidatorTier>","out: ConsolidatorStatusResult"],"sources":["../../src/commands/consolidator.ts"],"sourcesContent":["/**\n * `graphorin consolidator` - inspect and steer the background memory\n * consolidation pipeline.\n *\n * Surface (per Phase 15 § Consolidator):\n *\n * - `graphorin consolidator status` - current tier + pending CONFLICT-\n * CHECK runs + DLQ size + recent run history.\n * - `graphorin consolidator set-tier <free|cheap|standard|enterprise>`\n * - persist the requested tier so the next process startup picks it\n * up.\n * - `graphorin consolidator stop` - pause the consolidator until the\n * operator resumes it (next `graphorin start`).\n *\n * The consolidator runs **inside** the server process, so the CLI\n * cannot direct-control a live in-memory instance from a different\n * process. `status` reads the SQLite tables the running consolidator\n * writes to (`consolidator_state`, `consolidator_runs`,\n * `consolidator_failed_batches`, `conflict_check_pending`); `set-tier`\n * and `stop` honestly report UNSUPPORTED until a daemon-side control\n * channel exists (IP-4) - nothing polls an admin table.\n *\n * @packageDocumentation\n */\n\nimport type { ConsolidatorTier } from '@graphorin/memory';\n\nimport { EXIT_CODES } from '../internal/exit.js';\nimport {\n brand,\n type CommonOutputOptions,\n defaultPrintSink,\n emitReport,\n statusMarker,\n} from '../internal/output.js';\nimport { openStoreContext } from '../internal/store-context.js';\n\nconst VALID_TIERS: ReadonlyArray<ConsolidatorTier> = Object.freeze([\n 'free',\n 'cheap',\n 'standard',\n 'full',\n 'custom',\n] as const);\n\n/** @stable */\nexport interface ConsolidatorCommonOptions extends CommonOutputOptions {\n readonly config?: string;\n}\n\n/** @stable */\nexport interface ConsolidatorStatusResult {\n readonly tierHint?: ConsolidatorTier;\n readonly recentRuns: number;\n readonly successfulRuns: number;\n readonly failedRuns: number;\n readonly dlqSize: number;\n readonly pendingConflicts: number;\n readonly lastRunAt?: string;\n readonly lastRunStatus?: string;\n}\n\n/** @stable */\nexport async function runConsolidatorStatus(\n options: ConsolidatorCommonOptions = {},\n): Promise<ConsolidatorStatusResult> {\n const ctx = await openStoreContext({\n ...(options.config !== undefined ? { config: options.config } : {}),\n });\n try {\n const conn = ctx.store.connection;\n const recent = conn.get<{ n: number }>(\n 'SELECT COUNT(*) AS n FROM consolidator_runs WHERE started_at > ?',\n [Date.now() - 24 * 60 * 60_000],\n );\n // MCON-5: the store writes 'running' | 'completed' | 'failed' |\n // 'partial' | 'deferred' (consolidator-store.ts) - the old queries\n // asked for 'success'/'error'/'pending' and always returned 0.\n const success = conn.get<{ n: number }>(\n \"SELECT COUNT(*) AS n FROM consolidator_runs WHERE status = 'completed' AND started_at > ?\",\n [Date.now() - 24 * 60 * 60_000],\n );\n const failed = conn.get<{ n: number }>(\n \"SELECT COUNT(*) AS n FROM consolidator_runs WHERE status = 'failed' AND started_at > ?\",\n [Date.now() - 24 * 60 * 60_000],\n );\n const dlq = conn.get<{ n: number }>('SELECT COUNT(*) AS n FROM consolidator_failed_batches');\n // Pending conflict work lives in conflict_check_pending, not in\n // consolidator_runs ('pending' was never a run status).\n const pending = conn.get<{ n: number }>(\n 'SELECT COUNT(*) AS n FROM conflict_check_pending WHERE resolved_at IS NULL',\n );\n const last = conn.get<{ started_at: number; status: string }>(\n 'SELECT started_at, status FROM consolidator_runs ORDER BY started_at DESC LIMIT 1',\n );\n const out: ConsolidatorStatusResult = Object.freeze({\n recentRuns: recent?.n ?? 0,\n successfulRuns: success?.n ?? 0,\n failedRuns: failed?.n ?? 0,\n dlqSize: dlq?.n ?? 0,\n pendingConflicts: pending?.n ?? 0,\n ...(last !== undefined ? { lastRunAt: new Date(last.started_at).toISOString() } : {}),\n ...(last !== undefined ? { lastRunStatus: last.status } : {}),\n });\n emitReport(options, out, () => {\n const print = options.print ?? defaultPrintSink;\n const tier = out.tierHint ?? '<from running config>';\n print(brand(`consolidator status (tier hint: ${tier})`));\n print(\n ` ${statusMarker('ok')} runs in last 24h: ${out.recentRuns} (${out.successfulRuns} success, ${out.failedRuns} error)`,\n );\n print(` ${statusMarker(out.dlqSize > 0 ? 'warn' : 'ok')} dead-letter queue: ${out.dlqSize}`);\n print(\n ` ${statusMarker(out.pendingConflicts > 0 ? 'warn' : 'ok')} pending CONFLICT-CHECK runs: ${out.pendingConflicts}`,\n );\n if (out.lastRunAt !== undefined) {\n print(` last run: ${out.lastRunAt} (${out.lastRunStatus})`);\n }\n });\n return out;\n } finally {\n await ctx.close();\n }\n}\n\n/** @stable */\nexport interface ConsolidatorSetTierOptions extends ConsolidatorCommonOptions {\n readonly tier: ConsolidatorTier;\n}\n\n/** @stable */\nexport async function runConsolidatorSetTier(options: ConsolidatorSetTierOptions): Promise<{\n readonly tier: ConsolidatorTier;\n readonly applied: false;\n readonly unsupported: true;\n}> {\n if (!isTier(options.tier)) {\n throw new Error(\n `[graphorin/cli] invalid tier '${String(options.tier)}'. Allowed: ${VALID_TIERS.join(' | ')}.`,\n );\n }\n const ctx = await openStoreContext({\n ...(options.config !== undefined ? { config: options.config } : {}),\n });\n try {\n // IP-4: the old implementation upserted a `consolidator_admin` row\n // NOTHING polls (neither the daemon nor process startup reads it -\n // createConsolidator takes the tier from opts) and reported\n // success. Honest answer until a daemon-side control channel\n // exists: UNSUPPORTED with the working alternative.\n const result = { tier: options.tier, applied: false, unsupported: true } as const;\n emitReport(options, result, () => {\n const print = options.print ?? defaultPrintSink;\n print(\n brand(\n `runtime tier switching is not wired yet - set the tier in the server config (consolidator.tier: '${options.tier}') and restart, or use the server API when available.`,\n ),\n );\n });\n process.exitCode = EXIT_CODES.UNSUPPORTED;\n return result;\n } finally {\n await ctx.close();\n }\n}\n\n/** @stable */\nexport interface ConsolidatorStopOptions extends ConsolidatorCommonOptions {}\n\n/** @stable */\nexport async function runConsolidatorStop(\n options: ConsolidatorStopOptions = {},\n): Promise<{ readonly stopped: false; readonly unsupported: true }> {\n const ctx = await openStoreContext({\n ...(options.config !== undefined ? { config: options.config } : {}),\n });\n try {\n // IP-4: the old implementation persisted a pause flag NOTHING\n // honoured and told the operator the daemon would stop - the worst\n // possible lie when someone is trying to stop runaway LLM spend.\n const result = { stopped: false, unsupported: true } as const;\n emitReport(options, result, () => {\n const print = options.print ?? defaultPrintSink;\n print(\n brand(\n 'a CLI stop channel is not wired yet - to stop consolidation NOW, stop the server process (the consolidator runs inside it).',\n ),\n );\n print(\n brand(\n 'To stay stopped across restarts set consolidator.tier to a zero-budget tier in the config.',\n ),\n );\n });\n process.exitCode = EXIT_CODES.UNSUPPORTED;\n return result;\n } finally {\n await ctx.close();\n }\n}\n\nfunction isTier(value: unknown): value is ConsolidatorTier {\n return typeof value === 'string' && (VALID_TIERS as ReadonlyArray<string>).includes(value);\n}\n\n/**\n * Exit code emitted when a tier value fails validation. Re-exported\n * so the binary can surface the same code to downstream callers.\n *\n * @stable\n */\nexport const CONSOLIDATOR_INVALID_TIER_EXIT = EXIT_CODES.RECOVERABLE_FAILURE;\n"],"mappings":"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"}
|
|
@@ -26,7 +26,7 @@ interface DoctorCommandOptions extends CommonOutputOptions {
|
|
|
26
26
|
readonly systemdUnit?: string;
|
|
27
27
|
/** Run every check. Equivalent to passing every `--check-*` flag. */
|
|
28
28
|
readonly all?: boolean;
|
|
29
|
-
/** Test seam
|
|
29
|
+
/** Test seam - supply a custom systemd executor. */
|
|
30
30
|
readonly systemdRun?: (cmd: string) => Promise<string>;
|
|
31
31
|
}
|
|
32
32
|
/**
|
package/dist/commands/doctor.js
CHANGED
|
@@ -7,16 +7,16 @@ import { homedir } from "node:os";
|
|
|
7
7
|
|
|
8
8
|
//#region src/commands/doctor.ts
|
|
9
9
|
/**
|
|
10
|
-
* `graphorin doctor`
|
|
10
|
+
* `graphorin doctor` - host health check.
|
|
11
11
|
*
|
|
12
12
|
* Wraps the read-only library helpers in `@graphorin/security/hardening`
|
|
13
13
|
* (`checkPerms`, `checkSecrets`, `checkEncryption`, `checkSystemd`)
|
|
14
14
|
* with `--fix-perms` repair, `--all` aggregation, JSON output for CI
|
|
15
15
|
* pipelines, and exit-code semantics:
|
|
16
16
|
*
|
|
17
|
-
* - exit `0`
|
|
18
|
-
* - exit `1`
|
|
19
|
-
* - exit `2`
|
|
17
|
+
* - exit `0` - every check passed (`fail` count is `0`).
|
|
18
|
+
* - exit `1` - at least one check returned `'fail'`.
|
|
19
|
+
* - exit `2` - invocation could not even start (e.g. config missing
|
|
20
20
|
* when `--check-secrets` requires the bootstrapped store).
|
|
21
21
|
*
|
|
22
22
|
* The doctor never writes to disk unless `--fix-perms` is supplied.
|
|
@@ -81,7 +81,7 @@ async function runDoctor(options = {}) {
|
|
|
81
81
|
};
|
|
82
82
|
for (const c of checks) summary[c.status] += 1;
|
|
83
83
|
const report = Object.freeze({
|
|
84
|
-
version: "0.
|
|
84
|
+
version: "0.6.0",
|
|
85
85
|
home,
|
|
86
86
|
platform: process.platform,
|
|
87
87
|
checks: Object.freeze(checks),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"doctor.js","names":["checks: CheckResult[]","fixed: string[]","summary: Record<'ok' | 'warn' | 'fail' | 'skip', number>","report: DoctorReport"],"sources":["../../src/commands/doctor.ts"],"sourcesContent":["/**\n * `graphorin doctor` — host health check.\n *\n * Wraps the read-only library helpers in `@graphorin/security/hardening`\n * (`checkPerms`, `checkSecrets`, `checkEncryption`, `checkSystemd`)\n * with `--fix-perms` repair, `--all` aggregation, JSON output for CI\n * pipelines, and exit-code semantics:\n *\n * - exit `0` — every check passed (`fail` count is `0`).\n * - exit `1` — at least one check returned `'fail'`.\n * - exit `2` — invocation could not even start (e.g. config missing\n * when `--check-secrets` requires the bootstrapped store).\n *\n * The doctor never writes to disk unless `--fix-perms` is supplied.\n * When repair is requested the CLI calls `ensureFileMode(...)` /\n * `ensureDirMode(...)` from `@graphorin/security/hardening` and re-runs\n * the perms check so the report reflects the post-fix state.\n *\n * Source-of-truth: process-hardening + `graphorin doctor` policy\n * (DEC-135).\n *\n * @packageDocumentation\n */\n\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport process from 'node:process';\n\nimport {\n type CheckResult,\n checkEncryption,\n checkPerms,\n checkSecrets,\n checkSystemd,\n ensureDirMode,\n ensureFileMode,\n} from '@graphorin/security';\n\nimport { EXIT_CODES } from '../internal/exit.js';\nimport {\n brand,\n type CommonOutputOptions,\n defaultPrintSink,\n emitReport,\n statusMarker,\n} from '../internal/output.js';\n\n/**\n * @stable\n */\nexport interface DoctorCommandOptions extends CommonOutputOptions {\n /**\n * Override the directory the doctor checks. Defaults to\n * `~/.graphorin/`. Tests inject a fresh tmp dir.\n */\n readonly home?: string;\n /** Run the file-perms repair. */\n readonly fixPerms?: boolean;\n /** Run the file-perms check. Implied by `--all`. */\n readonly checkPerms?: boolean;\n /** Run the secrets-store check. */\n readonly checkSecrets?: boolean;\n /** Run the audit-encryption check. */\n readonly checkEncryption?: boolean;\n /** Run the systemd check. Linux-only. */\n readonly checkSystemd?: boolean;\n /** Optional systemd unit identifier (default `graphorin.service`). */\n readonly systemdUnit?: string;\n /** Run every check. Equivalent to passing every `--check-*` flag. */\n readonly all?: boolean;\n /** Test seam — supply a custom systemd executor. */\n readonly systemdRun?: (cmd: string) => Promise<string>;\n}\n\n/**\n * @stable\n */\nexport interface DoctorReport {\n readonly version: string;\n readonly home: string;\n readonly platform: NodeJS.Platform;\n readonly checks: ReadonlyArray<CheckResult>;\n readonly summary: {\n readonly ok: number;\n readonly warn: number;\n readonly fail: number;\n readonly skip: number;\n };\n readonly fixedPerms?: ReadonlyArray<string>;\n}\n\n/**\n * Programmatic entry point. Returns the {@link DoctorReport} so tests\n * and downstream automations consume the structured payload directly.\n *\n * @stable\n */\nexport async function runDoctor(options: DoctorCommandOptions = {}): Promise<DoctorReport> {\n const home = options.home ?? join(homedir(), '.graphorin');\n const expected = expectedFileModes(home);\n\n const enable = expandFlags(options);\n const checks: CheckResult[] = [];\n const fixed: string[] = [];\n\n if (enable.perms) {\n if (options.fixPerms === true) {\n const before = await checkPerms({ expected });\n for (const result of before) {\n if (result.status === 'fail' && expected[result.check] !== undefined) {\n const mode = expected[result.check] as number;\n try {\n if (mode === 0o700) {\n await ensureDirMode(result.check, mode);\n } else {\n await ensureFileMode(result.check, mode);\n }\n fixed.push(result.check);\n } catch (err) {\n checks.push({\n check: result.check,\n status: 'fail',\n message: `--fix-perms failed: ${(err as Error).message}`,\n });\n }\n }\n }\n // Re-run after the repair so the final report reflects the\n // post-fix state.\n const after = await checkPerms({ expected });\n checks.push(...after);\n } else {\n const result = await checkPerms({ expected });\n checks.push(...result);\n }\n }\n\n if (enable.secrets) {\n checks.push(...checkSecrets());\n }\n\n if (enable.encryption) {\n checks.push(...checkEncryption());\n }\n\n if (enable.systemd) {\n const unit =\n options.systemdUnit ?? (process.platform === 'linux' ? 'graphorin.service' : undefined);\n const result = await checkSystemd({\n ...(unit !== undefined ? { unit } : {}),\n ...(options.systemdRun !== undefined ? { run: options.systemdRun } : {}),\n });\n checks.push(...result);\n }\n\n const summary: Record<'ok' | 'warn' | 'fail' | 'skip', number> = {\n ok: 0,\n warn: 0,\n fail: 0,\n skip: 0,\n };\n for (const c of checks) {\n summary[c.status] += 1;\n }\n\n const report: DoctorReport = Object.freeze({\n version: '0.5.0',\n home,\n platform: process.platform,\n checks: Object.freeze(checks),\n summary: Object.freeze(summary),\n ...(fixed.length > 0 ? { fixedPerms: Object.freeze(fixed) } : {}),\n });\n\n emitReport(options, report, () => emitHumanReport(report, options));\n\n if (summary.fail > 0) {\n process.exitCode = EXIT_CODES.RECOVERABLE_FAILURE;\n }\n\n return report;\n}\n\n/**\n * Default expected file modes per the project's process-hardening\n * policy (DEC-135).\n *\n * @internal\n */\nexport function expectedFileModes(home: string): Readonly<Record<string, number>> {\n return Object.freeze({\n [home]: 0o700,\n [`${home}/config.json`]: 0o600,\n [`${home}/data.db`]: 0o600,\n [`${home}/audit.db`]: 0o600,\n [`${home}/secrets.kse`]: 0o600,\n });\n}\n\ninterface DoctorChecks {\n readonly perms: boolean;\n readonly secrets: boolean;\n readonly encryption: boolean;\n readonly systemd: boolean;\n}\n\nfunction expandFlags(options: DoctorCommandOptions): DoctorChecks {\n if (options.all === true) {\n return { perms: true, secrets: true, encryption: true, systemd: true };\n }\n const explicit =\n options.checkPerms === true ||\n options.checkSecrets === true ||\n options.checkEncryption === true ||\n options.checkSystemd === true ||\n options.fixPerms === true;\n if (!explicit) {\n return { perms: true, secrets: false, encryption: false, systemd: false };\n }\n return {\n perms: options.checkPerms === true || options.fixPerms === true,\n secrets: options.checkSecrets === true,\n encryption: options.checkEncryption === true,\n systemd: options.checkSystemd === true,\n };\n}\n\nfunction emitHumanReport(report: DoctorReport, options: DoctorCommandOptions): void {\n const print = options.print ?? defaultPrintSink;\n print(\n brand(\n `graphorin doctor v${report.version} (host: ${report.home}, platform: ${report.platform})`,\n ),\n );\n if (report.fixedPerms !== undefined) {\n print(brand(`--fix-perms repaired:`));\n for (const path of report.fixedPerms) print(` - ${path}`);\n }\n if (report.checks.length === 0) {\n print(brand('no checks were enabled. Pass --all or one of --check-*.'));\n return;\n }\n for (const c of report.checks) {\n print(` ${statusMarker(c.status)} ${c.check}: ${c.message}`);\n if (c.hint !== undefined) print(` -> ${c.hint}`);\n }\n print(\n brand(\n `summary: ${report.summary.ok} ok, ${report.summary.warn} warn, ${report.summary.fail} fail, ${report.summary.skip} skip`,\n ),\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"doctor.js","names":["checks: CheckResult[]","fixed: string[]","summary: Record<'ok' | 'warn' | 'fail' | 'skip', number>","report: DoctorReport"],"sources":["../../src/commands/doctor.ts"],"sourcesContent":["/**\n * `graphorin doctor` - host health check.\n *\n * Wraps the read-only library helpers in `@graphorin/security/hardening`\n * (`checkPerms`, `checkSecrets`, `checkEncryption`, `checkSystemd`)\n * with `--fix-perms` repair, `--all` aggregation, JSON output for CI\n * pipelines, and exit-code semantics:\n *\n * - exit `0` - every check passed (`fail` count is `0`).\n * - exit `1` - at least one check returned `'fail'`.\n * - exit `2` - invocation could not even start (e.g. config missing\n * when `--check-secrets` requires the bootstrapped store).\n *\n * The doctor never writes to disk unless `--fix-perms` is supplied.\n * When repair is requested the CLI calls `ensureFileMode(...)` /\n * `ensureDirMode(...)` from `@graphorin/security/hardening` and re-runs\n * the perms check so the report reflects the post-fix state.\n *\n * Source-of-truth: process-hardening + `graphorin doctor` policy\n * (DEC-135).\n *\n * @packageDocumentation\n */\n\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport process from 'node:process';\n\nimport {\n type CheckResult,\n checkEncryption,\n checkPerms,\n checkSecrets,\n checkSystemd,\n ensureDirMode,\n ensureFileMode,\n} from '@graphorin/security';\n\nimport { EXIT_CODES } from '../internal/exit.js';\nimport {\n brand,\n type CommonOutputOptions,\n defaultPrintSink,\n emitReport,\n statusMarker,\n} from '../internal/output.js';\n\n/**\n * @stable\n */\nexport interface DoctorCommandOptions extends CommonOutputOptions {\n /**\n * Override the directory the doctor checks. Defaults to\n * `~/.graphorin/`. Tests inject a fresh tmp dir.\n */\n readonly home?: string;\n /** Run the file-perms repair. */\n readonly fixPerms?: boolean;\n /** Run the file-perms check. Implied by `--all`. */\n readonly checkPerms?: boolean;\n /** Run the secrets-store check. */\n readonly checkSecrets?: boolean;\n /** Run the audit-encryption check. */\n readonly checkEncryption?: boolean;\n /** Run the systemd check. Linux-only. */\n readonly checkSystemd?: boolean;\n /** Optional systemd unit identifier (default `graphorin.service`). */\n readonly systemdUnit?: string;\n /** Run every check. Equivalent to passing every `--check-*` flag. */\n readonly all?: boolean;\n /** Test seam - supply a custom systemd executor. */\n readonly systemdRun?: (cmd: string) => Promise<string>;\n}\n\n/**\n * @stable\n */\nexport interface DoctorReport {\n readonly version: string;\n readonly home: string;\n readonly platform: NodeJS.Platform;\n readonly checks: ReadonlyArray<CheckResult>;\n readonly summary: {\n readonly ok: number;\n readonly warn: number;\n readonly fail: number;\n readonly skip: number;\n };\n readonly fixedPerms?: ReadonlyArray<string>;\n}\n\n/**\n * Programmatic entry point. Returns the {@link DoctorReport} so tests\n * and downstream automations consume the structured payload directly.\n *\n * @stable\n */\nexport async function runDoctor(options: DoctorCommandOptions = {}): Promise<DoctorReport> {\n const home = options.home ?? join(homedir(), '.graphorin');\n const expected = expectedFileModes(home);\n\n const enable = expandFlags(options);\n const checks: CheckResult[] = [];\n const fixed: string[] = [];\n\n if (enable.perms) {\n if (options.fixPerms === true) {\n const before = await checkPerms({ expected });\n for (const result of before) {\n if (result.status === 'fail' && expected[result.check] !== undefined) {\n const mode = expected[result.check] as number;\n try {\n if (mode === 0o700) {\n await ensureDirMode(result.check, mode);\n } else {\n await ensureFileMode(result.check, mode);\n }\n fixed.push(result.check);\n } catch (err) {\n checks.push({\n check: result.check,\n status: 'fail',\n message: `--fix-perms failed: ${(err as Error).message}`,\n });\n }\n }\n }\n // Re-run after the repair so the final report reflects the\n // post-fix state.\n const after = await checkPerms({ expected });\n checks.push(...after);\n } else {\n const result = await checkPerms({ expected });\n checks.push(...result);\n }\n }\n\n if (enable.secrets) {\n checks.push(...checkSecrets());\n }\n\n if (enable.encryption) {\n checks.push(...checkEncryption());\n }\n\n if (enable.systemd) {\n const unit =\n options.systemdUnit ?? (process.platform === 'linux' ? 'graphorin.service' : undefined);\n const result = await checkSystemd({\n ...(unit !== undefined ? { unit } : {}),\n ...(options.systemdRun !== undefined ? { run: options.systemdRun } : {}),\n });\n checks.push(...result);\n }\n\n const summary: Record<'ok' | 'warn' | 'fail' | 'skip', number> = {\n ok: 0,\n warn: 0,\n fail: 0,\n skip: 0,\n };\n for (const c of checks) {\n summary[c.status] += 1;\n }\n\n const report: DoctorReport = Object.freeze({\n version: '0.6.0',\n home,\n platform: process.platform,\n checks: Object.freeze(checks),\n summary: Object.freeze(summary),\n ...(fixed.length > 0 ? { fixedPerms: Object.freeze(fixed) } : {}),\n });\n\n emitReport(options, report, () => emitHumanReport(report, options));\n\n if (summary.fail > 0) {\n process.exitCode = EXIT_CODES.RECOVERABLE_FAILURE;\n }\n\n return report;\n}\n\n/**\n * Default expected file modes per the project's process-hardening\n * policy (DEC-135).\n *\n * @internal\n */\nexport function expectedFileModes(home: string): Readonly<Record<string, number>> {\n return Object.freeze({\n [home]: 0o700,\n [`${home}/config.json`]: 0o600,\n [`${home}/data.db`]: 0o600,\n [`${home}/audit.db`]: 0o600,\n [`${home}/secrets.kse`]: 0o600,\n });\n}\n\ninterface DoctorChecks {\n readonly perms: boolean;\n readonly secrets: boolean;\n readonly encryption: boolean;\n readonly systemd: boolean;\n}\n\nfunction expandFlags(options: DoctorCommandOptions): DoctorChecks {\n if (options.all === true) {\n return { perms: true, secrets: true, encryption: true, systemd: true };\n }\n const explicit =\n options.checkPerms === true ||\n options.checkSecrets === true ||\n options.checkEncryption === true ||\n options.checkSystemd === true ||\n options.fixPerms === true;\n if (!explicit) {\n return { perms: true, secrets: false, encryption: false, systemd: false };\n }\n return {\n perms: options.checkPerms === true || options.fixPerms === true,\n secrets: options.checkSecrets === true,\n encryption: options.checkEncryption === true,\n systemd: options.checkSystemd === true,\n };\n}\n\nfunction emitHumanReport(report: DoctorReport, options: DoctorCommandOptions): void {\n const print = options.print ?? defaultPrintSink;\n print(\n brand(\n `graphorin doctor v${report.version} (host: ${report.home}, platform: ${report.platform})`,\n ),\n );\n if (report.fixedPerms !== undefined) {\n print(brand(`--fix-perms repaired:`));\n for (const path of report.fixedPerms) print(` - ${path}`);\n }\n if (report.checks.length === 0) {\n print(brand('no checks were enabled. Pass --all or one of --check-*.'));\n return;\n }\n for (const c of report.checks) {\n print(` ${statusMarker(c.status)} ${c.check}: ${c.message}`);\n if (c.hint !== undefined) print(` -> ${c.hint}`);\n }\n print(\n brand(\n `summary: ${report.summary.ok} ok, ${report.summary.warn} warn, ${report.summary.fail} fail, ${report.summary.skip} skip`,\n ),\n );\n}\n"],"mappings":"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"}
|
package/dist/commands/guard.js
CHANGED
|
@@ -3,14 +3,14 @@ import { classifyTool, guardVariantForTier } from "@graphorin/security";
|
|
|
3
3
|
|
|
4
4
|
//#region src/commands/guard.ts
|
|
5
5
|
/**
|
|
6
|
-
* `graphorin guard`
|
|
6
|
+
* `graphorin guard` - inspect the memory-modification guard tier
|
|
7
7
|
* policy.
|
|
8
8
|
*
|
|
9
9
|
* Surface (per Phase 15 § Guard):
|
|
10
10
|
*
|
|
11
|
-
* - `graphorin guard status`
|
|
11
|
+
* - `graphorin guard status` - print the four tiers + their guard
|
|
12
12
|
* variants (per DEC-153).
|
|
13
|
-
* - `graphorin guard explain <toolName>`
|
|
13
|
+
* - `graphorin guard explain <toolName>` - derive the tier the
|
|
14
14
|
* classifier would assign to a tool with the supplied trust class
|
|
15
15
|
* and tags. The CLI accepts the metadata via `--tags` /
|
|
16
16
|
* `--trust-level` / `--allowed-secrets` so the explain operation
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"guard.js","names":["TIERS: ReadonlyArray<MemoryGuardTier>","rows: GuardStatusEntry[]","tool: ClassifiableTool","result: GuardExplainResult"],"sources":["../../src/commands/guard.ts"],"sourcesContent":["/**\n * `graphorin guard`
|
|
1
|
+
{"version":3,"file":"guard.js","names":["TIERS: ReadonlyArray<MemoryGuardTier>","rows: GuardStatusEntry[]","tool: ClassifiableTool","result: GuardExplainResult"],"sources":["../../src/commands/guard.ts"],"sourcesContent":["/**\n * `graphorin guard` - inspect the memory-modification guard tier\n * policy.\n *\n * Surface (per Phase 15 § Guard):\n *\n * - `graphorin guard status` - print the four tiers + their guard\n * variants (per DEC-153).\n * - `graphorin guard explain <toolName>` - derive the tier the\n * classifier would assign to a tool with the supplied trust class\n * and tags. The CLI accepts the metadata via `--tags` /\n * `--trust-level` / `--allowed-secrets` so the explain operation\n * matches the runtime classifier without requiring the operator to\n * boot the agent.\n *\n * @packageDocumentation\n */\n\nimport {\n type ClassifiableTool,\n classifyTool,\n guardVariantForTier,\n type MemoryGuardTier,\n} from '@graphorin/security';\n\nimport {\n brand,\n type CommonOutputOptions,\n defaultPrintSink,\n emitReport,\n statusMarker,\n} from '../internal/output.js';\n\n/** @stable */\nexport interface GuardCommonOptions extends CommonOutputOptions {}\n\nconst TIERS: ReadonlyArray<MemoryGuardTier> = Object.freeze([\n 'pure',\n 'side-effecting-no-memory',\n 'memory-aware',\n 'unknown',\n 'untrusted',\n] as const);\n\n/** @stable */\nexport interface GuardStatusEntry {\n readonly tier: MemoryGuardTier;\n readonly variant: ReturnType<typeof guardVariantForTier>;\n readonly description: string;\n}\n\n/** @stable */\nexport function runGuardStatus(options: GuardCommonOptions = {}): ReadonlyArray<GuardStatusEntry> {\n const rows: GuardStatusEntry[] = TIERS.map((tier) =>\n Object.freeze({\n tier,\n variant: guardVariantForTier(tier),\n description: descriptionFor(tier),\n }),\n );\n emitReport(options, rows, () => {\n const print = options.print ?? defaultPrintSink;\n print(brand('memory-modification guard tiers (DEC-153):'));\n for (const row of rows) {\n print(` ${statusMarker('info')} ${row.tier} -> ${row.variant} (${row.description})`);\n }\n });\n return rows;\n}\n\n/** @stable */\nexport interface GuardExplainOptions extends GuardCommonOptions {\n readonly toolName: string;\n readonly tags?: ReadonlyArray<string>;\n readonly secretsAllowed?: ReadonlyArray<string>;\n readonly trustLevel?: 'built-in' | 'user-defined' | 'trusted' | 'untrusted';\n readonly explicitTier?: MemoryGuardTier;\n}\n\n/** @stable */\nexport interface GuardExplainResult {\n readonly toolName: string;\n readonly tier: MemoryGuardTier;\n readonly variant: ReturnType<typeof guardVariantForTier>;\n readonly reason: string;\n}\n\n/** @stable */\nexport function runGuardExplain(options: GuardExplainOptions): GuardExplainResult {\n const tool: ClassifiableTool = {\n name: options.toolName,\n ...(options.tags !== undefined ? { tags: options.tags } : {}),\n ...(options.secretsAllowed !== undefined ? { secretsAllowed: options.secretsAllowed } : {}),\n ...(options.trustLevel !== undefined ? { trustLevel: options.trustLevel } : {}),\n ...(options.explicitTier !== undefined ? { memoryGuardTier: options.explicitTier } : {}),\n };\n const tier = classifyTool(tool);\n const variant = guardVariantForTier(tier);\n const reason = explainReason(tool, tier);\n const result: GuardExplainResult = Object.freeze({\n toolName: options.toolName,\n tier,\n variant,\n reason,\n });\n emitReport(options, result, () => {\n const print = options.print ?? defaultPrintSink;\n print(\n brand(\n `${statusMarker('info')} ${result.toolName} -> tier=${result.tier} (variant=${result.variant})`,\n ),\n );\n print(` reason: ${result.reason}`);\n });\n return result;\n}\n\nfunction descriptionFor(tier: MemoryGuardTier): string {\n switch (tier) {\n case 'pure':\n return 'no side effects, no guard required';\n case 'side-effecting-no-memory':\n return 'side effects but no long-term memory writes';\n case 'memory-aware':\n return 'API-boundary guard around memory operations';\n case 'unknown':\n return 'audit-only guard (default for unclassified tools)';\n case 'untrusted':\n return 'strict full guard with xxhash integrity check';\n }\n}\n\nfunction explainReason(tool: ClassifiableTool, tier: MemoryGuardTier): string {\n if (tool.memoryGuardTier !== undefined) {\n return `operator-set memoryGuardTier='${tool.memoryGuardTier}' (precedence wins)`;\n }\n if (tool.trustLevel === 'untrusted') {\n return `trustLevel='untrusted' forces tier='untrusted'`;\n }\n if (tier === 'memory-aware') {\n return `tags / secretsAllowed / name match the default memory-tag patterns`;\n }\n return `default classification (no memory hints found in tags / secretsAllowed)`;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAoCA,MAAMA,QAAwC,OAAO,OAAO;CAC1D;CACA;CACA;CACA;CACA;CACD,CAAU;;AAUX,SAAgB,eAAe,UAA8B,EAAE,EAAmC;CAChG,MAAMC,OAA2B,MAAM,KAAK,SAC1C,OAAO,OAAO;EACZ;EACA,SAAS,oBAAoB,KAAK;EAClC,aAAa,eAAe,KAAK;EAClC,CAAC,CACH;AACD,YAAW,SAAS,YAAY;EAC9B,MAAM,QAAQ,QAAQ,SAAS;AAC/B,QAAM,MAAM,6CAA6C,CAAC;AAC1D,OAAK,MAAM,OAAO,KAChB,OAAM,KAAK,aAAa,OAAO,CAAC,GAAG,IAAI,KAAK,MAAM,IAAI,QAAQ,IAAI,IAAI,YAAY,GAAG;GAEvF;AACF,QAAO;;;AAqBT,SAAgB,gBAAgB,SAAkD;CAChF,MAAMC,OAAyB;EAC7B,MAAM,QAAQ;EACd,GAAI,QAAQ,SAAS,SAAY,EAAE,MAAM,QAAQ,MAAM,GAAG,EAAE;EAC5D,GAAI,QAAQ,mBAAmB,SAAY,EAAE,gBAAgB,QAAQ,gBAAgB,GAAG,EAAE;EAC1F,GAAI,QAAQ,eAAe,SAAY,EAAE,YAAY,QAAQ,YAAY,GAAG,EAAE;EAC9E,GAAI,QAAQ,iBAAiB,SAAY,EAAE,iBAAiB,QAAQ,cAAc,GAAG,EAAE;EACxF;CACD,MAAM,OAAO,aAAa,KAAK;CAC/B,MAAM,UAAU,oBAAoB,KAAK;CACzC,MAAM,SAAS,cAAc,MAAM,KAAK;CACxC,MAAMC,SAA6B,OAAO,OAAO;EAC/C,UAAU,QAAQ;EAClB;EACA;EACA;EACD,CAAC;AACF,YAAW,SAAS,cAAc;EAChC,MAAM,QAAQ,QAAQ,SAAS;AAC/B,QACE,MACE,GAAG,aAAa,OAAO,CAAC,GAAG,OAAO,SAAS,WAAW,OAAO,KAAK,YAAY,OAAO,QAAQ,GAC9F,CACF;AACD,QAAM,aAAa,OAAO,SAAS;GACnC;AACF,QAAO;;AAGT,SAAS,eAAe,MAA+B;AACrD,SAAQ,MAAR;EACE,KAAK,OACH,QAAO;EACT,KAAK,2BACH,QAAO;EACT,KAAK,eACH,QAAO;EACT,KAAK,UACH,QAAO;EACT,KAAK,YACH,QAAO;;;AAIb,SAAS,cAAc,MAAwB,MAA+B;AAC5E,KAAI,KAAK,oBAAoB,OAC3B,QAAO,iCAAiC,KAAK,gBAAgB;AAE/D,KAAI,KAAK,eAAe,YACtB,QAAO;AAET,KAAI,SAAS,eACX,QAAO;AAET,QAAO"}
|
package/dist/commands/index.d.ts
CHANGED
|
@@ -12,10 +12,10 @@ import { PricingCommonOptions, PricingDiffOptions, PricingLookupOptions, Pricing
|
|
|
12
12
|
import { SecretsCommonOptions, SecretsDeleteOptions, SecretsGetOptions, SecretsGetResult, SecretsListOptions, SecretsRefOptions, SecretsRefResult, SecretsRotateOptions, SecretsSetOptions, runSecretsDelete, runSecretsGet, runSecretsList, runSecretsRef, runSecretsRotate, runSecretsSet } from "./secrets.js";
|
|
13
13
|
import { SkillTrustLevelInput, SkillsAuditOptions, SkillsCommonOptions, SkillsInspectOptions, SkillsInstallOptions, SkillsMigrateFrontmatterOptions, SkillsMigrateFrontmatterResult, runSkillsAudit, runSkillsInspect, runSkillsInstall, runSkillsMigrateFrontmatter } from "./skills.js";
|
|
14
14
|
import { SecretsSourceFlag, StartCommandOptions, runStart } from "./start.js";
|
|
15
|
-
import { StorageCleanupBackupsOptions, StorageCleanupBackupsResult, StorageCommonOptions, StorageEncryptOptions, StorageRekeyOptions, StorageStatusResult, runStorageCleanupBackups, runStorageEncrypt, runStorageRekey, runStorageStatus } from "./storage.js";
|
|
15
|
+
import { StorageBackupOptions, StorageBackupResult, StorageCleanupBackupsOptions, StorageCleanupBackupsResult, StorageCommonOptions, StorageEncryptOptions, StorageRekeyOptions, StorageStatusResult, runStorageBackup, runStorageCleanupBackups, runStorageEncrypt, runStorageRekey, runStorageStatus } from "./storage.js";
|
|
16
16
|
import { TelemetryStatusResult, runTelemetryDisable, runTelemetryEnable, runTelemetryInspect, runTelemetryStatus } from "./telemetry.js";
|
|
17
17
|
import { TokenCommonOptions, TokenCreateOptions, TokenCreateResult, TokenListOptions, TokenRekeyOptions, TokenRevokeOptions, TokenRotateOptions, TokenVerifyOptions, TokenVerifyResult, parseDuration, runTokenCreate, runTokenList, runTokenRekey, runTokenRevoke, runTokenRotate, runTokenVerify } from "./token.js";
|
|
18
18
|
import { ToolsLintOptions, ToolsLintReport, runToolsLint } from "./tools-lint.js";
|
|
19
19
|
import { TracesCommonOptions, TracesPruneOptions, TracesPruneResult, TracesStatusResult, runTracesPrune, runTracesStatus } from "./traces.js";
|
|
20
20
|
import { TriggersCommonOptions, TriggersDisableOptions, TriggersFireOptions, TriggersPruneOptions, TriggersPruneResult, TriggersStatusOptions, runTriggersDisable, runTriggersFire, runTriggersList, runTriggersPrune, runTriggersStatus } from "./triggers.js";
|
|
21
|
-
export { type AuditCommonOptions, type AuditExportOptions, type AuditExportResult, type AuditPruneOptions, type AuditVerifyResult, type AuthCommonOptions, type AuthListOptions, type AuthLoginOptions, type AuthRefreshOptions, type AuthRevokeOptions, CONSOLIDATOR_INVALID_TIER_EXIT, type ConsolidatorCommonOptions, type ConsolidatorSetTierOptions, type ConsolidatorStatusResult, type ConsolidatorStopOptions, type DoctorCommandOptions, type DoctorReport, type GuardCommonOptions, type GuardExplainOptions, type GuardExplainResult, type GuardStatusEntry, type InitCommandOptions, type InitCommandResult, type MemoryActivityConflict, type MemoryActivityEvent, type MemoryActivityOptions, type MemoryActivityResult, type MemoryCitingInsight, type MemoryCommonOptions, type MemoryConflictEntry, type MemoryHistoryEntry, type MemoryInspectEntity, type MemoryInspectFact, type MemoryInspectOptions, type MemoryInspectResult, type MemoryMigrateOptions, type MemoryReviewItem, type MemoryReviewOptions, type MemoryReviewResult, type MemoryStatusEmbedder, type MemoryStatusResult, type MemoryWhyOptions, type MemoryWhyRecall, type MemoryWhyResult, type MigrateCommandOptions, type MigrateCommandResult, type MigrateConfigOptions, type MigrateConfigResult, type MigrateExportOptions, type MigrateExportResult, type PricingCommonOptions, type PricingDiffOptions, type PricingLookupOptions, type PricingMissingOptions, type PricingRefreshOptions, type PricingStatusResult, type SecretsCommonOptions, type SecretsDeleteOptions, type SecretsGetOptions, type SecretsGetResult, type SecretsListOptions, type SecretsRefOptions, type SecretsRefResult, type SecretsRotateOptions, type SecretsSetOptions, type SecretsSourceFlag, type SkillTrustLevelInput, type SkillsAuditOptions, type SkillsCommonOptions, type SkillsInspectOptions, type SkillsInstallOptions, type SkillsMigrateFrontmatterOptions, type SkillsMigrateFrontmatterResult, type StartCommandOptions, type StorageCleanupBackupsOptions, type StorageCleanupBackupsResult, type StorageCommonOptions, type StorageEncryptOptions, type StorageRekeyOptions, type StorageStatusResult, type TelemetryStatusResult, type TokenCommonOptions, type TokenCreateOptions, type TokenCreateResult, type TokenListOptions, type TokenRekeyOptions, type TokenRevokeOptions, type TokenRotateOptions, type TokenVerifyOptions, type TokenVerifyResult, type ToolsLintOptions, type ToolsLintReport, type TracesCommonOptions, type TracesPruneOptions, type TracesPruneResult, type TracesStatusResult, type TriggersCommonOptions, type TriggersDisableOptions, type TriggersFireOptions, type TriggersPruneOptions, type TriggersPruneResult, type TriggersStatusOptions, expectedFileModes, parseDuration, runAuditExport, runAuditPrune, runAuditVerify, runAuthList, runAuthLogin, runAuthRefresh, runAuthRevoke, runAuthStatus, runConsolidatorSetTier, runConsolidatorStatus, runConsolidatorStop, runDoctor, runGuardExplain, runGuardStatus, runInit, runMemoryActivity, runMemoryInspect, runMemoryMigrate, runMemoryReview, runMemoryStatus, runMemoryWhy, runMigrate, runMigrateConfig, runMigrateExport, runPricingDiff, runPricingLookup, runPricingMissing, runPricingRefresh, runPricingStatus, runSecretsDelete, runSecretsGet, runSecretsList, runSecretsRef, runSecretsRotate, runSecretsSet, runSkillsAudit, runSkillsInspect, runSkillsInstall, runSkillsMigrateFrontmatter, runStart, runStorageCleanupBackups, runStorageEncrypt, runStorageRekey, runStorageStatus, runTelemetryDisable, runTelemetryEnable, runTelemetryInspect, runTelemetryStatus, runTokenCreate, runTokenList, runTokenRekey, runTokenRevoke, runTokenRotate, runTokenVerify, runToolsLint, runTracesPrune, runTracesStatus, runTriggersDisable, runTriggersFire, runTriggersList, runTriggersPrune, runTriggersStatus };
|
|
21
|
+
export { type AuditCommonOptions, type AuditExportOptions, type AuditExportResult, type AuditPruneOptions, type AuditVerifyResult, type AuthCommonOptions, type AuthListOptions, type AuthLoginOptions, type AuthRefreshOptions, type AuthRevokeOptions, CONSOLIDATOR_INVALID_TIER_EXIT, type ConsolidatorCommonOptions, type ConsolidatorSetTierOptions, type ConsolidatorStatusResult, type ConsolidatorStopOptions, type DoctorCommandOptions, type DoctorReport, type GuardCommonOptions, type GuardExplainOptions, type GuardExplainResult, type GuardStatusEntry, type InitCommandOptions, type InitCommandResult, type MemoryActivityConflict, type MemoryActivityEvent, type MemoryActivityOptions, type MemoryActivityResult, type MemoryCitingInsight, type MemoryCommonOptions, type MemoryConflictEntry, type MemoryHistoryEntry, type MemoryInspectEntity, type MemoryInspectFact, type MemoryInspectOptions, type MemoryInspectResult, type MemoryMigrateOptions, type MemoryReviewItem, type MemoryReviewOptions, type MemoryReviewResult, type MemoryStatusEmbedder, type MemoryStatusResult, type MemoryWhyOptions, type MemoryWhyRecall, type MemoryWhyResult, type MigrateCommandOptions, type MigrateCommandResult, type MigrateConfigOptions, type MigrateConfigResult, type MigrateExportOptions, type MigrateExportResult, type PricingCommonOptions, type PricingDiffOptions, type PricingLookupOptions, type PricingMissingOptions, type PricingRefreshOptions, type PricingStatusResult, type SecretsCommonOptions, type SecretsDeleteOptions, type SecretsGetOptions, type SecretsGetResult, type SecretsListOptions, type SecretsRefOptions, type SecretsRefResult, type SecretsRotateOptions, type SecretsSetOptions, type SecretsSourceFlag, type SkillTrustLevelInput, type SkillsAuditOptions, type SkillsCommonOptions, type SkillsInspectOptions, type SkillsInstallOptions, type SkillsMigrateFrontmatterOptions, type SkillsMigrateFrontmatterResult, type StartCommandOptions, type StorageBackupOptions, type StorageBackupResult, type StorageCleanupBackupsOptions, type StorageCleanupBackupsResult, type StorageCommonOptions, type StorageEncryptOptions, type StorageRekeyOptions, type StorageStatusResult, type TelemetryStatusResult, type TokenCommonOptions, type TokenCreateOptions, type TokenCreateResult, type TokenListOptions, type TokenRekeyOptions, type TokenRevokeOptions, type TokenRotateOptions, type TokenVerifyOptions, type TokenVerifyResult, type ToolsLintOptions, type ToolsLintReport, type TracesCommonOptions, type TracesPruneOptions, type TracesPruneResult, type TracesStatusResult, type TriggersCommonOptions, type TriggersDisableOptions, type TriggersFireOptions, type TriggersPruneOptions, type TriggersPruneResult, type TriggersStatusOptions, expectedFileModes, parseDuration, runAuditExport, runAuditPrune, runAuditVerify, runAuthList, runAuthLogin, runAuthRefresh, runAuthRevoke, runAuthStatus, runConsolidatorSetTier, runConsolidatorStatus, runConsolidatorStop, runDoctor, runGuardExplain, runGuardStatus, runInit, runMemoryActivity, runMemoryInspect, runMemoryMigrate, runMemoryReview, runMemoryStatus, runMemoryWhy, runMigrate, runMigrateConfig, runMigrateExport, runPricingDiff, runPricingLookup, runPricingMissing, runPricingRefresh, runPricingStatus, runSecretsDelete, runSecretsGet, runSecretsList, runSecretsRef, runSecretsRotate, runSecretsSet, runSkillsAudit, runSkillsInspect, runSkillsInstall, runSkillsMigrateFrontmatter, runStart, runStorageBackup, runStorageCleanupBackups, runStorageEncrypt, runStorageRekey, runStorageStatus, runTelemetryDisable, runTelemetryEnable, runTelemetryInspect, runTelemetryStatus, runTokenCreate, runTokenList, runTokenRekey, runTokenRevoke, runTokenRotate, runTokenVerify, runToolsLint, runTracesPrune, runTracesStatus, runTriggersDisable, runTriggersFire, runTriggersList, runTriggersPrune, runTriggersStatus };
|
package/dist/commands/index.js
CHANGED
|
@@ -12,11 +12,11 @@ import { runMigrateExport } from "./migrate-export.js";
|
|
|
12
12
|
import { runPricingDiff, runPricingLookup, runPricingMissing, runPricingRefresh, runPricingStatus } from "./pricing.js";
|
|
13
13
|
import { runSecretsDelete, runSecretsGet, runSecretsList, runSecretsRef, runSecretsRotate, runSecretsSet } from "./secrets.js";
|
|
14
14
|
import { runSkillsAudit, runSkillsInspect, runSkillsInstall, runSkillsMigrateFrontmatter } from "./skills.js";
|
|
15
|
-
import { runStorageCleanupBackups, runStorageEncrypt, runStorageRekey, runStorageStatus } from "./storage.js";
|
|
15
|
+
import { runStorageBackup, runStorageCleanupBackups, runStorageEncrypt, runStorageRekey, runStorageStatus } from "./storage.js";
|
|
16
16
|
import { runTelemetryDisable, runTelemetryEnable, runTelemetryInspect, runTelemetryStatus } from "./telemetry.js";
|
|
17
17
|
import { parseDuration, runTokenCreate, runTokenList, runTokenRekey, runTokenRevoke, runTokenRotate, runTokenVerify } from "./token.js";
|
|
18
18
|
import { runToolsLint } from "./tools-lint.js";
|
|
19
19
|
import { runTracesPrune, runTracesStatus } from "./traces.js";
|
|
20
20
|
import { runTriggersDisable, runTriggersFire, runTriggersList, runTriggersPrune, runTriggersStatus } from "./triggers.js";
|
|
21
21
|
|
|
22
|
-
export { CONSOLIDATOR_INVALID_TIER_EXIT, expectedFileModes, parseDuration, runAuditExport, runAuditPrune, runAuditVerify, runAuthList, runAuthLogin, runAuthRefresh, runAuthRevoke, runAuthStatus, runConsolidatorSetTier, runConsolidatorStatus, runConsolidatorStop, runDoctor, runGuardExplain, runGuardStatus, runInit, runMemoryActivity, runMemoryInspect, runMemoryMigrate, runMemoryReview, runMemoryStatus, runMemoryWhy, runMigrate, runMigrateConfig, runMigrateExport, runPricingDiff, runPricingLookup, runPricingMissing, runPricingRefresh, runPricingStatus, runSecretsDelete, runSecretsGet, runSecretsList, runSecretsRef, runSecretsRotate, runSecretsSet, runSkillsAudit, runSkillsInspect, runSkillsInstall, runSkillsMigrateFrontmatter, runStart, runStorageCleanupBackups, runStorageEncrypt, runStorageRekey, runStorageStatus, runTelemetryDisable, runTelemetryEnable, runTelemetryInspect, runTelemetryStatus, runTokenCreate, runTokenList, runTokenRekey, runTokenRevoke, runTokenRotate, runTokenVerify, runToolsLint, runTracesPrune, runTracesStatus, runTriggersDisable, runTriggersFire, runTriggersList, runTriggersPrune, runTriggersStatus };
|
|
22
|
+
export { CONSOLIDATOR_INVALID_TIER_EXIT, expectedFileModes, parseDuration, runAuditExport, runAuditPrune, runAuditVerify, runAuthList, runAuthLogin, runAuthRefresh, runAuthRevoke, runAuthStatus, runConsolidatorSetTier, runConsolidatorStatus, runConsolidatorStop, runDoctor, runGuardExplain, runGuardStatus, runInit, runMemoryActivity, runMemoryInspect, runMemoryMigrate, runMemoryReview, runMemoryStatus, runMemoryWhy, runMigrate, runMigrateConfig, runMigrateExport, runPricingDiff, runPricingLookup, runPricingMissing, runPricingRefresh, runPricingStatus, runSecretsDelete, runSecretsGet, runSecretsList, runSecretsRef, runSecretsRotate, runSecretsSet, runSkillsAudit, runSkillsInspect, runSkillsInstall, runSkillsMigrateFrontmatter, runStart, runStorageBackup, runStorageCleanupBackups, runStorageEncrypt, runStorageRekey, runStorageStatus, runTelemetryDisable, runTelemetryEnable, runTelemetryInspect, runTelemetryStatus, runTokenCreate, runTokenList, runTokenRekey, runTokenRevoke, runTokenRotate, runTokenVerify, runToolsLint, runTracesPrune, runTracesStatus, runTriggersDisable, runTriggersFire, runTriggersList, runTriggersPrune, runTriggersStatus };
|
package/dist/commands/init.d.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
//#region src/commands/init.d.ts
|
|
2
2
|
/**
|
|
3
|
-
* `graphorin init`
|
|
3
|
+
* `graphorin init` - interactive bootstrap.
|
|
4
4
|
*
|
|
5
5
|
* Writes a fresh `graphorin.config.ts` to the current working
|
|
6
6
|
* directory (or `--out`), prompts the operator for the cloud-upload
|
|
7
7
|
* sensitivity tier + storage encryption opt-in, and prints exactly
|
|
8
8
|
* one bootstrap admin token (held by the operator from this point
|
|
9
|
-
* onward
|
|
9
|
+
* onward - the tool never persists nor logs the raw value).
|
|
10
10
|
*
|
|
11
11
|
* `--non-interactive` accepts every choice through flags or env vars
|
|
12
12
|
* so the command works equally well in CI / image-build pipelines.
|
package/dist/commands/init.js
CHANGED
|
@@ -6,13 +6,13 @@ import { generatePepper, generateRawToken } from "@graphorin/security";
|
|
|
6
6
|
|
|
7
7
|
//#region src/commands/init.ts
|
|
8
8
|
/**
|
|
9
|
-
* `graphorin init`
|
|
9
|
+
* `graphorin init` - interactive bootstrap.
|
|
10
10
|
*
|
|
11
11
|
* Writes a fresh `graphorin.config.ts` to the current working
|
|
12
12
|
* directory (or `--out`), prompts the operator for the cloud-upload
|
|
13
13
|
* sensitivity tier + storage encryption opt-in, and prints exactly
|
|
14
14
|
* one bootstrap admin token (held by the operator from this point
|
|
15
|
-
* onward
|
|
15
|
+
* onward - the tool never persists nor logs the raw value).
|
|
16
16
|
*
|
|
17
17
|
* `--non-interactive` accepts every choice through flags or env vars
|
|
18
18
|
* so the command works equally well in CI / image-build pipelines.
|
|
@@ -116,7 +116,7 @@ export default defineConfig({
|
|
|
116
116
|
observability: {
|
|
117
117
|
logger: 'json',
|
|
118
118
|
},
|
|
119
|
-
// IP-5: no top-level 'defaults' block
|
|
119
|
+
// IP-5: no top-level 'defaults' block - the strict server-config
|
|
120
120
|
// parser rejects unknown keys, so the old render made a fresh
|
|
121
121
|
// 'graphorin init' fail on the very next migrate/start. The tier you
|
|
122
122
|
// chose at init is recorded below; enforce it via the memory
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"init.js","names":[],"sources":["../../src/commands/init.ts"],"sourcesContent":["/**\n * `graphorin init`
|
|
1
|
+
{"version":3,"file":"init.js","names":[],"sources":["../../src/commands/init.ts"],"sourcesContent":["/**\n * `graphorin init` - interactive bootstrap.\n *\n * Writes a fresh `graphorin.config.ts` to the current working\n * directory (or `--out`), prompts the operator for the cloud-upload\n * sensitivity tier + storage encryption opt-in, and prints exactly\n * one bootstrap admin token (held by the operator from this point\n * onward - the tool never persists nor logs the raw value).\n *\n * `--non-interactive` accepts every choice through flags or env vars\n * so the command works equally well in CI / image-build pipelines.\n *\n * @packageDocumentation\n */\n\nimport { mkdir, stat, writeFile } from 'node:fs/promises';\nimport { dirname, isAbsolute, resolve } from 'node:path';\nimport process from 'node:process';\n\nimport { generatePepper, generateRawToken } from '@graphorin/security';\n\nimport { confirm, select } from '../internal/prompts.js';\n\n/**\n * @stable\n */\nexport interface InitCommandOptions {\n readonly out?: string;\n readonly nonInteractive?: boolean;\n readonly cloudConsent?: 'public-only' | 'public-and-internal' | 'all-with-warnings';\n readonly encrypted?: boolean;\n readonly cwd?: string;\n /** Test seam: skip writing files (only print the report). */\n readonly dryRun?: boolean;\n /** Test seam: redirect stdout/err. */\n readonly print?: (line: string) => void;\n}\n\n/**\n * @stable\n */\nexport interface InitCommandResult {\n readonly configPath: string;\n readonly bootstrapToken: string;\n readonly serverPepperHex: string;\n readonly cloudConsent: 'public-only' | 'public-and-internal' | 'all-with-warnings';\n readonly storageEncrypted: boolean;\n}\n\n/**\n * @stable\n */\nexport async function runInit(options: InitCommandOptions = {}): Promise<InitCommandResult> {\n const cwd = options.cwd ?? process.cwd();\n const target = resolveOutPath(cwd, options.out);\n const print = options.print ?? ((line: string) => process.stderr.write(`${line}\\n`));\n\n const cloudConsent = await resolveCloudConsent(options);\n const storageEncrypted = await resolveEncryption(options);\n\n const pepper = generatePepper();\n const pepperHex = await pepper.useBuffer((buf) => buf.toString('hex'));\n\n const bootstrap = generateRawToken({ env: 'live' });\n\n if (options.dryRun !== true) {\n if (await fileExists(target)) {\n throw new Error(\n `[graphorin/cli] refusing to overwrite existing '${target}'. Move it aside or pass --out <path>.`,\n );\n }\n await mkdir(dirname(target), { recursive: true });\n await writeFile(target, renderConfig({ cloudConsent, storageEncrypted }), { mode: 0o600 });\n }\n\n print(`[graphorin/cli] wrote ${target}`);\n print(`[graphorin/cli] bootstrap admin token (shown ONCE):`);\n print(` ${bootstrap.raw}`);\n print(`[graphorin/cli] server pepper hex (store in your keyring as 'graphorin_server_pepper'):`);\n print(` ${pepperHex}`);\n print('');\n print('Next steps:');\n print(\n ` 1. Persist the pepper: graphorin secrets set graphorin_server_pepper --value ${pepperHex}`,\n );\n print(` 2. Persist the bootstrap token securely; do NOT commit it.`);\n print(` 3. Run \\`graphorin migrate --config ${target}\\` to apply storage migrations.`);\n print(` 4. Run \\`graphorin start --config ${target}\\` to launch the server.`);\n\n return Object.freeze({\n configPath: target,\n bootstrapToken: bootstrap.raw,\n serverPepperHex: pepperHex,\n cloudConsent,\n storageEncrypted,\n });\n}\n\nfunction resolveOutPath(cwd: string, out: string | undefined): string {\n if (out === undefined || out.length === 0) return resolve(cwd, 'graphorin.config.ts');\n return isAbsolute(out) ? out : resolve(cwd, out);\n}\n\nasync function fileExists(path: string): Promise<boolean> {\n try {\n await stat(path);\n return true;\n } catch {\n return false;\n }\n}\n\nasync function resolveCloudConsent(\n options: InitCommandOptions,\n): Promise<'public-only' | 'public-and-internal' | 'all-with-warnings'> {\n if (options.cloudConsent !== undefined) return options.cloudConsent;\n const envValue = process.env.GRAPHORIN_CLOUD_CONSENT;\n if (\n envValue === 'public-only' ||\n envValue === 'public-and-internal' ||\n envValue === 'all-with-warnings'\n ) {\n return envValue;\n }\n if (options.nonInteractive === true) return 'public-and-internal';\n const choice = await select(\n 'Cloud-upload consent for context sent to providers:',\n ['public-only', 'public-and-internal', 'all-with-warnings'],\n 'public-and-internal',\n );\n return choice as 'public-only' | 'public-and-internal' | 'all-with-warnings';\n}\n\nasync function resolveEncryption(options: InitCommandOptions): Promise<boolean> {\n if (options.encrypted !== undefined) return options.encrypted;\n if (process.env.GRAPHORIN_STORAGE_ENCRYPTED === '1') return true;\n if (process.env.GRAPHORIN_STORAGE_ENCRYPTED === '0') return false;\n if (options.nonInteractive === true) return false;\n return await confirm('Enable storage encryption (defense-in-depth on top of FDE)?', false);\n}\n\nfunction renderConfig(input: {\n readonly cloudConsent: 'public-only' | 'public-and-internal' | 'all-with-warnings';\n readonly storageEncrypted: boolean;\n}): string {\n return `import { defineConfig } from '@graphorin/server';\n\nexport default defineConfig({\n server: {\n host: '127.0.0.1',\n port: 8080,\n },\n auth: {\n kind: 'token',\n pepperRef: 'keyring:graphorin_server_pepper',\n },\n storage: {\n path: './.graphorin/data.db',\n mode: 'server',\n encryption: ${\n input.storageEncrypted\n ? `{\n enabled: true,\n passphraseRef: 'keyring:graphorin_db_passphrase',\n }`\n : `{ enabled: false }`\n },\n },\n audit: {\n enabled: ${input.storageEncrypted ? 'true' : 'false'},\n },\n secrets: {\n source: 'auto',\n strict: false,\n },\n observability: {\n logger: 'json',\n },\n // IP-5: no top-level 'defaults' block - the strict server-config\n // parser rejects unknown keys, so the old render made a fresh\n // 'graphorin init' fail on the very next migrate/start. The tier you\n // chose at init is recorded below; enforce it via the memory\n // package (createMemory contextEngine privacy options):\n // cloudUploadConsent: ${JSON.stringify(input.cloudConsent)}\n});\n`;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAoDA,eAAsB,QAAQ,UAA8B,EAAE,EAA8B;CAE1F,MAAM,SAAS,eADH,QAAQ,OAAO,QAAQ,KAAK,EACL,QAAQ,IAAI;CAC/C,MAAM,QAAQ,QAAQ,WAAW,SAAiB,QAAQ,OAAO,MAAM,GAAG,KAAK,IAAI;CAEnF,MAAM,eAAe,MAAM,oBAAoB,QAAQ;CACvD,MAAM,mBAAmB,MAAM,kBAAkB,QAAQ;CAGzD,MAAM,YAAY,MADH,gBAAgB,CACA,WAAW,QAAQ,IAAI,SAAS,MAAM,CAAC;CAEtE,MAAM,YAAY,iBAAiB,EAAE,KAAK,QAAQ,CAAC;AAEnD,KAAI,QAAQ,WAAW,MAAM;AAC3B,MAAI,MAAM,WAAW,OAAO,CAC1B,OAAM,IAAI,MACR,mDAAmD,OAAO,wCAC3D;AAEH,QAAM,MAAM,QAAQ,OAAO,EAAE,EAAE,WAAW,MAAM,CAAC;AACjD,QAAM,UAAU,QAAQ,aAAa;GAAE;GAAc;GAAkB,CAAC,EAAE,EAAE,MAAM,KAAO,CAAC;;AAG5F,OAAM,yBAAyB,SAAS;AACxC,OAAM,sDAAsD;AAC5D,OAAM,KAAK,UAAU,MAAM;AAC3B,OAAM,0FAA0F;AAChG,OAAM,KAAK,YAAY;AACvB,OAAM,GAAG;AACT,OAAM,cAAc;AACpB,OACE,kFAAkF,YACnF;AACD,OAAM,+DAA+D;AACrE,OAAM,yCAAyC,OAAO,iCAAiC;AACvF,OAAM,uCAAuC,OAAO,0BAA0B;AAE9E,QAAO,OAAO,OAAO;EACnB,YAAY;EACZ,gBAAgB,UAAU;EAC1B,iBAAiB;EACjB;EACA;EACD,CAAC;;AAGJ,SAAS,eAAe,KAAa,KAAiC;AACpE,KAAI,QAAQ,UAAa,IAAI,WAAW,EAAG,QAAO,QAAQ,KAAK,sBAAsB;AACrF,QAAO,WAAW,IAAI,GAAG,MAAM,QAAQ,KAAK,IAAI;;AAGlD,eAAe,WAAW,MAAgC;AACxD,KAAI;AACF,QAAM,KAAK,KAAK;AAChB,SAAO;SACD;AACN,SAAO;;;AAIX,eAAe,oBACb,SACsE;AACtE,KAAI,QAAQ,iBAAiB,OAAW,QAAO,QAAQ;CACvD,MAAM,WAAW,QAAQ,IAAI;AAC7B,KACE,aAAa,iBACb,aAAa,yBACb,aAAa,oBAEb,QAAO;AAET,KAAI,QAAQ,mBAAmB,KAAM,QAAO;AAM5C,QALe,MAAM,OACnB,uDACA;EAAC;EAAe;EAAuB;EAAoB,EAC3D,sBACD;;AAIH,eAAe,kBAAkB,SAA+C;AAC9E,KAAI,QAAQ,cAAc,OAAW,QAAO,QAAQ;AACpD,KAAI,QAAQ,IAAI,gCAAgC,IAAK,QAAO;AAC5D,KAAI,QAAQ,IAAI,gCAAgC,IAAK,QAAO;AAC5D,KAAI,QAAQ,mBAAmB,KAAM,QAAO;AAC5C,QAAO,MAAM,QAAQ,+DAA+D,MAAM;;AAG5F,SAAS,aAAa,OAGX;AACT,QAAO;;;;;;;;;;;;;;kBAeH,MAAM,mBACF;;;SAIA,qBACL;;;eAGU,MAAM,mBAAmB,SAAS,QAAQ;;;;;;;;;;;;;;2BAc9B,KAAK,UAAU,MAAM,aAAa,CAAC"}
|
|
@@ -44,7 +44,7 @@ interface MemoryMigrateOptions extends MemoryCommonOptions {
|
|
|
44
44
|
readonly embeddersModule?: string;
|
|
45
45
|
}
|
|
46
46
|
/**
|
|
47
|
-
* `graphorin memory migrate`
|
|
47
|
+
* `graphorin memory migrate` - embedder swap. The migration logic lives
|
|
48
48
|
* in `@graphorin/memory`'s `migrateEmbedder(...)`; the CLI prints a
|
|
49
49
|
* pointer when the operator did not supply the embedder factory module
|
|
50
50
|
* (the framework cannot guess the operator's embedder configuration).
|
|
@@ -117,7 +117,7 @@ interface MemoryInspectOptions extends MemoryCommonOptions {
|
|
|
117
117
|
readonly factId: string;
|
|
118
118
|
}
|
|
119
119
|
/**
|
|
120
|
-
* `graphorin memory inspect <factId>`
|
|
120
|
+
* `graphorin memory inspect <factId>` - surface everything the store
|
|
121
121
|
* knows about one fact: its retrieval-trust status + provenance, the
|
|
122
122
|
* full bi-temporal supersede chain it belongs to, the audit-log events
|
|
123
123
|
* recorded against it, the conflict decisions that referenced it, and
|
|
@@ -158,7 +158,7 @@ interface MemoryActivityOptions extends MemoryCommonOptions {
|
|
|
158
158
|
readonly limit?: number;
|
|
159
159
|
}
|
|
160
160
|
/**
|
|
161
|
-
* `graphorin memory activity`
|
|
161
|
+
* `graphorin memory activity` - a store-wide view of what the
|
|
162
162
|
* consolidator and reflection passes have been doing: how many facts /
|
|
163
163
|
* episodes / insights currently sit in quarantine, the most recent
|
|
164
164
|
* audit-log events (supersede / validate / quarantine / archive), and
|
|
@@ -191,7 +191,7 @@ interface MemoryWhyOptions extends MemoryCommonOptions {
|
|
|
191
191
|
readonly limit?: number;
|
|
192
192
|
}
|
|
193
193
|
/**
|
|
194
|
-
* `graphorin memory why`
|
|
194
|
+
* `graphorin memory why` - explain why facts were recalled, by decoding the
|
|
195
195
|
* `memory.search.semantic.explain` attribute off the persisted recall spans.
|
|
196
196
|
* Pure read-only inspection; requires the SQLite span exporter to have recorded
|
|
197
197
|
* spans (RP-17). Empty when nothing was recorded.
|
|
@@ -229,7 +229,7 @@ interface MemoryReviewOptions extends MemoryCommonOptions {
|
|
|
229
229
|
readonly force?: boolean;
|
|
230
230
|
}
|
|
231
231
|
/**
|
|
232
|
-
* `graphorin memory review`
|
|
232
|
+
* `graphorin memory review` - list the facts / episodes / insights / induced
|
|
233
233
|
* procedures the consolidator left in quarantine (read-only), or promote a
|
|
234
234
|
* reviewed item out of quarantine with `--promote <id>`. The promote path runs
|
|
235
235
|
* through the tier `validate(...)`, so an injection-flagged memory is refused
|