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
|
@@ -1,17 +1,24 @@
|
|
|
1
|
-
import { createHash } from "node:crypto";
|
|
1
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { isDeepStrictEqual } from "node:util";
|
|
5
5
|
import { splitLinesKeepEnds, unifiedDiff } from "../../core/difflib.js";
|
|
6
6
|
import { dumpYamlMapping, loadYamlMapping } from "../../core/yaml.js";
|
|
7
|
-
import { lintFullArtifactPayload } from "../../cli/commands/lint.js";
|
|
8
7
|
import { compactYamlFile, decisionProtectedOverflowCount, } from "../../hooks/compaction/index.js";
|
|
9
8
|
import { assertRealpathBoundary, loadArtifactRegistry, resolveArtifactPath, } from "../../registries/artifactRegistry.js";
|
|
10
|
-
import {
|
|
9
|
+
import { withStateMutation, } from "./mutation.js";
|
|
11
10
|
import { localDate, localTimestamp, nextEntryNumber, nextTaskNumber } from "./assign.js";
|
|
12
11
|
import { reject } from "./errors.js";
|
|
12
|
+
import { findByNumber, schemaViolation } from "./helpers.js";
|
|
13
13
|
import { validateArtifactBytes } from "./validate.js";
|
|
14
|
+
import { planEvaluationViolations, validatePlanCreateInput, validatePlanPublicationCandidate, } from "./planPublication.js";
|
|
14
15
|
import { dependencyReadyTasks } from "../../cli/capabilityContext/planState.js";
|
|
16
|
+
import { publishImmutableFile, } from "../archivePublication.js";
|
|
17
|
+
import { findArchivedReplay, isExactArchiveReplay, recoverArchivedEntry, } from "../archiveReplay.js";
|
|
18
|
+
import { discoverNumberedArchives } from "../archiveDiscovery.js";
|
|
19
|
+
import { hydrateDecisionRecords, updateDecisionOverlay, } from "../decisionOverlay.js";
|
|
20
|
+
import { repairHealthDuplicates, repairHealthProjectionBytes } from "../healthRepair.js";
|
|
21
|
+
import { executeExperimentPublication } from "./experimentPublication.js";
|
|
15
22
|
function mapping(value) {
|
|
16
23
|
return value && typeof value === "object" && !Array.isArray(value)
|
|
17
24
|
? value
|
|
@@ -22,25 +29,6 @@ function array(doc, key) {
|
|
|
22
29
|
? doc[key].filter((v) => Boolean(v && typeof v === "object" && !Array.isArray(v)))
|
|
23
30
|
: [];
|
|
24
31
|
}
|
|
25
|
-
function setNested(target, field, value) {
|
|
26
|
-
const parts = field.split(".");
|
|
27
|
-
let cursor = target;
|
|
28
|
-
for (const part of parts.slice(0, -1)) {
|
|
29
|
-
if (!cursor[part] || typeof cursor[part] !== "object" || Array.isArray(cursor[part]))
|
|
30
|
-
cursor[part] = {};
|
|
31
|
-
cursor = cursor[part];
|
|
32
|
-
}
|
|
33
|
-
cursor[parts.at(-1)] = value;
|
|
34
|
-
}
|
|
35
|
-
function project(entry, payload) {
|
|
36
|
-
const result = {};
|
|
37
|
-
for (const [key, expected] of Object.entries(payload)) {
|
|
38
|
-
const actual = mappingPath(entry, key);
|
|
39
|
-
if (expected !== undefined)
|
|
40
|
-
setNested(result, key, actual);
|
|
41
|
-
}
|
|
42
|
-
return result;
|
|
43
|
-
}
|
|
44
32
|
function mappingPath(entry, field) {
|
|
45
33
|
let value = entry;
|
|
46
34
|
for (const part of field.split(".")) {
|
|
@@ -50,9 +38,6 @@ function mappingPath(entry, field) {
|
|
|
50
38
|
}
|
|
51
39
|
return value;
|
|
52
40
|
}
|
|
53
|
-
function exactReplay(entry, payload) {
|
|
54
|
-
return Boolean(entry && isDeepStrictEqual(project(entry, payload), payload));
|
|
55
|
-
}
|
|
56
41
|
function readExisting(target) {
|
|
57
42
|
if (!fs.existsSync(target))
|
|
58
43
|
return { doc: {}, bytes: "" };
|
|
@@ -116,41 +101,28 @@ function normalizedDecisionPayload(values) {
|
|
|
116
101
|
];
|
|
117
102
|
return payload;
|
|
118
103
|
}
|
|
119
|
-
function
|
|
120
|
-
reject({ class: "schema_violation", message: violations[0], violations });
|
|
121
|
-
}
|
|
122
|
-
function validatePlanPublicationCandidate(bytes) {
|
|
123
|
-
const lint = lintFullArtifactPayload("plan", bytes);
|
|
124
|
-
const lintViolations = lint.checks
|
|
125
|
-
.filter((check) => check.status === "fail")
|
|
126
|
-
.map((check) => `strict prose lint ${check.name}: ${check.detail}; action: ${check.action}`);
|
|
127
|
-
const schemaViolations = validateArtifactBytes("plan", bytes).map((violation) => `schema validation: ${violation}`);
|
|
128
|
-
const violations = [...lintViolations, ...schemaViolations];
|
|
129
|
-
if (violations.length === 0)
|
|
130
|
-
return;
|
|
131
|
-
reject({
|
|
132
|
-
class: "schema_violation",
|
|
133
|
-
message: "plan publication candidate failed strict prose lint or schema validation; correct the reported violations and retry",
|
|
134
|
-
violations,
|
|
135
|
-
syntax: "agentera state plan create --input plan.yaml --format json",
|
|
136
|
-
example: "agentera state plan create --input plan.yaml --format json",
|
|
137
|
-
});
|
|
138
|
-
}
|
|
139
|
-
function findByNumber(entries, number) {
|
|
140
|
-
return entries.find((entry) => Number(entry.number) === number);
|
|
141
|
-
}
|
|
142
|
-
function mutateCandidate(req, doc) {
|
|
104
|
+
function mutateCandidate(req, doc, recovered) {
|
|
143
105
|
const candidate = structuredClone(doc);
|
|
144
106
|
if (req.artifact === "progress") {
|
|
145
107
|
const payload = req.callerPayload;
|
|
146
108
|
const entries = array(candidate, "cycles");
|
|
147
|
-
|
|
109
|
+
const replay = entries.find((entry) => isExactArchiveReplay(entry, payload));
|
|
110
|
+
if (replay)
|
|
111
|
+
return {
|
|
112
|
+
candidate,
|
|
113
|
+
written: replay,
|
|
114
|
+
assigned: { number: replay.number, timestamp: replay.timestamp },
|
|
115
|
+
replay: true,
|
|
116
|
+
};
|
|
117
|
+
if (recovered) {
|
|
118
|
+
recoverArchivedEntry(candidate, "cycles", entries, recovered, true);
|
|
148
119
|
return {
|
|
149
120
|
candidate,
|
|
150
|
-
written:
|
|
151
|
-
assigned: { number:
|
|
121
|
+
written: recovered,
|
|
122
|
+
assigned: { number: recovered.number, timestamp: recovered.timestamp },
|
|
152
123
|
replay: true,
|
|
153
124
|
};
|
|
125
|
+
}
|
|
154
126
|
const entry = buildProgress(candidate, req.values);
|
|
155
127
|
candidate.cycles = [entry, ...entries];
|
|
156
128
|
return {
|
|
@@ -163,8 +135,9 @@ function mutateCandidate(req, doc) {
|
|
|
163
135
|
if (req.artifact === "decisions" && req.spec.verb === "append") {
|
|
164
136
|
const entries = array(candidate, "decisions");
|
|
165
137
|
const payload = normalizedDecisionPayload(req.callerPayload);
|
|
166
|
-
|
|
167
|
-
|
|
138
|
+
const replay = entries.find((entry) => isExactArchiveReplay(entry, payload));
|
|
139
|
+
if (replay) {
|
|
140
|
+
const entry = replay;
|
|
168
141
|
return {
|
|
169
142
|
candidate,
|
|
170
143
|
written: entry,
|
|
@@ -172,14 +145,17 @@ function mutateCandidate(req, doc) {
|
|
|
172
145
|
replay: true,
|
|
173
146
|
};
|
|
174
147
|
}
|
|
148
|
+
if (recovered) {
|
|
149
|
+
recoverArchivedEntry(candidate, "decisions", entries, recovered, false);
|
|
150
|
+
return {
|
|
151
|
+
candidate,
|
|
152
|
+
written: recovered,
|
|
153
|
+
assigned: { number: recovered.number, date: recovered.date },
|
|
154
|
+
replay: true,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
175
157
|
const entry = buildDecision(candidate, req.values);
|
|
176
158
|
candidate.decisions = [...entries, entry];
|
|
177
|
-
if (decisionProtectedOverflowCount(candidate.decisions, Array.isArray(candidate.archive) ? candidate.archive : []) > 0) {
|
|
178
|
-
reject({
|
|
179
|
-
class: "conflict",
|
|
180
|
-
message: "decision append would exceed protected review capacity; review decision satisfaction first",
|
|
181
|
-
});
|
|
182
|
-
}
|
|
183
159
|
return {
|
|
184
160
|
candidate,
|
|
185
161
|
written: entry,
|
|
@@ -187,40 +163,17 @@ function mutateCandidate(req, doc) {
|
|
|
187
163
|
replay: false,
|
|
188
164
|
};
|
|
189
165
|
}
|
|
190
|
-
if (req.artifact === "decisions") {
|
|
191
|
-
const entries = array(candidate, "decisions");
|
|
192
|
-
const archived = array(candidate, "archive");
|
|
193
|
-
const number = Number(req.values.number);
|
|
194
|
-
const entry = findByNumber(entries, number) ?? findByNumber(archived, number);
|
|
195
|
-
if (!entry)
|
|
196
|
-
reject({
|
|
197
|
-
class: "unsupported_target",
|
|
198
|
-
message: `no decision with number ${number}; highest is ${Math.max(0, ...entries.map((e) => Number(e.number) || 0), ...archived.map((e) => Number(e.number) || 0))}`,
|
|
199
|
-
});
|
|
200
|
-
const satisfaction = mapping(req.values.satisfaction);
|
|
201
|
-
if (satisfaction.state === "provisionally_satisfied" &&
|
|
202
|
-
!String(satisfaction.evidence ?? "").trim()) {
|
|
203
|
-
reject({
|
|
204
|
-
class: "schema_violation",
|
|
205
|
-
message: "provisionally_satisfied requires non-empty --satisfaction-evidence",
|
|
206
|
-
});
|
|
207
|
-
}
|
|
208
|
-
if (satisfaction.state === "user_confirmed_satisfied") {
|
|
209
|
-
const confirmation = mapping(satisfaction.user_confirmation);
|
|
210
|
-
if (!String(confirmation.confirmed_by ?? "").trim() ||
|
|
211
|
-
!String(confirmation.confirmed_at ?? "").trim()) {
|
|
212
|
-
reject({
|
|
213
|
-
class: "schema_violation",
|
|
214
|
-
message: "user_confirmed_satisfied requires --confirmed-by and --confirmed-at",
|
|
215
|
-
});
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
if (isDeepStrictEqual(entry.satisfaction, satisfaction))
|
|
219
|
-
return { candidate, written: entry, assigned: { number }, replay: true };
|
|
220
|
-
entry.satisfaction = satisfaction;
|
|
221
|
-
return { candidate, written: entry, assigned: { number }, replay: false };
|
|
222
|
-
}
|
|
223
166
|
if (req.artifact === "health") {
|
|
167
|
+
if (req.spec.verb === "repair") {
|
|
168
|
+
if (!req.force)
|
|
169
|
+
reject({ class: "conflict", message: "health repair requires --force; use --dry-run to preview the edit" });
|
|
170
|
+
const number = Number(req.values.number);
|
|
171
|
+
const keep = req.values.keep === "last" ? "last" : "first";
|
|
172
|
+
const repaired = repairHealthDuplicates(candidate, number, keep);
|
|
173
|
+
if (repaired.removed === 0)
|
|
174
|
+
reject({ class: "unsupported_target", message: `health audit ${number} has no duplicate current-history rows to repair` });
|
|
175
|
+
return { candidate: repaired.candidate, written: { number, removed_rows: repaired.removed, retained: keep }, assigned: { number, removed_rows: repaired.removed }, replay: false };
|
|
176
|
+
}
|
|
224
177
|
if (req.input && ("audits" in req.input || "archive" in req.input)) {
|
|
225
178
|
reject({
|
|
226
179
|
class: "schema_violation",
|
|
@@ -230,8 +183,9 @@ function mutateCandidate(req, doc) {
|
|
|
230
183
|
}
|
|
231
184
|
const entries = array(candidate, "audits");
|
|
232
185
|
const payload = req.callerPayload;
|
|
233
|
-
|
|
234
|
-
|
|
186
|
+
const replay = entries.find((entry) => isExactArchiveReplay(entry, payload));
|
|
187
|
+
if (replay) {
|
|
188
|
+
const entry = replay;
|
|
235
189
|
return {
|
|
236
190
|
candidate,
|
|
237
191
|
written: entry,
|
|
@@ -239,6 +193,15 @@ function mutateCandidate(req, doc) {
|
|
|
239
193
|
replay: true,
|
|
240
194
|
};
|
|
241
195
|
}
|
|
196
|
+
if (recovered) {
|
|
197
|
+
recoverArchivedEntry(candidate, "audits", entries, recovered, false);
|
|
198
|
+
return {
|
|
199
|
+
candidate,
|
|
200
|
+
written: recovered,
|
|
201
|
+
assigned: { number: recovered.number, date: recovered.date },
|
|
202
|
+
replay: true,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
242
205
|
const entry = {
|
|
243
206
|
number: nextEntryNumber(candidate, "audits"),
|
|
244
207
|
date: req.input?.date ?? localDate(),
|
|
@@ -257,7 +220,7 @@ function mutateCandidate(req, doc) {
|
|
|
257
220
|
const sameName = tasks.find((task) => task.name === req.values.name);
|
|
258
221
|
const payload = req.callerPayload;
|
|
259
222
|
if (sameName) {
|
|
260
|
-
if (
|
|
223
|
+
if (isExactArchiveReplay(sameName, payload))
|
|
261
224
|
return {
|
|
262
225
|
candidate,
|
|
263
226
|
written: sameName,
|
|
@@ -416,7 +379,9 @@ function activeKey(artifact) {
|
|
|
416
379
|
? "decisions"
|
|
417
380
|
: artifact === "health"
|
|
418
381
|
? "audits"
|
|
419
|
-
: "
|
|
382
|
+
: artifact === "experiments"
|
|
383
|
+
? "experiments"
|
|
384
|
+
: "tasks";
|
|
420
385
|
}
|
|
421
386
|
function stateSlice(artifact, doc, archivePath) {
|
|
422
387
|
if (artifact === "plan") {
|
|
@@ -435,6 +400,7 @@ function stateSlice(artifact, doc, archivePath) {
|
|
|
435
400
|
active_count: active.length,
|
|
436
401
|
archive_count: archive.length,
|
|
437
402
|
next_number: nextEntryNumber(doc, activeKey(artifact)),
|
|
403
|
+
...(archivePath ? { archive_path: archivePath } : {}),
|
|
438
404
|
};
|
|
439
405
|
}
|
|
440
406
|
function diffText(before, after, target) {
|
|
@@ -468,6 +434,13 @@ function archivePathFor(projectRoot, target, doc, bytes) {
|
|
|
468
434
|
assertWriterBoundary(projectRoot, archivePath, "plan archive");
|
|
469
435
|
return archivePath;
|
|
470
436
|
}
|
|
437
|
+
function decisionRecordForUpdate(projectRoot, doc, number) {
|
|
438
|
+
const current = findByNumber(array(doc, "decisions"), number) ?? findByNumber(array(doc, "archive"), number);
|
|
439
|
+
if (current)
|
|
440
|
+
return current;
|
|
441
|
+
const archived = discoverNumberedArchives(projectRoot).entries.find((entry) => entry.artifactId === "decisions" && entry.entryNumber === number);
|
|
442
|
+
return archived?.record ?? null;
|
|
443
|
+
}
|
|
471
444
|
function latestArchivePath(archiveDirectory) {
|
|
472
445
|
if (!fs.existsSync(archiveDirectory))
|
|
473
446
|
return null;
|
|
@@ -478,26 +451,6 @@ function latestArchivePath(archiveDirectory) {
|
|
|
478
451
|
.at(-1);
|
|
479
452
|
return latest ? path.join(archiveDirectory, latest) : null;
|
|
480
453
|
}
|
|
481
|
-
function validatePlanCreateInput(input) {
|
|
482
|
-
const tasks = array(input, "tasks");
|
|
483
|
-
const numbers = tasks.map((task) => Number(task.number));
|
|
484
|
-
const expected = tasks.map((_, index) => index + 1);
|
|
485
|
-
if (!isDeepStrictEqual(numbers, expected)) {
|
|
486
|
-
reject({
|
|
487
|
-
class: "schema_violation",
|
|
488
|
-
message: "plan create task numbers must be unique and sequential starting from 1",
|
|
489
|
-
violations: ["PV1: task numbers must equal 1..N"],
|
|
490
|
-
});
|
|
491
|
-
}
|
|
492
|
-
if (mapping(input.header).status === "complete" &&
|
|
493
|
-
tasks.some((task) => task.status !== "complete")) {
|
|
494
|
-
reject({
|
|
495
|
-
class: "schema_violation",
|
|
496
|
-
message: "a complete plan cannot contain incomplete tasks",
|
|
497
|
-
violations: ["header.status complete requires every task complete"],
|
|
498
|
-
});
|
|
499
|
-
}
|
|
500
|
-
}
|
|
501
454
|
function canonicalPlanDocumentForWrite(doc) {
|
|
502
455
|
const header = mapping(doc.header);
|
|
503
456
|
const status = String(header.status ?? "");
|
|
@@ -525,48 +478,15 @@ function fsyncDirectory(directory) {
|
|
|
525
478
|
}
|
|
526
479
|
}
|
|
527
480
|
function ensureArchive(archivePath, bytes) {
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
let stageCreated = false;
|
|
532
|
-
let fd;
|
|
533
|
-
try {
|
|
534
|
-
fd = fs.openSync(stage, "wx");
|
|
535
|
-
stageCreated = true;
|
|
536
|
-
fs.writeFileSync(fd, bytes, "utf8");
|
|
537
|
-
fs.fsyncSync(fd);
|
|
538
|
-
fs.closeSync(fd);
|
|
539
|
-
fd = undefined;
|
|
540
|
-
try {
|
|
541
|
-
fs.linkSync(stage, archivePath);
|
|
542
|
-
fsyncDirectory(directory);
|
|
543
|
-
return true;
|
|
544
|
-
}
|
|
545
|
-
catch (error) {
|
|
546
|
-
if (error.code !== "EEXIST")
|
|
547
|
-
throw error;
|
|
548
|
-
if (fs.readFileSync(archivePath, "utf8") !== bytes) {
|
|
481
|
+
return publishImmutableFile(archivePath, bytes, {
|
|
482
|
+
onExisting: () => {
|
|
483
|
+
if (fs.readFileSync(archivePath, "utf8") !== bytes)
|
|
549
484
|
reject({
|
|
550
485
|
class: "conflict",
|
|
551
486
|
message: `archive path '${archivePath}' already exists with different content; historical archives are immutable`,
|
|
552
487
|
});
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
}
|
|
556
|
-
}
|
|
557
|
-
finally {
|
|
558
|
-
if (fd !== undefined)
|
|
559
|
-
fs.closeSync(fd);
|
|
560
|
-
if (stageCreated) {
|
|
561
|
-
try {
|
|
562
|
-
fs.unlinkSync(stage);
|
|
563
|
-
fsyncDirectory(directory);
|
|
564
|
-
}
|
|
565
|
-
catch {
|
|
566
|
-
// A published archive is authoritative; a leftover private stage is ignored on retry.
|
|
567
|
-
}
|
|
568
|
-
}
|
|
569
|
-
}
|
|
488
|
+
},
|
|
489
|
+
});
|
|
570
490
|
}
|
|
571
491
|
function publishStagedPlan(stage, target) {
|
|
572
492
|
fs.renameSync(stage, target);
|
|
@@ -605,10 +525,13 @@ function planLifecycle(req, target, existing, legacyLifecycle = false) {
|
|
|
605
525
|
message: `active plan "${mapping(existing.doc.header).title ?? existing.doc.title ?? "untitled"}" has status ${status || "unknown"} or incomplete tasks; archiving would discard incomplete work`,
|
|
606
526
|
syntax: "agentera state plan archive --force",
|
|
607
527
|
});
|
|
608
|
-
const
|
|
609
|
-
const
|
|
528
|
+
const preserveExactBytes = req.force && !legacyLifecycle;
|
|
529
|
+
const archiveDoc = preserveExactBytes ? existing.doc : canonicalPlanDocumentForArchive(existing.doc);
|
|
530
|
+
const archiveBytes = preserveExactBytes ? existing.bytes : dumpYamlMapping(archiveDoc);
|
|
610
531
|
const archivePath = archivePathFor(req.projectRoot, target, archiveDoc, archiveBytes);
|
|
611
|
-
validatePlanPublicationCandidate(archiveBytes
|
|
532
|
+
const predecessorValidation = validatePlanPublicationCandidate(archiveBytes, {
|
|
533
|
+
allowHistoricalBudgetOverflow: req.force,
|
|
534
|
+
});
|
|
612
535
|
if (!req.dryRun) {
|
|
613
536
|
// Archive creation and current-plan removal cannot be one filesystem transaction.
|
|
614
537
|
// The durable archive is published first; retries remove the current plan only after
|
|
@@ -621,6 +544,7 @@ function planLifecycle(req, target, existing, legacyLifecycle = false) {
|
|
|
621
544
|
diff: diffText(existing.bytes, "", target),
|
|
622
545
|
before: existing.doc,
|
|
623
546
|
after: {},
|
|
547
|
+
validationDiagnostics: predecessorValidation.diagnostics,
|
|
624
548
|
});
|
|
625
549
|
}
|
|
626
550
|
const input = structuredClone(req.input ?? {});
|
|
@@ -634,10 +558,23 @@ function planLifecycle(req, target, existing, legacyLifecycle = false) {
|
|
|
634
558
|
const oldStatus = String(mapping(existing.doc.header).status ?? existing.doc.status ?? "");
|
|
635
559
|
const existingWithoutLineage = structuredClone(existing.doc);
|
|
636
560
|
delete existingWithoutLineage.previous_plan_archived;
|
|
561
|
+
const existingHeader = mapping(existingWithoutLineage.header);
|
|
562
|
+
delete existingHeader.id;
|
|
563
|
+
existingWithoutLineage.header = existingHeader;
|
|
637
564
|
const sameWithoutLineage = isDeepStrictEqual(existingWithoutLineage, input);
|
|
638
565
|
if (sameWithoutLineage && !legacyLifecycle) {
|
|
639
|
-
|
|
640
|
-
|
|
566
|
+
const predecessorBytes = req.force ? existing.bytes : dumpYamlMapping(existing.doc);
|
|
567
|
+
const predecessorValidation = validatePlanPublicationCandidate(predecessorBytes, {
|
|
568
|
+
allowHistoricalBudgetOverflow: req.force,
|
|
569
|
+
});
|
|
570
|
+
return envelope(req, target, existing.doc, mapping(existing.doc.header), {}, true, null, req.dryRun
|
|
571
|
+
? {
|
|
572
|
+
diff: "",
|
|
573
|
+
before: existing.doc,
|
|
574
|
+
after: existing.doc,
|
|
575
|
+
validationDiagnostics: predecessorValidation.diagnostics,
|
|
576
|
+
}
|
|
577
|
+
: { validationDiagnostics: predecessorValidation.diagnostics });
|
|
641
578
|
}
|
|
642
579
|
if (!sameWithoutLineage) {
|
|
643
580
|
if (oldStatus !== "complete" && !req.force)
|
|
@@ -645,14 +582,16 @@ function planLifecycle(req, target, existing, legacyLifecycle = false) {
|
|
|
645
582
|
class: "conflict",
|
|
646
583
|
message: `active plan "${mapping(existing.doc.header).title ?? existing.doc.title ?? "untitled"}" has status ${oldStatus || "unknown"}; replacing it would discard incomplete work`,
|
|
647
584
|
});
|
|
648
|
-
predecessorBytes = dumpYamlMapping(existing.doc);
|
|
585
|
+
predecessorBytes = req.force && !legacyLifecycle ? existing.bytes : dumpYamlMapping(existing.doc);
|
|
649
586
|
archivePath = archivePathFor(req.projectRoot, target, existing.doc, predecessorBytes);
|
|
650
587
|
input.previous_plan_archived = path.relative(req.projectRoot, archivePath);
|
|
651
588
|
}
|
|
652
589
|
}
|
|
590
|
+
input.header = { ...mapping(input.header), id: `plan:${randomUUID()}` };
|
|
653
591
|
const bytes = dumpYamlMapping(input);
|
|
654
|
-
|
|
655
|
-
validatePlanPublicationCandidate(predecessorBytes)
|
|
592
|
+
const predecessorValidation = predecessorBytes
|
|
593
|
+
? validatePlanPublicationCandidate(predecessorBytes, { allowHistoricalBudgetOverflow: req.force })
|
|
594
|
+
: { diagnostics: [] };
|
|
656
595
|
validatePlanPublicationCandidate(bytes);
|
|
657
596
|
const finalDoc = loadYamlMapping(bytes);
|
|
658
597
|
if (!req.dryRun) {
|
|
@@ -679,6 +618,7 @@ function planLifecycle(req, target, existing, legacyLifecycle = false) {
|
|
|
679
618
|
diff: diffText(existing.bytes, bytes, target),
|
|
680
619
|
before: existing.doc,
|
|
681
620
|
after: finalDoc,
|
|
621
|
+
validationDiagnostics: predecessorValidation.diagnostics,
|
|
682
622
|
});
|
|
683
623
|
}
|
|
684
624
|
function envelope(req, target, finalDoc, written, assigned, replay, compaction, extra = {}) {
|
|
@@ -697,9 +637,15 @@ function envelope(req, target, finalDoc, written, assigned, replay, compaction,
|
|
|
697
637
|
assigned,
|
|
698
638
|
written,
|
|
699
639
|
state: stateSlice(req.artifact, finalDoc, extra.archivePath),
|
|
700
|
-
validation: {
|
|
640
|
+
validation: {
|
|
641
|
+
status: "pass",
|
|
642
|
+
violations: [],
|
|
643
|
+
...(extra.validationDiagnostics && extra.validationDiagnostics.length > 0
|
|
644
|
+
? { diagnostics: extra.validationDiagnostics }
|
|
645
|
+
: {}),
|
|
646
|
+
},
|
|
701
647
|
compaction: compaction && req.artifact === "decisions"
|
|
702
|
-
? { ...compaction, protected_overflow_count: 0 }
|
|
648
|
+
? { ...compaction, protected_overflow_count: extra.protectedOverflowCount ?? 0 }
|
|
703
649
|
: compaction,
|
|
704
650
|
};
|
|
705
651
|
if (req.dryRun && extra.diff !== undefined) {
|
|
@@ -709,79 +655,146 @@ function envelope(req, target, finalDoc, written, assigned, replay, compaction,
|
|
|
709
655
|
}
|
|
710
656
|
return result;
|
|
711
657
|
}
|
|
712
|
-
export function executeStateWrite(req) {
|
|
658
|
+
export function executeStateWrite(req, options = {}) {
|
|
713
659
|
assertWriterBoundary(req.projectRoot, path.join(req.projectRoot, ".agentera"), "writer lock");
|
|
714
660
|
const planPublication = req.artifact === "plan" && ["archive", "create"].includes(req.spec.verb);
|
|
715
|
-
|
|
661
|
+
if (req.dryRun && planPublication)
|
|
662
|
+
return executeStateWriteUnlocked(req);
|
|
663
|
+
return withStateMutation(req.projectRoot, (transaction) => executeStateWriteUnlocked(req, transaction), options);
|
|
664
|
+
}
|
|
665
|
+
function executeStateWriteUnlocked(req, transaction) {
|
|
666
|
+
if (req.artifact === "experiments" && req.spec.verb === "publish")
|
|
667
|
+
return executeExperimentPublication(req, transaction);
|
|
668
|
+
const record = loadArtifactRegistry().get(req.artifact);
|
|
669
|
+
if (!record)
|
|
670
|
+
throw new Error(`artifact '${req.artifact}' is not registered`);
|
|
671
|
+
let target;
|
|
716
672
|
try {
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
673
|
+
target = resolveArtifactPath(record, req.projectRoot, { strictWrite: true });
|
|
674
|
+
}
|
|
675
|
+
catch (error) {
|
|
676
|
+
const message = error.message;
|
|
677
|
+
if (message.startsWith("artifact '"))
|
|
678
|
+
reject({ class: "unsupported_target", message });
|
|
679
|
+
throw error;
|
|
680
|
+
}
|
|
681
|
+
const existing = readExisting(target);
|
|
682
|
+
const legacyLifecycle = req.artifact === "plan" && ["active", "completed"].includes(String(mapping(existing.doc.header).status ?? ""));
|
|
683
|
+
if (legacyLifecycle)
|
|
684
|
+
existing.doc = canonicalPlanDocumentForWrite(existing.doc);
|
|
685
|
+
if (existing.bytes) {
|
|
686
|
+
const validationBytes = req.artifact === "plan" ? dumpYamlMapping(existing.doc) : existing.bytes;
|
|
687
|
+
const violations = validateArtifactBytes(req.artifact, validationBytes);
|
|
688
|
+
if (violations.length) {
|
|
689
|
+
throw new Error(`existing artifact '${target}' is schema-invalid: ${violations.join("; ")}; repair it before retrying`);
|
|
729
690
|
}
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
if (existing.bytes) {
|
|
735
|
-
const validationBytes = req.artifact === "plan" ? dumpYamlMapping(existing.doc) : existing.bytes;
|
|
736
|
-
const violations = validateArtifactBytes(req.artifact, validationBytes);
|
|
737
|
-
if (violations.length) {
|
|
738
|
-
throw new Error(`existing artifact '${target}' is schema-invalid: ${violations.join("; ")}; repair it before retrying`);
|
|
739
|
-
}
|
|
691
|
+
if (req.artifact === "plan") {
|
|
692
|
+
const evaluationViolations = planEvaluationViolations(existing.doc);
|
|
693
|
+
if (evaluationViolations.length > 0)
|
|
694
|
+
throw new Error(`existing artifact '${target}' is schema-invalid: ${evaluationViolations.join("; ")}; repair it before retrying`);
|
|
740
695
|
}
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
696
|
+
}
|
|
697
|
+
if (req.artifact === "decisions" && req.spec.verb === "update") {
|
|
698
|
+
const number = Number(req.values.number);
|
|
699
|
+
if (!Number.isSafeInteger(number) || number < 1)
|
|
700
|
+
reject({
|
|
701
|
+
class: "schema_violation",
|
|
702
|
+
message: "decision update requires a positive decision number",
|
|
703
|
+
syntax: "agentera state decisions update --number N --satisfaction-state STATE --format json",
|
|
704
|
+
example: "agentera state decisions update --number 53 --satisfaction-state open --format json",
|
|
705
|
+
});
|
|
706
|
+
const record = decisionRecordForUpdate(req.projectRoot, existing.doc, number);
|
|
707
|
+
if (!record)
|
|
744
708
|
reject({
|
|
745
709
|
class: "unsupported_target",
|
|
746
|
-
message:
|
|
747
|
-
|
|
710
|
+
message: `no decision with number ${number}; numbered archive or current projection record is required`,
|
|
711
|
+
syntax: "agentera state decisions update --number N --satisfaction-state STATE --format json",
|
|
712
|
+
example: "agentera state decisions update --number 53 --satisfaction-state open --format json",
|
|
748
713
|
});
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
const
|
|
753
|
-
const
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
714
|
+
if (!transaction)
|
|
715
|
+
throw new Error("state mutation transaction is unavailable");
|
|
716
|
+
const update = updateDecisionOverlay(req.projectRoot, number, req.values.satisfaction, record.satisfaction, transaction, !req.dryRun);
|
|
717
|
+
const before = dumpYamlMapping(update.before);
|
|
718
|
+
const after = dumpYamlMapping(update.after);
|
|
719
|
+
return envelope(req, update.path, existing.doc, { satisfaction: update.satisfaction }, { number }, update.replay, null, { diff: diffText(before, after, update.path), before: update.before, after: update.after });
|
|
720
|
+
}
|
|
721
|
+
if (req.artifact === "plan" && ["archive", "create"].includes(req.spec.verb))
|
|
722
|
+
return planLifecycle(req, target, existing, legacyLifecycle);
|
|
723
|
+
if (!existing.bytes && req.artifact === "plan")
|
|
724
|
+
reject({
|
|
725
|
+
class: "unsupported_target",
|
|
726
|
+
message: "no active plan exists; create one before appending a task",
|
|
727
|
+
example: "agentera state plan create --input plan.yaml",
|
|
728
|
+
});
|
|
729
|
+
const numberedAppend = req.spec.verb === "append" && ["progress", "decisions", "health"].includes(req.artifact);
|
|
730
|
+
const replayPayload = req.artifact === "decisions"
|
|
731
|
+
? normalizedDecisionPayload(req.callerPayload)
|
|
732
|
+
: req.callerPayload;
|
|
733
|
+
const mutated = mutateCandidate(req, existing.doc, numberedAppend ? findArchivedReplay(req.projectRoot, req.artifact, replayPayload) : undefined);
|
|
734
|
+
if (mutated.replay && isDeepStrictEqual(mutated.candidate, existing.doc)) {
|
|
735
|
+
let archive;
|
|
736
|
+
if (numberedAppend && !req.dryRun) {
|
|
737
|
+
if (!transaction)
|
|
738
|
+
throw new Error("state mutation transaction is unavailable");
|
|
739
|
+
archive = transaction.publishArchive(req.artifact, Number(mutated.written.number), mutated.written);
|
|
740
|
+
}
|
|
741
|
+
return envelope(req, target, mutated.candidate, mutated.written, mutated.assigned, true, null, {
|
|
742
|
+
...(archive ? { archivePath: archive.path } : {}),
|
|
743
|
+
...(req.dryRun ? { diff: "", before: existing.doc, after: mutated.candidate } : {}),
|
|
744
|
+
});
|
|
745
|
+
}
|
|
746
|
+
const repairedBytes = req.artifact === "health" && req.spec.verb === "repair"
|
|
747
|
+
? repairHealthProjectionBytes(existing.bytes, Number(req.values.number), req.values.keep === "last" ? "last" : "first")
|
|
748
|
+
: null;
|
|
749
|
+
if (repairedBytes && repairedBytes.removed === 0)
|
|
750
|
+
reject({ class: "unsupported_target", message: `health audit ${Number(req.values.number)} has no duplicate current-history rows to repair` });
|
|
751
|
+
const candidateBytes = repairedBytes?.bytes ?? dumpYamlMapping(mutated.candidate);
|
|
752
|
+
const candidateViolations = validateArtifactBytes(req.artifact, candidateBytes);
|
|
753
|
+
if (candidateViolations.length)
|
|
754
|
+
schemaViolation(candidateViolations);
|
|
755
|
+
const stage = req.dryRun
|
|
756
|
+
? stagingPath(req, target)
|
|
757
|
+
: transaction?.stageProjection(target, candidateBytes);
|
|
758
|
+
if (!stage)
|
|
759
|
+
throw new Error("state mutation transaction is unavailable");
|
|
760
|
+
let compaction = null;
|
|
761
|
+
const protectedOverflowCount = req.artifact === "decisions"
|
|
762
|
+
? decisionProtectedOverflowCount(hydrateDecisionRecords(array(mutated.candidate, "decisions"), req.projectRoot), hydrateDecisionRecords(array(mutated.candidate, "archive"), req.projectRoot))
|
|
763
|
+
: 0;
|
|
764
|
+
try {
|
|
765
|
+
if (req.dryRun)
|
|
759
766
|
fs.writeFileSync(stage, candidateBytes);
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
const finalBytes = fs.readFileSync(stage, "utf8");
|
|
763
|
-
const finalViolations = validateArtifactBytes(req.artifact, finalBytes);
|
|
764
|
-
if (finalViolations.length)
|
|
765
|
-
throw new Error(`writer/compactor invariant failure: ${finalViolations.join("; ")}`);
|
|
766
|
-
const finalDoc = loadYamlMapping(finalBytes);
|
|
767
|
-
const diff = diffText(existing.bytes, finalBytes, target);
|
|
768
|
-
if (req.dryRun) {
|
|
769
|
-
return envelope(req, target, finalDoc, mutated.written, mutated.assigned, false, compaction, { diff, before: existing.doc, after: finalDoc });
|
|
770
|
-
}
|
|
771
|
-
fs.renameSync(stage, target);
|
|
772
|
-
return envelope(req, target, finalDoc, mutated.written, mutated.assigned, false, compaction);
|
|
767
|
+
if (req.spec.compacts) {
|
|
768
|
+
compaction = compactYamlFile(stage, req.artifact, req.projectRoot);
|
|
773
769
|
}
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
}
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
770
|
+
const finalBytes = fs.readFileSync(stage, "utf8");
|
|
771
|
+
const finalViolations = validateArtifactBytes(req.artifact, finalBytes);
|
|
772
|
+
if (finalViolations.length)
|
|
773
|
+
throw new Error(`writer/compactor invariant failure: ${finalViolations.join("; ")}`);
|
|
774
|
+
if (!req.dryRun)
|
|
775
|
+
transaction?.syncStaged(stage);
|
|
776
|
+
const finalDoc = loadYamlMapping(finalBytes);
|
|
777
|
+
const diff = diffText(existing.bytes, finalBytes, target);
|
|
778
|
+
if (req.dryRun) {
|
|
779
|
+
return envelope(req, target, finalDoc, mutated.written, mutated.assigned, false, compaction, { diff, before: existing.doc, after: finalDoc, protectedOverflowCount });
|
|
781
780
|
}
|
|
781
|
+
const archive = numberedAppend
|
|
782
|
+
? transaction?.publishArchive(req.artifact, Number(mutated.written.number), mutated.written)
|
|
783
|
+
: undefined;
|
|
784
|
+
if (!req.dryRun)
|
|
785
|
+
transaction?.publishProjection(stage, target);
|
|
786
|
+
return envelope(req, target, finalDoc, mutated.written, mutated.assigned, mutated.replay, compaction, { ...(archive ? { archivePath: archive.path } : {}), protectedOverflowCount });
|
|
782
787
|
}
|
|
783
788
|
finally {
|
|
784
|
-
|
|
789
|
+
try {
|
|
790
|
+
if (req.dryRun)
|
|
791
|
+
fs.unlinkSync(stage);
|
|
792
|
+
else
|
|
793
|
+
transaction?.removeStage(stage);
|
|
794
|
+
}
|
|
795
|
+
catch {
|
|
796
|
+
/* stage published or absent */
|
|
797
|
+
}
|
|
785
798
|
}
|
|
786
799
|
}
|
|
787
800
|
//# sourceMappingURL=transaction.js.map
|