@mmnto/totem 1.53.3 → 1.53.4
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/dist/config-schema.d.ts +28 -0
- package/dist/config-schema.d.ts.map +1 -1
- package/dist/config-schema.js +11 -0
- package/dist/config-schema.js.map +1 -1
- package/dist/errors.d.ts +1 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/parity-detect.d.ts +182 -0
- package/dist/parity-detect.d.ts.map +1 -0
- package/dist/parity-detect.js +478 -0
- package/dist/parity-detect.js.map +1 -0
- package/dist/parity-detect.test.d.ts +18 -0
- package/dist/parity-detect.test.d.ts.map +1 -0
- package/dist/parity-detect.test.js +274 -0
- package/dist/parity-detect.test.js.map +1 -0
- package/dist/parity-manifest.d.ts +177 -0
- package/dist/parity-manifest.d.ts.map +1 -0
- package/dist/parity-manifest.js +233 -0
- package/dist/parity-manifest.js.map +1 -0
- package/dist/parity-manifest.test.d.ts +12 -0
- package/dist/parity-manifest.test.d.ts.map +1 -0
- package/dist/parity-manifest.test.js +228 -0
- package/dist/parity-manifest.test.js.map +1 -0
- package/dist/sys/git.d.ts.map +1 -1
- package/dist/sys/git.js +12 -3
- package/dist/sys/git.js.map +1 -1
- package/dist/sys/git.test.js +71 -1
- package/dist/sys/git.test.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,478 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Version-pinned parity drift detector (PR-1, mmnto-ai/totem#2069).
|
|
3
|
+
*
|
|
4
|
+
* First detection slice on top of the merged manifest-parser skeleton (#2070).
|
|
5
|
+
* Senses ONE tractability class — `version-pinned` — and only its DEPS subset
|
|
6
|
+
* (the four `@mmnto/*` cohort-floor contracts: `mmnto-cli-version`,
|
|
7
|
+
* `mmnto-totem-version`, `mmnto-mcp-version`,
|
|
8
|
+
* `mmnto-pack-rust-architecture-version`). The verdict is **pin currency only**
|
|
9
|
+
* (Tenet 20 claim-class bound): does the consumer's `@mmnto/*` pin resolve to
|
|
10
|
+
* the current published cohort floor? It NEVER asserts semantic / file-content
|
|
11
|
+
* drift — that would over-claim the `version-pinned` class.
|
|
12
|
+
*
|
|
13
|
+
* Layering: core must NOT import cli's `DiagnosticResult` (wrong dependency
|
|
14
|
+
* direction). This module returns a core-local `ParityContractVerdict`; the CLI
|
|
15
|
+
* (`doctor-parity.ts`) maps it to `DiagnosticResult` and owns the
|
|
16
|
+
* `--strict`/`blocking` fail-promotion. The detector itself returns ONLY
|
|
17
|
+
* `pass`/`warn`/`skip` — never `fail` — so the gate edge stays a CLI concern.
|
|
18
|
+
*
|
|
19
|
+
* Design invariants (mirroring `parity-manifest.ts` + `strategy-resolver.ts`):
|
|
20
|
+
* - **Honest-absent (Tenet 14):** absence is never an error. Not-a-consumer,
|
|
21
|
+
* floor-unresolvable, or a doctrine pin this slice doesn't handle → `skip`
|
|
22
|
+
* (the manifest's `-` "cohort permits absence"). An *applicable* consumer
|
|
23
|
+
* missing an expected pin is also `skip` while the manifest is scaffold, but
|
|
24
|
+
* kept DISTINCT (expected-but-absent → a `warn` once the consumers lists are
|
|
25
|
+
* verified) so the `consumers` field still catches the missing case. Never a
|
|
26
|
+
* fabricated verdict.
|
|
27
|
+
* - **NEVER networks:** the cohort floor is derived LOCALLY — self-in-tree
|
|
28
|
+
* (the totem monorepo at the current git root) or a `../totem` sibling
|
|
29
|
+
* checkout. Neither reachable → honest-absent `skip` with a reason.
|
|
30
|
+
* - **Side-effect-free / no caching:** every call reads from scratch. Each
|
|
31
|
+
* filesystem / git seam is injectable so tests drive synthetic fixtures.
|
|
32
|
+
* - **Never throws:** every read failure degrades to a `skip`/`warn` verdict;
|
|
33
|
+
* the sensor must never crash the doctor pipeline.
|
|
34
|
+
*/
|
|
35
|
+
import * as fs from 'node:fs';
|
|
36
|
+
import * as path from 'node:path';
|
|
37
|
+
import semver from 'semver';
|
|
38
|
+
import { safeExec } from './sys/exec.js';
|
|
39
|
+
import { resolveGitRoot } from './sys/git.js';
|
|
40
|
+
// ─── Constants ──────────────────────────────────────────
|
|
41
|
+
/**
|
|
42
|
+
* Fixed scope for every PR-1 deps contract. The id slug (`mmnto-<x>-version`)
|
|
43
|
+
* is the literal middle of the package name `@mmnto/<x>`; the scope is constant.
|
|
44
|
+
*/
|
|
45
|
+
const MMNTO_SCOPE = '@mmnto/';
|
|
46
|
+
/** The `mmnto-…-version` id convention PR-1 maps to a deps package name. */
|
|
47
|
+
const DEPS_CONTRACT_ID = /^mmnto-(.+)-version$/;
|
|
48
|
+
/** Sibling totem-monorepo dirname probed for a cohort floor (mirrors `resolveStrategyRoot` layer 3). */
|
|
49
|
+
const SIBLING_TOTEM_DIRNAME = 'totem';
|
|
50
|
+
/** Bounded git-command timeout for the origin-remote probe (matches `sys/git.ts`). */
|
|
51
|
+
const GIT_REMOTE_TIMEOUT_MS = 15_000;
|
|
52
|
+
/**
|
|
53
|
+
* The dep fields, in resolution order, that a consumer can declare an `@mmnto/*`
|
|
54
|
+
* pin under. `dependencies` first so a normal runtime dep wins display, but the
|
|
55
|
+
* detector treats a declaration in ANY of these as "the pin is declared here."
|
|
56
|
+
*/
|
|
57
|
+
const DEP_FIELDS = ['dependencies', 'devDependencies', 'optionalDependencies'];
|
|
58
|
+
/**
|
|
59
|
+
* Derive the current repo's cohort id (e.g. `totem-status`) used to evaluate a
|
|
60
|
+
* contract's `consumers` applicability. Precedence:
|
|
61
|
+
* 1. git `origin` remote — `…mmnto-ai/<name>(.git)?` → `<name>`.
|
|
62
|
+
* 2. `package.json` `name` basename (scope stripped) — `@mmnto/totem-status`
|
|
63
|
+
* → `totem-status`.
|
|
64
|
+
* 3. git-root directory basename.
|
|
65
|
+
*
|
|
66
|
+
* Returns `undefined` only when nothing resolves. NEVER throws and NEVER
|
|
67
|
+
* networks beyond the local `git remote get-url` read (which is swallowed on
|
|
68
|
+
* failure — git being unavailable is a routine fall-through, not an error).
|
|
69
|
+
*/
|
|
70
|
+
export function deriveCohortRepoId(cwd, options = {}) {
|
|
71
|
+
// ── 1. git origin remote ──
|
|
72
|
+
const remoteUrl = readOriginRemote(cwd, options);
|
|
73
|
+
const fromRemote = repoIdFromRemoteUrl(remoteUrl);
|
|
74
|
+
if (fromRemote !== undefined)
|
|
75
|
+
return fromRemote;
|
|
76
|
+
// Anchor the fallbacks at the git root (not the deep cwd) so the dir-basename
|
|
77
|
+
// fallback names the repo, not a nested subdir. Lazy: only probed when the
|
|
78
|
+
// remote miss forces a fallback.
|
|
79
|
+
const root = resolveRootForFallback(cwd, options.gitRoot);
|
|
80
|
+
// ── 2. package.json name basename (scope stripped) ──
|
|
81
|
+
const fromPkg = repoIdFromPackageName(root);
|
|
82
|
+
if (fromPkg !== undefined)
|
|
83
|
+
return fromPkg;
|
|
84
|
+
// ── 3. git-root dir basename ──
|
|
85
|
+
const base = path.basename(root);
|
|
86
|
+
return base.length > 0 ? base : undefined;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Read the git `origin` remote URL, honoring the injected seams. A throwing
|
|
90
|
+
* reader (or a real git failure) is swallowed — the caller falls through to the
|
|
91
|
+
* package.json / dir-basename fallbacks.
|
|
92
|
+
*/
|
|
93
|
+
function readOriginRemote(cwd, options) {
|
|
94
|
+
if (options.readRemote !== undefined) {
|
|
95
|
+
try {
|
|
96
|
+
return options.readRemote(cwd);
|
|
97
|
+
// totem-context: an injected remote-reader that throws is treated as "no remote resolvable" (fall through to the package.json / dir-basename fallbacks); rethrowing would break the never-throws contract for a routine git miss.
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
return undefined;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
if ('remoteUrl' in options)
|
|
104
|
+
return options.remoteUrl;
|
|
105
|
+
try {
|
|
106
|
+
return safeExec('git', ['remote', 'get-url', 'origin'], {
|
|
107
|
+
cwd,
|
|
108
|
+
timeout: GIT_REMOTE_TIMEOUT_MS,
|
|
109
|
+
});
|
|
110
|
+
// totem-context: a missing remote / non-git dir / absent git binary is a routine fall-through to the package.json + dir-basename fallbacks, not a sensor failure — the doctor runs against repos without an origin remote by design.
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
return undefined;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
/** Extract `<name>` from a `…mmnto-ai/<name>(.git)?` remote URL, else undefined. */
|
|
117
|
+
function repoIdFromRemoteUrl(remoteUrl) {
|
|
118
|
+
if (typeof remoteUrl !== 'string' || remoteUrl.trim().length === 0)
|
|
119
|
+
return undefined;
|
|
120
|
+
// Match the org segment in both ssh (`git@…:mmnto-ai/x.git`) and https
|
|
121
|
+
// (`https://…/mmnto-ai/x.git`) forms; tolerate a trailing `.git` + slashes.
|
|
122
|
+
const match = remoteUrl.trim().match(/mmnto-ai\/([^/\s]+?)(?:\.git)?\/?$/);
|
|
123
|
+
return match?.[1];
|
|
124
|
+
}
|
|
125
|
+
/** Read `package.json` `name`, strip a leading `@scope/`, return the basename. */
|
|
126
|
+
function repoIdFromPackageName(root) {
|
|
127
|
+
const name = readPackageName(path.join(root, 'package.json'));
|
|
128
|
+
if (name === undefined)
|
|
129
|
+
return undefined;
|
|
130
|
+
// `@mmnto/totem-status` → `totem-status`; `plain-name` → `plain-name`.
|
|
131
|
+
const slash = name.lastIndexOf('/');
|
|
132
|
+
const base = slash === -1 ? name : name.slice(slash + 1);
|
|
133
|
+
return base.length > 0 ? base : undefined;
|
|
134
|
+
}
|
|
135
|
+
/** Resolve the anchor for fallbacks: injected gitRoot, else `resolveGitRoot(cwd)`, else cwd. */
|
|
136
|
+
function resolveRootForFallback(cwd, injected) {
|
|
137
|
+
if (injected !== undefined)
|
|
138
|
+
return injected ?? cwd;
|
|
139
|
+
try {
|
|
140
|
+
return resolveGitRoot(cwd) ?? cwd;
|
|
141
|
+
// totem-context: resolveGitRoot throws on permission errors / corrupted index; falling back to cwd keeps the dir-basename fallback working rather than crashing the sensor on a git hiccup.
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
return cwd;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
// ─── packageNameForContract ─────────────────────────────
|
|
148
|
+
/**
|
|
149
|
+
* Resolve the `@mmnto/*` (or vendor) package name a deps/vendor contract pins.
|
|
150
|
+
* Precedence:
|
|
151
|
+
* 1. **explicit `package:` field** (mmnto-ai/totem-strategy#517) — the
|
|
152
|
+
* machine-parseable name, derive-not-guess. Preferred when present.
|
|
153
|
+
* 2. **canonical-source path locator** — when `canonicalSource` carries a
|
|
154
|
+
* `:path/to/package.json` segment (e.g. `mmnto-cli-version`'s
|
|
155
|
+
* `mmnto-ai/totem:packages/cli/package.json#version`), read the `name`
|
|
156
|
+
* from that package.json under `floorRoot`. Authoritative — no id guess.
|
|
157
|
+
* 3. **id convention** — `mmnto-<x>-version` → `@mmnto/<x>` (fallback until all
|
|
158
|
+
* contracts carry `package:`).
|
|
159
|
+
*
|
|
160
|
+
* Returns `undefined` for contracts with no `package:`, no path locator, and an
|
|
161
|
+
* id that doesn't match the convention (e.g. `governance-doctrine`, `gate-config`)
|
|
162
|
+
* so ONLY the contracts this slice handles resolve a name; the CLI keeps the rest
|
|
163
|
+
* as `skip` stubs.
|
|
164
|
+
*
|
|
165
|
+
* @param floorRoot Optional root the canonical-source path locator anchors at
|
|
166
|
+
* (the resolved cohort-floor repo). Omit when only `package:` /
|
|
167
|
+
* the id convention is wanted.
|
|
168
|
+
*/
|
|
169
|
+
export function packageNameForContract(contract, floorRoot) {
|
|
170
|
+
// ── 1. explicit `package:` field (derive-not-guess; strategy#517) ──
|
|
171
|
+
if (typeof contract.package === 'string' && contract.package.trim().length > 0) {
|
|
172
|
+
return contract.package.trim();
|
|
173
|
+
}
|
|
174
|
+
// ── 2. canonical-source path locator → read `name` from that package.json ──
|
|
175
|
+
if (floorRoot !== undefined) {
|
|
176
|
+
const fromLocator = packageNameFromCanonicalSource(contract.canonicalSource, floorRoot);
|
|
177
|
+
if (fromLocator !== undefined)
|
|
178
|
+
return fromLocator;
|
|
179
|
+
}
|
|
180
|
+
// ── 3. id convention (mmnto-<x>-version → @mmnto/<x>) ──
|
|
181
|
+
const match = contract.id.match(DEPS_CONTRACT_ID);
|
|
182
|
+
if (match?.[1] === undefined)
|
|
183
|
+
return undefined;
|
|
184
|
+
// Concatenate (not template-interpolate) so the `@mmnto/` scope's own trailing
|
|
185
|
+
// `/` reads as the delimiter between scope and package slug.
|
|
186
|
+
return MMNTO_SCOPE + match[1];
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Read the `name` from the package.json referenced by a `repo:path#fragment`
|
|
190
|
+
* canonical-source locator, anchored at `floorRoot`. Returns undefined when the
|
|
191
|
+
* locator has no `:package.json` path segment or the file is unreadable.
|
|
192
|
+
*/
|
|
193
|
+
function packageNameFromCanonicalSource(canonicalSource, floorRoot) {
|
|
194
|
+
if (typeof canonicalSource !== 'string')
|
|
195
|
+
return undefined;
|
|
196
|
+
// Shape: `<repo>:<path>#<fragment>` — we only want the `<path>` between the
|
|
197
|
+
// first `:` and an optional `#`. A bare `mmnto-ai/totem` (no `:`) has no path.
|
|
198
|
+
const colon = canonicalSource.indexOf(':');
|
|
199
|
+
if (colon === -1)
|
|
200
|
+
return undefined;
|
|
201
|
+
const afterColon = canonicalSource.slice(colon + 1);
|
|
202
|
+
const hash = afterColon.indexOf('#');
|
|
203
|
+
const relPath = (hash === -1 ? afterColon : afterColon.slice(0, hash)).trim();
|
|
204
|
+
if (!relPath.endsWith('package.json'))
|
|
205
|
+
return undefined;
|
|
206
|
+
return readPackageName(path.join(floorRoot, relPath));
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Resolve the "current published version" cohort floor for `packageName`,
|
|
210
|
+
* derived LOCALLY (NEVER networks), in precedence order:
|
|
211
|
+
* (a) **self-in-tree** — the current git root IS the canonical-source repo
|
|
212
|
+
* (the totem monorepo): glob `<gitRoot>/packages/*/package.json`, find
|
|
213
|
+
* the one whose `name === packageName`, read its `version`.
|
|
214
|
+
* (b) **sibling** — `<gitRoot>/../totem` exists as a directory: glob its
|
|
215
|
+
* `packages/*/package.json` the same way.
|
|
216
|
+
* (c) **honest-absent** — neither reachable → `{ resolved: false, reason }`.
|
|
217
|
+
*
|
|
218
|
+
* NEVER fabricates a floor and NEVER fetches. Anchors at `gitRoot`, not cwd
|
|
219
|
+
* (mirroring `strategy-resolver.ts`). Read failures within the glob are
|
|
220
|
+
* swallowed per-file so one corrupt package.json can't crash the resolver.
|
|
221
|
+
*
|
|
222
|
+
* The floor is keyed structurally on `packageName` (the matching
|
|
223
|
+
* `packages/*/package.json` `name`), NOT on the consumer's cohort id — a
|
|
224
|
+
* misderived repoId can't mask a genuine in-tree floor.
|
|
225
|
+
*/
|
|
226
|
+
export function resolveCohortFloor(packageName, gitRoot) {
|
|
227
|
+
// ── (a) self-in-tree ──
|
|
228
|
+
// The canonical-source repo for every deps contract is the totem monorepo. If
|
|
229
|
+
// the current repo IS that monorepo, its own packages/*/package.json is the
|
|
230
|
+
// floor. We detect "is the monorepo" structurally (a packages/*/package.json
|
|
231
|
+
// whose name === packageName), not just by id, so a misderived repoId can't
|
|
232
|
+
// mask a genuine in-tree floor.
|
|
233
|
+
const selfVersion = readVersionFromPackagesGlob(gitRoot, packageName);
|
|
234
|
+
if (selfVersion !== undefined) {
|
|
235
|
+
return { resolved: true, version: selfVersion, source: 'self-in-tree' };
|
|
236
|
+
}
|
|
237
|
+
// ── (b) sibling ../totem ──
|
|
238
|
+
const sibling = path.normalize(path.join(gitRoot, '..', SIBLING_TOTEM_DIRNAME));
|
|
239
|
+
if (isDirectory(sibling)) {
|
|
240
|
+
const siblingVersion = readVersionFromPackagesGlob(sibling, packageName);
|
|
241
|
+
if (siblingVersion !== undefined) {
|
|
242
|
+
return { resolved: true, version: siblingVersion, source: 'sibling' };
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
// ── (c) honest-absent ──
|
|
246
|
+
return {
|
|
247
|
+
resolved: false,
|
|
248
|
+
reason: `cohort floor for ${packageName} not locally determinable; clone mmnto-ai/totem as a sibling (../${SIBLING_TOTEM_DIRNAME}) or run from the totem monorepo`,
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Glob `<root>/packages/*/package.json`, return the `version` of the one whose
|
|
253
|
+
* `name === packageName`. Returns undefined when `<root>/packages` is absent,
|
|
254
|
+
* no package matches, or every read fails. Per-file read failures are swallowed
|
|
255
|
+
* so one corrupt manifest doesn't sink the whole probe.
|
|
256
|
+
*/
|
|
257
|
+
function readVersionFromPackagesGlob(root, packageName) {
|
|
258
|
+
const packagesDir = path.join(root, 'packages');
|
|
259
|
+
let names;
|
|
260
|
+
try {
|
|
261
|
+
names = fs.readdirSync(packagesDir);
|
|
262
|
+
// totem-context: an absent or unreadable packages/ dir is the "not the monorepo here" signal — return undefined so the resolver falls through to the sibling / honest-absent layer rather than throwing.
|
|
263
|
+
}
|
|
264
|
+
catch {
|
|
265
|
+
return undefined;
|
|
266
|
+
}
|
|
267
|
+
for (const name of names) {
|
|
268
|
+
const entryPath = path.join(packagesDir, name);
|
|
269
|
+
// Use the statSync-based `isDirectory` (follows symlinks) rather than a
|
|
270
|
+
// `Dirent.isDirectory()` filter — pnpm / workspace setups symlink package
|
|
271
|
+
// dirs, which the lstat-like Dirent check reports as non-directories and
|
|
272
|
+
// would skip (GCA review #2071).
|
|
273
|
+
if (!isDirectory(entryPath))
|
|
274
|
+
continue;
|
|
275
|
+
const parsed = readPackageJson(path.join(entryPath, 'package.json'));
|
|
276
|
+
if (parsed?.name === packageName && typeof parsed.version === 'string') {
|
|
277
|
+
return parsed.version;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
return undefined;
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Detect drift for ONE `version-pinned` deps contract. Returns a
|
|
284
|
+
* `ParityContractVerdict`:
|
|
285
|
+
* - **pass** — the consumer's resolved-installed `@mmnto/*` version is ≥ the
|
|
286
|
+
* cohort floor (current).
|
|
287
|
+
* - **warn** — installed < floor (stale pin). Sensor-not-gate default: the
|
|
288
|
+
* detector returns `warn` even for a `blocking` contract; the CLI promotes
|
|
289
|
+
* to `fail` only under `--strict`.
|
|
290
|
+
* - **skip** — not-a-consumer / pin not declared / floor unresolvable / not a
|
|
291
|
+
* deps contract this slice handles / unparseable range (honest-absent).
|
|
292
|
+
*
|
|
293
|
+
* NEVER emits `fail` (CLI-edge concern). NEVER throws (read failures degrade to
|
|
294
|
+
* `skip`). NEVER networks (floor is local-only). NEVER asserts content drift
|
|
295
|
+
* (claim-class bound to pin currency).
|
|
296
|
+
*/
|
|
297
|
+
export function detectVersionPinnedContract(contract, ctx) {
|
|
298
|
+
// ── Applicability: consumers list ──
|
|
299
|
+
if (contract.consumers !== undefined) {
|
|
300
|
+
if (ctx.repoId === undefined) {
|
|
301
|
+
// Can't decide applicability without a cohort id — surface it (honest-
|
|
302
|
+
// absent) rather than silently treating the contract as applicable, which
|
|
303
|
+
// would make the consumers scope a no-op for this repo (Tenet 4 / Greptile
|
|
304
|
+
// review #2071).
|
|
305
|
+
return {
|
|
306
|
+
status: 'skip',
|
|
307
|
+
message: `cannot determine applicability — repo id unresolvable; contract is scoped to consumers [${contract.consumers.join(', ')}]`,
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
if (!contract.consumers.includes(ctx.repoId)) {
|
|
311
|
+
return {
|
|
312
|
+
status: 'skip',
|
|
313
|
+
message: `cohort permits absence here (${ctx.repoId} not in consumers)`,
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
// ── Resolve the package name (`package:` → locator → id convention) ──
|
|
318
|
+
// Prefer a name pre-resolved by the CLI (avoids re-reading a locator's
|
|
319
|
+
// package.json — Greptile review #2071); fall back to resolving here for
|
|
320
|
+
// standalone callers (tests). gitRoot is the floorRoot for a path locator.
|
|
321
|
+
const packageName = ctx.packageName ?? packageNameForContract(contract, ctx.gitRoot);
|
|
322
|
+
if (packageName === undefined) {
|
|
323
|
+
return {
|
|
324
|
+
status: 'skip',
|
|
325
|
+
message: `not a deps version-pinned contract this slice handles (${contract.id})`,
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
// ── Read the consumer package.json + its declared range for this pkg ──
|
|
329
|
+
const readPkg = ctx.readPackageJson ?? readPackageJson;
|
|
330
|
+
const consumerPkg = readPkg(path.join(ctx.cwd, 'package.json'));
|
|
331
|
+
const declaredRange = consumerPkg ? findDeclaredRange(consumerPkg, packageName) : undefined;
|
|
332
|
+
if (declaredRange === undefined) {
|
|
333
|
+
// Applicable-but-missing: we already passed the `consumers` gate above, so
|
|
334
|
+
// this repo IS an applicable consumer (in `consumers`, or `consumers` absent
|
|
335
|
+
// = applies to all) — an expected pin that's absent is drift, NOT
|
|
336
|
+
// cohort-permitted absence. Held as
|
|
337
|
+
// a skip-with-note while the manifest is scaffold (consumers lists are
|
|
338
|
+
// best-effort); flips to `warn` once verified. Kept DISTINCT from the
|
|
339
|
+
// not-a-consumer skip so the `consumers` field still does its job (strategy
|
|
340
|
+
// ack 2026-06-03T0156Z, design-call 3).
|
|
341
|
+
return {
|
|
342
|
+
status: 'skip',
|
|
343
|
+
message: `${packageName} expected but not declared — applicable consumer (scaffold: skip; becomes a drift warn once consumers are verified)`,
|
|
344
|
+
remediation: `Declare ${packageName} in this repo's package.json, or drop this repo from the contract's consumers list if it genuinely does not apply here.`,
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
// Guard an unparseable declared range BEFORE resolving anything else — a
|
|
348
|
+
// garbage range can't yield a currency verdict, so honest-absent skip.
|
|
349
|
+
if (semver.validRange(declaredRange) === null) {
|
|
350
|
+
return {
|
|
351
|
+
status: 'skip',
|
|
352
|
+
message: `${packageName} declared range "${declaredRange}" is not a valid semver range`,
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
// ── Resolve the consumer's installed version (fallback: minVersion(range)) ──
|
|
356
|
+
const installed = resolveInstalledVersion(ctx.cwd, packageName, declaredRange);
|
|
357
|
+
if (installed === undefined) {
|
|
358
|
+
return {
|
|
359
|
+
status: 'skip',
|
|
360
|
+
message: `${packageName} pinned (${declaredRange}) but no resolvable installed version`,
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
// ── Resolve the cohort floor (local-only; honest-absent on miss) ──
|
|
364
|
+
const floor = resolveCohortFloor(packageName, ctx.gitRoot);
|
|
365
|
+
if (!floor.resolved) {
|
|
366
|
+
return { status: 'skip', message: floor.reason };
|
|
367
|
+
}
|
|
368
|
+
// Guard an invalid floor version (corrupt in-tree manifest) → honest-absent.
|
|
369
|
+
if (semver.valid(floor.version) === null) {
|
|
370
|
+
return {
|
|
371
|
+
status: 'skip',
|
|
372
|
+
message: `cohort floor for ${packageName} ("${floor.version}") is not a valid version`,
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
// ── Verdict: pin currency only (semver compare) ──
|
|
376
|
+
// installed ≥ floor → current; installed < floor → stale. The claim is pin
|
|
377
|
+
// currency, NOT semantic content — a version-pinned contract may never assert
|
|
378
|
+
// file/content equality.
|
|
379
|
+
if (semver.gte(installed, floor.version)) {
|
|
380
|
+
return {
|
|
381
|
+
status: 'pass',
|
|
382
|
+
message: `${packageName} pin current — installed ${installed} ≥ cohort floor ${floor.version} (${floor.source})`,
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
return {
|
|
386
|
+
status: 'warn',
|
|
387
|
+
message: `${packageName} pin stale — declared ${declaredRange}, installed ${installed} < cohort floor ${floor.version} (${floor.source})`,
|
|
388
|
+
remediation: `Bump the ${packageName} dependency to >= ${floor.version} and reinstall, then re-run totem doctor --parity.`,
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
/**
|
|
392
|
+
* Resolve the consumer's installed `@mmnto/*` version. Walks UP the directory
|
|
393
|
+
* tree from `cwd` reading `<dir>/node_modules/<pkg>/package.json#version` at each
|
|
394
|
+
* ancestor — monorepo / pnpm / npm-workspace installs hoist deps to a parent or
|
|
395
|
+
* root `node_modules` rather than the sub-package's, so a cwd-only read would
|
|
396
|
+
* miss them (GCA review #2071). Mirrors Node's own upward node_modules
|
|
397
|
+
* resolution. On no hit, falls back to `semver.minVersion(declaredRange)` (the
|
|
398
|
+
* floor the caret range implies). Returns undefined only when neither resolves
|
|
399
|
+
* to a valid version.
|
|
400
|
+
*/
|
|
401
|
+
function resolveInstalledVersion(cwd, packageName, declaredRange) {
|
|
402
|
+
const segments = packageName.split('/');
|
|
403
|
+
let dir = path.resolve(cwd);
|
|
404
|
+
for (;;) {
|
|
405
|
+
const installedPkg = readPackageJson(path.join(dir, 'node_modules', ...segments, 'package.json'));
|
|
406
|
+
if (installedPkg?.version !== undefined && semver.valid(installedPkg.version) !== null) {
|
|
407
|
+
return installedPkg.version;
|
|
408
|
+
}
|
|
409
|
+
const parent = path.dirname(dir);
|
|
410
|
+
if (parent === dir)
|
|
411
|
+
break; // reached the filesystem root
|
|
412
|
+
dir = parent;
|
|
413
|
+
}
|
|
414
|
+
// Fallback: the minimum version the declared range admits. `minVersion`
|
|
415
|
+
// returns a SemVer or null; coerce to the bare version string.
|
|
416
|
+
const min = semver.minVersion(declaredRange);
|
|
417
|
+
return min?.version;
|
|
418
|
+
}
|
|
419
|
+
/** Find the declared range for `packageName` across the three dep fields, in order. */
|
|
420
|
+
function findDeclaredRange(pkg, packageName) {
|
|
421
|
+
for (const field of DEP_FIELDS) {
|
|
422
|
+
// `pkg` is loose untrusted JSON; optional-chain the field read so a missing
|
|
423
|
+
// or non-object dep section yields undefined instead of throwing, then
|
|
424
|
+
// type-check the value itself. Avoids a null-guard idiom that two compiled
|
|
425
|
+
// rules disagree on.
|
|
426
|
+
const range = pkg[field]?.[packageName];
|
|
427
|
+
if (typeof range === 'string' && range.trim().length > 0)
|
|
428
|
+
return range;
|
|
429
|
+
}
|
|
430
|
+
return undefined;
|
|
431
|
+
}
|
|
432
|
+
// ─── Shared filesystem helpers ──────────────────────────
|
|
433
|
+
/**
|
|
434
|
+
* Read + JSON-parse a package.json into the loose {@link PackageJsonShape}.
|
|
435
|
+
* Returns undefined on any read / parse failure or a non-object payload —
|
|
436
|
+
* mirroring `readEngineRange`'s ENOENT-tolerant JSON read so a missing or
|
|
437
|
+
* corrupt manifest is honest-absent, never a throw.
|
|
438
|
+
*/
|
|
439
|
+
function readPackageJson(absPath) {
|
|
440
|
+
let raw;
|
|
441
|
+
try {
|
|
442
|
+
raw = fs.readFileSync(absPath, 'utf-8');
|
|
443
|
+
// totem-context: a missing / unreadable package.json is the honest-absent signal callers degrade to a skip on; rethrowing would force every caller to wrap a routine absence in try/catch.
|
|
444
|
+
}
|
|
445
|
+
catch {
|
|
446
|
+
return undefined;
|
|
447
|
+
}
|
|
448
|
+
let parsed;
|
|
449
|
+
try {
|
|
450
|
+
parsed = JSON.parse(raw);
|
|
451
|
+
// totem-context: a corrupt package.json degrades to undefined (honest-absent skip), not a crash — the sensor must never sink the doctor pipeline on a malformed consumer manifest.
|
|
452
|
+
}
|
|
453
|
+
catch {
|
|
454
|
+
return undefined;
|
|
455
|
+
}
|
|
456
|
+
if (typeof parsed !== 'object' || parsed === null)
|
|
457
|
+
return undefined;
|
|
458
|
+
return parsed;
|
|
459
|
+
}
|
|
460
|
+
/** Read just the `name` from a package.json, or undefined on any failure. */
|
|
461
|
+
function readPackageName(absPath) {
|
|
462
|
+
const parsed = readPackageJson(absPath);
|
|
463
|
+
return typeof parsed?.name === 'string' ? parsed.name : undefined;
|
|
464
|
+
}
|
|
465
|
+
/**
|
|
466
|
+
* `fs.statSync` raises on missing paths and on EACCES/ENOTDIR; treat any stat
|
|
467
|
+
* failure as "not a directory" (mirrors `strategy-resolver.ts`).
|
|
468
|
+
*/
|
|
469
|
+
function isDirectory(p) {
|
|
470
|
+
try {
|
|
471
|
+
return fs.statSync(p).isDirectory();
|
|
472
|
+
// totem-context: stat failures (ENOENT, EACCES, ENOTDIR) are the sibling-miss signal; rethrowing would force the resolver to wrap a routine absence in try/catch.
|
|
473
|
+
}
|
|
474
|
+
catch {
|
|
475
|
+
return false;
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
//# sourceMappingURL=parity-detect.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parity-detect.js","sourceRoot":"","sources":["../src/parity-detect.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for the version-pinned parity drift detector (PR-1,
|
|
3
|
+
* mmnto-ai/totem#2069 on top of skeleton #2070).
|
|
4
|
+
*
|
|
5
|
+
* The detector is pure + side-effect-free: every filesystem / git seam is
|
|
6
|
+
* injectable so these tests drive it against synthetic fixtures, NOT the live
|
|
7
|
+
* cohort. Each invariant from the spec's "Invariants to lock via tests" list
|
|
8
|
+
* gets a case: pass / warn / fail-under-blocking+strict (the fail-promotion is a
|
|
9
|
+
* CLI-edge concern — the detector itself only ever returns warn here) / skip on
|
|
10
|
+
* not-declared / skip on not-a-consumer / skip on floor-unresolved /
|
|
11
|
+
* no-throw-on-read-failure / never-networks / claim-class-bound (pin currency
|
|
12
|
+
* only, never content equality).
|
|
13
|
+
*
|
|
14
|
+
* Mirrors the temp-file + discriminated-union style of `parity-manifest.test.ts`
|
|
15
|
+
* and the injected-seam style of `strategy-resolver.test.ts`.
|
|
16
|
+
*/
|
|
17
|
+
export {};
|
|
18
|
+
//# sourceMappingURL=parity-detect.test.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parity-detect.test.d.ts","sourceRoot":"","sources":["../src/parity-detect.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG"}
|