agentera 3.0.0-dev.32 → 3.0.0-dev.39
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bundle/CHANGELOG.md +75 -5
- package/bundle/references/adapters/package-publication.json +43 -0
- package/bundle/references/adapters/package-surface-characterization.md +12 -0
- package/bundle/references/analysis/evidence-tier-authority.yaml +23 -21
- package/bundle/references/analysis/verification-policy.yaml +1 -0
- package/bundle/references/artifacts/artifact-registry-interface-model.yaml +11 -2
- package/bundle/references/artifacts/glossary-entry-contract.yaml +1241 -0
- package/bundle/references/artifacts/state-storage-authority.yaml +149 -80
- package/bundle/references/cli/agent-ready-state-contract.yaml +84 -2
- package/bundle/references/cli/audience-namespace-cli-migration.yaml +12 -1
- package/bundle/references/cli/prime-consumer-compatibility.yaml +7 -1
- package/bundle/skills/agentera/SKILL.md +26 -26
- package/bundle/skills/agentera/capabilities/audit/schemas/validation.yaml +26 -8
- package/bundle/skills/agentera/capabilities/build/schemas/artifacts.yaml +21 -4
- package/bundle/skills/agentera/capabilities/build/schemas/exit.yaml +5 -4
- package/bundle/skills/agentera/capabilities/build/schemas/validation.yaml +67 -3
- package/bundle/skills/agentera/capabilities/discuss/schemas/artifacts.yaml +3 -2
- package/bundle/skills/agentera/capabilities/discuss/schemas/validation.yaml +64 -0
- package/bundle/skills/agentera/capabilities/plan/schemas/artifacts.yaml +4 -1
- package/bundle/skills/agentera/capabilities/plan/schemas/validation.yaml +30 -0
- package/bundle/skills/agentera/capabilities/profile/schemas/artifacts.yaml +5 -2
- package/bundle/skills/agentera/capabilities/profile/schemas/exit.yaml +3 -2
- package/bundle/skills/agentera/capabilities/profile/schemas/validation.yaml +30 -0
- package/bundle/skills/agentera/schemas/artifacts/glossary.yaml +82 -0
- package/bundle/skills/agentera/schemas/artifacts/plan.yaml +19 -4
- package/bundle/skills/agentera/schemas/artifacts/progress.yaml +92 -0
- package/dist/analytics/extractCorpus/evidenceTiers.js +46 -3
- package/dist/analytics/extractCorpus/evidenceTiers.js.map +1 -1
- package/dist/analytics/extractCorpus/tierReader.js +1 -1
- package/dist/analytics/extractCorpus/tierReader.js.map +1 -1
- package/dist/analytics/glossaryAdviceResolution.js +167 -0
- package/dist/analytics/glossaryAdviceResolution.js.map +1 -0
- package/dist/analytics/glossaryInputAcquisition.js +320 -0
- package/dist/analytics/glossaryInputAcquisition.js.map +1 -0
- package/dist/analytics/personalGlossaryAdmission.js +134 -0
- package/dist/analytics/personalGlossaryAdmission.js.map +1 -0
- package/dist/analytics/personalGlossaryProfile.js +219 -0
- package/dist/analytics/personalGlossaryProfile.js.map +1 -0
- package/dist/audit/terminologyDrift.js +282 -0
- package/dist/audit/terminologyDrift.js.map +1 -0
- package/dist/capabilities/audit/instructions.js +2 -1
- package/dist/capabilities/audit/instructions.js.map +1 -1
- package/dist/capabilities/build/instructions.js +41 -2
- package/dist/capabilities/build/instructions.js.map +1 -1
- package/dist/capabilities/discuss/instructions.js +14 -1
- package/dist/capabilities/discuss/instructions.js.map +1 -1
- package/dist/capabilities/index.js +1 -1
- package/dist/capabilities/index.js.map +1 -1
- package/dist/capabilities/orchestrate/instructions.js +2 -2
- package/dist/capabilities/plan/instructions.js +19 -2
- package/dist/capabilities/plan/instructions.js.map +1 -1
- package/dist/capabilities/profile/instructions.js +67 -1
- package/dist/capabilities/profile/instructions.js.map +1 -1
- package/dist/cli/capabilityContext/bespoke.js +4 -3
- package/dist/cli/capabilityContext/bespoke.js.map +1 -1
- package/dist/cli/capabilityContext/build.js +77 -24
- package/dist/cli/capabilityContext/build.js.map +1 -1
- package/dist/cli/capabilityContext/closeout.js +2 -2
- package/dist/cli/capabilityContext/closeout.js.map +1 -1
- package/dist/cli/capabilityContext/evidence.js +2 -6
- package/dist/cli/capabilityContext/evidence.js.map +1 -1
- package/dist/cli/capabilityContext/planState.js +59 -78
- package/dist/cli/capabilityContext/planState.js.map +1 -1
- package/dist/cli/capabilityContext/projectVerification.js +410 -0
- package/dist/cli/capabilityContext/projectVerification.js.map +1 -0
- package/dist/cli/capabilityContext/startup.js +21 -4
- package/dist/cli/capabilityContext/startup.js.map +1 -1
- package/dist/cli/commands/glossaryAdvice.js +139 -0
- package/dist/cli/commands/glossaryAdvice.js.map +1 -0
- package/dist/cli/commands/personalGlossary.js +177 -0
- package/dist/cli/commands/personalGlossary.js.map +1 -0
- package/dist/cli/commands/prime/buildExecutionRequest.js +165 -0
- package/dist/cli/commands/prime/buildExecutionRequest.js.map +1 -0
- package/dist/cli/commands/prime/collectEntityOrientation.js +18 -2
- package/dist/cli/commands/prime/collectEntityOrientation.js.map +1 -1
- package/dist/cli/commands/prime/collectOrientationState.js +7 -1
- package/dist/cli/commands/prime/collectOrientationState.js.map +1 -1
- package/dist/cli/commands/prime/orientationOutput.js +12 -2
- package/dist/cli/commands/prime/orientationOutput.js.map +1 -1
- package/dist/cli/commands/prime.js +37 -2
- package/dist/cli/commands/prime.js.map +1 -1
- package/dist/cli/commands/profileGrounding.js +77 -0
- package/dist/cli/commands/profileGrounding.js.map +1 -0
- package/dist/cli/commands/query.js +14 -4
- package/dist/cli/commands/query.js.map +1 -1
- package/dist/cli/commands/schema.js +10 -0
- package/dist/cli/commands/schema.js.map +1 -1
- package/dist/cli/commands/state/write.js +4 -1
- package/dist/cli/commands/state/write.js.map +1 -1
- package/dist/cli/dispatch/lifecycle.js +45 -0
- package/dist/cli/dispatch/lifecycle.js.map +1 -1
- package/dist/cli/dispatch/prime.js +22 -1
- package/dist/cli/dispatch/prime.js.map +1 -1
- package/dist/cli/dispatch/shared.js +1 -1
- package/dist/cli/dispatch/shared.js.map +1 -1
- package/dist/cli/help.js +23 -1
- package/dist/cli/help.js.map +1 -1
- package/dist/cli/migrationRequired.js +2 -0
- package/dist/cli/migrationRequired.js.map +1 -1
- package/dist/cli/orientation/attention.js +3 -1
- package/dist/cli/orientation/attention.js.map +1 -1
- package/dist/cli/orientation.js +2 -2
- package/dist/cli/orientation.js.map +1 -1
- package/dist/registries/artifactProtocolIds.js +2 -0
- package/dist/registries/artifactProtocolIds.js.map +1 -1
- package/dist/registries/artifactRegistry.js +2 -0
- package/dist/registries/artifactRegistry.js.map +1 -1
- package/dist/registries/evidenceTierContract.js +26 -16
- package/dist/registries/evidenceTierContract.js.map +1 -1
- package/dist/registries/glossaryAdviceContract.js +79 -0
- package/dist/registries/glossaryAdviceContract.js.map +1 -0
- package/dist/registries/glossaryCaveatContract.js +68 -0
- package/dist/registries/glossaryCaveatContract.js.map +1 -0
- package/dist/registries/glossaryConsumerContractValidation.js +792 -0
- package/dist/registries/glossaryConsumerContractValidation.js.map +1 -0
- package/dist/registries/glossaryEntryContract.js +747 -0
- package/dist/registries/glossaryEntryContract.js.map +1 -0
- package/dist/registries/glossaryTermIdentity.js +18 -0
- package/dist/registries/glossaryTermIdentity.js.map +1 -0
- package/dist/registries/glossaryTermOccurrence.js +21 -0
- package/dist/registries/glossaryTermOccurrence.js.map +1 -0
- package/dist/state/changelog.js +222 -0
- package/dist/state/changelog.js.map +1 -0
- package/dist/state/decisionEntities.js +18 -11
- package/dist/state/decisionEntities.js.map +1 -1
- package/dist/state/decisionMigrationProvenance.js +84 -1
- package/dist/state/decisionMigrationProvenance.js.map +1 -1
- package/dist/state/decisionOverlay.js +9 -32
- package/dist/state/decisionOverlay.js.map +1 -1
- package/dist/state/decisionRevision.js +69 -444
- package/dist/state/decisionRevision.js.map +1 -1
- package/dist/state/decisionRevisionMigration.js +90 -0
- package/dist/state/decisionRevisionMigration.js.map +1 -0
- package/dist/state/directRetrieval.js +3 -52
- package/dist/state/directRetrieval.js.map +1 -1
- package/dist/state/entityCutover.js +6 -6
- package/dist/state/entityCutover.js.map +1 -1
- package/dist/state/entityGlossaryAuthority.js +14 -0
- package/dist/state/entityGlossaryAuthority.js.map +1 -0
- package/dist/state/entityMigrationIdentity.js +6 -0
- package/dist/state/entityMigrationIdentity.js.map +1 -0
- package/dist/state/entityMigrationPreview.js +14 -23
- package/dist/state/entityMigrationPreview.js.map +1 -1
- package/dist/state/entityPublicationContext.js +374 -71
- package/dist/state/entityPublicationContext.js.map +1 -1
- package/dist/state/entityStorage.js +45 -29
- package/dist/state/entityStorage.js.map +1 -1
- package/dist/state/healthEntities.js +2 -4
- package/dist/state/healthEntities.js.map +1 -1
- package/dist/state/listRetrieval.js +7 -38
- package/dist/state/listRetrieval.js.map +1 -1
- package/dist/state/migrationSourceBinding.js +1 -1
- package/dist/state/migrationSourceBinding.js.map +1 -1
- package/dist/state/objectiveExperimentEntities.js +9 -9
- package/dist/state/objectiveExperimentEntities.js.map +1 -1
- package/dist/state/planEntities.js +57 -16
- package/dist/state/planEntities.js.map +1 -1
- package/dist/state/progressEntities.js +177 -39
- package/dist/state/progressEntities.js.map +1 -1
- package/dist/state/progressGlossaryCaveat.js +169 -0
- package/dist/state/progressGlossaryCaveat.js.map +1 -0
- package/dist/state/progressPublicationOrder.js +20 -0
- package/dist/state/progressPublicationOrder.js.map +1 -0
- package/dist/state/safeProjectFile.js +4 -1
- package/dist/state/safeProjectFile.js.map +1 -1
- package/dist/state/startupProjection.js +2 -70
- package/dist/state/startupProjection.js.map +1 -1
- package/dist/state/stateStorageAuthority.js +48 -0
- package/dist/state/stateStorageAuthority.js.map +1 -0
- package/dist/state/summarySourceRowAuthority.js +1 -1
- package/dist/state/summarySourceRowAuthority.js.map +1 -1
- package/dist/state/todoDocsEntities.js +8 -9
- package/dist/state/todoDocsEntities.js.map +1 -1
- package/dist/state/write/explain.js +101 -92
- package/dist/state/write/explain.js.map +1 -1
- package/dist/state/write/glossaryPublication.js +264 -0
- package/dist/state/write/glossaryPublication.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/operations.js +25 -27
- package/dist/state/write/operations.js.map +1 -1
- package/dist/state/write/planEvaluation.js +6 -2
- package/dist/state/write/planEvaluation.js.map +1 -1
- package/dist/state/write/transaction.js +7 -4
- package/dist/state/write/transaction.js.map +1 -1
- package/dist/upgrade/upgradeOrchestrator.js +15 -10
- package/dist/upgrade/upgradeOrchestrator.js.map +1 -1
- package/dist/validate/crossCapability.js +3 -0
- package/dist/validate/crossCapability.js.map +1 -1
- package/dist/validate/v1LegacyCruft.js +141 -0
- package/dist/validate/v1LegacyCruft.js.map +1 -0
- package/dist/validate/vocabularyAuthority.js +2 -0
- package/dist/validate/vocabularyAuthority.js.map +1 -1
- package/package.json +4 -3
- package/dist/state/decisionRevisionPublication.js +0 -532
- package/dist/state/decisionRevisionPublication.js.map +0 -1
|
@@ -1,31 +1,6 @@
|
|
|
1
|
-
import { createHash } from "node:crypto";
|
|
2
|
-
import fs from "node:fs";
|
|
3
1
|
import path from "node:path";
|
|
4
|
-
import { loadYamlMapping } from "../core/yaml.js";
|
|
5
2
|
import { resolveSourceRoot } from "../core/sourceRoot.js";
|
|
6
|
-
import {
|
|
7
|
-
import { canonicalRecordJson, readNumberedArchiveEntry, stateCurrentProjectionPath, validateStateRecord, } from "./archiveDiscovery.js";
|
|
8
|
-
import { legacyEntryNumber } from "./legacyIdentity.js";
|
|
9
|
-
import { reject } from "./write/errors.js";
|
|
10
|
-
// partitionDecisionViolations lives in decisionLegacyValidation.js, which in
|
|
11
|
-
// turn imports the coexistence primitives declared below. Both modules
|
|
12
|
-
// reference each other only inside functions, so the circular binding is
|
|
13
|
-
// resolved by the time either is called (same pattern as the
|
|
14
|
-
// decisionRevisionPublication.js re-export at the end of this file).
|
|
15
|
-
import { partitionDecisionViolations } from "./decisionLegacyValidation.js";
|
|
16
|
-
/**
|
|
17
|
-
* Focused accessor for the decision-content amendment contract declared in
|
|
18
|
-
* references/artifacts/state-storage-authority.yaml (`revisions:` and
|
|
19
|
-
* `compatibility.legacy_label_coexistence:`). It reads the same single
|
|
20
|
-
* authority file as the other focused accessors (e.g. gitBackfillAuthority) and
|
|
21
|
-
* projects only the amendment-relevant sections.
|
|
22
|
-
*
|
|
23
|
-
* The overlay (satisfaction) and revision (content) authorities share an
|
|
24
|
-
* identity prefix but never overlap fields. This module owns the revision
|
|
25
|
-
* contract, the revision document loader, base→revisions composition, and
|
|
26
|
-
* amendment preparation. Amendment publication with recovery is plan task 3.
|
|
27
|
-
*/
|
|
28
|
-
const AUTHORITY_RELATIVE_PATH = "references/artifacts/state-storage-authority.yaml";
|
|
3
|
+
import { loadStateStorageAuthority } from "./stateStorageAuthority.js";
|
|
29
4
|
const EXPECTED_AUTHORITY_SCHEMA = "agentera.stateStorageAuthority.v1";
|
|
30
5
|
function mapping(value) {
|
|
31
6
|
return value !== null && typeof value === "object" && !Array.isArray(value)
|
|
@@ -39,13 +14,14 @@ function requiredString(value, field) {
|
|
|
39
14
|
return value;
|
|
40
15
|
}
|
|
41
16
|
function requiredList(value, field) {
|
|
42
|
-
if (!Array.isArray(value) ||
|
|
17
|
+
if (!Array.isArray(value) ||
|
|
18
|
+
value.some((item) => typeof item !== "string" || item.length === 0)) {
|
|
43
19
|
throw new Error(`state storage authority field '${field}' must be a list of non-empty strings`);
|
|
44
20
|
}
|
|
45
21
|
return value;
|
|
46
22
|
}
|
|
47
23
|
function readAuthority(sourceRoot) {
|
|
48
|
-
const authority =
|
|
24
|
+
const authority = loadStateStorageAuthority(sourceRoot).document;
|
|
49
25
|
if (authority.schema_version !== EXPECTED_AUTHORITY_SCHEMA) {
|
|
50
26
|
throw new Error("state storage authority schema_version is unsupported");
|
|
51
27
|
}
|
|
@@ -54,11 +30,6 @@ function readAuthority(sourceRoot) {
|
|
|
54
30
|
}
|
|
55
31
|
return authority;
|
|
56
32
|
}
|
|
57
|
-
/**
|
|
58
|
-
* Cross-validation inputs extracted from the same authority: the decision
|
|
59
|
-
* overlay identity prefix and mutable paths. Revisions must share the overlay
|
|
60
|
-
* identity and never overlap an overlay mutable path.
|
|
61
|
-
*/
|
|
62
33
|
function overlayCrossCheck(authority) {
|
|
63
34
|
const overlays = mapping(authority.overlays);
|
|
64
35
|
const identityKey = requiredString(overlays.identity_key, "overlays.identity_key");
|
|
@@ -91,23 +62,32 @@ export function decisionRevisionContract(sourceRoot = resolveSourceRoot()) {
|
|
|
91
62
|
throw new Error("state storage authority revisions identity_key prefix must match the overlay identity");
|
|
92
63
|
}
|
|
93
64
|
const amendablePaths = requiredList(revisions.amendable_paths, "revisions.amendable_paths");
|
|
65
|
+
const legacyAmendablePaths = requiredList(revisions.legacy_amendable_paths, "revisions.legacy_amendable_paths");
|
|
66
|
+
const migrationAmendablePaths = requiredList(revisions.migration_amendable_paths, "revisions.migration_amendable_paths");
|
|
94
67
|
const identityPaths = requiredList(revisions.identity_paths, "revisions.identity_paths");
|
|
95
68
|
const temporalPaths = requiredList(revisions.temporal_paths, "revisions.temporal_paths");
|
|
96
69
|
const reserved = new Set([...identityPaths, ...temporalPaths]);
|
|
97
|
-
for (const
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
70
|
+
for (const [field, paths] of [["amendable_paths", amendablePaths], ["legacy_amendable_paths", legacyAmendablePaths], ["migration_amendable_paths", migrationAmendablePaths]]) {
|
|
71
|
+
for (const amendable of paths) {
|
|
72
|
+
if (reserved.has(amendable)) {
|
|
73
|
+
throw new Error(`state storage authority revisions.${field} must not include identity or temporal path '${amendable}'`);
|
|
74
|
+
}
|
|
75
|
+
if (overlay.mutablePaths.some((mutable) => amendable === mutable || amendable.startsWith(`${mutable}.`))) {
|
|
76
|
+
throw new Error(`state storage authority revisions.${field} must not overlap overlay mutable path '${amendable}'`);
|
|
77
|
+
}
|
|
103
78
|
}
|
|
104
79
|
}
|
|
105
80
|
return {
|
|
106
81
|
location,
|
|
82
|
+
legacySourceState: requiredString(revisions.legacy_source_state, "revisions.legacy_source_state"),
|
|
107
83
|
schemaVersion: requiredString(revisions.schema_version, "revisions.schema_version"),
|
|
108
84
|
identityKey,
|
|
109
85
|
identityPrefix,
|
|
110
86
|
amendablePaths,
|
|
87
|
+
legacyAmendablePaths,
|
|
88
|
+
migrationAmendablePaths,
|
|
89
|
+
currentConfidenceVocabulary: requiredList(mapping(mapping(authority.compatibility).legacy_label_coexistence).current_vocabulary, "compatibility.legacy_label_coexistence.current_vocabulary"),
|
|
90
|
+
knownLegacyConfidenceValues: requiredList(mapping(mapping(authority.compatibility).legacy_label_coexistence).known_legacy_examples, "compatibility.legacy_label_coexistence.known_legacy_examples"),
|
|
111
91
|
identityPaths,
|
|
112
92
|
temporalPaths,
|
|
113
93
|
separationFromOverlay: requiredString(revisions.separation_from_overlay, "revisions.separation_from_overlay"),
|
|
@@ -138,14 +118,6 @@ export function legacyLabelCoexistence(sourceRoot = resolveSourceRoot()) {
|
|
|
138
118
|
noSilentNormalization: requiredString(coexistence.no_silent_normalization, "compatibility.legacy_label_coexistence.no_silent_normalization"),
|
|
139
119
|
};
|
|
140
120
|
}
|
|
141
|
-
/**
|
|
142
|
-
* Evaluate the legacy-label coexistence rule for one confidence value.
|
|
143
|
-
* `touched=true` marks the value as new or amended content supplied by append
|
|
144
|
-
* or amend; `touched=false` marks an untouched inherited value on an existing
|
|
145
|
-
* record. The current vocabulary always passes. An untouched unsupported
|
|
146
|
-
* value is explicit legacy state (preserved, caveated, allowed). A touched
|
|
147
|
-
* unsupported value is rejected and requires the current vocabulary.
|
|
148
|
-
*/
|
|
149
121
|
export function classifyConfidenceLabel(contract, value, touched) {
|
|
150
122
|
const vocabulary = contract.currentVocabulary.join(", ");
|
|
151
123
|
if (contract.currentVocabulary.includes(value)) {
|
|
@@ -164,12 +136,6 @@ export function classifyConfidenceLabel(contract, value, touched) {
|
|
|
164
136
|
caveat: `confidence label '${value}' requires current vocabulary (${vocabulary}) for new or amended content`,
|
|
165
137
|
};
|
|
166
138
|
}
|
|
167
|
-
/**
|
|
168
|
-
* Revision provenance vocabulary (state-storage-authority `revisions.provenance`).
|
|
169
|
-
* A revision carries revision provenance only and is never labeled as
|
|
170
|
-
* historical archive provenance. A revision whose base was bootstrapped from a
|
|
171
|
-
* complete legacy projection may carry degraded_projection provenance.
|
|
172
|
-
*/
|
|
173
139
|
export const REVISION_PROVENANCE_VALUES = ["historical_revision", "degraded_projection"];
|
|
174
140
|
const REVISION_META_KEYS = new Set(["date", "provenance", "base_sha256"]);
|
|
175
141
|
const FORBIDDEN_REVISION_PROVENANCE = "historical_archive";
|
|
@@ -186,28 +152,12 @@ function decisionNumberFromId(id, contract) {
|
|
|
186
152
|
const parsed = Number(number);
|
|
187
153
|
return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : null;
|
|
188
154
|
}
|
|
189
|
-
/**
|
|
190
|
-
* Resolve the revision document path for a project. Mirrors
|
|
191
|
-
* {@link decisionOverlayPath}: project-relative, realpath-bounded.
|
|
192
|
-
*/
|
|
193
|
-
export function decisionRevisionPath(projectRoot, sourceRoot = resolveSourceRoot()) {
|
|
194
|
-
const contract = decisionRevisionContract(sourceRoot);
|
|
195
|
-
const target = path.resolve(projectRoot, contract.location);
|
|
196
|
-
assertRealpathBoundary(projectRoot, target, "decision revision");
|
|
197
|
-
return target;
|
|
198
|
-
}
|
|
199
|
-
/**
|
|
200
|
-
* Validate a revision document against the amendment contract. Returns a list of
|
|
201
|
-
* human-readable violations (empty when valid). The document must be a mapping
|
|
202
|
-
* keyed by stable decision ID; each value is an ordered list of revision
|
|
203
|
-
* records carrying only amendment-meta keys and amendable content paths.
|
|
204
|
-
*/
|
|
205
155
|
export function decisionRevisionViolations(document, contract = decisionRevisionContract()) {
|
|
206
156
|
if (!isRecord(document)) {
|
|
207
157
|
return ["decision revision root must be a mapping keyed by stable decision ID"];
|
|
208
158
|
}
|
|
209
|
-
const amendable = new Set(contract.
|
|
210
|
-
const allowed = new Set([...REVISION_META_KEYS, ...contract.
|
|
159
|
+
const amendable = new Set(contract.legacyAmendablePaths);
|
|
160
|
+
const allowed = new Set([...REVISION_META_KEYS, ...contract.legacyAmendablePaths]);
|
|
211
161
|
const violations = [];
|
|
212
162
|
for (const [stableId, value] of Object.entries(document)) {
|
|
213
163
|
if (decisionNumberFromId(stableId, contract) === null) {
|
|
@@ -219,12 +169,12 @@ export function decisionRevisionViolations(document, contract = decisionRevision
|
|
|
219
169
|
continue;
|
|
220
170
|
}
|
|
221
171
|
value.forEach((revision, index) => {
|
|
222
|
-
violations.push(...decisionRevisionRecordViolations(revision, `${stableId}[${index}]`, contract, allowed, amendable));
|
|
172
|
+
violations.push(...decisionRevisionRecordViolations(revision, `${stableId}[${index}]`, contract, allowed, amendable, contract.legacyAmendablePaths, true));
|
|
223
173
|
});
|
|
224
174
|
}
|
|
225
175
|
return violations;
|
|
226
176
|
}
|
|
227
|
-
function decisionRevisionRecordViolations(revision, label, contract, allowed = new Set([...REVISION_META_KEYS, ...contract.amendablePaths]), amendable = new Set(contract.amendablePaths)) {
|
|
177
|
+
function decisionRevisionRecordViolations(revision, label, contract, allowed = new Set([...REVISION_META_KEYS, ...contract.amendablePaths, ...contract.migrationAmendablePaths]), amendable = new Set([...contract.amendablePaths, ...contract.migrationAmendablePaths]), amendablePaths = [...contract.amendablePaths, ...contract.migrationAmendablePaths], legacy = false, allowLegacyConfidence = false) {
|
|
228
178
|
if (!isRecord(revision))
|
|
229
179
|
return [`${label} must be a mapping of amendable fields and revision provenance`];
|
|
230
180
|
const violations = [];
|
|
@@ -232,30 +182,67 @@ function decisionRevisionRecordViolations(revision, label, contract, allowed = n
|
|
|
232
182
|
if (!allowed.has(key))
|
|
233
183
|
violations.push(`${label} carries '${key}' which is not an amendable content path or revision meta key`);
|
|
234
184
|
}
|
|
185
|
+
if ("alternatives" in revision && ("alternatives.chosen" in revision || "alternatives.rejected" in revision)) {
|
|
186
|
+
violations.push(`${label} must not mix migration-only alternatives replacement with granular alternative changes`);
|
|
187
|
+
}
|
|
188
|
+
for (const key of amendable) {
|
|
189
|
+
if (!(key in revision))
|
|
190
|
+
continue;
|
|
191
|
+
const value = revision[key];
|
|
192
|
+
if (key === "alternatives") {
|
|
193
|
+
const alternatives = Array.isArray(value) ? value : [];
|
|
194
|
+
const malformed = !Array.isArray(value)
|
|
195
|
+
|| alternatives.length === 0
|
|
196
|
+
|| alternatives.some((entry) => {
|
|
197
|
+
const candidate = isRecord(entry) ? entry : {};
|
|
198
|
+
return typeof candidate.name !== "string"
|
|
199
|
+
|| candidate.name.length === 0
|
|
200
|
+
|| !["chosen", "rejected"].includes(String(candidate.status));
|
|
201
|
+
})
|
|
202
|
+
|| alternatives.filter((entry) => isRecord(entry) && entry.status === "chosen").length !== 1;
|
|
203
|
+
if (malformed) {
|
|
204
|
+
violations.push(`${label}.alternatives must be a non-empty list with exactly one named chosen alternative and zero or more named rejected alternatives`);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
else if (key === "alternatives.rejected") {
|
|
208
|
+
if (!Array.isArray(value) || value.some((entry) => typeof entry !== "string" || entry.length === 0)) {
|
|
209
|
+
violations.push(`${label}.alternatives.rejected must be a list of non-empty strings`);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
else if (typeof value !== "string" || value.length === 0) {
|
|
213
|
+
violations.push(`${label}.${key} must be a non-empty string`);
|
|
214
|
+
}
|
|
215
|
+
else if (key === "confidence" && !legacy && !contract.currentConfidenceVocabulary.includes(value) && !(allowLegacyConfidence && contract.knownLegacyConfidenceValues.includes(value))) {
|
|
216
|
+
violations.push(`${label}.confidence '${value}' is not one of: ${contract.currentConfidenceVocabulary.join(", ")}`);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
235
219
|
const provenance = revision.provenance;
|
|
236
220
|
if (provenance !== undefined) {
|
|
237
221
|
if (typeof provenance !== "string")
|
|
238
222
|
violations.push(`${label}.provenance must be a string`);
|
|
239
223
|
else if (provenance === FORBIDDEN_REVISION_PROVENANCE)
|
|
240
224
|
violations.push(`${label}.provenance must never claim historical_archive provenance`);
|
|
241
|
-
else if (!REVISION_PROVENANCE_VALUES.includes(provenance))
|
|
225
|
+
else if (!REVISION_PROVENANCE_VALUES.includes(provenance)) {
|
|
242
226
|
violations.push(`${label}.provenance '${provenance}' is not one of: ${REVISION_PROVENANCE_VALUES.join(", ")}`);
|
|
227
|
+
}
|
|
243
228
|
}
|
|
244
229
|
const baseSha = revision.base_sha256;
|
|
245
|
-
if (baseSha !== undefined && (typeof baseSha !== "string" || !/^[0-9a-f]{64}$/.test(baseSha)))
|
|
230
|
+
if (baseSha !== undefined && (typeof baseSha !== "string" || !/^[0-9a-f]{64}$/.test(baseSha))) {
|
|
246
231
|
violations.push(`${label}.base_sha256 must be a 64-character sha256 hex digest`);
|
|
232
|
+
}
|
|
247
233
|
const date = revision.date;
|
|
248
|
-
if (date !== undefined && (typeof date !== "string" || date.length === 0))
|
|
234
|
+
if (date !== undefined && (typeof date !== "string" || date.length === 0)) {
|
|
249
235
|
violations.push(`${label}.date must be a non-empty string`);
|
|
250
|
-
|
|
251
|
-
|
|
236
|
+
}
|
|
237
|
+
if (!Object.keys(revision).some((key) => amendable.has(key))) {
|
|
238
|
+
violations.push(`${label} must amend at least one content field (${amendablePaths.join(", ")})`);
|
|
239
|
+
}
|
|
252
240
|
return violations;
|
|
253
241
|
}
|
|
254
|
-
|
|
255
|
-
export function decisionRevisionEntityViolations(record, contract = decisionRevisionContract()) {
|
|
242
|
+
export function decisionRevisionEntityViolations(record, contract = decisionRevisionContract(), allowLegacyConfidence = false) {
|
|
256
243
|
if (!isRecord(record.changes))
|
|
257
244
|
return ["decision revision changes must be a mapping of amendable fields"];
|
|
258
|
-
const amendable = new Set(contract.amendablePaths);
|
|
245
|
+
const amendable = new Set([...contract.amendablePaths, ...contract.migrationAmendablePaths]);
|
|
259
246
|
const violations = Object.keys(record.changes)
|
|
260
247
|
.filter((key) => !amendable.has(key))
|
|
261
248
|
.map((key) => `decision revision changes carries '${key}' which is not an amendable content path`);
|
|
@@ -264,369 +251,7 @@ export function decisionRevisionEntityViolations(record, contract = decisionRevi
|
|
|
264
251
|
date: record.date,
|
|
265
252
|
provenance: record.provenance,
|
|
266
253
|
base_sha256: record.base_sha256,
|
|
267
|
-
}, "decision revision", contract));
|
|
254
|
+
}, "decision revision", contract, undefined, undefined, undefined, false, allowLegacyConfidence));
|
|
268
255
|
return violations;
|
|
269
256
|
}
|
|
270
|
-
export function loadDecisionRevision(projectRoot = process.cwd(), sourceRoot = resolveSourceRoot()) {
|
|
271
|
-
const target = decisionRevisionPath(projectRoot, sourceRoot);
|
|
272
|
-
if (!fs.existsSync(target))
|
|
273
|
-
return {};
|
|
274
|
-
const bytes = fs.readFileSync(target, "utf8");
|
|
275
|
-
let document;
|
|
276
|
-
try {
|
|
277
|
-
document = loadYamlMapping(bytes);
|
|
278
|
-
}
|
|
279
|
-
catch (error) {
|
|
280
|
-
throw new Error(`cannot parse decision revision document '${target}': ${error.message}`);
|
|
281
|
-
}
|
|
282
|
-
const violations = decisionRevisionViolations(document, decisionRevisionContract(sourceRoot));
|
|
283
|
-
if (violations.length > 0) {
|
|
284
|
-
throw new Error(`decision revision document '${target}' is invalid: ${violations.join("; ")}`);
|
|
285
|
-
}
|
|
286
|
-
return document;
|
|
287
|
-
}
|
|
288
|
-
/**
|
|
289
|
-
* Compose ordered revisions over an immutable base record. Each revision
|
|
290
|
-
* applies only its authority-declared amendable content paths, in declared
|
|
291
|
-
* order; no revision overwrites, reorders, or deletes a prior revision. The
|
|
292
|
-
* overlay (satisfaction) authority composes its own evidence separately and is
|
|
293
|
-
* not touched here.
|
|
294
|
-
*
|
|
295
|
-
* When `baseSha256` is supplied, the first revision that carries a
|
|
296
|
-
* `base_sha256` is checked against it; a mismatch marks `broken_hash` so the
|
|
297
|
-
* caller degrades rather than reconstructing inconsistent detail.
|
|
298
|
-
*/
|
|
299
|
-
export function composeDecisionRevision(entry, revisions, options = {}) {
|
|
300
|
-
const contract = options.contract ?? decisionRevisionContract();
|
|
301
|
-
if (!revisions || revisions.length === 0) {
|
|
302
|
-
return { record: structuredClone(entry), applied: false, fields: [], revisions: [], broken_hash: false };
|
|
303
|
-
}
|
|
304
|
-
const effective = structuredClone(entry);
|
|
305
|
-
const touched = new Set();
|
|
306
|
-
const provenance = [];
|
|
307
|
-
let brokenHash = false;
|
|
308
|
-
const expectedBase = options.baseSha256;
|
|
309
|
-
for (let index = 0; index < revisions.length; index++) {
|
|
310
|
-
const revision = revisions[index];
|
|
311
|
-
const fields = [];
|
|
312
|
-
for (const amendablePath of contract.amendablePaths) {
|
|
313
|
-
if (amendablePath in revision && revision[amendablePath] !== undefined) {
|
|
314
|
-
effective[amendablePath] = structuredClone(revision[amendablePath]);
|
|
315
|
-
touched.add(amendablePath);
|
|
316
|
-
fields.push(amendablePath);
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
// The first revision establishes the base lineage; a base_sha256 mismatch
|
|
320
|
-
// there means the immutable base drifted after the revision was published.
|
|
321
|
-
if (index === 0 && expectedBase !== undefined && typeof revision.base_sha256 === "string" && revision.base_sha256 !== expectedBase) {
|
|
322
|
-
brokenHash = true;
|
|
323
|
-
}
|
|
324
|
-
provenance.push({
|
|
325
|
-
index,
|
|
326
|
-
...(typeof revision.date === "string" ? { date: revision.date } : {}),
|
|
327
|
-
provenance: (typeof revision.provenance === "string" ? revision.provenance : "historical_revision"),
|
|
328
|
-
fields,
|
|
329
|
-
...(typeof revision.base_sha256 === "string" ? { base_sha256: revision.base_sha256 } : {}),
|
|
330
|
-
});
|
|
331
|
-
}
|
|
332
|
-
return {
|
|
333
|
-
record: effective,
|
|
334
|
-
applied: touched.size > 0,
|
|
335
|
-
fields: [...touched],
|
|
336
|
-
revisions: provenance,
|
|
337
|
-
broken_hash: brokenHash,
|
|
338
|
-
};
|
|
339
|
-
}
|
|
340
|
-
/**
|
|
341
|
-
* Load the revision snapshot once for a project. Returns `null` when no
|
|
342
|
-
* revision document exists. Throws a plain `Error` (with a descriptive
|
|
343
|
-
* message) when the document is unreadable or violates the contract, so
|
|
344
|
-
* callers can wrap the failure in their own retrieval envelope.
|
|
345
|
-
*/
|
|
346
|
-
export function loadDecisionRevisionSnapshot(projectRoot, sourceRoot = resolveSourceRoot()) {
|
|
347
|
-
const contract = decisionRevisionContract(sourceRoot);
|
|
348
|
-
const revisionPath = decisionRevisionPath(projectRoot, sourceRoot);
|
|
349
|
-
let document;
|
|
350
|
-
try {
|
|
351
|
-
document = loadDecisionRevision(projectRoot, sourceRoot);
|
|
352
|
-
// loadDecisionRevision returns {} when the document is absent; normalize to null
|
|
353
|
-
// so callers short-circuit the snapshot path entirely.
|
|
354
|
-
document = Object.keys(document).length > 0 ? document : {};
|
|
355
|
-
}
|
|
356
|
-
catch (error) {
|
|
357
|
-
throw new Error(`cannot read decision revision document: ${error.message}`);
|
|
358
|
-
}
|
|
359
|
-
if (Object.keys(document).length === 0)
|
|
360
|
-
return null;
|
|
361
|
-
return { path: revisionPath, document, amendablePaths: contract.amendablePaths, contract };
|
|
362
|
-
}
|
|
363
|
-
/**
|
|
364
|
-
* Bounded composition metadata for one stable ID in a list snapshot. Does not
|
|
365
|
-
* hash-verify lineage (that is the exact/get path's responsibility); it reports
|
|
366
|
-
* whether any amendable content field is amended and which, for truthful list
|
|
367
|
-
* provenance without reconstructing full detail.
|
|
368
|
-
*/
|
|
369
|
-
export function decisionRevisionFields(snapshot, stableId) {
|
|
370
|
-
if (!snapshot || !stableId || !snapshot.document[stableId]) {
|
|
371
|
-
return { applied: false, fields: [], count: 0 };
|
|
372
|
-
}
|
|
373
|
-
const revisions = snapshot.document[stableId];
|
|
374
|
-
const fields = new Set();
|
|
375
|
-
for (const entry of revisions) {
|
|
376
|
-
for (const amendable of snapshot.amendablePaths) {
|
|
377
|
-
if (amendable in entry && entry[amendable] !== undefined)
|
|
378
|
-
fields.add(amendable);
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
return { applied: fields.size > 0, fields: [...fields], count: revisions.length };
|
|
382
|
-
}
|
|
383
|
-
/**
|
|
384
|
-
* Compose a bounded list summary's effective record from the base and any
|
|
385
|
-
* revisions for the stable ID. Returns the base (cloned) when no revisions
|
|
386
|
-
* apply, so list summaries never reconstruct missing detail.
|
|
387
|
-
*/
|
|
388
|
-
export function composedRevisionRecord(snapshot, stableId, base) {
|
|
389
|
-
if (!snapshot || !stableId || !snapshot.document[stableId])
|
|
390
|
-
return structuredClone(base);
|
|
391
|
-
return composeDecisionRevision(base, snapshot.document[stableId], { contract: snapshot.contract }).record;
|
|
392
|
-
}
|
|
393
|
-
/**
|
|
394
|
-
* Locate a complete base record for amendment preparation. A verified numbered
|
|
395
|
-
* archive seeds a `historical_archive` base. When the decision has no numbered
|
|
396
|
-
* archive but is represented by a complete legacy full projection record, the
|
|
397
|
-
* exact projection values seed a hash-verified `degraded_projection` base
|
|
398
|
-
* (never historical_archive). Summary-only, conflicting, ambiguous,
|
|
399
|
-
* broken-hash, and incomplete targets are refused with a working correction
|
|
400
|
-
* rather than reconstructed.
|
|
401
|
-
*/
|
|
402
|
-
export function prepareDecisionAmendment(projectRoot, decisionNumber, requested, options = {}) {
|
|
403
|
-
if (!Number.isSafeInteger(decisionNumber) || decisionNumber <= 0) {
|
|
404
|
-
reject({
|
|
405
|
-
class: "invalid_request",
|
|
406
|
-
message: `decision number must be a positive integer; received ${decisionNumber}`,
|
|
407
|
-
syntax: "agentera state decisions amend --number N [--question ... --choice ... --reasoning ... --confidence firm ...] [--dry-run] --format json",
|
|
408
|
-
example: 'agentera state decisions amend --number 53 --choice "..." --reasoning "..." --confidence firm --dry-run --format json',
|
|
409
|
-
recovery: "Supply an existing numbered decision and retry; no files were changed.",
|
|
410
|
-
});
|
|
411
|
-
}
|
|
412
|
-
const sourceRoot = options.sourceRoot ?? resolveSourceRoot();
|
|
413
|
-
const contract = options.contract ?? decisionRevisionContract(sourceRoot);
|
|
414
|
-
const amendableSet = new Set(contract.amendablePaths);
|
|
415
|
-
const requestedFields = Object.keys(requested).filter((key) => amendableSet.has(key) && requested[key] !== undefined);
|
|
416
|
-
if (requestedFields.length === 0) {
|
|
417
|
-
reject({
|
|
418
|
-
class: "missing_argument",
|
|
419
|
-
message: "amend requires at least one amendable content field",
|
|
420
|
-
syntax: "agentera state decisions amend --number N [--question ... --choice ... --reasoning ... --confidence firm ...] [--dry-run] --format json",
|
|
421
|
-
example: 'agentera state decisions amend --number 53 --choice "..." --reasoning "..." --confidence firm --dry-run --format json',
|
|
422
|
-
recovery: `Supply at least one of ${contract.amendablePaths.join(", ")}; no files were changed.`,
|
|
423
|
-
});
|
|
424
|
-
}
|
|
425
|
-
// New/amended confidence must be current vocabulary; legacy labels are never normalized.
|
|
426
|
-
if (requestedFields.includes("confidence")) {
|
|
427
|
-
const value = requested.confidence;
|
|
428
|
-
const coexistence = legacyLabelCoexistence(sourceRoot);
|
|
429
|
-
const classification = classifyConfidenceLabel(coexistence, String(value), true);
|
|
430
|
-
if (!classification.allowed) {
|
|
431
|
-
reject({
|
|
432
|
-
class: "invalid_choice",
|
|
433
|
-
message: classification.caveat,
|
|
434
|
-
valid_values: coexistence.currentVocabulary,
|
|
435
|
-
syntax: "agentera state decisions amend --number N --confidence firm --format json",
|
|
436
|
-
example: "agentera state decisions amend --number 53 --confidence firm --dry-run --format json",
|
|
437
|
-
recovery: "Use a current-vocabulary confidence label for new or amended content; no files were changed.",
|
|
438
|
-
});
|
|
439
|
-
}
|
|
440
|
-
}
|
|
441
|
-
const lookup = readNumberedArchiveEntry(projectRoot, "decisions", decisionNumber, { sourceRoot });
|
|
442
|
-
if (lookup.rejection) {
|
|
443
|
-
const reason = lookup.rejection.reason;
|
|
444
|
-
const brokenHash = reason === "invalid_envelope" || reason === "read_failure" || reason === "unsafe_path";
|
|
445
|
-
reject({
|
|
446
|
-
class: "conflict",
|
|
447
|
-
message: `decision ${decisionNumber} amendment target has a broken numbered archive (${reason}: ${lookup.rejection.message})`,
|
|
448
|
-
syntax: "agentera state decisions amend --number N [--question ... --confidence firm ...] [--dry-run] --format json",
|
|
449
|
-
example: 'agentera state decisions amend --number 53 --choice "..." --reasoning "..." --confidence firm --dry-run --format json',
|
|
450
|
-
recovery: brokenHash
|
|
451
|
-
? "Preserve the affected archive bytes for diagnostics, repair the numbered record, then retry the amend."
|
|
452
|
-
: "Restore or publish the validated numbered archive for this ID before retrying; missing fields are not reconstructed.",
|
|
453
|
-
});
|
|
454
|
-
}
|
|
455
|
-
const projectionPath = stateCurrentProjectionPath(projectRoot, "decisions", sourceRoot);
|
|
456
|
-
let base;
|
|
457
|
-
let currentRepresentation;
|
|
458
|
-
if (lookup.entry) {
|
|
459
|
-
// A verified numbered archive seeds an immutable historical_archive base.
|
|
460
|
-
if (!lookup.entry.record) {
|
|
461
|
-
reject({
|
|
462
|
-
class: "conflict",
|
|
463
|
-
message: `decision ${decisionNumber} amendment target has a numbered archive without a complete record`,
|
|
464
|
-
syntax: "agentera state decisions amend --number N [--question ... --confidence firm ...] [--dry-run] --format json",
|
|
465
|
-
example: 'agentera state decisions amend --number 53 --choice "..." --reasoning "..." --confidence firm --dry-run --format json',
|
|
466
|
-
recovery: "Preserve the numbered archive for diagnostics, repair or republish its complete record, then retry the amend.",
|
|
467
|
-
});
|
|
468
|
-
}
|
|
469
|
-
base = {
|
|
470
|
-
record: lookup.entry.record,
|
|
471
|
-
sha256: lookup.entry.recordSha256,
|
|
472
|
-
provenance: "historical_archive",
|
|
473
|
-
source: "archive",
|
|
474
|
-
};
|
|
475
|
-
currentRepresentation = "full";
|
|
476
|
-
}
|
|
477
|
-
else {
|
|
478
|
-
// No numbered archive: bootstrap a hash-verified base from a complete legacy
|
|
479
|
-
// full projection record. Summary-only targets are refused; missing fields
|
|
480
|
-
// are never reconstructed.
|
|
481
|
-
const projection = legacyFullProjectionFor(projectRoot, decisionNumber, projectionPath, sourceRoot);
|
|
482
|
-
if (projection.kind === "missing" || projection.kind === "absent") {
|
|
483
|
-
reject({
|
|
484
|
-
class: "unsupported_target",
|
|
485
|
-
message: `decision ${decisionNumber} has no numbered archive and is not present in the current projection`,
|
|
486
|
-
syntax: "agentera state decisions amend --number N [--question ... --confidence firm ...] [--dry-run] --format json",
|
|
487
|
-
example: 'agentera state decisions amend --number 53 --choice "..." --reasoning "..." --confidence firm --dry-run --format json',
|
|
488
|
-
recovery: "Append or restore the decision with a complete record (and its numbered archive) before amending; missing fields are not reconstructed.",
|
|
489
|
-
});
|
|
490
|
-
}
|
|
491
|
-
else if (projection.kind === "ambiguous") {
|
|
492
|
-
reject({
|
|
493
|
-
class: "conflict",
|
|
494
|
-
message: `decision ${decisionNumber} is represented by multiple conflicting current projection entries`,
|
|
495
|
-
syntax: "agentera state decisions amend --number N [--question ... --confidence firm ...] [--dry-run] --format json",
|
|
496
|
-
example: 'agentera state decisions amend --number 53 --choice "..." --reasoning "..." --confidence firm --dry-run --format json',
|
|
497
|
-
recovery: "Remove the duplicate identity conflict according to the artifact schema, then retry the amend.",
|
|
498
|
-
});
|
|
499
|
-
}
|
|
500
|
-
else if (projection.kind === "summary") {
|
|
501
|
-
reject({
|
|
502
|
-
class: "unsupported_target",
|
|
503
|
-
message: `decision ${decisionNumber} is represented only by an irrecoverable legacy summary; a base cannot be hash-verified`,
|
|
504
|
-
syntax: "agentera state decisions amend --number N [--question ... --confidence firm ...] [--dry-run] --format json",
|
|
505
|
-
example: 'agentera state decisions amend --number 53 --choice "..." --reasoning "..." --confidence firm --dry-run --format json',
|
|
506
|
-
recovery: "Restore or publish the validated numbered archive for this ID before retrying; missing fields are not reconstructed.",
|
|
507
|
-
});
|
|
508
|
-
}
|
|
509
|
-
else {
|
|
510
|
-
// degraded_projection base: hash-verified from exact legacy projection values, never historical_archive.
|
|
511
|
-
base = {
|
|
512
|
-
record: projection.record,
|
|
513
|
-
sha256: sha256(projection.record),
|
|
514
|
-
provenance: "degraded_projection",
|
|
515
|
-
source: "legacy_full",
|
|
516
|
-
};
|
|
517
|
-
currentRepresentation = "full";
|
|
518
|
-
}
|
|
519
|
-
}
|
|
520
|
-
const revisionPath = decisionRevisionPath(projectRoot, sourceRoot);
|
|
521
|
-
let revisions = [];
|
|
522
|
-
try {
|
|
523
|
-
const document = loadDecisionRevision(projectRoot, sourceRoot);
|
|
524
|
-
revisions = document[`decisions:${decisionNumber}`] ?? [];
|
|
525
|
-
}
|
|
526
|
-
catch (error) {
|
|
527
|
-
reject({
|
|
528
|
-
class: "conflict",
|
|
529
|
-
message: `decision ${decisionNumber} amendment target has a broken revision document: ${error.message}`,
|
|
530
|
-
syntax: "agentera state decisions amend --number N [--question ... --confidence firm ...] [--dry-run] --format json",
|
|
531
|
-
example: 'agentera state decisions amend --number 53 --choice "..." --reasoning "..." --confidence firm --dry-run --format json',
|
|
532
|
-
recovery: "Preserve the revision document for diagnostics, repair its YAML and amendable content paths, then retry the amend.",
|
|
533
|
-
});
|
|
534
|
-
}
|
|
535
|
-
const composed = composeDecisionRevision(base.record, revisions, { contract, baseSha256: base.sha256 });
|
|
536
|
-
if (composed.broken_hash) {
|
|
537
|
-
reject({
|
|
538
|
-
class: "conflict",
|
|
539
|
-
message: `decision ${decisionNumber} base hash no longer matches the recorded revision base_sha256; the immutable base drifted after the revision was published`,
|
|
540
|
-
syntax: "agentera state decisions amend --number N [--question ... --confidence firm ...] [--dry-run] --format json",
|
|
541
|
-
example: 'agentera state decisions amend --number 53 --choice "..." --reasoning "..." --confidence firm --dry-run --format json',
|
|
542
|
-
recovery: "Preserve the immutable archive and existing revisions, reconcile the base drift, then retry the amend.",
|
|
543
|
-
});
|
|
544
|
-
}
|
|
545
|
-
const effective = structuredClone(composed.record);
|
|
546
|
-
for (const field of requestedFields) {
|
|
547
|
-
effective[field] = structuredClone(requested[field]);
|
|
548
|
-
}
|
|
549
|
-
const replay = composed.applied &&
|
|
550
|
-
requestedFields.every((field) => canonicalRecordJson(effective[field]) === canonicalRecordJson(composed.record[field] ?? effective[field])) &&
|
|
551
|
-
requestedFields.length === composed.fields.length &&
|
|
552
|
-
composed.fields.every((field) => requestedFields.includes(field));
|
|
553
|
-
return {
|
|
554
|
-
number: decisionNumber,
|
|
555
|
-
base,
|
|
556
|
-
revisions,
|
|
557
|
-
requested: { ...requested },
|
|
558
|
-
effective,
|
|
559
|
-
replay,
|
|
560
|
-
provenance: {
|
|
561
|
-
base: base.provenance,
|
|
562
|
-
archive: {
|
|
563
|
-
path: lookup.path,
|
|
564
|
-
available: Boolean(lookup.entry),
|
|
565
|
-
verified: Boolean(lookup.entry),
|
|
566
|
-
...(lookup.entry ? { record_sha256: lookup.entry.recordSha256 } : {}),
|
|
567
|
-
},
|
|
568
|
-
current_projection: { path: projectionPath, representation: currentRepresentation },
|
|
569
|
-
revision_path: revisionPath,
|
|
570
|
-
existing_revisions: revisions.length,
|
|
571
|
-
amended_fields: requestedFields,
|
|
572
|
-
},
|
|
573
|
-
};
|
|
574
|
-
}
|
|
575
|
-
export function legacyFullProjectionFor(projectRoot, decisionNumber, projectionPath, sourceRoot) {
|
|
576
|
-
if (!fs.existsSync(projectionPath))
|
|
577
|
-
return { kind: "missing" };
|
|
578
|
-
let bytes;
|
|
579
|
-
try {
|
|
580
|
-
bytes = fs.readFileSync(projectionPath, "utf8");
|
|
581
|
-
}
|
|
582
|
-
catch {
|
|
583
|
-
return { kind: "missing" };
|
|
584
|
-
}
|
|
585
|
-
let document;
|
|
586
|
-
try {
|
|
587
|
-
document = loadYamlMapping(bytes);
|
|
588
|
-
}
|
|
589
|
-
catch {
|
|
590
|
-
return { kind: "missing" };
|
|
591
|
-
}
|
|
592
|
-
const active = document.decisions;
|
|
593
|
-
if (!Array.isArray(active))
|
|
594
|
-
return { kind: "absent" };
|
|
595
|
-
let match;
|
|
596
|
-
for (const candidate of active) {
|
|
597
|
-
if (!isRecord(candidate))
|
|
598
|
-
continue;
|
|
599
|
-
if (legacyEntryNumber(candidate, "decisions", "number") !== decisionNumber)
|
|
600
|
-
continue;
|
|
601
|
-
if (match)
|
|
602
|
-
return { kind: "ambiguous" };
|
|
603
|
-
match = candidate;
|
|
604
|
-
}
|
|
605
|
-
if (!match) {
|
|
606
|
-
return { kind: "absent" };
|
|
607
|
-
}
|
|
608
|
-
// A complete legacy projection record validates against the decision schema;
|
|
609
|
-
// otherwise the entry is summary-only and must not be reconstructed. An
|
|
610
|
-
// untouched inherited legacy confidence label is explicit legacy state under
|
|
611
|
-
// the authority's coexistence rule: it does not degrade a complete record to
|
|
612
|
-
// summary, so the base remains hash-verifiable and amend proceeds (the label
|
|
613
|
-
// is reported as a compatibility caveat by the amendment path). Tolerance is
|
|
614
|
-
// confidence-only — every other violation still degrades to summary.
|
|
615
|
-
try {
|
|
616
|
-
const violations = validateStateRecord(sourceRoot, "decisions", match);
|
|
617
|
-
if (violations.length === 0)
|
|
618
|
-
return { kind: "full", record: match };
|
|
619
|
-
const partition = partitionDecisionViolations(violations, { decisions: [match] }, legacyLabelCoexistence(sourceRoot), new Set());
|
|
620
|
-
return partition.blocking.length === 0 ? { kind: "full", record: match } : { kind: "summary" };
|
|
621
|
-
}
|
|
622
|
-
catch {
|
|
623
|
-
return { kind: "summary" };
|
|
624
|
-
}
|
|
625
|
-
}
|
|
626
|
-
export function sha256(value) {
|
|
627
|
-
return createHash("sha256").update(canonicalRecordJson(value), "utf8").digest("hex");
|
|
628
|
-
}
|
|
629
|
-
// Re-export the publication primitives for backward-compatible imports.
|
|
630
|
-
// The implementation lives in decisionRevisionPublication.ts.
|
|
631
|
-
export { buildPublishedRevisionRecord, dispatchDecisionAmendment, findConflictingRevision, findPublishedRevision, projectRevisionOverride, publishDecisionAmendment, } from "./decisionRevisionPublication.js";
|
|
632
257
|
//# sourceMappingURL=decisionRevision.js.map
|