agentera 3.0.0-dev.20 → 3.0.0-dev.21
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/README.md +19 -0
- package/bundle/CHANGELOG.md +63 -70
- package/bundle/README.md +17 -0
- package/bundle/UPGRADE.md +38 -0
- package/bundle/references/adapters/package-surface-characterization.md +7 -0
- package/bundle/references/artifacts/artifact-registry-interface-model.yaml +3 -3
- package/bundle/references/artifacts/state-storage-authority.yaml +1446 -0
- package/bundle/references/cli/agent-ready-state-contract.yaml +11 -39
- package/bundle/references/cli/audience-namespace-cli-migration.yaml +30 -15
- package/bundle/references/meta/documentation-inventory.md +1 -0
- package/bundle/skills/agentera/capabilities/audit/schemas/artifacts.yaml +18 -10
- package/bundle/skills/agentera/capabilities/audit/schemas/validation.yaml +10 -10
- package/bundle/skills/agentera/capabilities/build/schemas/artifacts.yaml +11 -6
- package/bundle/skills/agentera/capabilities/discuss/schemas/artifacts.yaml +3 -2
- package/bundle/skills/agentera/capabilities/document/schemas/artifacts.yaml +15 -8
- package/bundle/skills/agentera/capabilities/document/schemas/validation.yaml +13 -13
- package/bundle/skills/agentera/capabilities/orchestrate/schemas/artifacts.yaml +15 -9
- package/bundle/skills/agentera/references/contract.md +65 -23
- package/bundle/skills/agentera/schemas/artifacts/decisions.yaml +15 -10
- package/bundle/skills/agentera/schemas/artifacts/experiments.yaml +134 -3
- package/bundle/skills/agentera/schemas/artifacts/health.yaml +16 -10
- package/bundle/skills/agentera/schemas/artifacts/objective.yaml +18 -4
- package/bundle/skills/agentera/schemas/artifacts/plan.yaml +23 -1
- package/bundle/skills/agentera/schemas/artifacts/progress.yaml +16 -10
- package/bundle/skills/agentera/schemas/artifacts/todo.yaml +34 -0
- package/dist/capabilities/audit/instructions.js +1 -1
- package/dist/capabilities/audit/instructions.js.map +1 -1
- package/dist/capabilities/build/instructions.js +1 -1
- package/dist/capabilities/build/instructions.js.map +1 -1
- package/dist/capabilities/discuss/instructions.js +1 -1
- package/dist/capabilities/discuss/instructions.js.map +1 -1
- package/dist/capabilities/document/instructions.js +1 -1
- package/dist/capabilities/document/instructions.js.map +1 -1
- package/dist/capabilities/optimize/instructions.js +1 -1
- package/dist/capabilities/optimize/instructions.js.map +1 -1
- package/dist/capabilities/orchestrate/instructions.js +1 -1
- package/dist/capabilities/orchestrate/instructions.js.map +1 -1
- package/dist/capabilities/plan/instructions.js +1 -1
- package/dist/capabilities/plan/instructions.js.map +1 -1
- package/dist/capabilities/profile/instructions.js +1 -1
- package/dist/capabilities/profile/instructions.js.map +1 -1
- package/dist/capabilities/vision/instructions.js +1 -1
- package/dist/capabilities/vision/instructions.js.map +1 -1
- package/dist/cli/appContext.js +14 -6
- package/dist/cli/appContext.js.map +1 -1
- package/dist/cli/capabilityContext/bespoke.js +3 -1
- package/dist/cli/capabilityContext/bespoke.js.map +1 -1
- package/dist/cli/capabilityContext/build.js +5 -4
- package/dist/cli/capabilityContext/build.js.map +1 -1
- package/dist/cli/capabilityContext/closeout.js +2 -1
- package/dist/cli/capabilityContext/closeout.js.map +1 -1
- package/dist/cli/capabilityContext/contract.js +29 -0
- package/dist/cli/capabilityContext/contract.js.map +1 -1
- package/dist/cli/capabilityContext/evidence.js +21 -20
- package/dist/cli/capabilityContext/evidence.js.map +1 -1
- package/dist/cli/capabilityContext/orchestration.js +3 -2
- package/dist/cli/capabilityContext/orchestration.js.map +1 -1
- package/dist/cli/capabilityContext/progress.js +9 -4
- package/dist/cli/capabilityContext/progress.js.map +1 -1
- package/dist/cli/capabilityContext/slim.js +37 -6
- package/dist/cli/capabilityContext/slim.js.map +1 -1
- package/dist/cli/capabilityContext/startup.js +30 -6
- package/dist/cli/capabilityContext/startup.js.map +1 -1
- package/dist/cli/capabilityContext/types.js +14 -4
- package/dist/cli/capabilityContext/types.js.map +1 -1
- package/dist/cli/commands/backfill.js +194 -0
- package/dist/cli/commands/backfill.js.map +1 -0
- package/dist/cli/commands/compact.js +40 -11
- package/dist/cli/commands/compact.js.map +1 -1
- package/dist/cli/commands/durability.js +85 -0
- package/dist/cli/commands/durability.js.map +1 -0
- package/dist/cli/commands/migrate.js +677 -0
- package/dist/cli/commands/migrate.js.map +1 -0
- package/dist/cli/commands/prime/collectOrientationState.js +23 -0
- package/dist/cli/commands/prime/collectOrientationState.js.map +1 -1
- package/dist/cli/commands/prime/orientationOutput.js +4 -2
- package/dist/cli/commands/prime/orientationOutput.js.map +1 -1
- package/dist/cli/commands/query.js +20 -2
- package/dist/cli/commands/query.js.map +1 -1
- package/dist/cli/commands/schema.js +247 -69
- package/dist/cli/commands/schema.js.map +1 -1
- package/dist/cli/commands/state/decisions.js +28 -10
- package/dist/cli/commands/state/decisions.js.map +1 -1
- package/dist/cli/commands/state/experimentRecords.js +156 -0
- package/dist/cli/commands/state/experimentRecords.js.map +1 -0
- package/dist/cli/commands/state/get.js +141 -0
- package/dist/cli/commands/state/get.js.map +1 -0
- package/dist/cli/commands/state/index.js +2 -2
- package/dist/cli/commands/state/index.js.map +1 -1
- package/dist/cli/commands/state/list.js +159 -0
- package/dist/cli/commands/state/list.js.map +1 -0
- package/dist/cli/commands/state/plan.js +146 -41
- package/dist/cli/commands/state/plan.js.map +1 -1
- package/dist/cli/commands/state/planTasks.js +164 -0
- package/dist/cli/commands/state/planTasks.js.map +1 -0
- package/dist/cli/commands/state/plans.js +154 -0
- package/dist/cli/commands/state/plans.js.map +1 -0
- package/dist/cli/commands/state/write.js +8 -3
- package/dist/cli/commands/state/write.js.map +1 -1
- package/dist/cli/dispatch/check.js +71 -4
- package/dist/cli/dispatch/check.js.map +1 -1
- package/dist/cli/dispatch/index.js +48 -30
- package/dist/cli/dispatch/index.js.map +1 -1
- package/dist/cli/dispatch/lifecycle.js +4 -4
- package/dist/cli/dispatch/lifecycle.js.map +1 -1
- package/dist/cli/dispatch/state.js +23 -3
- package/dist/cli/dispatch/state.js.map +1 -1
- package/dist/cli/errors.js +13 -14
- package/dist/cli/errors.js.map +1 -1
- package/dist/cli/help.js +162 -3
- package/dist/cli/help.js.map +1 -1
- package/dist/cli/orientation.js +146 -34
- package/dist/cli/orientation.js.map +1 -1
- package/dist/cli/planArtifacts.js +31 -2
- package/dist/cli/planArtifacts.js.map +1 -1
- package/dist/cli/prime-blob.js +1 -0
- package/dist/cli/prime-blob.js.map +1 -1
- package/dist/cli/startupCompletenessContract.js +3 -2
- package/dist/cli/startupCompletenessContract.js.map +1 -1
- package/dist/cli/stateQuery.js +5 -3
- package/dist/cli/stateQuery.js.map +1 -1
- package/dist/core/text.js +11 -0
- package/dist/core/text.js.map +1 -0
- package/dist/hooks/compaction/apply.js +224 -46
- package/dist/hooks/compaction/apply.js.map +1 -1
- package/dist/hooks/compaction/dryRun.js +2 -0
- package/dist/hooks/compaction/dryRun.js.map +1 -1
- package/dist/hooks/compaction/index.js +4 -4
- package/dist/hooks/compaction/index.js.map +1 -1
- package/dist/hooks/compaction/parse.js +73 -5
- package/dist/hooks/compaction/parse.js.map +1 -1
- package/dist/hooks/compaction/retention.js +22 -21
- package/dist/hooks/compaction/retention.js.map +1 -1
- package/dist/hooks/compaction/status.js +116 -23
- package/dist/hooks/compaction/status.js.map +1 -1
- package/dist/hooks/sessionStart.js +39 -7
- package/dist/hooks/sessionStart.js.map +1 -1
- package/dist/hooks/validateArtifact/index.js +2 -2
- package/dist/hooks/validateArtifact/index.js.map +1 -1
- package/dist/hooks/validateArtifact/markdown.js +9 -9
- package/dist/hooks/validateArtifact/markdown.js.map +1 -1
- package/dist/hooks/validateArtifact/traversal.js +11 -9
- package/dist/hooks/validateArtifact/traversal.js.map +1 -1
- package/dist/registries/artifactRegistry.js +147 -44
- package/dist/registries/artifactRegistry.js.map +1 -1
- package/dist/release/releaseMetadata.js +95 -1
- package/dist/release/releaseMetadata.js.map +1 -1
- package/dist/state/archiveDiscovery.js +589 -0
- package/dist/state/archiveDiscovery.js.map +1 -0
- package/dist/state/archivePublication.js +225 -0
- package/dist/state/archivePublication.js.map +1 -0
- package/dist/state/archiveRecovery.js +238 -0
- package/dist/state/archiveRecovery.js.map +1 -0
- package/dist/state/archiveReplay.js +54 -0
- package/dist/state/archiveReplay.js.map +1 -0
- package/dist/state/decisionOverlay.js +255 -0
- package/dist/state/decisionOverlay.js.map +1 -0
- package/dist/state/directRetrieval.js +322 -0
- package/dist/state/directRetrieval.js.map +1 -0
- package/dist/state/durability.js +451 -0
- package/dist/state/durability.js.map +1 -0
- package/dist/state/experimentArchive.js +64 -0
- package/dist/state/experimentArchive.js.map +1 -0
- package/dist/state/experimentIdentity.js +191 -0
- package/dist/state/experimentIdentity.js.map +1 -0
- package/dist/state/experimentRetrieval.js +447 -0
- package/dist/state/experimentRetrieval.js.map +1 -0
- package/dist/state/gitBackfill.js +561 -0
- package/dist/state/gitBackfill.js.map +1 -0
- package/dist/state/gitBackfillAuthority.js +141 -0
- package/dist/state/gitBackfillAuthority.js.map +1 -0
- package/dist/state/gitBackfillHistory.js +278 -0
- package/dist/state/gitBackfillHistory.js.map +1 -0
- package/dist/state/gitBackfillOutput.js +209 -0
- package/dist/state/gitBackfillOutput.js.map +1 -0
- package/dist/state/healthRepair.js +70 -0
- package/dist/state/healthRepair.js.map +1 -0
- package/dist/state/legacyIdentity.js +48 -0
- package/dist/state/legacyIdentity.js.map +1 -0
- package/dist/state/legacyMigration.js +516 -0
- package/dist/state/legacyMigration.js.map +1 -0
- package/dist/state/legacyMigrationParser.js +489 -0
- package/dist/state/legacyMigrationParser.js.map +1 -0
- package/dist/state/listAccounting.js +55 -0
- package/dist/state/listAccounting.js.map +1 -0
- package/dist/state/listClassification.js +18 -0
- package/dist/state/listClassification.js.map +1 -0
- package/dist/state/listRetrieval.js +782 -0
- package/dist/state/listRetrieval.js.map +1 -0
- package/dist/state/migrationAuthority.js +413 -0
- package/dist/state/migrationAuthority.js.map +1 -0
- package/dist/state/migrationEnrichment.js +35 -0
- package/dist/state/migrationEnrichment.js.map +1 -0
- package/dist/state/planIdentity.js +45 -0
- package/dist/state/planIdentity.js.map +1 -0
- package/dist/state/planRetrieval.js +314 -0
- package/dist/state/planRetrieval.js.map +1 -0
- package/dist/state/planTaskRetrieval.js +285 -0
- package/dist/state/planTaskRetrieval.js.map +1 -0
- package/dist/state/projectionPolicy.js +184 -0
- package/dist/state/projectionPolicy.js.map +1 -0
- package/dist/state/retrievalAuthority.js +326 -0
- package/dist/state/retrievalAuthority.js.map +1 -0
- package/dist/state/startupProjection.js +498 -0
- package/dist/state/startupProjection.js.map +1 -0
- package/dist/state/write/experimentPublication.js +193 -0
- package/dist/state/write/experimentPublication.js.map +1 -0
- package/dist/state/write/explain.js +28 -14
- package/dist/state/write/explain.js.map +1 -1
- package/dist/state/write/helpers.js +8 -0
- package/dist/state/write/helpers.js.map +1 -0
- package/dist/state/write/index.js +1 -0
- package/dist/state/write/index.js.map +1 -1
- package/dist/state/write/lock.js +24 -1
- package/dist/state/write/lock.js.map +1 -1
- package/dist/state/write/mutation.js +190 -0
- package/dist/state/write/mutation.js.map +1 -0
- package/dist/state/write/operations.js +36 -2
- package/dist/state/write/operations.js.map +1 -1
- package/dist/state/write/planPublication.js +172 -0
- package/dist/state/write/planPublication.js.map +1 -0
- package/dist/state/write/transaction.js +238 -225
- package/dist/state/write/transaction.js.map +1 -1
- package/dist/upgrade/migrateArtifactsV2ToV3.js +2 -2
- package/dist/upgrade/migrateArtifactsV2ToV3.js.map +1 -1
- package/dist/validate/crossCapability.js +9 -6
- package/dist/validate/crossCapability.js.map +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { loadYamlMapping } from "../core/yaml.js";
|
|
5
|
+
import { canonicalRecordJson } from "./archiveDiscovery.js";
|
|
6
|
+
export const PERSISTED_OBJECTIVE_ID = /^objective:[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
|
|
7
|
+
export const LEGACY_OBJECTIVE_ID = /^legacy-objective:[0-9a-f]{64}$/;
|
|
8
|
+
export const OBJECTIVE_ID = /^(objective:[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}|legacy-objective:[0-9a-f]{64})$/;
|
|
9
|
+
function mapping(value) {
|
|
10
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
11
|
+
}
|
|
12
|
+
function list(value) {
|
|
13
|
+
return Array.isArray(value) ? value.filter((entry) => entry !== null && typeof entry === "object" && !Array.isArray(entry)) : [];
|
|
14
|
+
}
|
|
15
|
+
/** Identity inputs deliberately exclude title, lifecycle, closure, root, and slug. */
|
|
16
|
+
export function canonicalObjectiveIdentityDocument(document) {
|
|
17
|
+
const objective = mapping(document.objective);
|
|
18
|
+
return {
|
|
19
|
+
baseline: mapping(document.baseline),
|
|
20
|
+
constraints: document.constraints ?? objective.constraints ?? [],
|
|
21
|
+
description: objective.description ?? document.description ?? "",
|
|
22
|
+
gates: mapping(document.gates),
|
|
23
|
+
measurement: objective.measurement ?? document.measurement ?? "",
|
|
24
|
+
metric: mapping(document.metric),
|
|
25
|
+
scope: mapping(document.scope),
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
export function resolveObjectiveIdentity(document) {
|
|
29
|
+
const canonicalJson = canonicalRecordJson(canonicalObjectiveIdentityDocument(document));
|
|
30
|
+
const persisted = mapping(document.header).id;
|
|
31
|
+
if (persisted !== undefined) {
|
|
32
|
+
if (typeof persisted !== "string" || !PERSISTED_OBJECTIVE_ID.test(persisted)) {
|
|
33
|
+
throw new Error("objective header.id is not a valid persisted objective identity");
|
|
34
|
+
}
|
|
35
|
+
return { stableId: persisted, persisted: true, canonicalJson, compatibility: "canonical" };
|
|
36
|
+
}
|
|
37
|
+
return {
|
|
38
|
+
stableId: `legacy-objective:${createHash("sha256").update(canonicalJson, "utf8").digest("hex")}`,
|
|
39
|
+
persisted: false,
|
|
40
|
+
canonicalJson,
|
|
41
|
+
compatibility: "legacy_derived_identity",
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
export function discoverObjectiveArtifacts(projectRoot) {
|
|
45
|
+
const inspected = [];
|
|
46
|
+
const diagnostics = [];
|
|
47
|
+
for (const root of ["optimize", "optimera"]) {
|
|
48
|
+
const directory = path.join(projectRoot, ".agentera", root);
|
|
49
|
+
let slugs = [];
|
|
50
|
+
try {
|
|
51
|
+
slugs = fs.readdirSync(directory).sort();
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
for (const slug of slugs) {
|
|
57
|
+
const objectivePath = path.join(directory, slug, "objective.yaml");
|
|
58
|
+
if (!fs.existsSync(objectivePath))
|
|
59
|
+
continue;
|
|
60
|
+
try {
|
|
61
|
+
const identity = resolveObjectiveIdentity(loadYamlMapping(fs.readFileSync(objectivePath, "utf8")));
|
|
62
|
+
inspected.push({ ...identity, path: objectivePath, slug, root });
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
diagnostics.push({
|
|
66
|
+
class: "corrupt",
|
|
67
|
+
message: error.message,
|
|
68
|
+
candidate_paths: [objectivePath],
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
const objectives = [];
|
|
74
|
+
for (const stableId of [...new Set(inspected.map((candidate) => candidate.stableId))].sort()) {
|
|
75
|
+
const candidates = inspected.filter((candidate) => candidate.stableId === stableId);
|
|
76
|
+
const ambiguous = new Set(candidates.map((candidate) => candidate.canonicalJson)).size > 1;
|
|
77
|
+
if (ambiguous) {
|
|
78
|
+
diagnostics.push({
|
|
79
|
+
class: "ambiguous",
|
|
80
|
+
message: `objective identity ${stableId} resolves to conflicting documents`,
|
|
81
|
+
stable_id: stableId,
|
|
82
|
+
candidate_paths: candidates.map((candidate) => candidate.path).sort(),
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
objectives.push({
|
|
86
|
+
stableId,
|
|
87
|
+
persisted: candidates.every((candidate) => candidate.persisted),
|
|
88
|
+
canonicalJson: candidates[0].canonicalJson,
|
|
89
|
+
compatibility: candidates.every((candidate) => candidate.persisted) ? "canonical" : "legacy_derived_identity",
|
|
90
|
+
paths: candidates.map((candidate) => candidate.path).sort(),
|
|
91
|
+
slugs: [...new Set(candidates.map((candidate) => candidate.slug))].sort(),
|
|
92
|
+
roots: [...new Set(candidates.map((candidate) => candidate.root))].sort(),
|
|
93
|
+
ambiguous,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
for (const slug of [...new Set(inspected.map((candidate) => candidate.slug))].sort()) {
|
|
97
|
+
const candidates = inspected.filter((candidate) => candidate.slug === slug);
|
|
98
|
+
const roots = new Set(candidates.map((candidate) => candidate.root));
|
|
99
|
+
const candidateIds = [...new Set(candidates.map((candidate) => candidate.stableId))].sort();
|
|
100
|
+
if (roots.size === 2 && candidateIds.length > 1) {
|
|
101
|
+
const candidatePaths = candidates.map((candidate) => candidate.path).sort();
|
|
102
|
+
diagnostics.push({
|
|
103
|
+
class: "ambiguous",
|
|
104
|
+
message: `canonical and legacy objective roots conflict for slug ${slug}`,
|
|
105
|
+
slug,
|
|
106
|
+
candidate_ids: candidateIds,
|
|
107
|
+
candidate_paths: candidatePaths,
|
|
108
|
+
});
|
|
109
|
+
for (const objective of objectives.filter((candidate) => candidateIds.includes(candidate.stableId)))
|
|
110
|
+
objective.ambiguous = true;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return { objectives, diagnostics };
|
|
114
|
+
}
|
|
115
|
+
export function inspectExperimentIdentities(objectiveId, document) {
|
|
116
|
+
if (!OBJECTIVE_ID.test(objectiveId))
|
|
117
|
+
throw new Error("objective identity is invalid");
|
|
118
|
+
const collections = [
|
|
119
|
+
["experiments", list(document.experiments)],
|
|
120
|
+
["archive", list(document.archive)],
|
|
121
|
+
["archived_experiments", list(document.archived_experiments)],
|
|
122
|
+
];
|
|
123
|
+
const counts = new Map();
|
|
124
|
+
for (const [, entries] of collections) {
|
|
125
|
+
for (const entry of entries) {
|
|
126
|
+
if (Number.isInteger(entry.number) && Number(entry.number) >= 0)
|
|
127
|
+
counts.set(Number(entry.number), (counts.get(Number(entry.number)) ?? 0) + 1);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
const caveats = [];
|
|
131
|
+
const entries = collections.flatMap(([collection, values]) => values.map((data, index) => {
|
|
132
|
+
const number = Number.isInteger(data.number) && Number(data.number) >= 0 ? Number(data.number) : null;
|
|
133
|
+
let compatibility = "canonical";
|
|
134
|
+
const entryCaveats = [];
|
|
135
|
+
if (data.number === undefined || data.number === null) {
|
|
136
|
+
compatibility = "legacy_missing_identity";
|
|
137
|
+
entryCaveats.push(`legacy ${collection}[${index}] is missing experiment number and remains visible but unaddressable`);
|
|
138
|
+
}
|
|
139
|
+
else if (number === null) {
|
|
140
|
+
compatibility = "legacy_invalid_identity";
|
|
141
|
+
entryCaveats.push(`legacy ${collection}[${index}] has invalid experiment number and remains visible but unaddressable`);
|
|
142
|
+
}
|
|
143
|
+
else if ((counts.get(number) ?? 0) > 1) {
|
|
144
|
+
compatibility = "legacy_duplicate_identity";
|
|
145
|
+
entryCaveats.push(`duplicate experiment number ${number} remains visible but ambiguous and unaddressable`);
|
|
146
|
+
}
|
|
147
|
+
caveats.push(...entryCaveats);
|
|
148
|
+
return {
|
|
149
|
+
stableId: number === null ? null : `${objectiveId}/experiment:${number}`,
|
|
150
|
+
number,
|
|
151
|
+
addressable: compatibility === "canonical",
|
|
152
|
+
compatibility,
|
|
153
|
+
provenance: { collection, index },
|
|
154
|
+
data,
|
|
155
|
+
caveats: entryCaveats,
|
|
156
|
+
};
|
|
157
|
+
}));
|
|
158
|
+
return { objectiveId, entries, caveats: [...new Set(caveats)] };
|
|
159
|
+
}
|
|
160
|
+
export class ExperimentIdentityError extends Error {
|
|
161
|
+
className;
|
|
162
|
+
details;
|
|
163
|
+
constructor(className, message, details = {}) {
|
|
164
|
+
super(message);
|
|
165
|
+
this.className = className;
|
|
166
|
+
this.details = details;
|
|
167
|
+
this.name = "ExperimentIdentityError";
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
/** Pure preflight used by later publication work; this function never changes project state. */
|
|
171
|
+
export function validateExperimentPublicationIdentity(discovery, projection, objectiveId, number) {
|
|
172
|
+
if (!OBJECTIVE_ID.test(objectiveId) || !Number.isInteger(number) || number < 0) {
|
|
173
|
+
throw new ExperimentIdentityError("invalid_request", "publication requires a valid objective identity and non-negative integer experiment number");
|
|
174
|
+
}
|
|
175
|
+
const objective = discovery.objectives.find((candidate) => candidate.stableId === objectiveId);
|
|
176
|
+
if (!objective)
|
|
177
|
+
throw new ExperimentIdentityError("not_found", `objective identity ${objectiveId} was not found`);
|
|
178
|
+
if (objective.ambiguous || projection.objectiveId !== objectiveId || projection.entries.some((entry) => !entry.addressable)) {
|
|
179
|
+
throw new ExperimentIdentityError("ambiguous", "experiment publication identity is ambiguous; no effects are allowed", {
|
|
180
|
+
stable_id: `${objectiveId}/experiment:${number}`,
|
|
181
|
+
candidate_paths: objective.paths,
|
|
182
|
+
compatibility_caveats: projection.caveats,
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
const stableId = `${objectiveId}/experiment:${number}`;
|
|
186
|
+
if (projection.entries.some((entry) => entry.stableId === stableId)) {
|
|
187
|
+
throw new ExperimentIdentityError("ambiguous", `experiment identity ${stableId} already exists; no effects are allowed`, { stable_id: stableId });
|
|
188
|
+
}
|
|
189
|
+
return stableId;
|
|
190
|
+
}
|
|
191
|
+
//# sourceMappingURL=experimentIdentity.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"experimentIdentity.js","sourceRoot":"","sources":["../../src/state/experimentIdentity.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,447 @@
|
|
|
1
|
+
import { createHash, createHmac, timingSafeEqual } from "node:crypto";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { deflateRawSync, inflateRawSync } from "node:zlib";
|
|
5
|
+
import YAML from "yaml";
|
|
6
|
+
import { loadYamlMapping } from "../core/yaml.js";
|
|
7
|
+
import { canonicalRecordJson } from "./archiveDiscovery.js";
|
|
8
|
+
import { StateRetrievalFailure } from "./directRetrieval.js";
|
|
9
|
+
import { OBJECTIVE_ID, discoverObjectiveArtifacts, inspectExperimentIdentities, } from "./experimentIdentity.js";
|
|
10
|
+
const CURSOR_VERSION = 1;
|
|
11
|
+
const ORDER = "experiment_number_desc";
|
|
12
|
+
const MAX_LIST_BYTES = 32_768;
|
|
13
|
+
const AUTHORITY = "references/artifacts/state-storage-authority.yaml";
|
|
14
|
+
function hash(value) {
|
|
15
|
+
return createHash("sha256").update(canonicalRecordJson(value), "utf8").digest("hex");
|
|
16
|
+
}
|
|
17
|
+
function mapping(value) {
|
|
18
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
19
|
+
}
|
|
20
|
+
function fail(exitCode, className, message, verb, details = {}) {
|
|
21
|
+
const list = verb === "list";
|
|
22
|
+
return new StateRetrievalFailure({
|
|
23
|
+
schemaVersion: "agentera.stateFailure.v1",
|
|
24
|
+
status: "fail",
|
|
25
|
+
error: {
|
|
26
|
+
class: className,
|
|
27
|
+
message,
|
|
28
|
+
syntax: list
|
|
29
|
+
? "agentera state experiments list --objective OBJECTIVE_ID [--limit N] [--cursor TOKEN] --format json"
|
|
30
|
+
: "agentera state experiments get --objective OBJECTIVE_ID --number N --format json",
|
|
31
|
+
example: list
|
|
32
|
+
? "agentera state experiments list --objective objective:123e4567-e89b-42d3-a456-426614174000 --limit 20 --format json"
|
|
33
|
+
: "agentera state experiments get --objective objective:123e4567-e89b-42d3-a456-426614174000 --number 0 --format json",
|
|
34
|
+
recovery: details.recovery ?? "Correct the objective-scoped experiment command and retry; no state was changed.",
|
|
35
|
+
valid_values: list
|
|
36
|
+
? ["--objective OBJECTIVE_ID", "--limit 1..100", "--cursor TOKEN", "--format text|json|yaml"]
|
|
37
|
+
: ["--objective OBJECTIVE_ID", "--number 0|1|2|...", "--format text|json|yaml"],
|
|
38
|
+
...details,
|
|
39
|
+
},
|
|
40
|
+
}, exitCode);
|
|
41
|
+
}
|
|
42
|
+
function selectedObjective(projectRoot, objectiveId, verb) {
|
|
43
|
+
if (!OBJECTIVE_ID.test(objectiveId)) {
|
|
44
|
+
throw fail(2, "invalid_request", `invalid objective identity '${objectiveId}'`, verb, {
|
|
45
|
+
valid_values: ["objective:<lowercase-rfc9562-uuid>", "legacy-objective:<64-lowercase-hex-digest>"],
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
const discovery = discoverObjectiveArtifacts(projectRoot);
|
|
49
|
+
const objective = discovery.objectives.find((candidate) => candidate.stableId === objectiveId);
|
|
50
|
+
if (!objective) {
|
|
51
|
+
throw fail(1, "not_found", `objective identity '${objectiveId}' was not found`, verb, {
|
|
52
|
+
stable_id: objectiveId,
|
|
53
|
+
recovery: "Use an objective identity returned by Agentera objective state, then retry.",
|
|
54
|
+
details: { compatibility_diagnostics: discovery.diagnostics },
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
if (objective.ambiguous) {
|
|
58
|
+
throw fail(1, "ambiguous", `objective identity '${objectiveId}' resolves to incompatible canonical or legacy candidates`, verb, {
|
|
59
|
+
stable_id: objectiveId,
|
|
60
|
+
recovery: "Repair the objective identity/root collision; Agentera will not choose by path or root preference.",
|
|
61
|
+
details: { candidate_paths: objective.paths, compatibility_diagnostics: discovery.diagnostics },
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
return objective;
|
|
65
|
+
}
|
|
66
|
+
function readProjection(objectiveId, objectivePath) {
|
|
67
|
+
const projectionPath = path.join(path.dirname(objectivePath), "experiments.yaml");
|
|
68
|
+
if (!fs.existsSync(projectionPath))
|
|
69
|
+
return { sources: [], diagnostics: [] };
|
|
70
|
+
try {
|
|
71
|
+
const projection = inspectExperimentIdentities(objectiveId, loadYamlMapping(fs.readFileSync(projectionPath, "utf8")));
|
|
72
|
+
return {
|
|
73
|
+
sources: projection.entries.map((entry) => ({
|
|
74
|
+
path: projectionPath,
|
|
75
|
+
collection: entry.provenance.collection,
|
|
76
|
+
index: entry.provenance.index,
|
|
77
|
+
record: entry.data,
|
|
78
|
+
addressable: entry.addressable,
|
|
79
|
+
compatibility: entry.compatibility,
|
|
80
|
+
caveats: entry.caveats,
|
|
81
|
+
})),
|
|
82
|
+
diagnostics: projection.caveats.map((message) => ({ class: "compatibility", path: projectionPath, message })),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
throw fail(1, "corrupt", `experiment projection '${projectionPath}' cannot be read safely`, "list", {
|
|
87
|
+
recovery: "Repair the selected objective's experiments.yaml, then retry.",
|
|
88
|
+
details: { path: projectionPath, message: error.message },
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
function archiveNumber(filename) {
|
|
93
|
+
if (!/^(0|[1-9][0-9]*)\.yaml$/.test(filename))
|
|
94
|
+
return undefined;
|
|
95
|
+
const number = Number(filename.slice(0, -5));
|
|
96
|
+
return Number.isSafeInteger(number) ? number : undefined;
|
|
97
|
+
}
|
|
98
|
+
function readArchives(objectiveId, objectivePath) {
|
|
99
|
+
const directory = path.join(path.dirname(objectivePath), "archive", "experiments");
|
|
100
|
+
let names;
|
|
101
|
+
try {
|
|
102
|
+
names = fs.readdirSync(directory).sort();
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
return { sources: [], diagnostics: [] };
|
|
106
|
+
}
|
|
107
|
+
const sources = [];
|
|
108
|
+
const diagnostics = [];
|
|
109
|
+
for (const name of names) {
|
|
110
|
+
const file = path.join(directory, name);
|
|
111
|
+
const number = archiveNumber(name);
|
|
112
|
+
try {
|
|
113
|
+
if (number === undefined || fs.lstatSync(file).isSymbolicLink() || !fs.lstatSync(file).isFile())
|
|
114
|
+
throw new Error("archive filename or file type is invalid");
|
|
115
|
+
const envelope = loadYamlMapping(fs.readFileSync(file, "utf8"));
|
|
116
|
+
const stableId = `${objectiveId}/experiment:${number}`;
|
|
117
|
+
if (envelope.schemaVersion !== "agentera.experimentArchive.v1")
|
|
118
|
+
throw new Error("unsupported experiment archive schemaVersion");
|
|
119
|
+
if (envelope.objective_id !== objectiveId || envelope.experiment_number !== number || envelope.stable_id !== stableId)
|
|
120
|
+
throw new Error("archive identity does not match its objective-scoped path");
|
|
121
|
+
if (!mapping(envelope.record) || envelope.record.number !== number)
|
|
122
|
+
throw new Error("archive record number does not match its envelope");
|
|
123
|
+
const digest = hash(envelope.record);
|
|
124
|
+
if (envelope.record_sha256 !== digest)
|
|
125
|
+
throw new Error("archive record_sha256 does not match its record");
|
|
126
|
+
if (!mapping(envelope.provenance) || envelope.provenance.authority !== AUTHORITY || envelope.provenance.objective_id !== objectiveId || envelope.provenance.experiment_id !== stableId) {
|
|
127
|
+
throw new Error("archive provenance does not match its authority and identity");
|
|
128
|
+
}
|
|
129
|
+
sources.push({ path: file, record: envelope.record, recordSha256: digest, provenance: envelope.provenance });
|
|
130
|
+
}
|
|
131
|
+
catch (error) {
|
|
132
|
+
diagnostics.push({ class: "corrupt", path: file, message: error.message, experiment_number: number ?? null });
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return { sources, diagnostics };
|
|
136
|
+
}
|
|
137
|
+
function logicalExperiment(objectiveId, number, projection, archives) {
|
|
138
|
+
const stableId = `${objectiveId}/experiment:${number}`;
|
|
139
|
+
const projectionPaths = new Map();
|
|
140
|
+
for (const source of projection)
|
|
141
|
+
projectionPaths.set(source.path, [...(projectionPaths.get(source.path) ?? []), source]);
|
|
142
|
+
const duplicateProjection = [...projectionPaths.values()].some((sources) => sources.length > 1);
|
|
143
|
+
const projectionHashes = new Set(projection.map((source) => hash(source.record)));
|
|
144
|
+
const archiveHashes = new Set(archives.map((source) => source.recordSha256));
|
|
145
|
+
const fullProjection = projection.filter((source) => source.collection === "experiments");
|
|
146
|
+
const summaryProjection = projection.filter((source) => source.collection !== "experiments");
|
|
147
|
+
const summaryDetail = summaryProjection.some((source) => Object.keys(source.record).some((field) => field !== "number"));
|
|
148
|
+
const immutableConflict = archiveHashes.size > 1 || (archives.length > 0 && fullProjection.some((source) => !archiveHashes.has(hash(source.record))));
|
|
149
|
+
const duplicate = duplicateProjection || projectionHashes.size > 1 && projection.length > 1;
|
|
150
|
+
const detailAvailability = archives.length > 0 || fullProjection.length > 0 ? "full" : summaryDetail ? "summary" : "unavailable";
|
|
151
|
+
const compatibility = immutableConflict
|
|
152
|
+
? "immutable_conflict"
|
|
153
|
+
: duplicate
|
|
154
|
+
? "legacy_duplicate_identity"
|
|
155
|
+
: archives.length > 0
|
|
156
|
+
? "canonical_archive"
|
|
157
|
+
: fullProjection.length > 0
|
|
158
|
+
? "legacy_full_without_archive"
|
|
159
|
+
: summaryDetail ? "legacy_summary_only" : "legacy_detail_unavailable";
|
|
160
|
+
const source = archives.length > 0
|
|
161
|
+
? projection.length > 0 ? "archive_and_projection" : "immutable_archive"
|
|
162
|
+
: fullProjection.length > 0 ? "retained_projection" : "legacy_summary_projection";
|
|
163
|
+
const fingerprint = hash(archives.length > 0
|
|
164
|
+
? {
|
|
165
|
+
stable_id: stableId,
|
|
166
|
+
evidence: "immutable_archive",
|
|
167
|
+
record_hashes: [...archiveHashes].sort(),
|
|
168
|
+
compatibility,
|
|
169
|
+
}
|
|
170
|
+
: {
|
|
171
|
+
stable_id: stableId,
|
|
172
|
+
evidence: fullProjection.length > 0 ? "retained_full" : summaryDetail ? "legacy_summary" : "unavailable",
|
|
173
|
+
record_hashes: [...projectionHashes].sort(),
|
|
174
|
+
compatibility,
|
|
175
|
+
});
|
|
176
|
+
return { key: stableId, stableId, number, projection, archives, addressable: !immutableConflict && !duplicate, compatibility, detailAvailability, source, fingerprint };
|
|
177
|
+
}
|
|
178
|
+
function loadObjective(projectRoot, objectiveId, verb) {
|
|
179
|
+
const objective = selectedObjective(projectRoot, objectiveId, verb);
|
|
180
|
+
const projections = [];
|
|
181
|
+
const archives = [];
|
|
182
|
+
const diagnostics = [];
|
|
183
|
+
for (const objectivePath of objective.paths) {
|
|
184
|
+
const projection = readProjection(objectiveId, objectivePath);
|
|
185
|
+
projections.push(...projection.sources);
|
|
186
|
+
diagnostics.push(...projection.diagnostics);
|
|
187
|
+
const archive = readArchives(objectiveId, objectivePath);
|
|
188
|
+
archives.push(...archive.sources);
|
|
189
|
+
diagnostics.push(...archive.diagnostics);
|
|
190
|
+
}
|
|
191
|
+
const numbers = [...new Set([
|
|
192
|
+
...projections.map((source) => Number(source.record.number)).filter((number) => Number.isSafeInteger(number) && number >= 0),
|
|
193
|
+
...archives.map((source) => Number(source.record.number)),
|
|
194
|
+
])].sort((left, right) => right - left);
|
|
195
|
+
const experiments = numbers.map((number) => logicalExperiment(objectiveId, number, projections.filter((source) => source.record.number === number), archives.filter((source) => source.record.number === number)));
|
|
196
|
+
for (const source of projections.filter((item) => !Number.isSafeInteger(item.record.number) || Number(item.record.number) < 0)) {
|
|
197
|
+
const fingerprint = hash({ path: source.path, collection: source.collection, index: source.index, record: source.record });
|
|
198
|
+
experiments.push({
|
|
199
|
+
key: `unaddressable:${fingerprint}`,
|
|
200
|
+
stableId: null,
|
|
201
|
+
number: null,
|
|
202
|
+
projection: [source],
|
|
203
|
+
archives: [],
|
|
204
|
+
addressable: false,
|
|
205
|
+
compatibility: source.compatibility,
|
|
206
|
+
detailAvailability: source.collection === "experiments" ? "full" : "summary",
|
|
207
|
+
source: "legacy_unaddressable_projection",
|
|
208
|
+
fingerprint,
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
return { objective, experiments, diagnostics };
|
|
212
|
+
}
|
|
213
|
+
function provenance(experiment, objective) {
|
|
214
|
+
return {
|
|
215
|
+
objective: {
|
|
216
|
+
stable_id: objective.stableId,
|
|
217
|
+
compatibility: objective.compatibility,
|
|
218
|
+
paths: objective.paths,
|
|
219
|
+
roots: objective.roots,
|
|
220
|
+
slugs: objective.slugs,
|
|
221
|
+
},
|
|
222
|
+
projection: experiment.projection.map((source) => ({ path: source.path, collection: source.collection, index: source.index })),
|
|
223
|
+
archive: experiment.archives.map((source) => ({ path: source.path, record_sha256: source.recordSha256, provenance: source.provenance })),
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
function summaryRecord(experiment) {
|
|
227
|
+
const source = experiment.archives[0]?.record ?? experiment.projection[0]?.record;
|
|
228
|
+
if (!source)
|
|
229
|
+
return undefined;
|
|
230
|
+
const summary = {};
|
|
231
|
+
for (const field of ["date", "label", "status", "summary", "conclusion"])
|
|
232
|
+
if (source[field] !== undefined)
|
|
233
|
+
summary[field] = source[field];
|
|
234
|
+
return Object.keys(summary).length > 0 ? summary : undefined;
|
|
235
|
+
}
|
|
236
|
+
function listEntry(experiment, objective) {
|
|
237
|
+
return {
|
|
238
|
+
stable_id: experiment.stableId,
|
|
239
|
+
artifact_id: "experiments",
|
|
240
|
+
experiment_number: experiment.number,
|
|
241
|
+
addressable: experiment.addressable,
|
|
242
|
+
detail_availability: experiment.detailAvailability,
|
|
243
|
+
compatibility: experiment.compatibility,
|
|
244
|
+
source: experiment.source,
|
|
245
|
+
provenance: provenance(experiment, objective),
|
|
246
|
+
retrieval: experiment.addressable
|
|
247
|
+
? { get: `agentera state experiments get --objective ${objective.stableId} --number ${experiment.number} --format json` }
|
|
248
|
+
: { get: null, recovery: "Repair the legacy identity or immutable conflict before exact retrieval." },
|
|
249
|
+
...(summaryRecord(experiment) ? { summary: summaryRecord(experiment) } : {}),
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
function snapshotId(experiments) {
|
|
253
|
+
return hash({ collection: "experiments.records", order: ORDER, candidates: experiments.map(({ key, fingerprint }) => ({ key, fingerprint })) });
|
|
254
|
+
}
|
|
255
|
+
function cursorKey(projectRoot) {
|
|
256
|
+
return createHash("sha256").update(`agentera-experiment-cursor\0${path.resolve(projectRoot)}`, "utf8").digest();
|
|
257
|
+
}
|
|
258
|
+
function sign(payload, projectRoot) {
|
|
259
|
+
return createHmac("sha256", cursorKey(projectRoot)).update(canonicalRecordJson(payload), "utf8").digest("hex");
|
|
260
|
+
}
|
|
261
|
+
function encodeCursor(payload, projectRoot) {
|
|
262
|
+
return deflateRawSync(Buffer.from(JSON.stringify({ ...payload, signature: sign(payload, projectRoot) }), "utf8")).toString("base64url");
|
|
263
|
+
}
|
|
264
|
+
function parseCursor(token, projectRoot, objectiveId) {
|
|
265
|
+
const invalid = (message) => {
|
|
266
|
+
throw fail(2, "cursor_invalid", message, "list", {
|
|
267
|
+
recovery: "Copy response.next_cursor exactly for the same objective, or omit --cursor to establish a new snapshot.",
|
|
268
|
+
details: { cursor: "opaque; do not parse or construct cursor tokens" },
|
|
269
|
+
});
|
|
270
|
+
};
|
|
271
|
+
if (!token || token.length > 100_000 || !/^[A-Za-z0-9_-]+$/.test(token))
|
|
272
|
+
invalid("cursor is not a valid opaque token");
|
|
273
|
+
let decoded;
|
|
274
|
+
try {
|
|
275
|
+
const compressed = Buffer.from(token, "base64url");
|
|
276
|
+
if (compressed.toString("base64url") !== token)
|
|
277
|
+
invalid("cursor is not a canonical opaque token");
|
|
278
|
+
decoded = JSON.parse(inflateRawSync(compressed, { maxOutputLength: 1_000_000 }).toString("utf8"));
|
|
279
|
+
}
|
|
280
|
+
catch {
|
|
281
|
+
invalid("cursor is not a valid opaque token");
|
|
282
|
+
}
|
|
283
|
+
if (!mapping(decoded))
|
|
284
|
+
invalid("cursor payload is invalid");
|
|
285
|
+
const { signature, ...unsigned } = decoded;
|
|
286
|
+
if (typeof signature !== "string" || !/^[0-9a-f]{64}$/.test(signature))
|
|
287
|
+
invalid("cursor signature is invalid");
|
|
288
|
+
const expected = sign(unsigned, projectRoot);
|
|
289
|
+
if (!timingSafeEqual(Buffer.from(signature), Buffer.from(expected)))
|
|
290
|
+
invalid("cursor signature is invalid");
|
|
291
|
+
if (unsigned.version !== CURSOR_VERSION || unsigned.collection !== "experiments.records" || unsigned.order !== ORDER || unsigned.objective_id !== objectiveId)
|
|
292
|
+
invalid("cursor is bound to a different collection, objective, or order");
|
|
293
|
+
if (!Number.isSafeInteger(unsigned.after) || Number(unsigned.after) < 1 || !Array.isArray(unsigned.candidate_keys) || unsigned.candidate_keys.some((key) => typeof key !== "string"))
|
|
294
|
+
invalid("cursor payload is invalid");
|
|
295
|
+
if (typeof unsigned.snapshot_id !== "string" || !/^[0-9a-f]{64}$/.test(unsigned.snapshot_id) || typeof unsigned.diagnostic_hash !== "string" || !/^[0-9a-f]{64}$/.test(unsigned.diagnostic_hash))
|
|
296
|
+
invalid("cursor snapshot is invalid");
|
|
297
|
+
return unsigned;
|
|
298
|
+
}
|
|
299
|
+
function snapshotExperiments(current, cursor) {
|
|
300
|
+
const byKey = new Map(current.experiments.map((experiment) => [experiment.key, experiment]));
|
|
301
|
+
const experiments = cursor.candidate_keys.map((key) => byKey.get(key));
|
|
302
|
+
if (experiments.some((experiment) => !experiment) || snapshotId(experiments) !== cursor.snapshot_id || hash(current.diagnostics) !== cursor.diagnostic_hash) {
|
|
303
|
+
throw fail(1, "cursor_snapshot_unavailable", "the objective experiment snapshot changed and cannot be resumed exactly", "list", {
|
|
304
|
+
recovery: "Start a new experiment listing without --cursor to establish a current objective snapshot.",
|
|
305
|
+
details: { snapshot_id: cursor.snapshot_id },
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
return experiments;
|
|
309
|
+
}
|
|
310
|
+
function withPage(loaded, experiments, start, retained, projectRoot, reason) {
|
|
311
|
+
const selected = experiments.slice(start, start + retained);
|
|
312
|
+
const remaining = experiments.length - start - selected.length;
|
|
313
|
+
const omitted = remaining > 0;
|
|
314
|
+
const snapshot = snapshotId(experiments);
|
|
315
|
+
const response = {
|
|
316
|
+
schemaVersion: "agentera.stateRetrieval.v1",
|
|
317
|
+
command: "state experiments list",
|
|
318
|
+
status: loaded.diagnostics.length > 0 || loaded.objective.compatibility !== "canonical" || experiments.some((entry) => !entry.addressable || entry.detailAvailability !== "full") ? "degraded" : "ok",
|
|
319
|
+
entries: selected.map((entry) => listEntry(entry, loaded.objective)),
|
|
320
|
+
counts: { total: experiments.length, returned: selected.length, remaining, omitted: remaining, diagnostics: loaded.diagnostics.length },
|
|
321
|
+
order: ORDER,
|
|
322
|
+
filters: { objective: loaded.objective.stableId },
|
|
323
|
+
snapshot: { id: snapshot, candidate_count: experiments.length, has_more: omitted },
|
|
324
|
+
source: {
|
|
325
|
+
artifact: "experiments",
|
|
326
|
+
storage: "objective_projection_and_immutable_objective_archive",
|
|
327
|
+
objective_paths: loaded.objective.paths,
|
|
328
|
+
compatibility_diagnostics: loaded.diagnostics,
|
|
329
|
+
},
|
|
330
|
+
source_contract: {
|
|
331
|
+
authority: AUTHORITY,
|
|
332
|
+
complete_for_experiment_list_retrieval: true,
|
|
333
|
+
storage_ownership: "objective_scoped_durable_records_and_bounded_10_40_50_projection",
|
|
334
|
+
},
|
|
335
|
+
retrieval: { get: `agentera state experiments get --objective ${loaded.objective.stableId} --number N --format json` },
|
|
336
|
+
omitted,
|
|
337
|
+
omitted_count: remaining,
|
|
338
|
+
omission_reason: omitted ? reason : null,
|
|
339
|
+
};
|
|
340
|
+
if (omitted) {
|
|
341
|
+
response.next_cursor = encodeCursor({
|
|
342
|
+
version: CURSOR_VERSION,
|
|
343
|
+
collection: "experiments.records",
|
|
344
|
+
objective_id: loaded.objective.stableId,
|
|
345
|
+
order: ORDER,
|
|
346
|
+
snapshot_id: snapshot,
|
|
347
|
+
candidate_keys: experiments.map((entry) => entry.key),
|
|
348
|
+
diagnostic_hash: hash(loaded.diagnostics),
|
|
349
|
+
after: start + selected.length,
|
|
350
|
+
}, projectRoot);
|
|
351
|
+
response.retrieval.continue = `agentera state experiments list --objective ${loaded.objective.stableId} --limit 20 --cursor ${response.next_cursor} --format json`;
|
|
352
|
+
}
|
|
353
|
+
return response;
|
|
354
|
+
}
|
|
355
|
+
function serializedBytes(response, format) {
|
|
356
|
+
const bytes = format === "yaml" ? YAML.stringify(response) : JSON.stringify(response, null, 2) + "\n";
|
|
357
|
+
return Buffer.byteLength(bytes, "utf8");
|
|
358
|
+
}
|
|
359
|
+
export function listExperiments(projectRoot, objectiveId, options) {
|
|
360
|
+
if (!Number.isSafeInteger(options.limit) || options.limit < 1 || options.limit > 100)
|
|
361
|
+
throw fail(2, "invalid_request", "experiment list limit must be an integer from 1 through 100", "list", { valid_values: ["1..100"] });
|
|
362
|
+
const loaded = loadObjective(projectRoot, objectiveId, "list");
|
|
363
|
+
const parsed = options.cursor ? parseCursor(options.cursor, projectRoot, objectiveId) : undefined;
|
|
364
|
+
const experiments = parsed ? snapshotExperiments(loaded, parsed) : loaded.experiments;
|
|
365
|
+
const start = parsed?.after ?? 0;
|
|
366
|
+
if (start > experiments.length)
|
|
367
|
+
throw fail(2, "cursor_invalid", "cursor position is outside its experiment snapshot", "list");
|
|
368
|
+
const requested = Math.min(options.limit, experiments.length - start);
|
|
369
|
+
let response = withPage(loaded, experiments, start, requested, projectRoot, "page_limit");
|
|
370
|
+
if (options.format !== "text") {
|
|
371
|
+
for (let retained = requested; retained > 0 && serializedBytes(response, options.format) > MAX_LIST_BYTES; retained -= 1)
|
|
372
|
+
response = withPage(loaded, experiments, start, retained - 1, projectRoot, "serialized_output_byte_budget");
|
|
373
|
+
if (serializedBytes(response, options.format) > MAX_LIST_BYTES || (experiments.length > start && response.entries.length === 0)) {
|
|
374
|
+
throw fail(1, "unsupported_state", `one experiment identity cannot fit within the ${MAX_LIST_BYTES}-byte ${options.format.toUpperCase()} list budget`, "list", {
|
|
375
|
+
recovery: `Fetch one known identity with agentera state experiments get --objective ${objectiveId} --number N --format json.`,
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
return response;
|
|
380
|
+
}
|
|
381
|
+
export function getExperiment(projectRoot, objectiveId, number) {
|
|
382
|
+
if (!Number.isSafeInteger(number) || number < 0)
|
|
383
|
+
throw fail(2, "invalid_request", "--number must be a non-negative integer without leading zeros", "get", { valid_values: ["0", "1", "2", "..."] });
|
|
384
|
+
const loaded = loadObjective(projectRoot, objectiveId, "get");
|
|
385
|
+
const corruptArchive = loaded.diagnostics.find((diagnostic) => diagnostic.class === "corrupt" && diagnostic.experiment_number === number);
|
|
386
|
+
if (corruptArchive)
|
|
387
|
+
throw fail(1, "corrupt", `experiment archive for '${objectiveId}/experiment:${number}' cannot be verified`, "get", {
|
|
388
|
+
stable_id: `${objectiveId}/experiment:${number}`,
|
|
389
|
+
entry_number: number,
|
|
390
|
+
recovery: "Preserve and repair the immutable archive envelope before retrying exact retrieval.",
|
|
391
|
+
details: { diagnostic: corruptArchive },
|
|
392
|
+
});
|
|
393
|
+
const experiment = loaded.experiments.find((candidate) => candidate.number === number);
|
|
394
|
+
if (!experiment)
|
|
395
|
+
throw fail(1, "not_found", `experiment '${objectiveId}/experiment:${number}' was not found`, "get", {
|
|
396
|
+
stable_id: `${objectiveId}/experiment:${number}`,
|
|
397
|
+
entry_number: number,
|
|
398
|
+
recovery: `List known identities with agentera state experiments list --objective ${objectiveId} --format json, then retry.`,
|
|
399
|
+
});
|
|
400
|
+
if (experiment.compatibility === "immutable_conflict")
|
|
401
|
+
throw fail(1, "immutable_conflict", `experiment '${experiment.stableId}' differs between immutable archive and retained projection`, "get", {
|
|
402
|
+
stable_id: experiment.stableId,
|
|
403
|
+
entry_number: number,
|
|
404
|
+
recovery: "Preserve the immutable archive and reconcile the conflicting projection before retrying.",
|
|
405
|
+
details: { provenance: provenance(experiment, loaded.objective) },
|
|
406
|
+
});
|
|
407
|
+
if (!experiment.addressable)
|
|
408
|
+
throw fail(1, "ambiguous", `experiment '${experiment.stableId}' has duplicate retained identities`, "get", {
|
|
409
|
+
stable_id: experiment.stableId,
|
|
410
|
+
entry_number: number,
|
|
411
|
+
recovery: "Repair duplicate projection identities; Agentera will not choose by array order.",
|
|
412
|
+
details: { provenance: provenance(experiment, loaded.objective) },
|
|
413
|
+
});
|
|
414
|
+
const archive = experiment.archives[0];
|
|
415
|
+
const retainedFull = experiment.projection.find((source) => source.collection === "experiments");
|
|
416
|
+
const summary = experiment.projection.find((source) => source.collection !== "experiments");
|
|
417
|
+
const record = archive?.record ?? retainedFull?.record ?? summary?.record;
|
|
418
|
+
if (!record || experiment.detailAvailability === "unavailable")
|
|
419
|
+
throw fail(1, "incomplete", `experiment '${experiment.stableId}' exists but detail is unavailable`, "get", {
|
|
420
|
+
stable_id: experiment.stableId, entry_number: number,
|
|
421
|
+
recovery: "Restore a retained or immutable experiment record; Agentera will not reconstruct dropped legacy detail.",
|
|
422
|
+
});
|
|
423
|
+
return {
|
|
424
|
+
schemaVersion: "agentera.stateRetrieval.v1",
|
|
425
|
+
command: "state experiments get",
|
|
426
|
+
status: experiment.detailAvailability === "full" ? "ok" : "degraded",
|
|
427
|
+
entry: listEntry(experiment, loaded.objective),
|
|
428
|
+
record,
|
|
429
|
+
source: {
|
|
430
|
+
artifact: "experiments",
|
|
431
|
+
objective_id: objectiveId,
|
|
432
|
+
experiment_number: number,
|
|
433
|
+
selected: archive ? "immutable_archive" : retainedFull ? "retained_projection" : "legacy_summary_projection",
|
|
434
|
+
archive_verified: Boolean(archive),
|
|
435
|
+
projection_verified_against_archive: Boolean(archive && retainedFull),
|
|
436
|
+
provenance: provenance(experiment, loaded.objective),
|
|
437
|
+
},
|
|
438
|
+
source_contract: {
|
|
439
|
+
authority: AUTHORITY,
|
|
440
|
+
complete_for_experiment_retrieval: true,
|
|
441
|
+
detail_availability: experiment.detailAvailability,
|
|
442
|
+
compatibility_truth: experiment.detailAvailability === "summary" ? "summary_only_legacy_detail_is_not_reconstructed" : "full_detail_available",
|
|
443
|
+
storage_ownership: archive ? "immutable_objective_archive" : "bounded_objective_projection",
|
|
444
|
+
},
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
//# sourceMappingURL=experimentRetrieval.js.map
|