instar 1.3.1077 → 1.3.1079
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/core/StandardsEnforcementAuditor.d.ts +35 -1
- package/dist/core/StandardsEnforcementAuditor.d.ts.map +1 -1
- package/dist/core/StandardsEnforcementAuditor.js +323 -38
- package/dist/core/StandardsEnforcementAuditor.js.map +1 -1
- package/dist/data/standards-guard-index.json +1016 -0
- package/dist/data/standards-guard-index.meta.json +5 -0
- package/dist/data/standards-registry.md +2 -2
- package/dist/data/standards-registry.meta.json +2 -2
- package/package.json +1 -1
- package/scripts/generate-standards-registry-asset.mjs +25 -2
- package/scripts/standards-coverage.mjs +88 -5
- package/src/data/builtin-manifest.json +2 -2
- package/src/data/standards-guard-index.json +1016 -0
- package/src/data/standards-guard-index.meta.json +5 -0
- package/src/data/standards-registry.md +2 -2
- package/src/data/standards-registry.meta.json +2 -2
- package/upgrades/1.3.1078.md +41 -0
- package/upgrades/1.3.1079.md +83 -0
- package/upgrades/side-effects/conformance-source-freshness.md +48 -0
- package/upgrades/side-effects/standards-false-claim-guard.md +156 -0
|
@@ -238,12 +238,33 @@ export interface CoverageReport {
|
|
|
238
238
|
* a confident zero.
|
|
239
239
|
*/
|
|
240
240
|
export interface GuardTreeProvenance {
|
|
241
|
-
/** The directory refs were resolved against. */
|
|
241
|
+
/** The directory refs were resolved against, or the packed-index sentinel. */
|
|
242
242
|
projectDir: string;
|
|
243
|
+
/** The configured directory, retained when a packed index supersedes a stale local tree. */
|
|
244
|
+
configuredProjectDir: string;
|
|
243
245
|
/** Whether that directory actually contains an analyzable instar source tree. */
|
|
244
246
|
analyzable: boolean;
|
|
245
247
|
/** The markers probed, so a reader can see WHY it was judged (un)analyzable. */
|
|
246
248
|
markersFound: string[];
|
|
249
|
+
/** The independently checkable basis used to select this guard evidence. */
|
|
250
|
+
basis: 'executing-source-tree' | 'source-tree-index-match' | 'packed-source-index-match' | 'configured-tree-unverified' | 'not-probed';
|
|
251
|
+
/** True only when the tree/index is tied to the code and constitution being read. */
|
|
252
|
+
freshnessVerified: boolean;
|
|
253
|
+
/** Plain-English explanation of what established (or failed to establish) freshness. */
|
|
254
|
+
freshnessReason: string;
|
|
255
|
+
/** sha256 of the packed source index, when that is the basis. */
|
|
256
|
+
sourceIndexSha256: string | null;
|
|
257
|
+
/** Registry sha the packed index was generated against. */
|
|
258
|
+
registrySha256: string | null;
|
|
259
|
+
/** Package version the packed index was generated for. */
|
|
260
|
+
packageVersion: string | null;
|
|
261
|
+
}
|
|
262
|
+
export interface GuardTreeIndex {
|
|
263
|
+
schemaVersion: 1;
|
|
264
|
+
generatedFrom: 'source-tree';
|
|
265
|
+
registrySha256: string;
|
|
266
|
+
packageVersion: string;
|
|
267
|
+
guards: VerifiedGuard[];
|
|
247
268
|
}
|
|
248
269
|
export interface AuditorOptions {
|
|
249
270
|
/** Path to docs/STANDARDS-REGISTRY.md. */
|
|
@@ -342,6 +363,19 @@ export declare function stripComments(code: string): string;
|
|
|
342
363
|
* non-resolving ref, which is the conservative direction.
|
|
343
364
|
*/
|
|
344
365
|
export declare function containedRefPath(projectDir: string, ref: string): string | null;
|
|
366
|
+
/**
|
|
367
|
+
* Build the deterministic evidence index shipped beside the packed constitution.
|
|
368
|
+
*
|
|
369
|
+
* This runs in the real source checkout during `npm run build`, where the source tree
|
|
370
|
+
* is unambiguous. Runtime installs need not guess which old checkout happens to sit in
|
|
371
|
+
* `projectDir`; they verify and consume this same-build index instead.
|
|
372
|
+
*/
|
|
373
|
+
export declare function buildGuardTreeIndex(projectDir: string, registryMarkdown: string, packageVersion: string): GuardTreeIndex;
|
|
374
|
+
/**
|
|
375
|
+
* Select a real checkout only when its audit-relevant evidence exactly matches the
|
|
376
|
+
* same-build packed index. Landmark presence alone is deliberately insufficient.
|
|
377
|
+
*/
|
|
378
|
+
export declare function findMatchingGuardTree(candidates: string[], expected: GuardTreeIndex, registryMarkdown: string, packageVersion: string): string | null;
|
|
345
379
|
export declare function computeInputHash(opts: AuditorOptions): string;
|
|
346
380
|
/**
|
|
347
381
|
* Derive how much the assessment has EARNED. Extracted as a function on purpose:
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"StandardsEnforcementAuditor.d.ts","sourceRoot":"","sources":["../../src/core/StandardsEnforcementAuditor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;
|
|
1
|
+
{"version":3,"file":"StandardsEnforcementAuditor.d.ts","sourceRoot":"","sources":["../../src/core/StandardsEnforcementAuditor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAQH,OAAO,EAA2C,KAAK,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAE7G,MAAM,MAAM,eAAe,GAAG,SAAS,GAAG,MAAM,GAAG,MAAM,GAAG,WAAW,GAAG,iBAAiB,CAAC;AAE5F,MAAM,WAAW,aAAa;IAC5B,6DAA6D;IAC7D,GAAG,EAAE,MAAM,CAAC;IACZ,uCAAuC;IACvC,IAAI,EAAE,MAAM,GAAG,OAAO,GAAG,QAAQ,CAAC;IAClC,uEAAuE;IACvE;;;;;;;;;;;;;;;OAeG;IACH,WAAW,EAAE,OAAO,CAAC;IACrB,6EAA6E;IAC7E,SAAS,CAAC,EAAE,OAAO,CAAC,eAAe,EAAE,iBAAiB,CAAC,CAAC;CACzD;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,eAAe,EAAE,eAAe,CAAC;IACjC,MAAM,EAAE,aAAa,EAAE,CAAC;IACxB,mFAAmF;IACnF,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;;;;GASG;AACH,MAAM,WAAW,kBAAkB;IACjC;;;;;;;;;OASG;IACH,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,kEAAkE;IAClE,IAAI,EAAE,MAAM,CAAC;IACb,mFAAmF;IACnF,KAAK,EAAE,MAAM,CAAC;IACd,yFAAyF;IACzF,eAAe,EAAE,MAAM,CAAC;IACxB,4CAA4C;IAC5C,MAAM,EAAE,MAAM,CAAC;IACf,6EAA6E;IAC7E,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,sCAAsC;IACtC,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,yFAAyF;IACzF,QAAQ,EAAE,OAAO,CAAC;IAClB,cAAc,EAAE,MAAM,EAAE,CAAC;CAC1B;AAED,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC,eAAe,EAAE,MAAM,CAAC,CAAC;IACxC;;;;;;OAMG;IACH,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B;;;;;;;;;;OAUG;IACH,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,uDAAuD;IACvD,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,gDAAgD;IAChD,aAAa,EAAE,MAAM,CAAC;IACtB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA0CG;IACH,oBAAoB,EAAE,UAAU,GAAG,YAAY,GAAG,eAAe,CAAC;IAClE,0EAA0E;IAC1E,gBAAgB,EAAE,MAAM,CAAC;IACzB;;;;;;OAMG;IACH,qBAAqB,EAAE,OAAO,CAAC;IAC/B;;;;;;;;;;;;;;OAcG;IACH,gBAAgB,EAAE,qBAAqB,CAAC;IACxC,wEAAwE;IACxE,qBAAqB,EAAE,MAAM,CAAC;IAC9B,iDAAiD;IACjD,QAAQ,EAAE,kBAAkB,CAAC;IAC7B;;;;;;;;;;;;;;OAcG;IACH,MAAM,EAAE,mBAAmB,CAAC;CAC7B;AAED,MAAM,WAAW,cAAc;IAC7B,WAAW,EAAE,MAAM,CAAC;IACpB,gGAAgG;IAChG,SAAS,EAAE,MAAM,CAAC;IAClB;;;;;OAKG;IACH,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,gBAAgB,EAAE,CAAC;IAC9B,OAAO,EAAE,eAAe,CAAC;CAC1B;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,mBAAmB;IAClC,8EAA8E;IAC9E,UAAU,EAAE,MAAM,CAAC;IACnB,4FAA4F;IAC5F,oBAAoB,EAAE,MAAM,CAAC;IAC7B,iFAAiF;IACjF,UAAU,EAAE,OAAO,CAAC;IACpB,gFAAgF;IAChF,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,4EAA4E;IAC5E,KAAK,EACD,uBAAuB,GACvB,yBAAyB,GACzB,2BAA2B,GAC3B,4BAA4B,GAC5B,YAAY,CAAC;IACjB,qFAAqF;IACrF,iBAAiB,EAAE,OAAO,CAAC;IAC3B,wFAAwF;IACxF,eAAe,EAAE,MAAM,CAAC;IACxB,iEAAiE;IACjE,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,2DAA2D;IAC3D,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,0DAA0D;IAC1D,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B;AAED,MAAM,WAAW,cAAc;IAC7B,aAAa,EAAE,CAAC,CAAC;IACjB,aAAa,EAAE,aAAa,CAAC;IAC7B,cAAc,EAAE,MAAM,CAAC;IACvB,cAAc,EAAE,MAAM,CAAC;IACvB,MAAM,EAAE,aAAa,EAAE,CAAC;CACzB;AAcD,MAAM,WAAW,cAAc;IAC7B,0CAA0C;IAC1C,YAAY,EAAE,MAAM,CAAC;IACrB,qDAAqD;IACrD,UAAU,EAAE,MAAM,CAAC;IACnB;;;;;;;;;;;;;;;;;OAiBG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;;;;;;;;;OAUG;IACH,SAAS,CAAC,EAAE,iBAAiB,CAAC;CAC/B;AAMD,0DAA0D;AAC1D,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,EAAE,iBAAiB,CAAC,CAmB1F;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,kBAAkB,CAChC,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,GACf;IAAE,QAAQ,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,eAAe,GAAG,IAAI,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAgCvE;AAyBD;;;;GAIG;AACH;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAMlD;AA8CD,kEAAkE;AAClE;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAQ/E;AAwCD;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CACjC,UAAU,EAAE,MAAM,EAClB,gBAAgB,EAAE,MAAM,EACxB,cAAc,EAAE,MAAM,GACrB,cAAc,CA0BhB;AA2BD;;;GAGG;AACH,wBAAgB,qBAAqB,CACnC,UAAU,EAAE,MAAM,EAAE,EACpB,QAAQ,EAAE,cAAc,EACxB,gBAAgB,EAAE,MAAM,EACxB,cAAc,EAAE,MAAM,GACrB,MAAM,GAAG,IAAI,CA0Bf;AAiVD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,cAAc,GAAG,MAAM,CAE7D;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,0BAA0B,CACxC,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,kBAAkB;AAC5B;;;;;;;;;;GAUG;AACH,SAAS,CAAC,EAAE,iBAAiB;AAC7B;;;;GAIG;AACH,MAAM,CAAC,EAAE,mBAAmB;AAC5B;;;;;GAKG;AACH,cAAc,CAAC,EAAE;IAAE,UAAU,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,GACzD;IAAE,UAAU,EAAE,eAAe,CAAC,sBAAsB,CAAC,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAkEzE;AAED;;;;;GAKG;AACH;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,UAAU,EAAE,MAAM,GAAG,mBAAmB,CAoCtE;AAiCD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,4BAA4B,CAC1C,UAAU,EAAE,MAAM,EAClB,cAAc,EAAE,MAAM,GACrB;IAAE,UAAU,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CAI3C;AAED,wBAAgB,eAAe,CAC7B,IAAI,EAAE,cAAc,EACpB,KAAK,CAAC,EAAE,cAAc,GAAG,IAAI,GAC5B,cAAc,CAgIhB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,sBAAsB,CACpC,UAAU,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,YAAY,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,GACnH,cAAc,CAoDhB;AAED,uFAAuF;AACvF,wBAAgB,UAAU,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAY1D"}
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
import fs from 'node:fs';
|
|
25
25
|
import path from 'node:path';
|
|
26
26
|
import crypto from 'node:crypto';
|
|
27
|
+
import { fileURLToPath } from 'node:url';
|
|
27
28
|
import { parseStandardsRegistryDetailed, runRegistryCanary } from './StandardsRegistryParser.js';
|
|
28
29
|
import { extractEnforcementRefs, flattenRefs } from './StandardEnforcementExtractor.js';
|
|
29
30
|
import { earnsVerified, readAuthoredConstitution } from './standardsRegistryPath.js';
|
|
@@ -290,6 +291,251 @@ function verifyRefs(refs, projectDir, routeTable, symbolIndex) {
|
|
|
290
291
|
return g;
|
|
291
292
|
});
|
|
292
293
|
}
|
|
294
|
+
function guardKey(guard) {
|
|
295
|
+
return `${guard.kind}\u0000${guard.ref}`;
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* Build the deterministic evidence index shipped beside the packed constitution.
|
|
299
|
+
*
|
|
300
|
+
* This runs in the real source checkout during `npm run build`, where the source tree
|
|
301
|
+
* is unambiguous. Runtime installs need not guess which old checkout happens to sit in
|
|
302
|
+
* `projectDir`; they verify and consume this same-build index instead.
|
|
303
|
+
*/
|
|
304
|
+
export function buildGuardTreeIndex(projectDir, registryMarkdown, packageVersion) {
|
|
305
|
+
const { articles } = parseStandardsRegistryDetailed(registryMarkdown);
|
|
306
|
+
const extracted = articles.map((article) => extractEnforcementRefs(article));
|
|
307
|
+
const refsByKey = new Map();
|
|
308
|
+
const wantedMarkers = new Set();
|
|
309
|
+
for (const refs of extracted) {
|
|
310
|
+
for (const ref of flattenRefs(refs)) {
|
|
311
|
+
refsByKey.set(`${ref.kind}\u0000${ref.ref}`, ref);
|
|
312
|
+
if (ref.kind === 'marker')
|
|
313
|
+
wantedMarkers.add(ref.ref);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
const routeTable = loadRouteTable(projectDir);
|
|
317
|
+
const symbolIndex = buildSymbolIndex(projectDir, wantedMarkers);
|
|
318
|
+
const guards = verifyRefs([...refsByKey.values()].sort((a, b) => guardKey(a).localeCompare(guardKey(b))), projectDir, routeTable, symbolIndex);
|
|
319
|
+
return {
|
|
320
|
+
schemaVersion: 1,
|
|
321
|
+
generatedFrom: 'source-tree',
|
|
322
|
+
registrySha256: crypto.createHash('sha256').update(registryMarkdown).digest('hex'),
|
|
323
|
+
packageVersion,
|
|
324
|
+
guards,
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
function executingPackageRoot() {
|
|
328
|
+
try {
|
|
329
|
+
return fileURLToPath(new URL('../../', import.meta.url));
|
|
330
|
+
}
|
|
331
|
+
catch {
|
|
332
|
+
// @silent-fallback-ok: this is one candidate identity only. The packed-index
|
|
333
|
+
// resolver below remains authoritative for published installs and returns a
|
|
334
|
+
// surfaced, untrustworthy provenance when it cannot verify its own artifacts.
|
|
335
|
+
return null;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
function sameDirectory(a, b) {
|
|
339
|
+
try {
|
|
340
|
+
return fs.realpathSync(a) === fs.realpathSync(b);
|
|
341
|
+
}
|
|
342
|
+
catch {
|
|
343
|
+
// @silent-fallback-ok: inability to prove path identity is a negative result,
|
|
344
|
+
// never permission to label a configured tree as current.
|
|
345
|
+
return false;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
function sameGuardIndex(left, right) {
|
|
349
|
+
return JSON.stringify(left) === JSON.stringify(right);
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* Select a real checkout only when its audit-relevant evidence exactly matches the
|
|
353
|
+
* same-build packed index. Landmark presence alone is deliberately insufficient.
|
|
354
|
+
*/
|
|
355
|
+
export function findMatchingGuardTree(candidates, expected, registryMarkdown, packageVersion) {
|
|
356
|
+
const seen = new Set();
|
|
357
|
+
for (const candidate of candidates) {
|
|
358
|
+
let canonical;
|
|
359
|
+
try {
|
|
360
|
+
canonical = fs.realpathSync(candidate);
|
|
361
|
+
}
|
|
362
|
+
catch {
|
|
363
|
+
// @silent-fallback-ok: a missing candidate is not a degraded audit result; it
|
|
364
|
+
// simply cannot prove identity and the next bounded candidate is evaluated.
|
|
365
|
+
continue;
|
|
366
|
+
}
|
|
367
|
+
if (seen.has(canonical))
|
|
368
|
+
continue;
|
|
369
|
+
seen.add(canonical);
|
|
370
|
+
if (!probeGuardTree(canonical).analyzable)
|
|
371
|
+
continue;
|
|
372
|
+
let observed;
|
|
373
|
+
try {
|
|
374
|
+
observed = buildGuardTreeIndex(canonical, registryMarkdown, packageVersion);
|
|
375
|
+
}
|
|
376
|
+
catch {
|
|
377
|
+
// @silent-fallback-ok: one optional candidate that cannot be fully indexed
|
|
378
|
+
// has not established identity. Continue to the next bounded candidate; the
|
|
379
|
+
// packed fallback retains analysis and exposes its own basis.
|
|
380
|
+
continue;
|
|
381
|
+
}
|
|
382
|
+
if (sameGuardIndex(observed, expected))
|
|
383
|
+
return canonical;
|
|
384
|
+
}
|
|
385
|
+
return null;
|
|
386
|
+
}
|
|
387
|
+
function guardTreeCandidates(configuredProjectDir) {
|
|
388
|
+
const candidates = [
|
|
389
|
+
configuredProjectDir,
|
|
390
|
+
path.join(configuredProjectDir, 'repo'),
|
|
391
|
+
process.cwd(),
|
|
392
|
+
];
|
|
393
|
+
const packageRoot = executingPackageRoot();
|
|
394
|
+
if (packageRoot)
|
|
395
|
+
candidates.push(packageRoot);
|
|
396
|
+
// Agent deployments keep a bounded fleet at `<agents>/<name>` and development
|
|
397
|
+
// agents keep their checkout at `<agent>/repo`. Search only that one known level:
|
|
398
|
+
// no home-directory crawl, no arbitrary project discovery.
|
|
399
|
+
const agentsDir = path.dirname(configuredProjectDir);
|
|
400
|
+
if (path.basename(agentsDir) === 'agents') {
|
|
401
|
+
try {
|
|
402
|
+
for (const entry of fs.readdirSync(agentsDir, { withFileTypes: true }).slice(0, 100)) {
|
|
403
|
+
if (entry.isDirectory())
|
|
404
|
+
candidates.push(path.join(agentsDir, entry.name, 'repo'));
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
catch {
|
|
408
|
+
// @silent-fallback-ok: fleet-layout discovery is an optional bounded candidate
|
|
409
|
+
// source. Package-local evidence below retains the capability and reports its basis.
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
return candidates;
|
|
413
|
+
}
|
|
414
|
+
function readPackedGuardIndex(registrySha256, runningPackageVersion, configuredProjectDir) {
|
|
415
|
+
let indexPath;
|
|
416
|
+
let metaPath;
|
|
417
|
+
try {
|
|
418
|
+
indexPath = fileURLToPath(new URL('../data/standards-guard-index.json', import.meta.url));
|
|
419
|
+
metaPath = fileURLToPath(new URL('../data/standards-guard-index.meta.json', import.meta.url));
|
|
420
|
+
}
|
|
421
|
+
catch {
|
|
422
|
+
// @silent-fallback-ok: malformed module URL means this candidate is unavailable;
|
|
423
|
+
// the caller returns configured-tree-unverified with the failed freshness claim
|
|
424
|
+
// visible in the report.
|
|
425
|
+
return null;
|
|
426
|
+
}
|
|
427
|
+
let bytes;
|
|
428
|
+
let parsed;
|
|
429
|
+
let meta;
|
|
430
|
+
try {
|
|
431
|
+
bytes = fs.readFileSync(indexPath);
|
|
432
|
+
parsed = JSON.parse(bytes.toString('utf-8'));
|
|
433
|
+
meta = JSON.parse(fs.readFileSync(metaPath, 'utf-8'));
|
|
434
|
+
}
|
|
435
|
+
catch {
|
|
436
|
+
// @silent-fallback-ok: a missing/unreadable index is not substituted or trusted.
|
|
437
|
+
// The caller exposes the absence as an unverified guard-tree basis.
|
|
438
|
+
return null;
|
|
439
|
+
}
|
|
440
|
+
const index = parsed;
|
|
441
|
+
const stamp = meta;
|
|
442
|
+
const observedSha256 = crypto.createHash('sha256').update(bytes).digest('hex');
|
|
443
|
+
const structurallyValid = index.schemaVersion === 1
|
|
444
|
+
&& index.generatedFrom === 'source-tree'
|
|
445
|
+
&& typeof index.registrySha256 === 'string'
|
|
446
|
+
&& typeof index.packageVersion === 'string'
|
|
447
|
+
&& Array.isArray(index.guards)
|
|
448
|
+
&& index.guards.every((guard) => guard !== null
|
|
449
|
+
&& typeof guard === 'object'
|
|
450
|
+
&& typeof guard.ref === 'string'
|
|
451
|
+
&& ['file', 'route', 'marker'].includes(guard.kind)
|
|
452
|
+
&& typeof guard.refResolves === 'boolean');
|
|
453
|
+
const stampMatches = stamp.sha256 === observedSha256
|
|
454
|
+
&& stamp.registrySha256 === registrySha256
|
|
455
|
+
&& stamp.packageVersion === runningPackageVersion
|
|
456
|
+
&& index.registrySha256 === registrySha256
|
|
457
|
+
&& index.packageVersion === runningPackageVersion;
|
|
458
|
+
if (!structurallyValid || !stampMatches)
|
|
459
|
+
return null;
|
|
460
|
+
const indexedGuards = new Map();
|
|
461
|
+
for (const guard of index.guards) {
|
|
462
|
+
indexedGuards.set(guardKey(guard), { ...guard });
|
|
463
|
+
}
|
|
464
|
+
return {
|
|
465
|
+
provenance: {
|
|
466
|
+
projectDir: '(packed same-build source index)',
|
|
467
|
+
configuredProjectDir,
|
|
468
|
+
analyzable: true,
|
|
469
|
+
markersFound: ['packed guard evidence'],
|
|
470
|
+
basis: 'packed-source-index-match',
|
|
471
|
+
freshnessVerified: true,
|
|
472
|
+
freshnessReason: `the source evidence index sha256 ${observedSha256} matches its build meta, registry ` +
|
|
473
|
+
`sha256 ${registrySha256}, and running package version ${runningPackageVersion}`,
|
|
474
|
+
sourceIndexSha256: observedSha256,
|
|
475
|
+
registrySha256,
|
|
476
|
+
packageVersion: runningPackageVersion,
|
|
477
|
+
},
|
|
478
|
+
indexedGuards,
|
|
479
|
+
packedIndex: index,
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
function resolveGuardEvidence(opts, registryMarkdown) {
|
|
483
|
+
const registrySha256 = crypto.createHash('sha256').update(registryMarkdown).digest('hex');
|
|
484
|
+
const configuredProbe = probeGuardTree(opts.projectDir);
|
|
485
|
+
if (opts.integrity?.basis === 'packed-meta-match') {
|
|
486
|
+
const packed = readPackedGuardIndex(registrySha256, opts.integrity.runningPackageVersion, opts.projectDir);
|
|
487
|
+
if (packed && packed.packedIndex && opts.integrity.runningPackageVersion) {
|
|
488
|
+
const matched = findMatchingGuardTree(guardTreeCandidates(opts.projectDir), packed.packedIndex, registryMarkdown, opts.integrity.runningPackageVersion);
|
|
489
|
+
if (matched) {
|
|
490
|
+
const probe = probeGuardTree(matched);
|
|
491
|
+
const packageRoot = executingPackageRoot();
|
|
492
|
+
const executing = packageRoot !== null && sameDirectory(packageRoot, matched);
|
|
493
|
+
return {
|
|
494
|
+
provenance: {
|
|
495
|
+
...probe,
|
|
496
|
+
configuredProjectDir: opts.projectDir,
|
|
497
|
+
basis: executing ? 'executing-source-tree' : 'source-tree-index-match',
|
|
498
|
+
freshnessVerified: true,
|
|
499
|
+
freshnessReason: `the live tree's complete audit-evidence index exactly matches packed source index ` +
|
|
500
|
+
`${packed.provenance.sourceIndexSha256}; landmark-only candidates were rejected`,
|
|
501
|
+
sourceIndexSha256: packed.provenance.sourceIndexSha256,
|
|
502
|
+
registrySha256,
|
|
503
|
+
packageVersion: opts.integrity.runningPackageVersion,
|
|
504
|
+
},
|
|
505
|
+
indexedGuards: null,
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
if (packed)
|
|
510
|
+
return packed;
|
|
511
|
+
}
|
|
512
|
+
return {
|
|
513
|
+
provenance: {
|
|
514
|
+
...configuredProbe,
|
|
515
|
+
configuredProjectDir: opts.projectDir,
|
|
516
|
+
basis: 'configured-tree-unverified',
|
|
517
|
+
freshnessVerified: false,
|
|
518
|
+
freshnessReason: configuredProbe.analyzable
|
|
519
|
+
? 'the configured tree has the audit landmarks, but no same-build identity ties it to the packed constitution; a coherent stale tree passes landmark probes by construction'
|
|
520
|
+
: 'the configured tree lacks the complete source landmarks and no valid same-build source index was available',
|
|
521
|
+
sourceIndexSha256: null,
|
|
522
|
+
registrySha256,
|
|
523
|
+
packageVersion: opts.integrity?.basis === 'packed-meta-match'
|
|
524
|
+
? opts.integrity.runningPackageVersion
|
|
525
|
+
: null,
|
|
526
|
+
},
|
|
527
|
+
indexedGuards: null,
|
|
528
|
+
};
|
|
529
|
+
}
|
|
530
|
+
function verifyRefsFromResolution(refs, resolution, projectDir, routeTable, symbolIndex) {
|
|
531
|
+
if (resolution.indexedGuards === null) {
|
|
532
|
+
return verifyRefs(refs, projectDir, routeTable, symbolIndex);
|
|
533
|
+
}
|
|
534
|
+
return refs.map((ref) => {
|
|
535
|
+
const indexed = resolution.indexedGuards.get(`${ref.kind}\u0000${ref.ref}`);
|
|
536
|
+
return indexed ? { ...indexed } : { ref: ref.ref, kind: ref.kind, refResolves: false };
|
|
537
|
+
});
|
|
538
|
+
}
|
|
293
539
|
/** Classify a standard by its strongest VERIFIED guard. */
|
|
294
540
|
function classifyStandard(guards) {
|
|
295
541
|
let best = null;
|
|
@@ -371,7 +617,7 @@ function guardStateSignal(projectDir, prior) {
|
|
|
371
617
|
}
|
|
372
618
|
return h.digest('hex').slice(0, 16);
|
|
373
619
|
}
|
|
374
|
-
|
|
620
|
+
function computeInputHashForResolution(opts, guardResolution) {
|
|
375
621
|
// An UNREADABLE registry must not hash identically to an empty-but-present one:
|
|
376
622
|
// that conflation is the honest-denominators shape at the cache layer, where a
|
|
377
623
|
// missing constitution would share a cache slot with a genuinely blank one.
|
|
@@ -442,7 +688,15 @@ export function computeInputHash(opts) {
|
|
|
442
688
|
const authoredSignal = authored === null
|
|
443
689
|
? 'no-authored-source'
|
|
444
690
|
: crypto.createHash('sha256').update(authored).digest('hex').slice(0, 16);
|
|
445
|
-
|
|
691
|
+
const guardBasisSignal = guardResolution
|
|
692
|
+
? `${guardResolution.provenance.basis}:${guardResolution.provenance.sourceIndexSha256 ?? 'live'}:` +
|
|
693
|
+
`${guardResolution.provenance.projectDir}`
|
|
694
|
+
: 'guard-basis-not-resolved';
|
|
695
|
+
return `${regHash}.${auxHash}.${authoredSignal}.${repoStructureSignal(opts.projectDir)}.` +
|
|
696
|
+
crypto.createHash('sha256').update(guardBasisSignal).digest('hex').slice(0, 16);
|
|
697
|
+
}
|
|
698
|
+
export function computeInputHash(opts) {
|
|
699
|
+
return computeInputHashForResolution(opts);
|
|
446
700
|
}
|
|
447
701
|
/**
|
|
448
702
|
* Derive how much the assessment has EARNED. Extracted as a function on purpose:
|
|
@@ -518,34 +772,28 @@ authoredSource) {
|
|
|
518
772
|
};
|
|
519
773
|
}
|
|
520
774
|
const verdict = earnsVerified(integrity);
|
|
521
|
-
if (verdict.verified) {
|
|
775
|
+
if (!verdict.verified) {
|
|
776
|
+
return {
|
|
777
|
+
confidence: 'unverified',
|
|
778
|
+
reason: `internal checks passed over ${total} standards parsed from ${registry.path} ` +
|
|
779
|
+
`(${registry.bytes} bytes), but ${verdict.reason} — so nothing establishes this is the CURRENT ` +
|
|
780
|
+
`constitution rather than a coherent older copy; a stale registry passes every internal check ` +
|
|
781
|
+
`by construction`,
|
|
782
|
+
};
|
|
783
|
+
}
|
|
784
|
+
if (guards && !guards.freshnessVerified) {
|
|
522
785
|
return {
|
|
523
|
-
confidence: '
|
|
524
|
-
reason:
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
// The REGISTRY half now carries three currency operands. The GUARD half carries
|
|
528
|
-
// none: refs resolve against whatever src/ sits in projectDir, and on a deployed
|
|
529
|
-
// agent that is a copy which can be months older than the constitution being
|
|
530
|
-
// graded (measured: an agent home's routes.ts dated May 28 against a current
|
|
531
|
-
// registry). So `verified` speaks for the constitution's currency, NOT for the
|
|
532
|
-
// currency of the tree its guards were looked up in.
|
|
533
|
-
//
|
|
534
|
-
// Naming the limit rather than silently widening the word is the same repair this
|
|
535
|
-
// change already made twice. Establishing guard-tree currency is a separate job
|
|
536
|
-
// and is recorded as one, not quietly folded in here.
|
|
537
|
-
`SCOPE NOTE: this verdict covers the CONSTITUTION's currency only. The enforcement ` +
|
|
538
|
-
`refs were resolved against ${guards?.projectDir ?? 'the configured project dir'}, ` +
|
|
539
|
-
`whose currency is not established by anything here — on a deployed agent that tree ` +
|
|
540
|
-
`can be older than the constitution being graded`,
|
|
786
|
+
confidence: 'untrustworthy',
|
|
787
|
+
reason: `the constitution is package-stamped, but guard-tree freshness was not established: ` +
|
|
788
|
+
`${guards.freshnessReason}. The coverage counts remain observable for diagnosis, but a stale ` +
|
|
789
|
+
`tree passes capability landmarks by construction and cannot support a trustworthy ratio`,
|
|
541
790
|
};
|
|
542
791
|
}
|
|
543
792
|
return {
|
|
544
|
-
confidence: '
|
|
545
|
-
reason:
|
|
546
|
-
`
|
|
547
|
-
`
|
|
548
|
-
`by construction`,
|
|
793
|
+
confidence: 'verified',
|
|
794
|
+
reason: `${total} standards parsed from ${registry.path}, and ${verdict.reason}. ` +
|
|
795
|
+
`The guard evidence is also current: ${guards?.freshnessReason ?? 'the caller supplied no guard provenance'}. ` +
|
|
796
|
+
`This is package/build currency, not a runtime execution check and not tamper-resistant`,
|
|
549
797
|
};
|
|
550
798
|
}
|
|
551
799
|
/**
|
|
@@ -590,7 +838,18 @@ export function probeGuardTree(projectDir) {
|
|
|
590
838
|
// `analyzable` permanently false the moment anyone adds a third `check()` — which is
|
|
591
839
|
// exactly the failure this probe was just repaired from, encoded so it can recur.
|
|
592
840
|
const analyzable = markersFound.includes('src/server/routes.ts') && markersFound.includes('src/core');
|
|
593
|
-
return {
|
|
841
|
+
return {
|
|
842
|
+
projectDir,
|
|
843
|
+
configuredProjectDir: projectDir,
|
|
844
|
+
analyzable,
|
|
845
|
+
markersFound,
|
|
846
|
+
basis: 'configured-tree-unverified',
|
|
847
|
+
freshnessVerified: false,
|
|
848
|
+
freshnessReason: 'landmark presence proves analyzability, not source-tree identity or freshness',
|
|
849
|
+
sourceIndexSha256: null,
|
|
850
|
+
registrySha256: null,
|
|
851
|
+
packageVersion: null,
|
|
852
|
+
};
|
|
594
853
|
}
|
|
595
854
|
/**
|
|
596
855
|
* Refuse a basis that does not belong to these bytes.
|
|
@@ -648,7 +907,15 @@ export function compareAgainstAuthoredSource(projectDir, gradedMarkdown) {
|
|
|
648
907
|
return { answerable: true, matches: authored === gradedMarkdown };
|
|
649
908
|
}
|
|
650
909
|
export function computeCoverage(opts, prior) {
|
|
651
|
-
|
|
910
|
+
// Read once before source resolution: the guard index is tied to these exact
|
|
911
|
+
// registry bytes, and candidate checkouts are selected by reproducing that index.
|
|
912
|
+
const registryMarkdown = opts.registryMarkdown ?? fs.readFileSync(opts.registryPath, 'utf-8');
|
|
913
|
+
assertResolutionCoherent(opts, registryMarkdown);
|
|
914
|
+
const guardResolution = resolveGuardEvidence(opts, registryMarkdown);
|
|
915
|
+
const inputHash = computeInputHashForResolution(opts, guardResolution);
|
|
916
|
+
const currentGuardSignal = guardResolution.provenance.sourceIndexSha256 !== null
|
|
917
|
+
? `source-index:${guardResolution.provenance.sourceIndexSha256}`
|
|
918
|
+
: guardStateSignal(guardResolution.provenance.projectDir, prior);
|
|
652
919
|
// TWO conditions, not one.
|
|
653
920
|
//
|
|
654
921
|
// `inputHash` covers the registry bytes, the basis, the paths and a coarse repo
|
|
@@ -664,14 +931,13 @@ export function computeCoverage(opts, prior) {
|
|
|
664
931
|
// prior-derived value into the key makes the first computation (no prior) and the
|
|
665
932
|
// second (with prior) disagree by construction, so the short-circuit would never fire
|
|
666
933
|
// — which is how the first attempt at this failed, caught by an existing test.
|
|
667
|
-
if (prior && prior.inputHash === inputHash && prior.guardSignal ===
|
|
934
|
+
if (prior && prior.inputHash === inputHash && prior.guardSignal === currentGuardSignal) {
|
|
668
935
|
// Inputs unchanged → the deterministic report is byte-identical to the prior;
|
|
669
936
|
// return it (only its timestamp would differ on recompute). The short-circuit.
|
|
670
937
|
return prior;
|
|
671
938
|
}
|
|
672
939
|
// Read ONCE and keep what the parse saw: the audit must be able to state the
|
|
673
940
|
// denominator it computed over (honest-denominators instance 4).
|
|
674
|
-
const registryMarkdown = opts.registryMarkdown ?? fs.readFileSync(opts.registryPath, 'utf-8');
|
|
675
941
|
const { articles, diagnostics } = parseStandardsRegistryDetailed(registryMarkdown);
|
|
676
942
|
const canary = runRegistryCanary(articles, diagnostics);
|
|
677
943
|
const registry = {
|
|
@@ -685,20 +951,24 @@ export function computeCoverage(opts, prior) {
|
|
|
685
951
|
canaryOk: canary.ok,
|
|
686
952
|
canaryFailures: canary.failures,
|
|
687
953
|
};
|
|
688
|
-
|
|
689
|
-
const
|
|
690
|
-
const routeTable =
|
|
954
|
+
const guards = guardResolution.provenance;
|
|
955
|
+
const liveProjectDir = guards.projectDir;
|
|
956
|
+
const routeTable = guardResolution.indexedGuards === null
|
|
957
|
+
? loadRouteTable(liveProjectDir)
|
|
958
|
+
: new Set();
|
|
691
959
|
// Collect every wanted marker across all articles → ONE bounded src walk.
|
|
692
960
|
const extracted = articles.map((a) => ({ a, refs: extractEnforcementRefs(a) }));
|
|
693
961
|
const wantedMarkers = new Set();
|
|
694
962
|
for (const { refs } of extracted)
|
|
695
963
|
for (const m of refs.markers)
|
|
696
964
|
wantedMarkers.add(m);
|
|
697
|
-
const symbolIndex =
|
|
965
|
+
const symbolIndex = guardResolution.indexedGuards === null
|
|
966
|
+
? buildSymbolIndex(liveProjectDir, wantedMarkers)
|
|
967
|
+
: new Set();
|
|
698
968
|
const classifiedAt = new Date().toISOString();
|
|
699
969
|
const standards = extracted.map(({ a, refs }) => {
|
|
700
970
|
const flat = flattenRefs(refs);
|
|
701
|
-
const guards =
|
|
971
|
+
const guards = verifyRefsFromResolution(flat, guardResolution, liveProjectDir, routeTable, symbolIndex);
|
|
702
972
|
const enforcementKind = classifyStandard(guards);
|
|
703
973
|
const danglingRefs = guards.filter((g) => !g.refResolves).map((g) => g.ref).sort();
|
|
704
974
|
return { standard: a.name, family: a.family, enforcementKind, guards, danglingRefs, classifiedAt };
|
|
@@ -716,7 +986,9 @@ export function computeCoverage(opts, prior) {
|
|
|
716
986
|
const enforcedRatio = total === 0 ? null : Number((enforced / total).toFixed(4));
|
|
717
987
|
const gaps = standards.filter((s) => s.enforcementKind === 'documented-only').map((s) => s.standard);
|
|
718
988
|
const danglingCount = standards.reduce((n, s) => n + s.danglingRefs.length, 0);
|
|
719
|
-
const { confidence: assessmentConfidence, reason: confidenceReason } = deriveAssessmentConfidence(total, registry, opts.integrity, guards,
|
|
989
|
+
const { confidence: assessmentConfidence, reason: confidenceReason } = deriveAssessmentConfidence(total, registry, opts.integrity, guards, guards.basis === 'executing-source-tree' || guards.basis === 'source-tree-index-match'
|
|
990
|
+
? compareAgainstAuthoredSource(liveProjectDir, registryMarkdown)
|
|
991
|
+
: { answerable: false, matches: false });
|
|
720
992
|
const report = {
|
|
721
993
|
generatedAt: classifiedAt,
|
|
722
994
|
inputHash,
|
|
@@ -741,7 +1013,9 @@ export function computeCoverage(opts, prior) {
|
|
|
741
1013
|
},
|
|
742
1014
|
};
|
|
743
1015
|
// Computed AFTER the standards exist, because it digests the refs this pass resolved.
|
|
744
|
-
report.guardSignal =
|
|
1016
|
+
report.guardSignal = guardResolution.provenance.sourceIndexSha256 !== null
|
|
1017
|
+
? `source-index:${guardResolution.provenance.sourceIndexSha256}`
|
|
1018
|
+
: guardStateSignal(liveProjectDir, report);
|
|
745
1019
|
return report;
|
|
746
1020
|
}
|
|
747
1021
|
/**
|
|
@@ -792,7 +1066,18 @@ export function unusableCoverageReport(resolution) {
|
|
|
792
1066
|
registry,
|
|
793
1067
|
// No pass ran, so the guard tree was never probed. Reporting `analyzable:
|
|
794
1068
|
// false` here would assert a fact about the repo that this path never checked.
|
|
795
|
-
guards: {
|
|
1069
|
+
guards: {
|
|
1070
|
+
projectDir: '(not probed — the registry was unusable)',
|
|
1071
|
+
configuredProjectDir: '(unknown)',
|
|
1072
|
+
analyzable: false,
|
|
1073
|
+
markersFound: [],
|
|
1074
|
+
basis: 'not-probed',
|
|
1075
|
+
freshnessVerified: false,
|
|
1076
|
+
freshnessReason: 'the registry was unusable, so no guard source was selected',
|
|
1077
|
+
sourceIndexSha256: null,
|
|
1078
|
+
registrySha256: registry.sha256,
|
|
1079
|
+
packageVersion: null,
|
|
1080
|
+
},
|
|
796
1081
|
},
|
|
797
1082
|
};
|
|
798
1083
|
}
|