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.
Files changed (228) hide show
  1. package/README.md +19 -0
  2. package/bundle/CHANGELOG.md +63 -70
  3. package/bundle/README.md +17 -0
  4. package/bundle/UPGRADE.md +38 -0
  5. package/bundle/references/adapters/package-surface-characterization.md +7 -0
  6. package/bundle/references/artifacts/artifact-registry-interface-model.yaml +3 -3
  7. package/bundle/references/artifacts/state-storage-authority.yaml +1446 -0
  8. package/bundle/references/cli/agent-ready-state-contract.yaml +11 -39
  9. package/bundle/references/cli/audience-namespace-cli-migration.yaml +30 -15
  10. package/bundle/references/meta/documentation-inventory.md +1 -0
  11. package/bundle/skills/agentera/capabilities/audit/schemas/artifacts.yaml +18 -10
  12. package/bundle/skills/agentera/capabilities/audit/schemas/validation.yaml +10 -10
  13. package/bundle/skills/agentera/capabilities/build/schemas/artifacts.yaml +11 -6
  14. package/bundle/skills/agentera/capabilities/discuss/schemas/artifacts.yaml +3 -2
  15. package/bundle/skills/agentera/capabilities/document/schemas/artifacts.yaml +15 -8
  16. package/bundle/skills/agentera/capabilities/document/schemas/validation.yaml +13 -13
  17. package/bundle/skills/agentera/capabilities/orchestrate/schemas/artifacts.yaml +15 -9
  18. package/bundle/skills/agentera/references/contract.md +65 -23
  19. package/bundle/skills/agentera/schemas/artifacts/decisions.yaml +15 -10
  20. package/bundle/skills/agentera/schemas/artifacts/experiments.yaml +134 -3
  21. package/bundle/skills/agentera/schemas/artifacts/health.yaml +16 -10
  22. package/bundle/skills/agentera/schemas/artifacts/objective.yaml +18 -4
  23. package/bundle/skills/agentera/schemas/artifacts/plan.yaml +23 -1
  24. package/bundle/skills/agentera/schemas/artifacts/progress.yaml +16 -10
  25. package/bundle/skills/agentera/schemas/artifacts/todo.yaml +34 -0
  26. package/dist/capabilities/audit/instructions.js +1 -1
  27. package/dist/capabilities/audit/instructions.js.map +1 -1
  28. package/dist/capabilities/build/instructions.js +1 -1
  29. package/dist/capabilities/build/instructions.js.map +1 -1
  30. package/dist/capabilities/discuss/instructions.js +1 -1
  31. package/dist/capabilities/discuss/instructions.js.map +1 -1
  32. package/dist/capabilities/document/instructions.js +1 -1
  33. package/dist/capabilities/document/instructions.js.map +1 -1
  34. package/dist/capabilities/optimize/instructions.js +1 -1
  35. package/dist/capabilities/optimize/instructions.js.map +1 -1
  36. package/dist/capabilities/orchestrate/instructions.js +1 -1
  37. package/dist/capabilities/orchestrate/instructions.js.map +1 -1
  38. package/dist/capabilities/plan/instructions.js +1 -1
  39. package/dist/capabilities/plan/instructions.js.map +1 -1
  40. package/dist/capabilities/profile/instructions.js +1 -1
  41. package/dist/capabilities/profile/instructions.js.map +1 -1
  42. package/dist/capabilities/vision/instructions.js +1 -1
  43. package/dist/capabilities/vision/instructions.js.map +1 -1
  44. package/dist/cli/appContext.js +14 -6
  45. package/dist/cli/appContext.js.map +1 -1
  46. package/dist/cli/capabilityContext/bespoke.js +3 -1
  47. package/dist/cli/capabilityContext/bespoke.js.map +1 -1
  48. package/dist/cli/capabilityContext/build.js +5 -4
  49. package/dist/cli/capabilityContext/build.js.map +1 -1
  50. package/dist/cli/capabilityContext/closeout.js +2 -1
  51. package/dist/cli/capabilityContext/closeout.js.map +1 -1
  52. package/dist/cli/capabilityContext/contract.js +29 -0
  53. package/dist/cli/capabilityContext/contract.js.map +1 -1
  54. package/dist/cli/capabilityContext/evidence.js +21 -20
  55. package/dist/cli/capabilityContext/evidence.js.map +1 -1
  56. package/dist/cli/capabilityContext/orchestration.js +3 -2
  57. package/dist/cli/capabilityContext/orchestration.js.map +1 -1
  58. package/dist/cli/capabilityContext/progress.js +9 -4
  59. package/dist/cli/capabilityContext/progress.js.map +1 -1
  60. package/dist/cli/capabilityContext/slim.js +37 -6
  61. package/dist/cli/capabilityContext/slim.js.map +1 -1
  62. package/dist/cli/capabilityContext/startup.js +30 -6
  63. package/dist/cli/capabilityContext/startup.js.map +1 -1
  64. package/dist/cli/capabilityContext/types.js +14 -4
  65. package/dist/cli/capabilityContext/types.js.map +1 -1
  66. package/dist/cli/commands/backfill.js +194 -0
  67. package/dist/cli/commands/backfill.js.map +1 -0
  68. package/dist/cli/commands/compact.js +40 -11
  69. package/dist/cli/commands/compact.js.map +1 -1
  70. package/dist/cli/commands/durability.js +85 -0
  71. package/dist/cli/commands/durability.js.map +1 -0
  72. package/dist/cli/commands/migrate.js +677 -0
  73. package/dist/cli/commands/migrate.js.map +1 -0
  74. package/dist/cli/commands/prime/collectOrientationState.js +23 -0
  75. package/dist/cli/commands/prime/collectOrientationState.js.map +1 -1
  76. package/dist/cli/commands/prime/orientationOutput.js +4 -2
  77. package/dist/cli/commands/prime/orientationOutput.js.map +1 -1
  78. package/dist/cli/commands/query.js +20 -2
  79. package/dist/cli/commands/query.js.map +1 -1
  80. package/dist/cli/commands/schema.js +247 -69
  81. package/dist/cli/commands/schema.js.map +1 -1
  82. package/dist/cli/commands/state/decisions.js +28 -10
  83. package/dist/cli/commands/state/decisions.js.map +1 -1
  84. package/dist/cli/commands/state/experimentRecords.js +156 -0
  85. package/dist/cli/commands/state/experimentRecords.js.map +1 -0
  86. package/dist/cli/commands/state/get.js +141 -0
  87. package/dist/cli/commands/state/get.js.map +1 -0
  88. package/dist/cli/commands/state/index.js +2 -2
  89. package/dist/cli/commands/state/index.js.map +1 -1
  90. package/dist/cli/commands/state/list.js +159 -0
  91. package/dist/cli/commands/state/list.js.map +1 -0
  92. package/dist/cli/commands/state/plan.js +146 -41
  93. package/dist/cli/commands/state/plan.js.map +1 -1
  94. package/dist/cli/commands/state/planTasks.js +164 -0
  95. package/dist/cli/commands/state/planTasks.js.map +1 -0
  96. package/dist/cli/commands/state/plans.js +154 -0
  97. package/dist/cli/commands/state/plans.js.map +1 -0
  98. package/dist/cli/commands/state/write.js +8 -3
  99. package/dist/cli/commands/state/write.js.map +1 -1
  100. package/dist/cli/dispatch/check.js +71 -4
  101. package/dist/cli/dispatch/check.js.map +1 -1
  102. package/dist/cli/dispatch/index.js +48 -30
  103. package/dist/cli/dispatch/index.js.map +1 -1
  104. package/dist/cli/dispatch/lifecycle.js +4 -4
  105. package/dist/cli/dispatch/lifecycle.js.map +1 -1
  106. package/dist/cli/dispatch/state.js +23 -3
  107. package/dist/cli/dispatch/state.js.map +1 -1
  108. package/dist/cli/errors.js +13 -14
  109. package/dist/cli/errors.js.map +1 -1
  110. package/dist/cli/help.js +162 -3
  111. package/dist/cli/help.js.map +1 -1
  112. package/dist/cli/orientation.js +146 -34
  113. package/dist/cli/orientation.js.map +1 -1
  114. package/dist/cli/planArtifacts.js +31 -2
  115. package/dist/cli/planArtifacts.js.map +1 -1
  116. package/dist/cli/prime-blob.js +1 -0
  117. package/dist/cli/prime-blob.js.map +1 -1
  118. package/dist/cli/startupCompletenessContract.js +3 -2
  119. package/dist/cli/startupCompletenessContract.js.map +1 -1
  120. package/dist/cli/stateQuery.js +5 -3
  121. package/dist/cli/stateQuery.js.map +1 -1
  122. package/dist/core/text.js +11 -0
  123. package/dist/core/text.js.map +1 -0
  124. package/dist/hooks/compaction/apply.js +224 -46
  125. package/dist/hooks/compaction/apply.js.map +1 -1
  126. package/dist/hooks/compaction/dryRun.js +2 -0
  127. package/dist/hooks/compaction/dryRun.js.map +1 -1
  128. package/dist/hooks/compaction/index.js +4 -4
  129. package/dist/hooks/compaction/index.js.map +1 -1
  130. package/dist/hooks/compaction/parse.js +73 -5
  131. package/dist/hooks/compaction/parse.js.map +1 -1
  132. package/dist/hooks/compaction/retention.js +22 -21
  133. package/dist/hooks/compaction/retention.js.map +1 -1
  134. package/dist/hooks/compaction/status.js +116 -23
  135. package/dist/hooks/compaction/status.js.map +1 -1
  136. package/dist/hooks/sessionStart.js +39 -7
  137. package/dist/hooks/sessionStart.js.map +1 -1
  138. package/dist/hooks/validateArtifact/index.js +2 -2
  139. package/dist/hooks/validateArtifact/index.js.map +1 -1
  140. package/dist/hooks/validateArtifact/markdown.js +9 -9
  141. package/dist/hooks/validateArtifact/markdown.js.map +1 -1
  142. package/dist/hooks/validateArtifact/traversal.js +11 -9
  143. package/dist/hooks/validateArtifact/traversal.js.map +1 -1
  144. package/dist/registries/artifactRegistry.js +147 -44
  145. package/dist/registries/artifactRegistry.js.map +1 -1
  146. package/dist/release/releaseMetadata.js +95 -1
  147. package/dist/release/releaseMetadata.js.map +1 -1
  148. package/dist/state/archiveDiscovery.js +589 -0
  149. package/dist/state/archiveDiscovery.js.map +1 -0
  150. package/dist/state/archivePublication.js +225 -0
  151. package/dist/state/archivePublication.js.map +1 -0
  152. package/dist/state/archiveRecovery.js +238 -0
  153. package/dist/state/archiveRecovery.js.map +1 -0
  154. package/dist/state/archiveReplay.js +54 -0
  155. package/dist/state/archiveReplay.js.map +1 -0
  156. package/dist/state/decisionOverlay.js +255 -0
  157. package/dist/state/decisionOverlay.js.map +1 -0
  158. package/dist/state/directRetrieval.js +322 -0
  159. package/dist/state/directRetrieval.js.map +1 -0
  160. package/dist/state/durability.js +451 -0
  161. package/dist/state/durability.js.map +1 -0
  162. package/dist/state/experimentArchive.js +64 -0
  163. package/dist/state/experimentArchive.js.map +1 -0
  164. package/dist/state/experimentIdentity.js +191 -0
  165. package/dist/state/experimentIdentity.js.map +1 -0
  166. package/dist/state/experimentRetrieval.js +447 -0
  167. package/dist/state/experimentRetrieval.js.map +1 -0
  168. package/dist/state/gitBackfill.js +561 -0
  169. package/dist/state/gitBackfill.js.map +1 -0
  170. package/dist/state/gitBackfillAuthority.js +141 -0
  171. package/dist/state/gitBackfillAuthority.js.map +1 -0
  172. package/dist/state/gitBackfillHistory.js +278 -0
  173. package/dist/state/gitBackfillHistory.js.map +1 -0
  174. package/dist/state/gitBackfillOutput.js +209 -0
  175. package/dist/state/gitBackfillOutput.js.map +1 -0
  176. package/dist/state/healthRepair.js +70 -0
  177. package/dist/state/healthRepair.js.map +1 -0
  178. package/dist/state/legacyIdentity.js +48 -0
  179. package/dist/state/legacyIdentity.js.map +1 -0
  180. package/dist/state/legacyMigration.js +516 -0
  181. package/dist/state/legacyMigration.js.map +1 -0
  182. package/dist/state/legacyMigrationParser.js +489 -0
  183. package/dist/state/legacyMigrationParser.js.map +1 -0
  184. package/dist/state/listAccounting.js +55 -0
  185. package/dist/state/listAccounting.js.map +1 -0
  186. package/dist/state/listClassification.js +18 -0
  187. package/dist/state/listClassification.js.map +1 -0
  188. package/dist/state/listRetrieval.js +782 -0
  189. package/dist/state/listRetrieval.js.map +1 -0
  190. package/dist/state/migrationAuthority.js +413 -0
  191. package/dist/state/migrationAuthority.js.map +1 -0
  192. package/dist/state/migrationEnrichment.js +35 -0
  193. package/dist/state/migrationEnrichment.js.map +1 -0
  194. package/dist/state/planIdentity.js +45 -0
  195. package/dist/state/planIdentity.js.map +1 -0
  196. package/dist/state/planRetrieval.js +314 -0
  197. package/dist/state/planRetrieval.js.map +1 -0
  198. package/dist/state/planTaskRetrieval.js +285 -0
  199. package/dist/state/planTaskRetrieval.js.map +1 -0
  200. package/dist/state/projectionPolicy.js +184 -0
  201. package/dist/state/projectionPolicy.js.map +1 -0
  202. package/dist/state/retrievalAuthority.js +326 -0
  203. package/dist/state/retrievalAuthority.js.map +1 -0
  204. package/dist/state/startupProjection.js +498 -0
  205. package/dist/state/startupProjection.js.map +1 -0
  206. package/dist/state/write/experimentPublication.js +193 -0
  207. package/dist/state/write/experimentPublication.js.map +1 -0
  208. package/dist/state/write/explain.js +28 -14
  209. package/dist/state/write/explain.js.map +1 -1
  210. package/dist/state/write/helpers.js +8 -0
  211. package/dist/state/write/helpers.js.map +1 -0
  212. package/dist/state/write/index.js +1 -0
  213. package/dist/state/write/index.js.map +1 -1
  214. package/dist/state/write/lock.js +24 -1
  215. package/dist/state/write/lock.js.map +1 -1
  216. package/dist/state/write/mutation.js +190 -0
  217. package/dist/state/write/mutation.js.map +1 -0
  218. package/dist/state/write/operations.js +36 -2
  219. package/dist/state/write/operations.js.map +1 -1
  220. package/dist/state/write/planPublication.js +172 -0
  221. package/dist/state/write/planPublication.js.map +1 -0
  222. package/dist/state/write/transaction.js +238 -225
  223. package/dist/state/write/transaction.js.map +1 -1
  224. package/dist/upgrade/migrateArtifactsV2ToV3.js +2 -2
  225. package/dist/upgrade/migrateArtifactsV2ToV3.js.map +1 -1
  226. package/dist/validate/crossCapability.js +9 -6
  227. package/dist/validate/crossCapability.js.map +1 -1
  228. package/package.json +2 -2
@@ -0,0 +1,782 @@
1
+ import { createHash, createHmac, timingSafeEqual } from "node:crypto";
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import { loadYamlMapping } from "../core/yaml.js";
5
+ import { resolveSourceRoot } from "../core/sourceRoot.js";
6
+ import { assertRealpathBoundary } from "../registries/artifactRegistry.js";
7
+ import { canonicalRecordJson, decisionOverlayContract, discoverNumberedArchives, numberedArchiveContract, stateCurrentProjectionPath, validateStateRecord, } from "./archiveDiscovery.js";
8
+ import { decisionOverlayPath, loadDecisionOverlay } from "./decisionOverlay.js";
9
+ import { loadProjectionPolicy, serializedProjectionBytes } from "./projectionPolicy.js";
10
+ import { StateRetrievalFailure } from "./directRetrieval.js";
11
+ import { physicalCounts, provenanceCounts } from "./listAccounting.js";
12
+ import { legacyIdentity } from "./legacyIdentity.js";
13
+ import { classifyStateRows } from "./listClassification.js";
14
+ const CURSOR_VERSION = 1;
15
+ const LIST_ORDER = "entry_number_desc";
16
+ const LIST_AUTHORITY = "references/artifacts/state-storage-authority.yaml";
17
+ function isMapping(value) {
18
+ return value !== null && typeof value === "object" && !Array.isArray(value);
19
+ }
20
+ function jsonValue(value) {
21
+ if (value === null || typeof value === "string" || typeof value === "boolean")
22
+ return value;
23
+ if (typeof value === "number" && Number.isFinite(value))
24
+ return value;
25
+ if (Array.isArray(value)) {
26
+ const values = [];
27
+ for (const item of value) {
28
+ const converted = jsonValue(item);
29
+ if (converted === undefined)
30
+ return undefined;
31
+ values.push(converted);
32
+ }
33
+ return values;
34
+ }
35
+ if (isMapping(value)) {
36
+ const object = {};
37
+ for (const [key, child] of Object.entries(value)) {
38
+ const converted = jsonValue(child);
39
+ if (converted === undefined)
40
+ return undefined;
41
+ object[key] = converted;
42
+ }
43
+ return object;
44
+ }
45
+ return undefined;
46
+ }
47
+ function positiveNumber(value) {
48
+ if (typeof value === "number" && Number.isSafeInteger(value) && value > 0)
49
+ return value;
50
+ if (typeof value === "string" && /^[1-9][0-9]*$/.test(value)) {
51
+ const parsed = Number(value);
52
+ return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : null;
53
+ }
54
+ return null;
55
+ }
56
+ function compactSummary(value) {
57
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
58
+ return value;
59
+ }
60
+ if (!isMapping(value))
61
+ return undefined;
62
+ const selected = {};
63
+ for (const key of [
64
+ "number",
65
+ "date",
66
+ "timestamp",
67
+ "summary",
68
+ "what",
69
+ "choice",
70
+ "question",
71
+ "trajectory",
72
+ "type",
73
+ "phase",
74
+ ]) {
75
+ const converted = jsonValue(value[key]);
76
+ if (converted !== undefined && (typeof converted !== "object" || converted === null))
77
+ selected[key] = converted;
78
+ }
79
+ return Object.keys(selected).length > 0 ? selected : undefined;
80
+ }
81
+ function hashValue(value) {
82
+ return createHash("sha256").update(canonicalRecordJson(value), "utf8").digest("hex");
83
+ }
84
+ function listSyntax(artifactId = "<artifact-id>") {
85
+ return `agentera state ${artifactId} list [--limit N] [--cursor TOKEN] --format json`;
86
+ }
87
+ function listExample(artifactId, cursor = false) {
88
+ return cursor
89
+ ? `agentera state ${artifactId} list --limit 20 --cursor TOKEN --format json`
90
+ : `agentera state ${artifactId} list --limit 20 --format json`;
91
+ }
92
+ function listFailure(exitCode, className, message, artifactId, recovery, details, validValues) {
93
+ return new StateRetrievalFailure({
94
+ schemaVersion: "agentera.stateFailure.v1",
95
+ status: "fail",
96
+ error: {
97
+ class: className,
98
+ message,
99
+ syntax: listSyntax(artifactId),
100
+ example: listExample(artifactId, className === "cursor_invalid" || className === "cursor_snapshot_unavailable"),
101
+ recovery,
102
+ artifact_id: artifactId,
103
+ ...(details ? { details } : {}),
104
+ ...(validValues ? { valid_values: validValues } : {}),
105
+ },
106
+ }, exitCode);
107
+ }
108
+ function cursorKey(projectRoot, sourceRoot) {
109
+ return createHash("sha256")
110
+ .update(`agentera-state-list-cursor\0${path.resolve(projectRoot)}\0${path.resolve(sourceRoot)}`, "utf8")
111
+ .digest();
112
+ }
113
+ function cursorSignature(payload, projectRoot, sourceRoot) {
114
+ return createHmac("sha256", cursorKey(projectRoot, sourceRoot))
115
+ .update(canonicalRecordJson(payload), "utf8")
116
+ .digest("hex");
117
+ }
118
+ function encodeCursor(payload, projectRoot, sourceRoot) {
119
+ const signed = { ...payload, signature: cursorSignature(payload, projectRoot, sourceRoot) };
120
+ return Buffer.from(JSON.stringify(signed), "utf8").toString("base64url");
121
+ }
122
+ function invalidCursor(artifactId, message, projectRoot, sourceRoot) {
123
+ return listFailure(2, "cursor_invalid", message, artifactId, "Copy response.next_cursor exactly, or omit --cursor to establish a new snapshot.", { cursor: "opaque; do not parse or construct cursor tokens", project_root: path.resolve(projectRoot), source_root: path.resolve(sourceRoot) });
124
+ }
125
+ function parseCursor(artifactId, token, filters, projectRoot, sourceRoot) {
126
+ if (token.length === 0 || token.length > 100_000 || !/^[A-Za-z0-9_-]+$/.test(token)) {
127
+ throw invalidCursor(artifactId, "cursor is not a valid opaque token", projectRoot, sourceRoot);
128
+ }
129
+ let decoded;
130
+ try {
131
+ const bytes = Buffer.from(token, "base64url");
132
+ if (bytes.length === 0 || Buffer.from(bytes).toString("base64url") !== token)
133
+ throw new Error("non-canonical encoding");
134
+ decoded = JSON.parse(bytes.toString("utf8"));
135
+ }
136
+ catch {
137
+ throw invalidCursor(artifactId, "cursor is not a valid opaque token", projectRoot, sourceRoot);
138
+ }
139
+ if (!isMapping(decoded))
140
+ throw invalidCursor(artifactId, "cursor payload is not a valid opaque token", projectRoot, sourceRoot);
141
+ const signature = decoded.signature;
142
+ const unsigned = { ...decoded };
143
+ delete unsigned.signature;
144
+ if (typeof signature !== "string" || !/^[0-9a-f]{64}$/.test(signature)) {
145
+ throw invalidCursor(artifactId, "cursor signature is missing or malformed", projectRoot, sourceRoot);
146
+ }
147
+ const expected = cursorSignature(unsigned, projectRoot, sourceRoot);
148
+ if (!timingSafeEqual(Buffer.from(signature, "hex"), Buffer.from(expected, "hex"))) {
149
+ throw invalidCursor(artifactId, "cursor signature is invalid; the token may be tampered or copied from another project", projectRoot, sourceRoot);
150
+ }
151
+ const payload = unsigned;
152
+ if (payload.version !== CURSOR_VERSION ||
153
+ payload.artifact_id !== artifactId ||
154
+ payload.order !== LIST_ORDER ||
155
+ !isMapping(payload.filters) ||
156
+ canonicalRecordJson(payload.filters) !== canonicalRecordJson(filters) ||
157
+ typeof payload.snapshot_id !== "string" ||
158
+ !/^[0-9a-f]{64}$/.test(payload.snapshot_id) ||
159
+ !Number.isSafeInteger(payload.candidate_count) ||
160
+ payload.candidate_count < 1 ||
161
+ !Number.isSafeInteger(payload.candidate_max) ||
162
+ payload.candidate_max < 1 ||
163
+ typeof payload.candidate_start_key !== "string" ||
164
+ typeof payload.candidate_end_key !== "string" ||
165
+ typeof payload.after_key !== "string" ||
166
+ !Number.isSafeInteger(payload.after) ||
167
+ payload.after < 1 ||
168
+ payload.after > payload.candidate_max + 1) {
169
+ throw invalidCursor(artifactId, "cursor payload is invalid or bound to a different list", projectRoot, sourceRoot);
170
+ }
171
+ return { payload };
172
+ }
173
+ function projectionHash(current) {
174
+ return hashValue(current.record ?? current.summary ?? null);
175
+ }
176
+ function readCurrentProjection(projectRoot, artifactId, contract, sourceRoot) {
177
+ const projectionPath = stateCurrentProjectionPath(projectRoot, artifactId, sourceRoot);
178
+ try {
179
+ assertRealpathBoundary(projectRoot, projectionPath, `${artifactId} projection`);
180
+ }
181
+ catch (error) {
182
+ throw listFailure(1, "corrupt", `current ${artifactId} projection path is unsafe`, artifactId, "Repair the declared current projection path, then retry the list command.", {
183
+ path: projectionPath,
184
+ reason: error.message,
185
+ });
186
+ }
187
+ let stat;
188
+ try {
189
+ stat = fs.lstatSync(projectionPath);
190
+ }
191
+ catch (error) {
192
+ if (error.code === "ENOENT")
193
+ return { path: projectionPath, exists: false, entries: [] };
194
+ throw listFailure(1, "corrupt", `cannot read current ${artifactId} projection`, artifactId, "Repair the current projection file and retry the list command.", { path: projectionPath });
195
+ }
196
+ if (stat.isSymbolicLink() || !stat.isFile()) {
197
+ throw listFailure(1, "corrupt", `current ${artifactId} projection is not a regular file`, artifactId, "Restore the declared projection as a regular project-local file, then retry the list command.", { path: projectionPath });
198
+ }
199
+ let document;
200
+ try {
201
+ document = loadYamlMapping(fs.readFileSync(projectionPath, "utf8"));
202
+ }
203
+ catch (error) {
204
+ throw listFailure(1, "corrupt", `cannot parse current ${artifactId} projection: ${error.message}`, artifactId, "Repair the current projection YAML and retry the list command.", { path: projectionPath });
205
+ }
206
+ const collection = document[contract.entryCollection];
207
+ if (!Array.isArray(collection)) {
208
+ throw listFailure(1, "corrupt", `current ${artifactId} projection is missing its '${contract.entryCollection}' collection`, artifactId, "Restore the declared current entry collection and retry the list command.", { path: projectionPath });
209
+ }
210
+ const entries = [];
211
+ for (const [index, candidate] of collection.entries()) {
212
+ const identity = legacyIdentity(candidate, artifactId, contract.entryNumberField);
213
+ let representation = "summary";
214
+ if (isMapping(candidate)) {
215
+ try {
216
+ const violations = validateStateRecord(sourceRoot, artifactId, candidate);
217
+ representation = violations.every((violation) => /number|entry/i.test(violation)) ? "full" : violations.length === 0 ? "full" : "summary";
218
+ }
219
+ catch (error) {
220
+ throw listFailure(1, "unsupported_state", `current ${artifactId} projection could not be validated: ${error.message}`, artifactId, "Use a state schema supported by the storage authority, repair the current projection, then retry the list command.", { path: projectionPath });
221
+ }
222
+ }
223
+ const current = {
224
+ path: projectionPath,
225
+ rowKey: `${projectionPath}#${contract.entryCollection}:${index}`,
226
+ origin: "active",
227
+ identity: identity.kind,
228
+ entryNumber: identity.number,
229
+ representation,
230
+ ...(representation === "full" && isMapping(candidate) ? { record: candidate } : { summary: compactSummary(candidate) }),
231
+ projectionHash: "",
232
+ };
233
+ current.projectionHash = projectionHash(current);
234
+ entries.push(current);
235
+ }
236
+ const archive = document.archive;
237
+ if (Array.isArray(archive)) {
238
+ for (const [index, candidate] of archive.entries()) {
239
+ const identity = legacyIdentity(candidate, artifactId, contract.entryNumberField);
240
+ const current = {
241
+ path: projectionPath,
242
+ rowKey: `${projectionPath}#archive:${index}`,
243
+ origin: "summary",
244
+ identity: identity.kind,
245
+ entryNumber: identity.number,
246
+ representation: "summary",
247
+ summary: compactSummary(candidate),
248
+ projectionHash: "",
249
+ };
250
+ current.projectionHash = projectionHash(current);
251
+ entries.push(current);
252
+ }
253
+ }
254
+ return { path: projectionPath, exists: true, entries };
255
+ }
256
+ function overlayState(projectRoot, artifactId, sourceRoot) {
257
+ if (artifactId !== "decisions")
258
+ return null;
259
+ const contract = decisionOverlayContract(sourceRoot);
260
+ const overlayPath = decisionOverlayPath(projectRoot, sourceRoot);
261
+ let document;
262
+ try {
263
+ document = loadDecisionOverlay(projectRoot, sourceRoot);
264
+ }
265
+ catch (error) {
266
+ throw listFailure(1, "corrupt", `cannot read decision overlay: ${error.message}`, artifactId, "Repair the authority-declared decision overlay, then retry the list command.", { path: overlayPath });
267
+ }
268
+ return {
269
+ path: overlayPath,
270
+ revision: hashValue(document),
271
+ document,
272
+ mutablePaths: contract.mutablePaths,
273
+ };
274
+ }
275
+ function rejectionNumber(rejected, artifactId) {
276
+ const expected = path.join(`${path.sep}${artifactId}`, "");
277
+ if (!rejected.path.includes(expected))
278
+ return null;
279
+ const match = /(?:^|[\\/])([1-9][0-9]*)\.yaml$/.exec(rejected.path);
280
+ return match ? positiveNumber(match[1]) : null;
281
+ }
282
+ function rejectionBelongsToArtifact(rejected, artifactId) {
283
+ return rejected.path.includes(path.join(`${path.sep}${artifactId}`, ""));
284
+ }
285
+ function physicalRowFromCurrent(current) {
286
+ return {
287
+ rowKey: current.rowKey,
288
+ source: "current_projection",
289
+ origin: current.origin,
290
+ path: current.path,
291
+ identity: current.identity,
292
+ entryNumber: current.entryNumber,
293
+ representation: current.representation,
294
+ projectionHash: current.projectionHash,
295
+ ...(current.record ? { record: current.record } : {}),
296
+ ...(current.summary !== undefined ? { summary: current.summary } : {}),
297
+ };
298
+ }
299
+ function physicalRowFromArchive(entry) {
300
+ return {
301
+ rowKey: entry.path,
302
+ source: "archive",
303
+ origin: "numbered_archive",
304
+ path: entry.path,
305
+ identity: "canonical_number",
306
+ entryNumber: entry.entryNumber,
307
+ representation: "full",
308
+ projectionHash: entry.recordSha256,
309
+ record: entry.record,
310
+ };
311
+ }
312
+ function physicalRowFromRejection(rejected, entryNumber) {
313
+ return {
314
+ rowKey: rejected.path,
315
+ source: "archive",
316
+ origin: "rejected_archive",
317
+ path: rejected.path,
318
+ identity: entryNumber === null ? "unaddressable" : "canonical_number",
319
+ entryNumber,
320
+ representation: "unavailable",
321
+ projectionHash: hashValue({ path: rejected.path, reason: rejected.reason, message: rejected.message }),
322
+ rejection: rejected,
323
+ };
324
+ }
325
+ function sortKey(artifactId, entryNumber, rowKey) {
326
+ if (entryNumber !== null) {
327
+ const descending = String(Number.MAX_SAFE_INTEGER - entryNumber).padStart(16, "0");
328
+ return `0:${descending}:${artifactId}:${entryNumber}`;
329
+ }
330
+ return `1:${rowKey}`;
331
+ }
332
+ function buildCandidates(projectRoot, artifactId, contract, sourceRoot) {
333
+ const current = readCurrentProjection(projectRoot, artifactId, contract, sourceRoot);
334
+ const discovery = discoverNumberedArchives(projectRoot, { sourceRoot, artifactId });
335
+ const grouped = new Map();
336
+ const unaddressable = [];
337
+ const addRow = (row, archive, corruptArchive) => {
338
+ if (row.entryNumber === null || row.identity === "ambiguous") {
339
+ unaddressable.push(row);
340
+ return;
341
+ }
342
+ const group = grouped.get(row.entryNumber) ?? { rows: [] };
343
+ group.rows.push(row);
344
+ if (archive)
345
+ group.archive = archive;
346
+ if (corruptArchive)
347
+ group.corruptArchive = corruptArchive;
348
+ grouped.set(row.entryNumber, group);
349
+ };
350
+ for (const entry of discovery.entries)
351
+ addRow(physicalRowFromArchive(entry), entry);
352
+ for (const rejected of discovery.rejected) {
353
+ if (!rejectionBelongsToArtifact(rejected, artifactId))
354
+ continue;
355
+ const number = rejectionNumber(rejected, artifactId);
356
+ addRow(physicalRowFromRejection(rejected, number), undefined, number === null ? undefined : rejected);
357
+ }
358
+ for (const entry of current.entries)
359
+ addRow(physicalRowFromCurrent(entry));
360
+ const candidates = [];
361
+ for (const [entryNumber, group] of grouped) {
362
+ const rows = group.rows;
363
+ const currentRow = rows.find((row) => row.source === "current_projection");
364
+ const identity = rows.find((row) => row.identity === "explicit_decision_shorthand")?.identity ?? "canonical_number";
365
+ candidates.push({
366
+ stableId: `${artifactId}:${entryNumber}`,
367
+ artifactId,
368
+ entryNumber,
369
+ identity,
370
+ classification: classifyStateRows(rows),
371
+ rows,
372
+ sortKey: sortKey(artifactId, entryNumber, rows[0]?.rowKey ?? ""),
373
+ ...(group.archive ? { archive: group.archive } : {}),
374
+ ...(group.corruptArchive ? { corruptArchive: group.corruptArchive } : {}),
375
+ ...(currentRow ? { current: current.entries.find((entry) => entry.rowKey === currentRow.rowKey) } : {}),
376
+ });
377
+ }
378
+ for (const row of unaddressable) {
379
+ candidates.push({
380
+ stableId: null,
381
+ artifactId,
382
+ entryNumber: null,
383
+ identity: row.identity,
384
+ classification: row.rejection ? "corrupt" : row.identity === "ambiguous" ? "ambiguous" : "unaddressable",
385
+ rows: [row],
386
+ sortKey: sortKey(artifactId, null, row.rowKey),
387
+ });
388
+ }
389
+ candidates.sort((left, right) => left.sortKey.localeCompare(right.sortKey));
390
+ return { candidates, projection: { path: current.path, exists: current.exists }, rejected: discovery.rejected };
391
+ }
392
+ function stringValues(value) {
393
+ if (typeof value === "string")
394
+ return [value];
395
+ if (Array.isArray(value))
396
+ return value.flatMap(stringValues);
397
+ if (isMapping(value))
398
+ return Object.entries(value).flatMap(([key, child]) => [key, ...stringValues(child)]);
399
+ return [];
400
+ }
401
+ function matchesFilter(candidate, filters) {
402
+ const value = candidate.archive?.record ?? candidate.current?.record ?? candidate.current?.summary ?? candidate.rows[0]?.summary;
403
+ if (filters.topic && !stringValues(value).some((item) => item.toLowerCase().includes(filters.topic.toLowerCase())))
404
+ return false;
405
+ if (filters.status) {
406
+ const record = isMapping(value) ? value : {};
407
+ const status = candidate.artifactId === "progress" ? record.type : record.status;
408
+ if (String(status ?? "").toLowerCase() !== filters.status.toLowerCase())
409
+ return false;
410
+ }
411
+ if (filters.dimension) {
412
+ const record = isMapping(value) ? value : {};
413
+ const needle = filters.dimension.toLowerCase();
414
+ const grades = record.grades;
415
+ const details = record.dimensions_detail;
416
+ const gradeMatch = isMapping(grades) && Object.keys(grades).some((key) => key.toLowerCase().includes(needle));
417
+ const detailMatch = Array.isArray(details) && details.some((detail) => isMapping(detail) && String(detail.name ?? "").toLowerCase().includes(needle));
418
+ if (!gradeMatch && !detailMatch)
419
+ return false;
420
+ }
421
+ return true;
422
+ }
423
+ function filtersObject(artifactId, filters) {
424
+ if (artifactId === "progress")
425
+ return { topic: filters.topic ?? null, status: filters.status ?? null };
426
+ if (artifactId === "decisions")
427
+ return { topic: filters.topic ?? null };
428
+ if (artifactId === "health")
429
+ return { dimension: filters.dimension ?? null };
430
+ return {};
431
+ }
432
+ function validateListFilters(artifactId, filters) {
433
+ const allowed = artifactId === "progress" ? ["topic", "status"] : artifactId === "decisions" ? ["topic"] : ["dimension"];
434
+ for (const [key, value] of Object.entries(filters)) {
435
+ if (value === null || value === undefined)
436
+ continue;
437
+ if (!allowed.includes(key) || typeof value !== "string" || value.length === 0) {
438
+ throw listFailure(2, "invalid_request", `unsupported or empty list filter '${key}' for ${artifactId}; valid filters: ${allowed.join(", ")}`, artifactId, `Use ${listSyntax(artifactId)} with an allowed filter value.`);
439
+ }
440
+ }
441
+ }
442
+ function snapshotId(candidates, artifactId, filters, overlayRevision) {
443
+ const identity = {
444
+ artifact_id: artifactId,
445
+ order: LIST_ORDER,
446
+ filters,
447
+ overlay_revision: overlayRevision,
448
+ candidates: candidates.map((candidate) => ({
449
+ stable_id: candidate.stableId,
450
+ sort_key: candidate.sortKey,
451
+ physical_rows: candidate.rows.map((row) => ({
452
+ row_key: row.rowKey,
453
+ source: row.source,
454
+ identity: row.identity,
455
+ entry_number: row.entryNumber,
456
+ hash: row.projectionHash,
457
+ rejection: row.rejection?.reason ?? null,
458
+ })),
459
+ })),
460
+ };
461
+ return hashValue(identity);
462
+ }
463
+ function overlayFields(overlay, stableId) {
464
+ if (!overlay || !overlay.document[stableId])
465
+ return [];
466
+ const selected = overlay.document[stableId];
467
+ return overlay.mutablePaths.filter((field) => {
468
+ let value = selected;
469
+ for (const part of field.split(".")) {
470
+ if (!isMapping(value))
471
+ return false;
472
+ value = value[part];
473
+ }
474
+ return value !== undefined;
475
+ });
476
+ }
477
+ function recordStatus(candidate, overlay) {
478
+ if (overlay && candidate.stableId !== null) {
479
+ const selected = overlay.document[candidate.stableId];
480
+ const state = isMapping(selected?.satisfaction) ? selected.satisfaction.state : undefined;
481
+ if (typeof state === "string")
482
+ return state;
483
+ }
484
+ const record = candidate.archive?.record ?? candidate.current?.record;
485
+ if (!record)
486
+ return "unknown";
487
+ if (typeof record.status === "string")
488
+ return record.status;
489
+ if (typeof record.type === "string")
490
+ return record.type;
491
+ if (typeof record.trajectory === "string")
492
+ return record.trajectory;
493
+ const satisfaction = record.satisfaction;
494
+ if (isMapping(satisfaction) && typeof satisfaction.state === "string")
495
+ return satisfaction.state;
496
+ return "unknown";
497
+ }
498
+ function listEntry(candidate, overlay, projectRoot, contract) {
499
+ const archivePath = candidate.archive?.path ?? candidate.corruptArchive?.path ?? path.join(path.resolve(projectRoot), contract.archiveRoot, candidate.artifactId, `${candidate.entryNumber ?? "unaddressable"}${contract.archiveExtension}`);
500
+ const hasArchive = candidate.archive !== undefined;
501
+ const currentStatus = candidate.current ? (candidate.current.representation === "full" ? "active" : "summary") : "archive_only";
502
+ const source = hasArchive ? "archive" : candidate.corruptArchive ? "archive" : candidate.current?.representation === "full" ? "legacy_full" : "legacy_summary";
503
+ const detailAvailability = hasArchive ? "full" : candidate.current?.representation === "full" ? "full" : candidate.current ? "summary" : candidate.rows[0]?.representation ?? "unavailable";
504
+ const fields = overlayFields(overlay, candidate.stableId ?? "");
505
+ const addressable = candidate.stableId !== null;
506
+ const physicalRows = candidate.rows.map((row) => ({
507
+ row_id: row.rowKey,
508
+ source: row.source,
509
+ origin: row.origin,
510
+ path: row.path,
511
+ identity: row.identity,
512
+ entry_number: row.entryNumber,
513
+ representation: row.representation,
514
+ ...(row.rejection ? { rejection: row.rejection.reason } : {}),
515
+ }));
516
+ const entry = {
517
+ stable_id: candidate.stableId,
518
+ artifact_id: candidate.artifactId,
519
+ entry_number: candidate.entryNumber,
520
+ addressable,
521
+ identity: candidate.identity,
522
+ classification: candidate.classification,
523
+ physical_count: candidate.rows.length,
524
+ cursor_key: candidate.sortKey,
525
+ current_status: currentStatus,
526
+ detail_availability: detailAvailability,
527
+ source,
528
+ compatibility: candidate.classification === "conflict" || candidate.classification === "ambiguous" || candidate.classification === "corrupt"
529
+ ? "blocked"
530
+ : hasArchive && !candidate.corruptArchive ? "complete" : "degraded",
531
+ record_status: recordStatus(candidate, overlay),
532
+ provenance: {
533
+ archive: {
534
+ path: archivePath,
535
+ available: hasArchive || candidate.corruptArchive !== undefined,
536
+ verified: hasArchive,
537
+ ...(candidate.archive ? { record_sha256: candidate.archive.recordSha256 } : {}),
538
+ ...(candidate.corruptArchive ? { rejection: candidate.corruptArchive.reason } : {}),
539
+ },
540
+ current_projection: {
541
+ path: candidate.current?.path ?? path.join(path.resolve(projectRoot), `.agentera/${candidate.artifactId}.yaml`),
542
+ present: candidate.current !== undefined,
543
+ representation: candidate.current?.representation ?? "missing",
544
+ },
545
+ physical_rows: physicalRows,
546
+ },
547
+ ...(addressable
548
+ ? { retrieval: { command: `agentera state ${candidate.artifactId} get --number ${candidate.entryNumber} --format json`, available: candidate.classification !== "conflict" && candidate.classification !== "ambiguous" && candidate.classification !== "corrupt" } }
549
+ : { retrieval: { available: false, reason: "unaddressable", message: "No stable ID was inferred from this physical row; list output is the only supported view." } }),
550
+ };
551
+ if (overlay && addressable) {
552
+ entry.overlay_applied = fields.length > 0;
553
+ entry.provenance = {
554
+ ...entry.provenance,
555
+ overlay: { path: overlay.path, applied: fields.length > 0, fields, revision: overlay.revision },
556
+ };
557
+ }
558
+ const sourceValue = candidate.current?.summary ?? candidate.archive?.record ?? candidate.current?.record;
559
+ const summary = compactSummary(sourceValue);
560
+ if (summary !== undefined)
561
+ entry.summary = summary;
562
+ return entry;
563
+ }
564
+ function textList(value) {
565
+ const lines = [
566
+ `command: ${value.command}`,
567
+ `status: ${value.status}`,
568
+ `snapshot: ${String(value.snapshot.id)}${value.snapshot.has_more ? " (more)" : ""}`,
569
+ `counts: ${JSON.stringify(value.counts)}`,
570
+ `filters: ${JSON.stringify(value.filters)}`,
571
+ "entries:",
572
+ ];
573
+ for (const raw of value.entries ?? []) {
574
+ const entry = raw;
575
+ lines.push(`- ${entry.stable_id} current=${entry.current_status} detail=${entry.detail_availability} source=${entry.source} compatibility=${entry.compatibility} status=${entry.record_status}`);
576
+ lines.push(` provenance: ${JSON.stringify(entry.provenance)}`);
577
+ if (entry.summary !== undefined)
578
+ lines.push(` summary: ${JSON.stringify(entry.summary)}`);
579
+ }
580
+ if (value.next_cursor)
581
+ lines.push(`next_cursor: ${value.next_cursor}`);
582
+ if (value.omitted)
583
+ lines.push(`omitted: ${value.omitted_count} (${value.omission_reason})`);
584
+ return lines.join("\n") + "\n";
585
+ }
586
+ function responseBytes(value, format) {
587
+ return format === "text"
588
+ ? Buffer.byteLength(textList(value), "utf8")
589
+ : serializedProjectionBytes(value, format);
590
+ }
591
+ function copyEntryWithoutOptionalDetails(entry) {
592
+ const copy = { ...entry };
593
+ delete copy.summary;
594
+ delete copy.record_status;
595
+ return copy;
596
+ }
597
+ function omissionMetadata(command, count, entries) {
598
+ return {
599
+ omitted: count > 0,
600
+ omitted_count: count,
601
+ omission_reason: "list_output_byte_budget",
602
+ retrieval: { available: true, command: `agentera ${command} [--cursor TOKEN] --format json` },
603
+ omission_provenance: provenanceCounts(entries),
604
+ };
605
+ }
606
+ export function listStateEntries(projectRoot, artifactId, limit, filters = {}, cursor, options = {}) {
607
+ const sourceRoot = options.sourceRoot ?? resolveSourceRoot();
608
+ let contract;
609
+ try {
610
+ contract = numberedArchiveContract(artifactId, sourceRoot);
611
+ }
612
+ catch (error) {
613
+ throw listFailure(2, "unsupported_artifact", `unsupported state artifact '${artifactId}'`, artifactId, "Use one of the numbered archive artifacts and retry the list command.", { reason: error.message }, ["progress", "decisions", "health"]);
614
+ }
615
+ if (!Number.isSafeInteger(limit) || limit < 1 || limit > 100) {
616
+ throw listFailure(2, "invalid_request", "list limit must be an integer from 1 through 100", artifactId, "Use --limit with a value from 1 through 100, or omit it for the default of 20.");
617
+ }
618
+ validateListFilters(artifactId, filters);
619
+ const normalizedFilters = filtersObject(artifactId, filters);
620
+ const parsed = cursor !== undefined ? parseCursor(artifactId, cursor, normalizedFilters, projectRoot, sourceRoot) : null;
621
+ const built = buildCandidates(projectRoot, artifactId, contract, sourceRoot);
622
+ const overlay = overlayState(projectRoot, artifactId, sourceRoot);
623
+ const allCandidates = built.candidates.filter((candidate) => matchesFilter(candidate, filters));
624
+ const overlayRevision = overlay?.revision ?? "not_applicable";
625
+ const currentSnapshotId = snapshotId(allCandidates, artifactId, normalizedFilters, overlayRevision);
626
+ let snapshotCandidates = allCandidates;
627
+ let firstPage = parsed === null;
628
+ if (parsed) {
629
+ const oldCandidates = allCandidates.filter((candidate) => candidate.sortKey >= parsed.payload.candidate_start_key && candidate.sortKey <= parsed.payload.candidate_end_key);
630
+ const oldSnapshotId = snapshotId(oldCandidates, artifactId, normalizedFilters, overlayRevision);
631
+ if (oldSnapshotId !== parsed.payload.snapshot_id ||
632
+ oldCandidates.length !== parsed.payload.candidate_count ||
633
+ (oldCandidates.length > 0 && oldCandidates[0].sortKey !== parsed.payload.candidate_start_key)) {
634
+ throw listFailure(1, "cursor_snapshot_unavailable", `cursor snapshot for state ${artifactId} is no longer available; an existing candidate changed or disappeared`, artifactId, "Start a new listing without --cursor to establish a current snapshot.", {
635
+ snapshot_id: parsed.payload.snapshot_id,
636
+ current_snapshot_id: currentSnapshotId,
637
+ });
638
+ }
639
+ snapshotCandidates = oldCandidates;
640
+ firstPage = false;
641
+ }
642
+ const snapshot = snapshotId(snapshotCandidates, artifactId, normalizedFilters, overlayRevision);
643
+ const pageCandidates = parsed ? snapshotCandidates.filter((candidate) => candidate.sortKey > parsed.payload.after_key) : snapshotCandidates;
644
+ const selected = pageCandidates.slice(0, limit);
645
+ const rows = selected.map((candidate) => listEntry(candidate, overlay, projectRoot, contract));
646
+ const remaining = pageCandidates.length - selected.length;
647
+ const nextAfterCandidate = selected.at(-1);
648
+ const nextAfterKey = nextAfterCandidate?.sortKey ?? parsed?.payload.after_key ?? "";
649
+ const nextAfter = nextAfterCandidate?.entryNumber ?? parsed?.payload.after ?? 1;
650
+ const nextCursor = remaining > 0
651
+ ? encodeCursor({
652
+ version: CURSOR_VERSION,
653
+ artifact_id: artifactId,
654
+ filters: normalizedFilters,
655
+ order: LIST_ORDER,
656
+ snapshot_id: snapshot,
657
+ candidate_count: snapshotCandidates.length,
658
+ candidate_max: snapshotCandidates[0]?.entryNumber ?? 1,
659
+ candidate_start_key: snapshotCandidates[0]?.sortKey ?? "",
660
+ candidate_end_key: snapshotCandidates.at(-1)?.sortKey ?? "",
661
+ after_key: nextAfterKey,
662
+ after: nextAfter,
663
+ }, projectRoot, sourceRoot)
664
+ : undefined;
665
+ const rowCounts = { active: 0, summary: 0, archive_only: 0 };
666
+ for (const candidate of snapshotCandidates) {
667
+ const state = candidate.current ? (candidate.current.representation === "full" ? "active" : "summary") : "archive_only";
668
+ rowCounts[state] += 1;
669
+ }
670
+ const allPhysicalCounts = physicalCounts(snapshotCandidates);
671
+ const selectedPhysical = selected.reduce((total, candidate) => total + candidate.rows.length, 0);
672
+ const blocked = snapshotCandidates.some((candidate) => ["conflict", "ambiguous", "corrupt"].includes(candidate.classification));
673
+ const degraded = snapshotCandidates.some((candidate) => candidate.classification !== "canonical" || !candidate.archive);
674
+ return {
675
+ command: `state ${artifactId} list`,
676
+ status: built.rejected.length > 0 || degraded ? "degraded" : "ok",
677
+ entries: rows,
678
+ counts: {
679
+ total: snapshotCandidates.length,
680
+ returned: rows.length,
681
+ remaining,
682
+ ...allPhysicalCounts,
683
+ returned_physical: selectedPhysical,
684
+ omitted: Math.max(0, Number(allPhysicalCounts.physical ?? 0) - selectedPhysical),
685
+ active: rowCounts.active,
686
+ summary: rowCounts.summary,
687
+ archive_only: rowCounts.archive_only,
688
+ },
689
+ source: {
690
+ artifact: artifactId,
691
+ current_projection: { path: built.projection.path, exists: built.projection.exists },
692
+ archive: { root: path.join(path.resolve(projectRoot), contract.archiveRoot, artifactId), validated_entries: snapshotCandidates.reduce((count, candidate) => count + candidate.rows.filter((row) => row.origin === "numbered_archive").length, 0), rejected_count: built.rejected.length },
693
+ },
694
+ filters: normalizedFilters,
695
+ snapshot: {
696
+ id: snapshot,
697
+ first_page: firstPage,
698
+ order: LIST_ORDER,
699
+ has_more: remaining > 0,
700
+ candidate_count: snapshotCandidates.length,
701
+ candidate_max: snapshotCandidates[0]?.entryNumber ?? 0,
702
+ candidate_start_key: snapshotCandidates[0]?.sortKey ?? "",
703
+ candidate_end_key: snapshotCandidates.at(-1)?.sortKey ?? "",
704
+ page_start: parsed?.payload.after ?? (snapshotCandidates[0]?.entryNumber ?? 0) + 1,
705
+ append_behavior: "entries appended after this snapshot are excluded",
706
+ },
707
+ source_contract: {
708
+ authority: LIST_AUTHORITY,
709
+ compatibility: blocked ? "blocked" : degraded ? "degraded" : "complete",
710
+ detail: "Rows preserve explicit canonical numbers and decision Dnn shorthand. Unaddressable rows remain list-only; mirrors, duplicates, conflicts, and ambiguity are classified without inferred identity.",
711
+ retrieval: `agentera state ${artifactId} get --number N --format json`,
712
+ cursor: "opaque; bound to artifact, filters, order, candidate identity, archive hashes, and overlay revision",
713
+ },
714
+ ...(nextCursor ? { next_cursor: nextCursor } : {}),
715
+ };
716
+ }
717
+ export function boundStateList(response, format, sourceRoot = resolveSourceRoot(), projectRoot = process.cwd()) {
718
+ const policy = loadProjectionPolicy(sourceRoot);
719
+ if (responseBytes(response, format) <= policy.maxUtf8Bytes)
720
+ return response;
721
+ const originalEntries = response.entries.map((entry) => entry);
722
+ const entriesWithoutOptionalDetails = originalEntries.map((entry) => copyEntryWithoutOptionalDetails(entry));
723
+ const optionalDetailOmitted = originalEntries.filter((entry, index) => Object.keys(entry).length !== Object.keys(entriesWithoutOptionalDetails[index]).length).length;
724
+ const boundedPage = (entries, omittedRows) => {
725
+ const snapshot = response.snapshot;
726
+ const artifactId = String(response.source.artifact ?? "state");
727
+ const candidateMax = Number(snapshot.candidate_max ?? 0);
728
+ const candidateCount = Number(snapshot.candidate_count ?? response.counts.total ?? 0);
729
+ const lastEntry = entries.at(-1);
730
+ const lastNumber = lastEntry ? positiveNumber(String(lastEntry.entry_number)) : null;
731
+ const lastKey = typeof lastEntry?.cursor_key === "string" ? lastEntry.cursor_key : null;
732
+ const continuation = omittedRows > 0 && lastKey !== null
733
+ ? encodeCursor({
734
+ version: CURSOR_VERSION,
735
+ artifact_id: artifactId,
736
+ filters: response.filters,
737
+ order: LIST_ORDER,
738
+ snapshot_id: String(snapshot.id),
739
+ candidate_count: candidateCount,
740
+ candidate_max: candidateMax,
741
+ candidate_start_key: String(snapshot.candidate_start_key ?? ""),
742
+ candidate_end_key: String(snapshot.candidate_end_key ?? ""),
743
+ after_key: lastKey,
744
+ after: lastNumber ?? 1,
745
+ }, projectRoot, sourceRoot)
746
+ : omittedRows === 0
747
+ ? response.next_cursor
748
+ : undefined;
749
+ const bounded = {
750
+ ...response,
751
+ status: optionalDetailOmitted + omittedRows > 0 ? "degraded" : response.status,
752
+ entries,
753
+ counts: {
754
+ ...response.counts,
755
+ returned: entries.length,
756
+ remaining: Number(response.counts.remaining ?? 0) + omittedRows,
757
+ returned_physical: entries.reduce((total, entry) => total + Number(entry.physical_count ?? 1), 0),
758
+ omitted: Math.max(0, Number(response.counts.physical ?? response.counts.total ?? 0) - entries.reduce((total, entry) => total + Number(entry.physical_count ?? 1), 0)),
759
+ },
760
+ snapshot: { ...snapshot, has_more: continuation !== undefined },
761
+ ...(optionalDetailOmitted + omittedRows > 0
762
+ ? omissionMetadata(response.command, optionalDetailOmitted + omittedRows, entries)
763
+ : {}),
764
+ };
765
+ delete bounded.next_cursor;
766
+ if (continuation)
767
+ bounded.next_cursor = continuation;
768
+ return bounded;
769
+ };
770
+ for (let retainedCount = entriesWithoutOptionalDetails.length; retainedCount > 0; retainedCount -= 1) {
771
+ const entries = entriesWithoutOptionalDetails.slice(0, retainedCount);
772
+ const omittedRows = entriesWithoutOptionalDetails.length - retainedCount;
773
+ const bounded = boundedPage(entries, omittedRows);
774
+ if (responseBytes(bounded, format) <= policy.maxUtf8Bytes)
775
+ return bounded;
776
+ }
777
+ throw listFailure(1, "unsupported_state", `list output cannot emit an advancing row page within the ${policy.maxUtf8Bytes}-byte ${format} authority budget`, String(response.source.artifact ?? "state"), "Reduce the requested output to a supported state artifact or repair the authority budget before retrying; no continuation cursor was issued.", { format, candidate_count: Number(response.snapshot.candidate_count ?? response.counts.total ?? 0) });
778
+ }
779
+ export function renderStateListText(response) {
780
+ return textList(response);
781
+ }
782
+ //# sourceMappingURL=listRetrieval.js.map