agentera 3.0.0-dev.27 → 3.0.0-dev.28
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/bundle/CHANGELOG.md +10 -0
- package/bundle/DESIGN.md +3 -7
- package/bundle/README.md +1 -4
- package/bundle/UPGRADE.md +33 -0
- package/bundle/fixtures/routing/hybrid-corpus.yaml +154 -0
- package/bundle/references/adapters/package-registry.yaml +2 -0
- package/bundle/references/adapters/package-surface-characterization.md +2 -2
- package/bundle/references/analysis/benchmark.md +2 -9
- package/bundle/references/artifacts/state-storage-authority.yaml +298 -13
- package/bundle/references/cli/capability-instruction-structure.md +2 -2
- package/bundle/references/cli/hybrid-route-contract.yaml +416 -0
- package/bundle/references/cli/prime-consumer-compatibility.yaml +12 -11
- package/bundle/references/cli/routing-model.md +136 -91
- package/bundle/references/cli/trigger-schema-enrichment.md +50 -46
- package/bundle/references/cli/vocabulary.md +7 -9
- package/bundle/references/meta/documentation-inventory.md +1 -3
- package/bundle/skills/agentera/SKILL.md +50 -18
- package/bundle/skills/agentera/capabilities/audit/schemas/triggers.yaml +13 -35
- package/bundle/skills/agentera/capabilities/build/schemas/triggers.yaml +14 -36
- package/bundle/skills/agentera/capabilities/design/schemas/triggers.yaml +5 -34
- package/bundle/skills/agentera/capabilities/discuss/schemas/triggers.yaml +6 -40
- package/bundle/skills/agentera/capabilities/document/schemas/triggers.yaml +5 -34
- package/bundle/skills/agentera/capabilities/optimize/schemas/triggers.yaml +4 -35
- package/bundle/skills/agentera/capabilities/orchestrate/schemas/triggers.yaml +5 -37
- package/bundle/skills/agentera/capabilities/plan/schemas/triggers.yaml +8 -31
- package/bundle/skills/agentera/capabilities/profile/schemas/triggers.yaml +8 -37
- package/bundle/skills/agentera/capabilities/research/schemas/triggers.yaml +5 -29
- package/bundle/skills/agentera/capabilities/status/schemas/triggers.yaml +7 -47
- package/bundle/skills/agentera/capabilities/vision/schemas/triggers.yaml +7 -39
- package/bundle/skills/agentera/capability_schema_contract.yaml +29 -29
- package/bundle/skills/agentera/route-phrases.yaml +81 -0
- package/bundle/skills/agentera/schemas/artifacts/plan.yaml +1 -1
- package/dist/capabilities/discuss/instructions.js +1 -1
- package/dist/capabilities/discuss/instructions.js.map +1 -1
- package/dist/capabilities/plan/instructions.js +1 -1
- package/dist/capabilities/plan/instructions.js.map +1 -1
- package/dist/cli/capabilityContext/evidence.js +1 -1
- package/dist/cli/capabilityContext/evidence.js.map +1 -1
- package/dist/cli/commands/entityMigrate.js +7 -3
- package/dist/cli/commands/entityMigrate.js.map +1 -1
- package/dist/cli/commands/prime/briefOrientation.js +10 -3
- package/dist/cli/commands/prime/briefOrientation.js.map +1 -1
- package/dist/cli/commands/prime/collectEntityOrientation.js +64 -4
- package/dist/cli/commands/prime/collectEntityOrientation.js.map +1 -1
- package/dist/cli/commands/prime/orientationOutput.js +41 -15
- package/dist/cli/commands/prime/orientationOutput.js.map +1 -1
- package/dist/cli/commands/prime.js +4 -3
- package/dist/cli/commands/prime.js.map +1 -1
- package/dist/cli/commands/route.js +142 -0
- package/dist/cli/commands/route.js.map +1 -0
- package/dist/cli/commands/state/write.js +19 -0
- package/dist/cli/commands/state/write.js.map +1 -1
- package/dist/cli/commands/verify.js +17 -1
- package/dist/cli/commands/verify.js.map +1 -1
- package/dist/cli/dispatch/index.js +18 -0
- package/dist/cli/dispatch/index.js.map +1 -1
- package/dist/cli/errors.js +2 -2
- package/dist/cli/errors.js.map +1 -1
- package/dist/cli/help.js +20 -1
- package/dist/cli/help.js.map +1 -1
- package/dist/cli/migrationRequired.js +1 -1
- package/dist/cli/migrationRequired.js.map +1 -1
- package/dist/eval/hybridRouteEvaluation.js +156 -0
- package/dist/eval/hybridRouteEvaluation.js.map +1 -0
- package/dist/registries/capabilityContract.js +5 -20
- package/dist/registries/capabilityContract.js.map +1 -1
- package/dist/registries/hybridRoute.js +203 -0
- package/dist/registries/hybridRoute.js.map +1 -0
- package/dist/registries/hybridRouteReceipt.js +221 -0
- package/dist/registries/hybridRouteReceipt.js.map +1 -0
- package/dist/registries/triggerLoader.js +7 -43
- package/dist/registries/triggerLoader.js.map +1 -1
- package/dist/release/releaseMetadata.js +2 -0
- package/dist/release/releaseMetadata.js.map +1 -1
- package/dist/state/archiveDiscovery.js +24 -35
- package/dist/state/archiveDiscovery.js.map +1 -1
- package/dist/state/canonicalMigrationRecord.js +37 -0
- package/dist/state/canonicalMigrationRecord.js.map +1 -0
- package/dist/state/decisionEntities.js +73 -33
- package/dist/state/decisionEntities.js.map +1 -1
- package/dist/state/decisionLegacyValidation.js +31 -0
- package/dist/state/decisionLegacyValidation.js.map +1 -1
- package/dist/state/decisionMigrationProvenance.js +117 -0
- package/dist/state/decisionMigrationProvenance.js.map +1 -0
- package/dist/state/durability.js +24 -5
- package/dist/state/durability.js.map +1 -1
- package/dist/state/entityCutover.js +2 -2
- package/dist/state/entityCutover.js.map +1 -1
- package/dist/state/entityMigrationCausality.js +99 -0
- package/dist/state/entityMigrationCausality.js.map +1 -0
- package/dist/state/entityMigrationPreview.js +138 -77
- package/dist/state/entityMigrationPreview.js.map +1 -1
- package/dist/state/entityStorage.js +59 -24
- package/dist/state/entityStorage.js.map +1 -1
- package/dist/state/healthEntities.js +28 -31
- package/dist/state/healthEntities.js.map +1 -1
- package/dist/state/legacySummaryRecord.js +7 -0
- package/dist/state/legacySummaryRecord.js.map +1 -0
- package/dist/state/listCursor.js +43 -0
- package/dist/state/listCursor.js.map +1 -0
- package/dist/state/migrationSourceBinding.js +29 -0
- package/dist/state/migrationSourceBinding.js.map +1 -0
- package/dist/state/progressEntities.js +35 -59
- package/dist/state/progressEntities.js.map +1 -1
- package/dist/state/retrievalAuthority.js +10 -0
- package/dist/state/retrievalAuthority.js.map +1 -1
- package/dist/state/summaryEntityRead.js +26 -0
- package/dist/state/summaryEntityRead.js.map +1 -0
- package/dist/state/summaryMigrationProvenance.js +67 -0
- package/dist/state/summaryMigrationProvenance.js.map +1 -0
- package/dist/state/summarySourceRowAuthority.js +37 -0
- package/dist/state/summarySourceRowAuthority.js.map +1 -0
- package/dist/validate/capability.js +11 -2
- package/dist/validate/capability.js.map +1 -1
- package/package.json +2 -2
- package/bundle/references/analysis/analytics-boundary-proportional-2026-07-20.yaml +0 -74
- package/bundle/references/analysis/evidence/analytics-boundary-proportional-2026-07-20/source-focused-1.json.gz +0 -0
- package/bundle/references/analysis/evidence/analytics-boundary-proportional-2026-07-20/source-focused-2.json.gz +0 -0
- package/bundle/references/analysis/evidence/analytics-boundary-proportional-2026-07-20/source-focused-3.json.gz +0 -0
- package/bundle/references/analysis/evidence/migration-pagination-proportional-2026-07-20/source-focused-1.json.gz +0 -0
- package/bundle/references/analysis/evidence/migration-pagination-proportional-2026-07-20/source-focused-2.json.gz +0 -0
- package/bundle/references/analysis/evidence/migration-pagination-proportional-2026-07-20/source-focused-3.json.gz +0 -0
- package/bundle/references/analysis/evidence/verification-baseline-2026-07-20/precommit-cli-route.log.gz +0 -0
- package/bundle/references/analysis/evidence/verification-baseline-2026-07-20/precommit-cli.log.gz +0 -0
- package/bundle/references/analysis/evidence/verification-baseline-2026-07-20/precommit-documentation.log.gz +0 -0
- package/bundle/references/analysis/evidence/verification-baseline-2026-07-20/recompute.mjs +0 -98
- package/bundle/references/analysis/evidence/verification-baseline-2026-07-20/source-1.json.gz +0 -0
- package/bundle/references/analysis/evidence/verification-baseline-2026-07-20/source-2.json.gz +0 -0
- package/bundle/references/analysis/evidence/verification-baseline-2026-07-20/source-3.json.gz +0 -0
- package/bundle/references/analysis/migration-pagination-proportional-2026-07-20.yaml +0 -64
- package/bundle/references/analysis/verification-baseline-2026-07-20.yaml +0 -313
- package/bundle/references/analysis/verification-final-proof-2026-07-21.yaml +0 -340
- package/bundle/references/analysis/worker-policy-2026-07-21.yaml +0 -207
|
@@ -8,14 +8,18 @@ import { discoverPlanArtifacts, planDocumentParts } from "../cli/planArtifacts.j
|
|
|
8
8
|
import { parseTodoMarkdownListItem } from "../cli/todoMarkdown.js";
|
|
9
9
|
import { assertRealpathBoundary, docsPathOverridesFromBytes, loadArtifactRecord, resolveArtifactPath } from "../registries/artifactRegistry.js";
|
|
10
10
|
import { canonicalRecordJson, validateStateRecord } from "./archiveDiscovery.js";
|
|
11
|
-
import { canonicalEntityEnvelopeBytes, validateCanonicalEntityTargets } from "./entityStorage.js";
|
|
11
|
+
import { canonicalEntityEnvelopeBytes, entityForbiddenCanonicalAliases, entityPreservedAggregateCollections, validateCanonicalEntityTargets } from "./entityStorage.js";
|
|
12
12
|
import { discoverObjectiveArtifacts, inspectExperimentIdentities } from "./experimentIdentity.js";
|
|
13
13
|
import { decisionRevisionContract, decisionRevisionViolations } from "./decisionRevision.js";
|
|
14
|
+
import { classifyCompleteDecisionConfidence, decisionLegacyCoexistence } from "./decisionLegacyValidation.js";
|
|
14
15
|
import { assertValidatedProjectRoot, validateRealProjectRoot } from "./projectRoot.js";
|
|
15
16
|
import { todoDocsRecordViolations } from "./todoDocsEntityValidation.js";
|
|
16
17
|
import { yamlArchiveEntry } from "../hooks/compaction/retention.js";
|
|
17
18
|
import { truncateWords } from "../hooks/compaction/dryRun.js";
|
|
18
19
|
import { legacyIdentity } from "./legacyIdentity.js";
|
|
20
|
+
import { canonicalMigrationRecord } from "./canonicalMigrationRecord.js";
|
|
21
|
+
import { legacySummaryRecord } from "./legacySummaryRecord.js";
|
|
22
|
+
import { applyCausalBlockers } from "./entityMigrationCausality.js";
|
|
19
23
|
import { projectPathIsStable as migrationPathIsStable, readProjectFileSnapshot, resolveProjectDescriptorPath, snapshotProjectPath as snapshotMigrationPath, } from "./safeProjectFile.js";
|
|
20
24
|
export const ENTITY_MIGRATION_PREVIEW_MAX_OUTPUT_BYTES = 32_768;
|
|
21
25
|
const DEFAULT_LIMIT = 100;
|
|
@@ -25,7 +29,8 @@ const NUMBERED = {
|
|
|
25
29
|
decisions: { file: ".agentera/decisions.yaml", collection: "decisions", boundary: "decision" },
|
|
26
30
|
health: { file: ".agentera/health.yaml", collection: "audits", boundary: "health_audit" },
|
|
27
31
|
};
|
|
28
|
-
const
|
|
32
|
+
const COMPACTED_SUMMARY_ARTIFACTS = new Set(["progress", "decisions", "health"]);
|
|
33
|
+
const BLOCKING = new Set(["duplicate", "conflict", "corrupt", "unsupported"]);
|
|
29
34
|
const INVENTORY_ORDER = "artifact_then_boundary_then_source_identity_then_source_path";
|
|
30
35
|
const INVENTORY_FILTER = "complete_declared_inventory";
|
|
31
36
|
const AUTHORITY_PATH = "references/artifacts/state-storage-authority.yaml";
|
|
@@ -36,6 +41,24 @@ function mapping(value) {
|
|
|
36
41
|
function hash(value) {
|
|
37
42
|
return createHash("sha256").update(value).digest("hex");
|
|
38
43
|
}
|
|
44
|
+
function completeRecordAssessment(sourceRoot, artifact, record, source, sourcePath) {
|
|
45
|
+
if (artifact !== "decisions") {
|
|
46
|
+
const violations = validateStateRecord(sourceRoot, artifact, record);
|
|
47
|
+
return { valid: violations.length === 0, violations };
|
|
48
|
+
}
|
|
49
|
+
const assessment = classifyCompleteDecisionConfidence(record, decisionLegacyCoexistence(sourceRoot), (candidate) => validateStateRecord(sourceRoot, artifact, candidate), source);
|
|
50
|
+
return {
|
|
51
|
+
valid: assessment.status !== "invalid",
|
|
52
|
+
violations: assessment.violations,
|
|
53
|
+
...(assessment.status === "inherited" ? { inheritedConfidenceProvenance: {
|
|
54
|
+
kind: "inherited_decision_confidence",
|
|
55
|
+
source: source === "active" ? "current_projection" : "verified_archive",
|
|
56
|
+
source_path: sourcePath,
|
|
57
|
+
source_record_sha256: hash(canonicalRecordJson(record)),
|
|
58
|
+
confidence: record.confidence,
|
|
59
|
+
} } : {}),
|
|
60
|
+
};
|
|
61
|
+
}
|
|
39
62
|
function relative(root, target) {
|
|
40
63
|
return path.relative(root, target).replaceAll(path.sep, "/");
|
|
41
64
|
}
|
|
@@ -172,7 +195,7 @@ function inventoryFailureObservations(files, observations) {
|
|
|
172
195
|
});
|
|
173
196
|
}
|
|
174
197
|
}
|
|
175
|
-
function numberedObservations(root, sourceRoot, files, observations) {
|
|
198
|
+
function numberedObservations(root, sourceRoot, files, observations, aggregateCollections) {
|
|
176
199
|
const correlationSource = files.find((source) => source.relative === ".agentera/archive/recovery/projection-correlation.yaml");
|
|
177
200
|
let correlationManifest = null;
|
|
178
201
|
if (correlationSource?.kind === "file") {
|
|
@@ -197,6 +220,10 @@ function numberedObservations(root, sourceRoot, files, observations) {
|
|
|
197
220
|
if (current?.kind === "file") {
|
|
198
221
|
try {
|
|
199
222
|
const document = parseYaml(current);
|
|
223
|
+
const allowedCollections = new Set(aggregateCollections[declaration.file] ?? []);
|
|
224
|
+
for (const field of Object.keys(document).filter((field) => Array.isArray(document[field]) && !allowedCollections.has(field)).sort()) {
|
|
225
|
+
observations.push({ key: `${artifact}:undeclared_collection:${field}`, artifact, boundary: "migration_inventory", path: declaration.file, provenance: "source_inventory", record: null, detail: "corrupt", relationships: [], message: `aggregate collection '${field}' is not declared for '${declaration.file}' by the state storage authority` });
|
|
226
|
+
}
|
|
200
227
|
const currentValues = document[declaration.collection];
|
|
201
228
|
if (!Array.isArray(currentValues))
|
|
202
229
|
throw new Error(`field '${declaration.collection}' must be a list`);
|
|
@@ -204,17 +231,20 @@ function numberedObservations(root, sourceRoot, files, observations) {
|
|
|
204
231
|
if (Array.isArray(document.archive))
|
|
205
232
|
collections.push(["archive", document.archive]);
|
|
206
233
|
collections.flatMap(([collection, values]) => values.map((value, index) => ({ collection, value, index }))).forEach(({ collection, value, index }) => {
|
|
207
|
-
const record =
|
|
234
|
+
const record = legacySummaryRecord(value);
|
|
208
235
|
const identity = legacyIdentity(record, artifact, "number");
|
|
209
236
|
const number = identity.number;
|
|
210
237
|
const key = number ? `${artifact}:${number}` : `${artifact}:${collection}[${index}]`;
|
|
211
|
-
const
|
|
238
|
+
const assessment = record && typeof record.number === "number"
|
|
239
|
+
? completeRecordAssessment(sourceRoot, artifact, record, "active", declaration.file)
|
|
240
|
+
: { valid: false, violations: ["record number is required"] };
|
|
241
|
+
const valid = assessment.valid;
|
|
212
242
|
const summary = Boolean(record && (typeof record.summary === "string" || record.detail_availability === "summary_only"));
|
|
213
243
|
const text = typeof record?.summary === "string" ? record.summary : "";
|
|
214
244
|
const decisionRefs = artifact === "decisions" ? [...text.matchAll(/\bD([1-9][0-9]*)\b/g)] : [];
|
|
215
245
|
const residue = artifact === "decisions" && decisionRefs.length > 1 && !number;
|
|
216
|
-
observations.push({ key: residue ? `historical_projection_residue:${declaration.file}:${collection}[${index}]` : key, artifact, boundary: residue ? "historical_projection_residue" : declaration.boundary, path: declaration.file, provenance: residue ? "historical_projection_residue" : "current_projection", record, detail: valid ? "full" : summary ? "summary" : "corrupt", relationships: [], message: valid || summary ? undefined : "projection record does not satisfy the declared full-detail schema", ...(residue ? { migrationProvenance: { classification: "historical_projection_residue", path: declaration.file, collection, index, content_sha256: hash(canonicalRecordJson(record)), text, inbound_structured_references: 0 } } : {}) });
|
|
217
|
-
if (artifact === "decisions" && mapping(record?.satisfaction)) {
|
|
246
|
+
observations.push({ key: residue ? `historical_projection_residue:${declaration.file}:${collection}[${index}]` : key, artifact, boundary: residue ? "historical_projection_residue" : declaration.boundary, path: declaration.file, provenance: residue ? "historical_projection_residue" : "current_projection", record, detail: valid ? "full" : summary ? "summary" : "corrupt", relationships: [], sourceRecordSha256: hash(canonicalRecordJson(value)), message: valid || summary ? undefined : "projection record does not satisfy the declared full-detail schema", ...(assessment.inheritedConfidenceProvenance ? { inheritedConfidenceProvenance: assessment.inheritedConfidenceProvenance } : {}), ...(residue ? { migrationProvenance: { classification: "historical_projection_residue", path: declaration.file, collection, index, content_sha256: hash(canonicalRecordJson(record)), text, inbound_structured_references: 0 } } : {}) });
|
|
247
|
+
if (artifact === "decisions" && valid && mapping(record?.satisfaction)) {
|
|
218
248
|
observations.push({ key: `decision_satisfaction:${key}`, artifact: "decisions", boundary: "decision_satisfaction", path: declaration.file, provenance: "current_projection", record: mapping(record?.satisfaction), detail: "full", relationships: [{ field: "decision", target: key }] });
|
|
219
249
|
}
|
|
220
250
|
});
|
|
@@ -249,10 +279,10 @@ function numberedObservations(root, sourceRoot, files, observations) {
|
|
|
249
279
|
|| mapping(correlation.overlay)?.final_record_sha256 !== expected)
|
|
250
280
|
throw new Error("recovered archive does not match its immutable projection correlation manifest entry");
|
|
251
281
|
}
|
|
252
|
-
const
|
|
253
|
-
if (
|
|
254
|
-
throw new Error(`archive record is invalid: ${violations.join("; ")}`);
|
|
255
|
-
observations.push({ key, artifact, boundary: declaration.boundary, path: source.relative, provenance: "verified_archive", record, detail: "full", relationships: [], ...(mapping(envelope.recovery_provenance) ? { migrationProvenance: mapping(envelope.recovery_provenance) } : {}) });
|
|
282
|
+
const assessment = completeRecordAssessment(sourceRoot, artifact, record, "archive", source.relative);
|
|
283
|
+
if (!assessment.valid)
|
|
284
|
+
throw new Error(`archive record is invalid: ${assessment.violations.join("; ")}`);
|
|
285
|
+
observations.push({ key, artifact, boundary: declaration.boundary, path: source.relative, provenance: "verified_archive", record, detail: "full", relationships: [], ...(assessment.inheritedConfidenceProvenance ? { inheritedConfidenceProvenance: assessment.inheritedConfidenceProvenance } : {}), ...(mapping(envelope.recovery_provenance) ? { migrationProvenance: mapping(envelope.recovery_provenance) } : {}) });
|
|
256
286
|
if (artifact === "decisions" && mapping(record.satisfaction)) {
|
|
257
287
|
observations.push({
|
|
258
288
|
key: `decision_satisfaction:${key}`,
|
|
@@ -506,15 +536,26 @@ function previewId(fingerprint, key) {
|
|
|
506
536
|
const bytes = createHash("sha256").update(`agentera.entity-preview.v1\0${fingerprint}\0${key}`, "utf8").digest();
|
|
507
537
|
return Array.from(bytes.subarray(0, 10), (byte) => String.fromCharCode(97 + byte % 26)).join("");
|
|
508
538
|
}
|
|
509
|
-
function
|
|
539
|
+
function summaryBoundary(artifact, fallback) {
|
|
540
|
+
return { progress: "progress_summary", decisions: "decision_summary", health: "health_summary" }[artifact] ?? fallback;
|
|
541
|
+
}
|
|
542
|
+
function canonicalSummaryBodies(group, forbiddenAliases) {
|
|
543
|
+
return new Set(group.flatMap((item) => item.record
|
|
544
|
+
? [canonicalRecordJson(canonicalMigrationRecord(summaryBoundary(item.artifact, item.boundary), item.record, forbiddenAliases))]
|
|
545
|
+
: []));
|
|
546
|
+
}
|
|
547
|
+
function classify(group, forbiddenAliases) {
|
|
510
548
|
if (group.some((item) => item.provenance === "historical_projection_residue"))
|
|
511
549
|
return "historical_projection_residue";
|
|
512
550
|
if (group.some((item) => item.key.startsWith("unsupported:")))
|
|
513
551
|
return "unsupported";
|
|
514
552
|
if (group.some((item) => item.detail === "corrupt"))
|
|
515
553
|
return "corrupt";
|
|
516
|
-
if (group.every((item) => item.detail === "summary"))
|
|
517
|
-
|
|
554
|
+
if (group.every((item) => item.detail === "summary")) {
|
|
555
|
+
if (!COMPACTED_SUMMARY_ARTIFACTS.has(group[0]?.artifact ?? ""))
|
|
556
|
+
return "corrupt";
|
|
557
|
+
return canonicalSummaryBodies(group, forbiddenAliases).size === 1 ? "valid_compacted_summary" : "conflict";
|
|
558
|
+
}
|
|
518
559
|
const full = group.filter((item) => item.detail === "full" && item.record);
|
|
519
560
|
const bodies = new Set(full.map((item) => canonicalRecordJson(item.record)));
|
|
520
561
|
if (bodies.size > 1)
|
|
@@ -522,11 +563,12 @@ function classify(group) {
|
|
|
522
563
|
const projections = full.filter((item) => item.provenance === "current_projection");
|
|
523
564
|
const archive = group.find((item) => item.provenance === "verified_archive" && item.record);
|
|
524
565
|
const summaries = group.filter((item) => item.detail === "summary" && item.record);
|
|
525
|
-
if (
|
|
526
|
-
const
|
|
566
|
+
if (full.length && summaries.length) {
|
|
567
|
+
const selected = archive ?? full[0];
|
|
568
|
+
const recovery = selected.migrationProvenance;
|
|
527
569
|
const recorded = mapping(recovery?.current_projection)?.record_sha256;
|
|
528
|
-
|
|
529
|
-
|
|
570
|
+
if (!summaries.every((item) => exactProjectionMirror(item.artifact, selected.record, item.record, recorded)))
|
|
571
|
+
return "conflict";
|
|
530
572
|
}
|
|
531
573
|
if (archive || group.some((item) => item.provenance === "verified_plan_archive"))
|
|
532
574
|
return "verified_full";
|
|
@@ -538,52 +580,26 @@ function exactProjectionMirror(artifact, full, summary, recorded) {
|
|
|
538
580
|
if (recorded === hash(canonicalRecordJson(summary)))
|
|
539
581
|
return true;
|
|
540
582
|
if (artifact === "health" && typeof summary.summary === "string" && typeof full.number === "number" && typeof full.date === "string" && typeof full.trajectory === "string") {
|
|
541
|
-
|
|
583
|
+
if (summary.summary === `Audit ${full.number} · ${full.date} (${truncateWords(full.trajectory, 10)})`)
|
|
584
|
+
return true;
|
|
542
585
|
}
|
|
543
586
|
if (artifact !== "progress" && artifact !== "decisions" && artifact !== "health")
|
|
544
587
|
return false;
|
|
545
588
|
return canonicalRecordJson(yamlArchiveEntry(artifact, full)) === canonicalRecordJson(summary);
|
|
546
589
|
}
|
|
547
|
-
function
|
|
548
|
-
const record = structuredClone(source);
|
|
549
|
-
for (const field of ["id", "artifact", "number", "stable_id", "artifact_id", "entry_number", "task_number", "plan_id", "objective_id", "experiment_number"])
|
|
550
|
-
delete record[field];
|
|
551
|
-
if (boundary === "decision")
|
|
552
|
-
delete record.satisfaction;
|
|
553
|
-
if (boundary === "plan" || boundary === "objective") {
|
|
554
|
-
const header = record.header;
|
|
555
|
-
if (mapping(header))
|
|
556
|
-
delete header.id;
|
|
557
|
-
}
|
|
558
|
-
if (boundary === "plan") {
|
|
559
|
-
delete record.tasks;
|
|
560
|
-
delete record.previous_plan_archived;
|
|
561
|
-
const header = record.header;
|
|
562
|
-
if (mapping(header)) {
|
|
563
|
-
const planHeader = header;
|
|
564
|
-
if (planHeader.status === "active")
|
|
565
|
-
planHeader.status = "open";
|
|
566
|
-
if (planHeader.status === "completed")
|
|
567
|
-
planHeader.status = "complete";
|
|
568
|
-
}
|
|
569
|
-
}
|
|
570
|
-
for (const relationship of relationships) {
|
|
571
|
-
if (relationship.field === "depends_on") {
|
|
572
|
-
record.depends_on = relationships.filter(({ field }) => field === "depends_on").map(({ target_id }) => target_id);
|
|
573
|
-
}
|
|
574
|
-
else {
|
|
575
|
-
record[relationship.field] = relationship.target_id;
|
|
576
|
-
}
|
|
577
|
-
}
|
|
578
|
-
return record;
|
|
579
|
-
}
|
|
580
|
-
function buildEntries(project, fingerprint, observations) {
|
|
590
|
+
function buildEntries(project, fingerprint, observations, forbiddenAliases) {
|
|
581
591
|
const groups = new Map();
|
|
582
592
|
for (const observation of observations)
|
|
583
593
|
groups.set(observation.key, [...(groups.get(observation.key) ?? []), observation]);
|
|
584
594
|
const ids = new Map();
|
|
585
|
-
for (const key of [...groups.keys()].sort())
|
|
586
|
-
|
|
595
|
+
for (const key of [...groups.keys()].sort()) {
|
|
596
|
+
const group = groups.get(key);
|
|
597
|
+
const canonicalSummaries = group.every((item) => item.detail === "summary") && COMPACTED_SUMMARY_ARTIFACTS.has(group[0]?.artifact ?? "")
|
|
598
|
+
? [...canonicalSummaryBodies(group, forbiddenAliases)].sort()
|
|
599
|
+
: [];
|
|
600
|
+
const identityBinding = canonicalSummaries.length === 1 ? hash(canonicalRecordJson(canonicalSummaries)) : fingerprint;
|
|
601
|
+
ids.set(key, previewId(identityBinding, key));
|
|
602
|
+
}
|
|
587
603
|
const collisions = new Set();
|
|
588
604
|
const idOwners = new Map();
|
|
589
605
|
for (const [key, id] of ids) {
|
|
@@ -595,12 +611,16 @@ function buildEntries(project, fingerprint, observations) {
|
|
|
595
611
|
else
|
|
596
612
|
idOwners.set(id, key);
|
|
597
613
|
}
|
|
598
|
-
const entries = [...groups.entries()].map(([key,
|
|
599
|
-
|
|
614
|
+
const entries = [...groups.entries()].map(([key, unsortedGroup]) => {
|
|
615
|
+
const group = [...unsortedGroup].sort((left, right) => `${left.path}\0${left.provenance}\0${canonicalRecordJson(left.record)}\0${left.sourceRecordSha256 ?? ""}`.localeCompare(`${right.path}\0${right.provenance}\0${canonicalRecordJson(right.record)}\0${right.sourceRecordSha256 ?? ""}`));
|
|
616
|
+
let classification = classify(group, forbiddenAliases);
|
|
600
617
|
if (collisions.has(key))
|
|
601
618
|
classification = "conflict";
|
|
602
619
|
const primary = group[0];
|
|
603
|
-
const
|
|
620
|
+
const contentObservation = group.find((item) => item.provenance === "verified_archive" && item.detail === "full" && item.record)
|
|
621
|
+
?? group.find((item) => item.detail === "full" && item.record)
|
|
622
|
+
?? group.find((item) => item.detail === "summary" && item.record);
|
|
623
|
+
const content = contentObservation?.record ?? null;
|
|
604
624
|
const contentSha256s = [...new Set(group.flatMap((item) => item.record ? [hash(canonicalRecordJson(item.record))] : []))].sort();
|
|
605
625
|
const mirrored = contentSha256s.length === 1 && group.filter((item) => item.record).length > 1;
|
|
606
626
|
const id = ids.get(key);
|
|
@@ -611,25 +631,35 @@ function buildEntries(project, fingerprint, observations) {
|
|
|
611
631
|
target_id: relationship.target ? ids.get(relationship.target) ?? null : null,
|
|
612
632
|
status: relationship.target && groups.has(relationship.target) ? "resolved" : "unresolved",
|
|
613
633
|
}));
|
|
614
|
-
const
|
|
634
|
+
const summary = classification === "valid_compacted_summary";
|
|
635
|
+
const boundary = summary ? summaryBoundary(primary.artifact, primary.boundary) : contentObservation?.boundary ?? primary.boundary;
|
|
636
|
+
const record = content ? canonicalMigrationRecord(boundary, content, forbiddenAliases, summary ? [] : relationships) : {};
|
|
637
|
+
if (summary && contentObservation && content) {
|
|
638
|
+
record.migration_provenance = {
|
|
639
|
+
source_path: contentObservation.path,
|
|
640
|
+
source_record_sha256: contentObservation.sourceRecordSha256 ?? hash(canonicalRecordJson(content)),
|
|
641
|
+
};
|
|
642
|
+
}
|
|
615
643
|
const residue = classification === "historical_projection_residue";
|
|
616
644
|
const blocked = BLOCKING.has(classification) || relationships.some((relationship) => relationship.status === "unresolved");
|
|
617
645
|
return {
|
|
618
646
|
source_identity: key,
|
|
619
647
|
source_paths: [...new Set(group.map((item) => item.path))].sort(),
|
|
620
648
|
artifact: primary.artifact,
|
|
621
|
-
boundary
|
|
649
|
+
boundary,
|
|
622
650
|
classification,
|
|
623
|
-
detail_availability:
|
|
651
|
+
detail_availability: summary ? "summary" : content ? "full" : "unavailable",
|
|
652
|
+
compatibility: summary ? "degraded" : "current",
|
|
624
653
|
provenance: [...new Set([...group.map((item) => item.provenance), ...(mirrored ? ["mirrored"] : [])])].sort(),
|
|
625
654
|
content_sha256: contentSha256s.length === 1 ? contentSha256s[0] : null,
|
|
626
655
|
content_sha256s: contentSha256s,
|
|
627
656
|
physical_record_count: group.length,
|
|
628
|
-
proposed_target: blocked || residue ? null : { id, path: `.agentera/entities/${primary.artifact}/${
|
|
657
|
+
proposed_target: blocked || residue ? null : { id, path: `.agentera/entities/${primary.artifact}/${boundary}/${id}.yaml` },
|
|
629
658
|
target_sha256: null,
|
|
630
659
|
relationships,
|
|
631
660
|
record,
|
|
632
661
|
recovery: blocked ? recovery(project, primary.path) : "none",
|
|
662
|
+
...(contentObservation?.inheritedConfidenceProvenance ? { canonical_migration_provenance: contentObservation.inheritedConfidenceProvenance } : {}),
|
|
633
663
|
...(group.some((item) => item.migrationProvenance) ? { migration_provenance: group.flatMap((item) => item.migrationProvenance ? [item.migrationProvenance] : []) } : {}),
|
|
634
664
|
};
|
|
635
665
|
}).sort((a, b) => `${a.artifact}\0${a.boundary}\0${a.source_identity}\0${a.source_paths[0]}`.localeCompare(`${b.artifact}\0${b.boundary}\0${b.source_identity}\0${b.source_paths[0]}`));
|
|
@@ -651,12 +681,12 @@ function buildEntries(project, fingerprint, observations) {
|
|
|
651
681
|
return entries;
|
|
652
682
|
}
|
|
653
683
|
export function validateEntityMigrationTargets(project, entries, sourceRoot) {
|
|
654
|
-
const targets = entries.flatMap((entry) => entry.proposed_target && entry.artifact && entry.boundary ? [{ sourceIdentity: entry.source_identity, path: entry.proposed_target.path, id: entry.proposed_target.id, artifact: entry.artifact, boundary: entry.boundary, record: entry.record }] : []);
|
|
684
|
+
const targets = entries.flatMap((entry) => entry.proposed_target && entry.artifact && entry.boundary ? [{ sourceIdentity: entry.source_identity, path: entry.proposed_target.path, id: entry.proposed_target.id, artifact: entry.artifact, boundary: entry.boundary, record: entry.record, migrationProvenance: entry.canonical_migration_provenance }] : []);
|
|
655
685
|
const diagnostics = validateCanonicalEntityTargets(project, targets, sourceRoot);
|
|
656
686
|
for (const entry of entries) {
|
|
657
687
|
if (!entry.proposed_target || !entry.artifact)
|
|
658
688
|
continue;
|
|
659
|
-
const actual = hash(canonicalEntityEnvelopeBytes({ id: entry.proposed_target.id, artifact: entry.artifact, record: entry.record }));
|
|
689
|
+
const actual = hash(canonicalEntityEnvelopeBytes({ id: entry.proposed_target.id, artifact: entry.artifact, record: entry.record, migrationProvenance: entry.canonical_migration_provenance }));
|
|
660
690
|
if (entry.target_sha256 !== actual)
|
|
661
691
|
diagnostics.push({ sourceIdentity: entry.source_identity, path: entry.proposed_target.path, message: `canonical target bytes have SHA-256 '${actual}', not manifest binding '${entry.target_sha256 ?? "missing"}'` });
|
|
662
692
|
}
|
|
@@ -679,19 +709,20 @@ function migrationInventory(projectRoot, sourceRoot, resolveDescriptorPath) {
|
|
|
679
709
|
const fingerprint = sourceFingerprint(files);
|
|
680
710
|
const observations = [];
|
|
681
711
|
inventoryFailureObservations(files, observations);
|
|
682
|
-
numberedObservations(project, sourceRoot, files, observations);
|
|
712
|
+
numberedObservations(project, sourceRoot, files, observations, entityPreservedAggregateCollections(sourceRoot));
|
|
683
713
|
decisionEvidence(sourceRoot, files, observations);
|
|
684
714
|
planObservations(project, files, observations);
|
|
685
715
|
objectiveObservations(project, files, observations);
|
|
686
716
|
todoAndDocs(project, todoPath, files, observations);
|
|
687
717
|
const preserved = preservedSingletons(files);
|
|
688
|
-
const completeInventory = buildEntries(project, fingerprint, observations);
|
|
718
|
+
const completeInventory = buildEntries(project, fingerprint, observations, entityForbiddenCanonicalAliases(sourceRoot));
|
|
689
719
|
const preservedResidues = completeInventory.filter((entry) => entry.classification === "historical_projection_residue");
|
|
690
720
|
const completeEntries = completeInventory.filter((entry) => entry.classification !== "historical_projection_residue");
|
|
691
721
|
for (const entry of completeEntries) {
|
|
692
722
|
if (entry.proposed_target && entry.artifact)
|
|
693
|
-
entry.target_sha256 = hash(canonicalEntityEnvelopeBytes({ id: entry.proposed_target.id, artifact: entry.artifact, record: entry.record }));
|
|
723
|
+
entry.target_sha256 = hash(canonicalEntityEnvelopeBytes({ id: entry.proposed_target.id, artifact: entry.artifact, record: entry.record, migrationProvenance: entry.canonical_migration_provenance }));
|
|
694
724
|
}
|
|
725
|
+
let causal = applyCausalBlockers(completeEntries, project, (sourcePath) => recovery(project, sourcePath));
|
|
695
726
|
const targetDiagnostics = validateEntityMigrationTargets(project, completeEntries, sourceRoot);
|
|
696
727
|
for (const diagnostic of targetDiagnostics) {
|
|
697
728
|
const entry = completeEntries.find((candidate) => candidate.source_identity === diagnostic.sourceIdentity);
|
|
@@ -701,8 +732,10 @@ function migrationInventory(projectRoot, sourceRoot, resolveDescriptorPath) {
|
|
|
701
732
|
entry.proposed_target = null;
|
|
702
733
|
entry.target_sha256 = null;
|
|
703
734
|
entry.recovery = recovery(project, entry.source_paths[0]);
|
|
735
|
+
causal.rootReasons.set(entry.source_identity, `target_invalid: ${diagnostic.message}`);
|
|
704
736
|
}
|
|
705
|
-
|
|
737
|
+
causal = applyCausalBlockers(completeEntries, project, (sourcePath) => recovery(project, sourcePath), causal.rootReasons);
|
|
738
|
+
const classes = ["verified_full", "recoverable_degraded_full_projection", "valid_compacted_summary", "duplicate", "conflict", "corrupt", "unsupported", "historical_projection_residue"];
|
|
706
739
|
const counts = Object.fromEntries(classes.map((classification) => [classification, completeInventory.filter((entry) => entry.classification === classification).length]));
|
|
707
740
|
counts.total = completeInventory.length;
|
|
708
741
|
counts.publishable_entities = completeEntries.filter(({ proposed_target }) => proposed_target !== null).length;
|
|
@@ -713,15 +746,43 @@ function migrationInventory(projectRoot, sourceRoot, resolveDescriptorPath) {
|
|
|
713
746
|
counts.conflicts = counts.duplicate + counts.conflict;
|
|
714
747
|
counts.relationships = completeEntries.reduce((total, entry) => total + entry.relationships.length, 0);
|
|
715
748
|
counts.unresolved_relationships = completeEntries.reduce((total, entry) => total + entry.relationships.filter((relationship) => relationship.status === "unresolved").length, 0);
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
749
|
+
const rootDiagnostics = completeEntries
|
|
750
|
+
.filter((entry) => causal.rootReasons.has(entry.source_identity))
|
|
751
|
+
.map((entry) => {
|
|
752
|
+
const unresolved = entry.relationships.find((relationship) => relationship.status === "unresolved");
|
|
753
|
+
const reason = causal.rootReasons.get(entry.source_identity) ?? `${entry.classification} source requires explicit recovery`;
|
|
754
|
+
const observationMessage = observations.find((item) => item.key === entry.source_identity)?.message;
|
|
755
|
+
return {
|
|
756
|
+
classification: entry.classification,
|
|
757
|
+
path: entry.source_paths[0],
|
|
758
|
+
source_identity: entry.source_identity,
|
|
759
|
+
...(unresolved ? { relationship_field: unresolved.field, target_source_identity: unresolved.target_source_identity } : {}),
|
|
760
|
+
message: reason === `${entry.classification} source requires explicit recovery` && observationMessage ? observationMessage : reason,
|
|
761
|
+
recovery: entry.recovery,
|
|
762
|
+
};
|
|
763
|
+
});
|
|
764
|
+
const dependentDiagnostics = completeEntries.flatMap((entry) => {
|
|
765
|
+
if (causal.rootReasons.has(entry.source_identity))
|
|
766
|
+
return [];
|
|
767
|
+
return entry.relationships.flatMap((relationship) => {
|
|
768
|
+
if (relationship.status !== "resolved" || !relationship.target_source_identity)
|
|
769
|
+
return [];
|
|
770
|
+
return causal.rootsFor(relationship.target_source_identity).map((rootSourceIdentity) => ({
|
|
771
|
+
classification: "dependent_blocker",
|
|
772
|
+
path: entry.source_paths[0],
|
|
773
|
+
source_identity: entry.source_identity,
|
|
774
|
+
relationship_field: relationship.field,
|
|
775
|
+
target_source_identity: relationship.target_source_identity,
|
|
776
|
+
root_source_identity: rootSourceIdentity,
|
|
777
|
+
message: `source '${entry.source_identity}' relationship '${relationship.field}' is blocked by root source '${rootSourceIdentity}'`,
|
|
778
|
+
recovery: recovery(project, entry.source_paths[0]),
|
|
779
|
+
}));
|
|
780
|
+
});
|
|
781
|
+
});
|
|
782
|
+
const diagnostics = [...rootDiagnostics, ...dependentDiagnostics].sort((left, right) => `${left.source_identity}\0${left.classification}\0${left.relationship_field ?? ""}\0${left.target_source_identity ?? ""}\0${left.root_source_identity ?? ""}`.localeCompare(`${right.source_identity}\0${right.classification}\0${right.relationship_field ?? ""}\0${right.target_source_identity ?? ""}\0${right.root_source_identity ?? ""}`));
|
|
783
|
+
counts.root_blockers = rootDiagnostics.length;
|
|
784
|
+
counts.dependent_blockers = dependentDiagnostics.length;
|
|
785
|
+
counts.blockers = counts.root_blockers + counts.dependent_blockers;
|
|
725
786
|
const digestEntries = completeEntries.map(({ record: _record, ...entry }) => entry);
|
|
726
787
|
const digestResidues = preservedResidues.map(({ record: _record, ...entry }) => entry);
|
|
727
788
|
const digestBody = { source_fingerprint: fingerprint, authority, selectors: { project, filter: INVENTORY_FILTER, order: INVENTORY_ORDER }, entries: digestEntries, preserved_residues: digestResidues, preserved_singletons: preserved, counts, diagnostics };
|