mirror of
https://github.com/affaan-m/ECC.git
synced 2026-09-18 15:50:25 +02:00
Independent local Codex review PASS at 472cfa94fb, no P0/P1. Independent retrospective, CLI, envelope and capsule tests: 73 passed. CI run 34664487219 attempt 2 passed all 42 jobs. Read-only offline capsule grouping only; no provenance, scoring, execution or promotion claim. Rollback: revert this squash commit.
102 lines
4.1 KiB
JavaScript
102 lines
4.1 KiB
JavaScript
'use strict';
|
|
|
|
/** Read-only retrospective preparation over explicit, quiescent local capsules.
|
|
* Counts are declarations in the records, never scores or promotion evidence.
|
|
* Inherits capsule.project's local-reader limits; not hostile-filesystem isolation.
|
|
*/
|
|
const capsule = require('./capsule');
|
|
const { LINEAGES, EFFECT_CLASSES } = require('./envelope');
|
|
const { hashValue } = require('./canonical');
|
|
|
|
const MAX_INPUTS = 100;
|
|
const SOURCE_FIELDS = ['entry_count', 'root_hash', 'journal_sha256', 'projection_hash'];
|
|
|
|
class RetrospectiveError extends Error {
|
|
constructor(code, message, inputIndex) {
|
|
super(message);
|
|
this.name = 'RetrospectiveError';
|
|
this.code = code;
|
|
if (inputIndex !== undefined) this.input_index = inputIndex;
|
|
}
|
|
}
|
|
|
|
function readProjection(dir, index) {
|
|
try {
|
|
return capsule.project(dir);
|
|
} catch {
|
|
// Reader errors can contain private paths or journal content. No partial
|
|
// report or unchecked diagnostic content crosses the report boundary.
|
|
throw new RetrospectiveError('retrospective.invalid_capsule', `capsule at input index ${index} failed verification`, index);
|
|
}
|
|
}
|
|
|
|
const compare = (a, b) => a < b ? -1 : a > b ? 1 : 0;
|
|
const identity = projection => JSON.stringify([projection.run_id, projection.capsule_id]);
|
|
|
|
function summarizeGroup(harnessVersion, projections) {
|
|
const total = keys => Object.fromEntries(keys.map(key => [key, 0]));
|
|
const byLineage = total(LINEAGES);
|
|
const byEffect = total(EFFECT_CLASSES);
|
|
for (const projection of projections) {
|
|
for (const key of LINEAGES) byLineage[key] += projection.by_lineage[key];
|
|
for (const key of EFFECT_CLASSES) byEffect[key] += projection.by_effect_class[key];
|
|
}
|
|
return {
|
|
harness_version: harnessVersion,
|
|
capsule_count: projections.length,
|
|
entry_count: projections.reduce((sum, item) => sum + item.entry_count, 0),
|
|
by_lineage: byLineage,
|
|
by_effect_class: byEffect,
|
|
sources: [...projections].sort((a, b) => compare(identity(a), identity(b)))
|
|
.map(item => ({
|
|
identity_hash: hashValue([item.run_id, item.capsule_id]),
|
|
...Object.fromEntries(SOURCE_FIELDS.map(key => [key, item[key]])),
|
|
})),
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Group up to 100 selected snapshots of one task family by harness_version.
|
|
* Duplicate identities count once only if the verified projections match.
|
|
* Different checkpoints of the same identity are ambiguous and refused.
|
|
* Does not read saved projections, copy payloads, write files or invoke tools.
|
|
*/
|
|
function groupCapsules(dirs) {
|
|
if (!Array.isArray(dirs) || dirs.length < 1 || dirs.length > MAX_INPUTS
|
|
|| !Array.from(dirs).every(dir => typeof dir === 'string' && dir.trim() && !dir.includes('\0'))) {
|
|
throw new RetrospectiveError('retrospective.invalid_inputs', `supply 1 to ${MAX_INPUTS} capsule directory paths`);
|
|
}
|
|
const projections = dirs.map(readProjection);
|
|
const family = projections[0].task_family;
|
|
const unique = new Map();
|
|
for (const [index, projection] of projections.entries()) {
|
|
if (projection.task_family !== family) {
|
|
throw new RetrospectiveError('retrospective.mixed_task_families', 'all capsules must have the same task family', index);
|
|
}
|
|
const key = identity(projection);
|
|
const previous = unique.get(key);
|
|
if (previous && previous.projection_hash !== projection.projection_hash) {
|
|
throw new RetrospectiveError('retrospective.conflicting_identity', 'conflicting snapshots share a capsule identity', index);
|
|
}
|
|
unique.set(key, projection);
|
|
}
|
|
const byHarness = new Map();
|
|
for (const projection of unique.values()) {
|
|
const version = projection.harness_version;
|
|
byHarness.set(version, [...(byHarness.get(version) || []), projection]);
|
|
}
|
|
const report = {
|
|
schema: 'capsule-retrospective/v1',
|
|
report_only: true,
|
|
task_family: family,
|
|
input_count: dirs.length,
|
|
capsule_count: unique.size,
|
|
duplicate_count: dirs.length - unique.size,
|
|
groups: [...byHarness].sort(([a], [b]) => compare(a, b))
|
|
.map(([version, items]) => summarizeGroup(version, items)),
|
|
};
|
|
return { ...report, report_hash: hashValue(report) };
|
|
}
|
|
|
|
module.exports = { groupCapsules, MAX_INPUTS, RetrospectiveError };
|