@microwiseai/snapshot 0.6.0 → 0.7.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.
Files changed (37) hide show
  1. package/dist/commands/current.d.ts +5 -1
  2. package/dist/commands/current.d.ts.map +1 -1
  3. package/dist/commands/current.js +89 -2
  4. package/dist/commands/current.js.map +1 -1
  5. package/dist/commands/doctor.d.ts.map +1 -1
  6. package/dist/commands/doctor.js +46 -0
  7. package/dist/commands/doctor.js.map +1 -1
  8. package/dist/commands/install.d.ts +41 -0
  9. package/dist/commands/install.d.ts.map +1 -1
  10. package/dist/commands/install.js +328 -10
  11. package/dist/commands/install.js.map +1 -1
  12. package/dist/commands/publish.d.ts.map +1 -1
  13. package/dist/commands/publish.js +44 -24
  14. package/dist/commands/publish.js.map +1 -1
  15. package/dist/index.js +5 -2
  16. package/dist/index.js.map +1 -1
  17. package/dist/lib/core-floor.d.ts +49 -0
  18. package/dist/lib/core-floor.d.ts.map +1 -0
  19. package/dist/lib/core-floor.js +78 -0
  20. package/dist/lib/core-floor.js.map +1 -0
  21. package/dist/lib/install-verify.d.ts +101 -0
  22. package/dist/lib/install-verify.d.ts.map +1 -0
  23. package/dist/lib/install-verify.js +143 -0
  24. package/dist/lib/install-verify.js.map +1 -0
  25. package/dist/lib/ledger.d.ts +212 -0
  26. package/dist/lib/ledger.d.ts.map +1 -0
  27. package/dist/lib/ledger.js +266 -0
  28. package/dist/lib/ledger.js.map +1 -0
  29. package/dist/lib/pin-override.d.ts +53 -0
  30. package/dist/lib/pin-override.d.ts.map +1 -0
  31. package/dist/lib/pin-override.js +77 -0
  32. package/dist/lib/pin-override.js.map +1 -0
  33. package/dist/lib/version-validator.d.ts +9 -0
  34. package/dist/lib/version-validator.d.ts.map +1 -1
  35. package/dist/lib/version-validator.js +69 -0
  36. package/dist/lib/version-validator.js.map +1 -1
  37. package/package.json +9 -2
@@ -0,0 +1,101 @@
1
+ /**
2
+ * S1 — Post-install verification & convergence (SSOT enforcement).
3
+ *
4
+ * The manifest (snapshot.json) is the single source of truth: it pins every npm
5
+ * package to one exact version. But `npm install -g` is non-deterministic — a
6
+ * later batch/step can silently re-dedupe an earlier explicit pin to a different
7
+ * version on disk (see architecture/beta-pipeline-robustness-2026-08-30 §1 E2/E4).
8
+ *
9
+ * This module answers ONE question fail-loud: after install finishes, does the
10
+ * disk actually match the manifest pins? If not, we report `expected X, got Y`
11
+ * and the caller exits non-zero (no more "green on broken").
12
+ */
13
+ export interface VersionMismatch {
14
+ /** Package name, e.g. "@ist/sesh". */
15
+ name: string;
16
+ /** The exact version the manifest pinned. */
17
+ expected: string;
18
+ /** The actual version found on disk, or null if the package is missing. */
19
+ got: string | null;
20
+ }
21
+ export interface VerificationResult {
22
+ /** True iff every package in the map matches its pin on disk. */
23
+ ok: boolean;
24
+ /** Every package whose disk version != pin (or is missing). */
25
+ mismatches: VersionMismatch[];
26
+ /** Count of packages whose disk version == pin. */
27
+ verifiedCount: number;
28
+ /** Total packages checked. */
29
+ total: number;
30
+ }
31
+ /** A function that returns the on-disk version of a global npm package, or null. */
32
+ export type VersionReader = (name: string) => string | null;
33
+ /** Test seam: reset the cached global npm root (used by unit tests). */
34
+ export declare function _resetGlobalRootCache(): void;
35
+ /**
36
+ * Read the ACTUAL on-disk version of a globally-installed npm package by reading
37
+ * its installed package.json directly from the global root. This reflects what
38
+ * `require()`/the runtime will actually load — unlike `npm ls` which reports the
39
+ * resolution tree. One `npm root -g` call is amortized across all packages
40
+ * (cached), so this is O(1) process spawns regardless of package count.
41
+ *
42
+ * Returns the version string, or null if the package is not installed on disk.
43
+ */
44
+ export declare function readInstalledNpmVersion(name: string): string | null;
45
+ /**
46
+ * Verify that every package in `installMap` (name → exact pin) is present on
47
+ * disk at exactly that version.
48
+ *
49
+ * Pure & side-effect free (aside from the injected reader): pass a custom
50
+ * `readVersion` for unit tests; defaults to reading the real global npm tree.
51
+ */
52
+ export declare function verifyInstallMap(installMap: Record<string, string>, opts?: {
53
+ readVersion?: VersionReader;
54
+ }): VerificationResult;
55
+ /**
56
+ * S1 — convergence. Dependency-injected & pure (no console, no direct npm) so it
57
+ * is fully unit-testable and the integrator (install.ts) owns logging/exit.
58
+ *
59
+ * Given the manifest npm install-map and any FB pin overrides:
60
+ * 1. compute the effective map (pins with overrides applied),
61
+ * 2. verify it against the disk,
62
+ * 3. while unverified and within `maxRounds`, call `install(name, expected)`
63
+ * for each mismatched package (exact, isolated re-pin) then re-verify.
64
+ *
65
+ * Returns the FINAL VerificationResult (ok=true iff the disk converged). The
66
+ * integrator decides how to react (exit non-zero, log, fold into the ledger).
67
+ *
68
+ * @param installMap manifest npm pins (name → exact version)
69
+ * @param overrides FB pin overrides (name → version); {} for none
70
+ * @param deps.install re-pin one package to an exact version (side-effecting)
71
+ * @param deps.readVersion disk reader (defaults to readInstalledNpmVersion)
72
+ * @param deps.maxRounds max convergence rounds (default 3)
73
+ */
74
+ export declare function runConverge(installMap: Record<string, string>, overrides: Record<string, string>, deps: {
75
+ install: (name: string, version: string) => void;
76
+ readVersion?: VersionReader;
77
+ maxRounds?: number;
78
+ }): VerificationResult;
79
+ /**
80
+ * Minimal read-only view of the install ledger. The ledger SCHEMA is owned by
81
+ * lib/ledger.ts (S3); this is a structural subset used only to read `expected`
82
+ * pins for offline verification (e.g. `snapshot doctor`) without importing the
83
+ * ledger module — so verify stays dependency-light and merge-clean.
84
+ */
85
+ export interface LedgerLike {
86
+ packages?: Array<{
87
+ name?: unknown;
88
+ expected?: unknown;
89
+ }>;
90
+ }
91
+ /**
92
+ * Extract a `name → expected pin` map from a parsed ledger, defensively (unknown
93
+ * shapes / missing fields are skipped rather than throwing). Returns {} when the
94
+ * ledger has no usable expected pins.
95
+ */
96
+ export declare function expectedMapFromLedger(ledger: LedgerLike | null | undefined): Record<string, string>;
97
+ /**
98
+ * Format mismatches into a fail-loud, multi-line `expected X, got Y` report.
99
+ */
100
+ export declare function formatVerificationFailure(mismatches: VersionMismatch[]): string;
101
+ //# sourceMappingURL=install-verify.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"install-verify.d.ts","sourceRoot":"","sources":["../../src/lib/install-verify.ts"],"names":[],"mappings":"AAKA;;;;;;;;;;;GAWG;AAEH,MAAM,WAAW,eAAe;IAC9B,sCAAsC;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,6CAA6C;IAC7C,QAAQ,EAAE,MAAM,CAAC;IACjB,2EAA2E;IAC3E,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;CACpB;AAED,MAAM,WAAW,kBAAkB;IACjC,iEAAiE;IACjE,EAAE,EAAE,OAAO,CAAC;IACZ,+DAA+D;IAC/D,UAAU,EAAE,eAAe,EAAE,CAAC;IAC9B,mDAAmD;IACnD,aAAa,EAAE,MAAM,CAAC;IACtB,8BAA8B;IAC9B,KAAK,EAAE,MAAM,CAAC;CACf;AAED,oFAAoF;AACpF,MAAM,MAAM,aAAa,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC;AAmB5D,wEAAwE;AACxE,wBAAgB,qBAAqB,IAAI,IAAI,CAE5C;AAED;;;;;;;;GAQG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAYnE;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC9B,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAClC,IAAI,GAAE;IAAE,WAAW,CAAC,EAAE,aAAa,CAAA;CAAO,GACzC,kBAAkB,CAsBpB;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,WAAW,CACzB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAClC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EACjC,IAAI,EAAE;IACJ,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACjD,WAAW,CAAC,EAAE,aAAa,CAAC;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,GACA,kBAAkB,CAapB;AAED;;;;;GAKG;AACH,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,CAAC,EAAE,OAAO,CAAC;QAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;CAC1D;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAUnG;AAED;;GAEG;AACH,wBAAgB,yBAAyB,CAAC,UAAU,EAAE,eAAe,EAAE,GAAG,MAAM,CAU/E"}
@@ -0,0 +1,143 @@
1
+ import { execSync } from 'child_process';
2
+ import { existsSync, readFileSync } from 'fs';
3
+ import { join } from 'path';
4
+ import { applyPinOverrides } from './pin-override.js';
5
+ let cachedGlobalRoot;
6
+ /**
7
+ * Resolve the global npm `node_modules` root once (`npm root -g`) and cache it.
8
+ * Returns null if npm is unavailable.
9
+ */
10
+ function getGlobalNpmRoot() {
11
+ if (cachedGlobalRoot !== undefined)
12
+ return cachedGlobalRoot;
13
+ try {
14
+ const root = execSync('npm root -g', { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'] }).trim();
15
+ cachedGlobalRoot = root || null;
16
+ }
17
+ catch {
18
+ cachedGlobalRoot = null;
19
+ }
20
+ return cachedGlobalRoot;
21
+ }
22
+ /** Test seam: reset the cached global npm root (used by unit tests). */
23
+ export function _resetGlobalRootCache() {
24
+ cachedGlobalRoot = undefined;
25
+ }
26
+ /**
27
+ * Read the ACTUAL on-disk version of a globally-installed npm package by reading
28
+ * its installed package.json directly from the global root. This reflects what
29
+ * `require()`/the runtime will actually load — unlike `npm ls` which reports the
30
+ * resolution tree. One `npm root -g` call is amortized across all packages
31
+ * (cached), so this is O(1) process spawns regardless of package count.
32
+ *
33
+ * Returns the version string, or null if the package is not installed on disk.
34
+ */
35
+ export function readInstalledNpmVersion(name) {
36
+ const root = getGlobalNpmRoot();
37
+ if (!root)
38
+ return null;
39
+ // Scoped names ("@ist/sesh") join correctly into "<root>/@ist/sesh".
40
+ const pkgJsonPath = join(root, name, 'package.json');
41
+ if (!existsSync(pkgJsonPath))
42
+ return null;
43
+ try {
44
+ const pkg = JSON.parse(readFileSync(pkgJsonPath, 'utf-8'));
45
+ return typeof pkg.version === 'string' ? pkg.version : null;
46
+ }
47
+ catch {
48
+ return null;
49
+ }
50
+ }
51
+ /**
52
+ * Verify that every package in `installMap` (name → exact pin) is present on
53
+ * disk at exactly that version.
54
+ *
55
+ * Pure & side-effect free (aside from the injected reader): pass a custom
56
+ * `readVersion` for unit tests; defaults to reading the real global npm tree.
57
+ */
58
+ export function verifyInstallMap(installMap, opts = {}) {
59
+ const readVersion = opts.readVersion ?? readInstalledNpmVersion;
60
+ const mismatches = [];
61
+ let verifiedCount = 0;
62
+ const names = Object.keys(installMap).sort((a, b) => a.localeCompare(b));
63
+ for (const name of names) {
64
+ const expected = installMap[name];
65
+ const got = readVersion(name);
66
+ if (got === expected) {
67
+ verifiedCount++;
68
+ }
69
+ else {
70
+ mismatches.push({ name, expected, got });
71
+ }
72
+ }
73
+ return {
74
+ ok: mismatches.length === 0,
75
+ mismatches,
76
+ verifiedCount,
77
+ total: names.length,
78
+ };
79
+ }
80
+ /**
81
+ * S1 — convergence. Dependency-injected & pure (no console, no direct npm) so it
82
+ * is fully unit-testable and the integrator (install.ts) owns logging/exit.
83
+ *
84
+ * Given the manifest npm install-map and any FB pin overrides:
85
+ * 1. compute the effective map (pins with overrides applied),
86
+ * 2. verify it against the disk,
87
+ * 3. while unverified and within `maxRounds`, call `install(name, expected)`
88
+ * for each mismatched package (exact, isolated re-pin) then re-verify.
89
+ *
90
+ * Returns the FINAL VerificationResult (ok=true iff the disk converged). The
91
+ * integrator decides how to react (exit non-zero, log, fold into the ledger).
92
+ *
93
+ * @param installMap manifest npm pins (name → exact version)
94
+ * @param overrides FB pin overrides (name → version); {} for none
95
+ * @param deps.install re-pin one package to an exact version (side-effecting)
96
+ * @param deps.readVersion disk reader (defaults to readInstalledNpmVersion)
97
+ * @param deps.maxRounds max convergence rounds (default 3)
98
+ */
99
+ export function runConverge(installMap, overrides, deps) {
100
+ const effectiveMap = applyPinOverrides(installMap, overrides);
101
+ const readVersion = deps.readVersion ?? readInstalledNpmVersion;
102
+ const maxRounds = deps.maxRounds ?? 3;
103
+ let verification = verifyInstallMap(effectiveMap, { readVersion });
104
+ for (let round = 1; round <= maxRounds && !verification.ok; round++) {
105
+ for (const m of verification.mismatches) {
106
+ deps.install(m.name, m.expected);
107
+ }
108
+ verification = verifyInstallMap(effectiveMap, { readVersion });
109
+ }
110
+ return verification;
111
+ }
112
+ /**
113
+ * Extract a `name → expected pin` map from a parsed ledger, defensively (unknown
114
+ * shapes / missing fields are skipped rather than throwing). Returns {} when the
115
+ * ledger has no usable expected pins.
116
+ */
117
+ export function expectedMapFromLedger(ledger) {
118
+ const map = {};
119
+ const pkgs = ledger?.packages;
120
+ if (!Array.isArray(pkgs))
121
+ return map;
122
+ for (const p of pkgs) {
123
+ if (p && typeof p.name === 'string' && typeof p.expected === 'string') {
124
+ map[p.name] = p.expected;
125
+ }
126
+ }
127
+ return map;
128
+ }
129
+ /**
130
+ * Format mismatches into a fail-loud, multi-line `expected X, got Y` report.
131
+ */
132
+ export function formatVerificationFailure(mismatches) {
133
+ const lines = [
134
+ `Install verification failed: ${mismatches.length} package(s) do not match the manifest pin.`,
135
+ `The disk state diverged from the snapshot (SSOT). Re-run with --converge to auto-repair.`,
136
+ ];
137
+ for (const m of mismatches) {
138
+ const got = m.got === null ? '(not installed)' : m.got;
139
+ lines.push(` ✗ ${m.name}: expected ${m.expected}, got ${got}`);
140
+ }
141
+ return lines.join('\n');
142
+ }
143
+ //# sourceMappingURL=install-verify.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"install-verify.js","sourceRoot":"","sources":["../../src/lib/install-verify.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,212 @@
1
+ /**
2
+ * ============================================================
3
+ * Install Ledger (S3 — beta-pipeline-robustness-2026-08-30 §4)
4
+ * ============================================================
5
+ *
6
+ * Records, at the END of an install, the provenance of every npm package that
7
+ * was installed: the manifest pin we asked for (`expected`), the version that
8
+ * actually landed on disk (`resolved`), the tarball URL it came from, the
9
+ * installer/step that produced it, and the ordered list of install steps.
10
+ *
11
+ * Goal (AC3.1): after any install, "이 버전이 어디서 왔나" ("where did this
12
+ * version come from?") is answerable from the ledger ALONE — no reverse
13
+ * engineering of the global node_modules.
14
+ *
15
+ * SCHEMA OWNERSHIP (manager freeze 2026-08-30): this file is the SSOT for the
16
+ * ledger schema; ledger.json is owned by S3 (this worker). Field names follow
17
+ * the 기획서. S1 (verify) and FB (pin override) live in verify-owned libs and
18
+ * do NOT own this schema. Dependency direction is one-way (verify → ledger):
19
+ * ledger.ts imports NOTHING from install-verify.ts / pin-override.ts.
20
+ *
21
+ * Writing the ledger MUST NOT break an install — every entry point here is
22
+ * best-effort and swallows its own errors.
23
+ */
24
+ /** Current ledger schema version. Bump on breaking field changes. */
25
+ export declare const LEDGER_SCHEMA_VERSION: 1;
26
+ /** One recorded install step, in execution order. */
27
+ export interface LedgerStep {
28
+ /** 1-based execution order across the whole install. */
29
+ order: number;
30
+ /**
31
+ * Which phase of the install produced this step. Stable identifiers:
32
+ * 'pre-install-batch' — the single up-front `npm install -g` batch
33
+ * 'pre-install-fallback' — per-package retry after the pre-install batch failed
34
+ * 'layer-batch' — a per-snapshot-layer npm batch (installSnapshot)
35
+ * 'layer-fallback' — per-package retry after a layer batch failed
36
+ * 'layer-parallel' — non-npm (glpkg/generic) parallel install
37
+ * 'skit' — skit-adapter install
38
+ */
39
+ phase: string;
40
+ /** Installer name: 'npm-proxy' | 'glpkg' | 'skit' | ... */
41
+ installer: string;
42
+ /** The exact command executed (or a synthetic label for non-shell installers). */
43
+ command: string;
44
+ /** name@version specs this step targeted. */
45
+ specs: string[];
46
+ /** Outcome of the step. */
47
+ result: 'ok' | 'fallback' | 'failed';
48
+ }
49
+ /** Provenance for a single npm package, resolved after install. */
50
+ export interface LedgerPackage {
51
+ /** Package name, e.g. "@ist/sesh". */
52
+ name: string;
53
+ /** Manifest pin we requested (SSOT intent). null if not declared as npm here. */
54
+ expected: string | null;
55
+ /** Version actually on disk after install (from `npm ls -g`). null if absent. */
56
+ resolved: string | null;
57
+ /** Tarball URL the resolved version was fetched from (from `npm ls -g`). */
58
+ tarballUrl: string | null;
59
+ /** Human/structured summary answering "where did this version come from?". */
60
+ resolutionSource: string;
61
+ /** Installer that handled this package. */
62
+ installer: string;
63
+ }
64
+ /**
65
+ * S1 (verify) verdict, folded into the ledger so verification and provenance
66
+ * live in one artifact. WRITTEN BY THE VERIFY WIRING (install.ts integrator),
67
+ * shaped to verifyInstallMap()'s return contract. Optional: absent until the
68
+ * verify pass runs.
69
+ */
70
+ export interface LedgerVerification {
71
+ ok: boolean;
72
+ verifiedCount: number;
73
+ total: number;
74
+ mismatches: Array<{
75
+ name: string;
76
+ expected: string | null;
77
+ got: string | null;
78
+ }>;
79
+ }
80
+ /**
81
+ * AC1.4 — core-floor verdict folded into the ledger. Records every below-floor
82
+ * copy of the singleton package (@ist/adapters-core) found in the nested tree.
83
+ * Written by the install.ts integrator (coreFloorAssert lives in core-floor.ts).
84
+ */
85
+ export interface LedgerCoreFloor {
86
+ ok: boolean;
87
+ pkg: string;
88
+ minVersion: string;
89
+ offenders: Array<{
90
+ path: string;
91
+ version: string;
92
+ }>;
93
+ }
94
+ /** The install ledger written to ~/.config/ist-snapshot/ledger.json. */
95
+ export interface InstallLedger {
96
+ schemaVersion: typeof LEDGER_SCHEMA_VERSION;
97
+ snapshot: {
98
+ name: string;
99
+ version: string;
100
+ };
101
+ /** ISO timestamp the ledger was recorded. */
102
+ recordedAt: string;
103
+ /** snapshot CLI version that produced this ledger. */
104
+ cliVersion: string;
105
+ /** Registry the packages were fetched through (registry-proxy URL). */
106
+ registry: string;
107
+ packages: LedgerPackage[];
108
+ installSteps: LedgerStep[];
109
+ /** FB pin overrides applied this install (name → version). Empty unless FB used. */
110
+ overrides: Record<string, string>;
111
+ /** S1 verification verdict, if the verify pass ran. */
112
+ verification?: LedgerVerification;
113
+ /** AC1.4 core-floor verdict (nested singleton floor), if the assert ran. */
114
+ coreFloor?: LedgerCoreFloor;
115
+ /**
116
+ * DEEP (NOT --depth=0) `npm ls -g <names> --json --all` output captured
117
+ * post-install, stored verbatim. Scoped to the install's npm packages +
118
+ * @ist/adapters-core so NESTED copies are visible — this both (a) recovers
119
+ * "who required version X?" and (b) feeds coreFloorAssert (AC1.4). null if the
120
+ * query failed.
121
+ */
122
+ dependencyTreeRaw: unknown | null;
123
+ }
124
+ /**
125
+ * Accumulates install steps in execution order. One instance per install;
126
+ * threaded through installSnapshot via InstallSnapshotOptions.recorder.
127
+ */
128
+ export declare class StepRecorder {
129
+ private steps;
130
+ record(step: Omit<LedgerStep, 'order'>): void;
131
+ getSteps(): LedgerStep[];
132
+ }
133
+ /** Path to the ledger file (sits next to current.json). */
134
+ export declare function getLedgerPath(): string;
135
+ export interface ResolvedQuery {
136
+ /** name → { version, tarballUrl } as read from the global tree. */
137
+ map: Map<string, {
138
+ version: string | null;
139
+ tarballUrl: string | null;
140
+ }>;
141
+ /** The raw parsed `npm ls` JSON (for dependencyTreeRaw), or null on failure. */
142
+ raw: unknown | null;
143
+ }
144
+ /**
145
+ * Query the globally-installed version + tarball URL for the given package
146
+ * names via a single `npm ls -g ... --json` call, and return the raw tree too.
147
+ * Best-effort: on total failure returns an all-null map and raw=null.
148
+ *
149
+ * NOTE: this is provenance extraction (version + tarball + raw tree), NOT the
150
+ * verify worker's single-name readInstalledNpmVersion(). No duplication of
151
+ * that helper — S3 never imports install-verify.
152
+ */
153
+ export declare function queryResolvedVersions(names: string[]): ResolvedQuery;
154
+ /**
155
+ * Derive the canonical npm tarball URL for a resolved package.
156
+ *
157
+ * npm 7+ does NOT persist the fetched tarball URL for global installs
158
+ * (`npm ls -g --json` omits `resolved`, package.json is stripped of `_resolved`,
159
+ * and there is no global lockfile). So when the authoritative URL is missing we
160
+ * reconstruct the standard registry path — the same one npm would fetch from the
161
+ * scope's configured registry: `<registry>/<name>/-/<unscoped>-<version>.tgz`.
162
+ *
163
+ * Returns null when the version is unknown (nothing to point at). Best-effort:
164
+ * the registry is the one configured for the scope during install (registry-proxy).
165
+ */
166
+ export declare function deriveTarballUrl(registry: string, name: string, version: string | null): string | null;
167
+ /**
168
+ * Capture a DEEP dependency tree (`npm ls -g <names> --json --all`) for the
169
+ * ledger's `dependencyTreeRaw` and for coreFloorAssert (AC1.4).
170
+ *
171
+ * ★ Deliberately NOT `--depth=0`: nested copies must be visible (an old
172
+ * @ist/adapters-core buried under another package is exactly what re-fragments
173
+ * the registry). To avoid the cost of dumping the ENTIRE global tree, we scope
174
+ * the query to the install's npm packages + the singleton package — with filter
175
+ * args, `npm ls` prunes to the paths that reach those names (full depth), which
176
+ * is bounded yet still surfaces every nested copy of the targets.
177
+ *
178
+ * Best-effort: returns null on failure (ledger writing never fails an install).
179
+ */
180
+ export declare function queryDependencyTree(names: string[]): unknown | null;
181
+ export interface BuildLedgerInput {
182
+ snapshotName: string;
183
+ snapshotVersion: string;
184
+ cliVersion: string;
185
+ registry: string;
186
+ /** Manifest pins for npm packages: name -> version. The "expected" set. */
187
+ expected: Record<string, string>;
188
+ /** Installer used for the npm packages (typically 'npm-proxy'). */
189
+ installer: string;
190
+ steps: LedgerStep[];
191
+ /** FB pin overrides (name -> version), if any. */
192
+ overrides?: Record<string, string>;
193
+ /**
194
+ * DEEP dependency tree for `dependencyTreeRaw` (from queryDependencyTree).
195
+ * When provided it takes precedence over the resolver's shallow tree — the
196
+ * integrator captures a nested-visible tree (AC1.4). null/undefined → fall
197
+ * back to the resolver's tree.
198
+ */
199
+ dependencyTreeRaw?: unknown | null;
200
+ }
201
+ /**
202
+ * Assemble the ledger: query disk for resolved versions/tarballs, then join
203
+ * against the expected pins and install steps to explain each package's origin.
204
+ * `now` is injected (callers stamp the timestamp) to keep this pure/testable.
205
+ * `resolver` is injectable for tests; defaults to the real `npm ls -g` query.
206
+ */
207
+ export declare function buildLedger(input: BuildLedgerInput, now: string, resolver?: (names: string[]) => ResolvedQuery): InstallLedger;
208
+ /** Write the ledger to disk. Best-effort — never throws into the caller. */
209
+ export declare function writeLedger(ledger: InstallLedger): boolean;
210
+ /** Read the ledger from disk, or null if absent/unreadable. */
211
+ export declare function readLedger(): InstallLedger | null;
212
+ //# sourceMappingURL=ledger.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ledger.d.ts","sourceRoot":"","sources":["../../src/lib/ledger.ts"],"names":[],"mappings":"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"}