agentera 3.0.0-dev.72 → 3.0.0-dev.74

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 (92) hide show
  1. package/README.md +111 -0
  2. package/bundle/.agentera-build-source.json +8 -0
  3. package/bundle/CHANGELOG.md +17 -17
  4. package/bundle/extract-corpus-parity.json +1 -1
  5. package/bundle/references/adapters/package-publication.json +49 -53
  6. package/bundle/references/adapters/package-registry.yaml +5 -1
  7. package/bundle/references/adapters/package-surface-characterization.md +6 -4
  8. package/bundle/references/analysis/evidence-tier-authority.yaml +45 -4
  9. package/bundle/references/analysis/personal-glossary-evaluation-authority.yaml +175 -0
  10. package/bundle/references/analysis/personal-glossary-evaluation-corpus.yaml +291 -0
  11. package/bundle/references/analysis/personal-glossary-holdout.yaml +192 -0
  12. package/bundle/references/artifacts/glossary-entry-contract.yaml +1533 -35
  13. package/bundle/references/cli/update-channels.yaml +2 -2
  14. package/bundle/references/cli/vocabulary.md +1 -1
  15. package/bundle/references/meta/retained-reference-authority.yaml +15 -0
  16. package/bundle/skills/agentera/capabilities/profile/schemas/artifacts.yaml +11 -7
  17. package/bundle/skills/agentera/capabilities/profile/schemas/exit.yaml +6 -3
  18. package/bundle/skills/agentera/capabilities/profile/schemas/validation.yaml +13 -7
  19. package/bundle/skills/agentera/schemas/artifacts/glossary.yaml +5 -0
  20. package/bundle/skills/agentera/schemas/artifacts/plan.yaml +7 -0
  21. package/dist/.agentera-build-source.json +8 -0
  22. package/dist/analytics/extractCorpus/copilotSessions.js +13 -4
  23. package/dist/analytics/extractCorpus/core.js +223 -3
  24. package/dist/analytics/extractCorpus/corpus.js +18 -1
  25. package/dist/analytics/extractCorpus/cursorSessions.js +22 -4
  26. package/dist/analytics/extractCorpus/evidenceTiers.js +123 -17
  27. package/dist/analytics/extractCorpus/filesystemSources.js +4 -0
  28. package/dist/analytics/extractCorpus/index.js +2 -2
  29. package/dist/analytics/extractCorpus/jsonlSessions.js +52 -3
  30. package/dist/analytics/extractCorpus/sqliteSessions.js +12 -3
  31. package/dist/analytics/personalGlossaryAdmission.js +26 -46
  32. package/dist/analytics/personalGlossaryCandidateProjection.js +692 -0
  33. package/dist/analytics/personalGlossaryCandidateProjectionExcerpts.js +144 -0
  34. package/dist/analytics/personalGlossaryCandidateReadView.js +23 -0
  35. package/dist/analytics/personalGlossaryCurrentGeneration.js +25 -0
  36. package/dist/analytics/personalGlossaryDecision.js +203 -0
  37. package/dist/analytics/personalGlossaryExplicit.js +788 -0
  38. package/dist/analytics/personalGlossaryExplicitMining.js +285 -0
  39. package/dist/analytics/personalGlossaryExplicitScope.js +41 -0
  40. package/dist/analytics/personalGlossaryExplicitSegments.js +418 -0
  41. package/dist/analytics/personalGlossaryExplicitTypes.js +26 -0
  42. package/dist/analytics/personalGlossaryLexicalClassifier.js +175 -0
  43. package/dist/analytics/personalGlossaryProfile.js +13 -3
  44. package/dist/analytics/personalGlossaryRecurrence.js +618 -0
  45. package/dist/analytics/personalGlossaryReviewRecordStorage.js +428 -0
  46. package/dist/analytics/personalGlossaryReviewRecords.js +332 -0
  47. package/dist/capabilities/index.js +5 -1
  48. package/dist/capabilities/profile/instructions.js +50 -15
  49. package/dist/cli/commands/personalGlossaryCandidateReads.js +744 -0
  50. package/dist/cli/commands/personalGlossaryDecision.js +349 -0
  51. package/dist/cli/commands/personalGlossaryPublish.js +484 -0
  52. package/dist/cli/commands/personalGlossaryReviewRecordReads.js +209 -0
  53. package/dist/cli/commands/personalGlossaryReviewRecords.js +623 -0
  54. package/dist/cli/commands/prime/briefOrientation.js +118 -19
  55. package/dist/cli/commands/report.js +18 -2
  56. package/dist/cli/commands/schema.js +179 -0
  57. package/dist/cli/commands/verify.js +51 -9
  58. package/dist/cli/dispatch/lifecycle.js +51 -6
  59. package/dist/cli/help.js +48 -2
  60. package/dist/cli/migrationRequired.js +4 -1
  61. package/dist/core/atomicWriter.js +4 -1
  62. package/dist/core/developmentInvocation.js +4 -1
  63. package/dist/core/yaml.js +53 -1
  64. package/dist/eval/glossaryEvaluation.js +751 -0
  65. package/dist/eval/glossaryEvaluationProcess.js +29 -0
  66. package/dist/eval/glossaryEvaluationRunner.js +571 -0
  67. package/dist/eval/glossaryEvaluationSuccessReport.js +297 -0
  68. package/dist/registries/activationTuples.js +31 -3
  69. package/dist/registries/evidenceTierContract.js +4 -0
  70. package/dist/registries/explicitSegmentGrammarContract.js +619 -0
  71. package/dist/registries/glossaryCandidateContractPaths.js +6 -0
  72. package/dist/registries/glossaryCandidateContracts.js +820 -0
  73. package/dist/registries/glossaryCandidateDecisionAuthority.js +119 -0
  74. package/dist/registries/glossaryCandidateDecisionContract.js +62 -0
  75. package/dist/registries/glossaryCandidateProjectionAuthority.js +197 -0
  76. package/dist/registries/glossaryCandidateProjectionContract.js +104 -0
  77. package/dist/registries/glossaryEntryContract.js +105 -103
  78. package/dist/registries/glossaryEntryTemporal.js +12 -0
  79. package/dist/registries/glossaryMiningAuthority.js +764 -0
  80. package/dist/registries/glossaryProfileFullContract.js +24 -0
  81. package/dist/registries/glossaryReviewRecordsAuthority.js +426 -0
  82. package/dist/registries/glossaryReviewRecordsContract.js +129 -0
  83. package/dist/registries/glossaryTermIdentity.js +122 -0
  84. package/dist/registries/packagePublication.js +3 -3
  85. package/dist/registries/personalGlossaryContracts.js +1 -0
  86. package/dist/release/releaseMetadata.js +14 -7
  87. package/dist/upgrade/npxPlatformStatus.js +2 -1
  88. package/dist/upgrade/projectIntegration.js +7 -1
  89. package/dist/validate/activationArtifactEvidence.js +18 -4
  90. package/dist/validate/activationEvidenceManifest.js +101 -22
  91. package/package.json +2 -4
  92. package/dist/cli/commands/personalGlossary.js +0 -176
@@ -0,0 +1,623 @@
1
+ import fs from "node:fs";
2
+ import { dispositionPersonalGlossaryReviewRecord, queuePersonalGlossaryReviewRecord, validPersonalGlossaryReviewGenerationBinding, validPersonalGlossaryReviewMetadataBinding, } from "../../analytics/personalGlossaryReviewRecords.js";
3
+ import { loadYamlMapping } from "../../core/yaml.js";
4
+ import { personalGlossaryReviewRecordsContract } from "../../registries/glossaryReviewRecordsContract.js";
5
+ import { getPersonalGlossaryReviewRecord, listPersonalGlossaryReviewRecords, } from "./personalGlossaryReviewRecordReads.js";
6
+ import { emitInvalidInput } from "../errors.js";
7
+ import { emitStructured } from "../structured.js";
8
+ const SHA256 = /^[a-f0-9]{64}$/u;
9
+ const MAX_CURSOR_UTF8_BYTES = 4_096;
10
+ function mapping(value) {
11
+ return value !== null && typeof value === "object" && !Array.isArray(value);
12
+ }
13
+ function sameStrings(actual, expected) {
14
+ return JSON.stringify(actual) === JSON.stringify(expected);
15
+ }
16
+ function contract() {
17
+ const value = personalGlossaryReviewRecordsContract();
18
+ const statuses = value.listStatuses;
19
+ if (value.command !== "agentera report personal-glossary-reviews" ||
20
+ value.queueRequestSchemaVersion !== "agentera.personalGlossaryReviewQueueRequest.v1" ||
21
+ !sameStrings(value.queueRequestFields, ["schema_version", "receipt"]) ||
22
+ value.queueMaxRequestUtf8Bytes !== 16_384 ||
23
+ value.queueResultSchemaVersion !== "agentera.personalGlossaryReviewQueueResult.v1" ||
24
+ !sameStrings(value.queueResultStatuses, ["queued", "unchanged_replay", "suppressed", "reopened"]) ||
25
+ value.queueMaxResultUtf8Bytes !== 4_096 ||
26
+ value.dispositionRequestSchemaVersion !== "agentera.personalGlossaryReviewDispositionRequest.v1" ||
27
+ !sameStrings(value.dispositionRequestFields, ["schema_version", "review_id", "receipt", "approval"]) ||
28
+ value.dispositionMaxRequestUtf8Bytes !== 16_384 ||
29
+ value.dispositionResultSchemaVersion !== "agentera.personalGlossaryReviewDispositionResult.v1" ||
30
+ !sameStrings(value.dispositionResultStatuses, ["disposed", "unchanged_replay"]) ||
31
+ value.dispositionMaxResultUtf8Bytes !== 4_096 ||
32
+ !sameStrings(value.dispositionPublicationAuthorizationDispositions, ["accept", "correct"]) ||
33
+ !sameStrings(value.dispositionPublicationAuthorizationFields, ["review_id", "review_record_sha256"]) ||
34
+ value.retrievalSchemaVersion !== "agentera.personalGlossaryReviewRetrieval.v1" ||
35
+ value.retrievalOwner !== "current_user" ||
36
+ value.listDefaultLimit !== 20 ||
37
+ value.listMaximumLimit !== 50 ||
38
+ value.listMaxSerializedUtf8Bytes !== 32_768 ||
39
+ value.listOrder !== "queued_at_desc_then_review_id_asc" ||
40
+ !sameStrings(statuses, ["pending", "terminal"]) ||
41
+ value.cursorAuthority !==
42
+ "references/artifacts/state-storage-authority.yaml#entity_target.public_retrieval.policy.cursor" ||
43
+ value.cursorVocabulary !== "opaque_snapshot_cursor" ||
44
+ !sameStrings(value.cursorBinding, ["collection", "owner", "filters", "limit", "order", "snapshot"]) ||
45
+ value.cursorInvalidBehavior !== "cursor_invalid" ||
46
+ value.cursorUnavailableBehavior !== "cursor_snapshot_unavailable" ||
47
+ !sameStrings(value.exactRequiredBindings, [
48
+ "review_id",
49
+ "candidate_id",
50
+ "candidate_revision",
51
+ "generation",
52
+ "policy_version",
53
+ ]) ||
54
+ value.exactCurrentBindingField !== "candidate_projection_sha256" ||
55
+ value.exactMaxSerializedUtf8Bytes !== 8_192) {
56
+ throw new TypeError("personal glossary review-record command contract is unavailable");
57
+ }
58
+ return {
59
+ command: value.command,
60
+ queueRequestSchemaVersion: value.queueRequestSchemaVersion,
61
+ queueRequestFields: value.queueRequestFields,
62
+ queueMaxRequestUtf8Bytes: value.queueMaxRequestUtf8Bytes,
63
+ queueResultSchemaVersion: value.queueResultSchemaVersion,
64
+ queueResultMaxUtf8Bytes: value.queueMaxResultUtf8Bytes,
65
+ dispositionRequestSchemaVersion: value.dispositionRequestSchemaVersion,
66
+ dispositionRequestFields: value.dispositionRequestFields,
67
+ dispositionMaxRequestUtf8Bytes: value.dispositionMaxRequestUtf8Bytes,
68
+ dispositionResultSchemaVersion: value.dispositionResultSchemaVersion,
69
+ dispositionResultMaxUtf8Bytes: value.dispositionMaxResultUtf8Bytes,
70
+ dispositionPublicationAuthorizationDispositions: value.dispositionPublicationAuthorizationDispositions,
71
+ dispositionPublicationAuthorizationFields: value.dispositionPublicationAuthorizationFields,
72
+ retrievalSchemaVersion: value.retrievalSchemaVersion,
73
+ owner: value.retrievalOwner,
74
+ defaultLimit: value.listDefaultLimit,
75
+ maximumLimit: value.listMaximumLimit,
76
+ listMaxSerializedUtf8Bytes: value.listMaxSerializedUtf8Bytes,
77
+ order: value.listOrder,
78
+ statuses,
79
+ cursorAuthority: value.cursorAuthority,
80
+ cursorVocabulary: value.cursorVocabulary,
81
+ cursorBinding: value.cursorBinding,
82
+ cursorInvalidBehavior: value.cursorInvalidBehavior,
83
+ cursorUnavailableBehavior: value.cursorUnavailableBehavior,
84
+ exactBindings: value.exactRequiredBindings,
85
+ exactMaxSerializedUtf8Bytes: value.exactMaxSerializedUtf8Bytes,
86
+ };
87
+ }
88
+ function queueSyntax(value) {
89
+ return `${value.command} queue --input <file|-> --format json`;
90
+ }
91
+ function dispositionSyntax(value) {
92
+ return `${value.command} disposition --input <file|-> --format json`;
93
+ }
94
+ function listSyntax(value) {
95
+ return `${value.command} list [--status pending|terminal] [--limit N] [--cursor TOKEN] --format json`;
96
+ }
97
+ function exactSyntax(value) {
98
+ return `${value.command} get --review-id ID --candidate-id ID --candidate-revision REVISION --generation GENERATION --policy-version POLICY --format json`;
99
+ }
100
+ function invalid(io, body) {
101
+ return emitInvalidInput(io, { format: "json", body });
102
+ }
103
+ function failure(io, schemaVersion, command, syntax, example, body) {
104
+ emitStructured({
105
+ schemaVersion,
106
+ command,
107
+ status: "fail",
108
+ error: { ...body, syntax, example },
109
+ }, "json", io.out ?? ((text) => process.stdout.write(text)));
110
+ return 1;
111
+ }
112
+ function argvPart(argument) {
113
+ const separator = argument.indexOf("=");
114
+ return separator < 0
115
+ ? { name: argument }
116
+ : { name: argument.slice(0, separator), inline: argument.slice(separator + 1) };
117
+ }
118
+ function boundedText(value, maximum) {
119
+ return value.length > 0 && Buffer.byteLength(value, "utf8") <= maximum;
120
+ }
121
+ function parseQueue(argv, value) {
122
+ let input;
123
+ let format = false;
124
+ for (let index = 0; index < argv.length; index += 1) {
125
+ const { name, inline } = argvPart(argv[index]);
126
+ if (name !== "--input" && name !== "--format") {
127
+ return { class: "unrecognized_argument", message: "unrecognized review-record argument", syntax: queueSyntax(value) };
128
+ }
129
+ const option = inline ?? argv[++index];
130
+ if (!option || option.startsWith("--")) {
131
+ return { class: "missing_argument", message: `${name} requires a value`, syntax: `${name} VALUE` };
132
+ }
133
+ if (name === "--format") {
134
+ if (format)
135
+ return { class: "mutually_exclusive", message: "--format may only be supplied once", syntax: queueSyntax(value) };
136
+ if (option !== "json")
137
+ return { class: "invalid_choice", message: "personal-glossary-reviews requires --format json", valid_values: ["json"] };
138
+ format = true;
139
+ continue;
140
+ }
141
+ if (input !== undefined)
142
+ return { class: "mutually_exclusive", message: "--input may only be supplied once", syntax: queueSyntax(value) };
143
+ input = option;
144
+ }
145
+ return input ? { input } : { class: "missing_argument", message: "--input is required", syntax: queueSyntax(value) };
146
+ }
147
+ function parseDisposition(argv, value) {
148
+ let input;
149
+ let format = false;
150
+ for (let index = 0; index < argv.length; index += 1) {
151
+ const { name, inline } = argvPart(argv[index]);
152
+ if (name !== "--input" && name !== "--format") {
153
+ return { class: "unrecognized_argument", message: "unrecognized review disposition argument", syntax: dispositionSyntax(value) };
154
+ }
155
+ const option = inline ?? argv[++index];
156
+ if (!option || option.startsWith("--")) {
157
+ return { class: "missing_argument", message: `${name} requires a value`, syntax: `${name} VALUE` };
158
+ }
159
+ if (name === "--format") {
160
+ if (format)
161
+ return { class: "mutually_exclusive", message: "--format may only be supplied once", syntax: dispositionSyntax(value) };
162
+ if (option !== "json")
163
+ return { class: "invalid_choice", message: "personal-glossary-reviews requires --format json", valid_values: ["json"] };
164
+ format = true;
165
+ continue;
166
+ }
167
+ if (input !== undefined)
168
+ return { class: "mutually_exclusive", message: "--input may only be supplied once", syntax: dispositionSyntax(value) };
169
+ input = option;
170
+ }
171
+ return input ? { input } : { class: "missing_argument", message: "--input is required", syntax: dispositionSyntax(value) };
172
+ }
173
+ function parseList(argv, value) {
174
+ let limit = value.defaultLimit;
175
+ let cursor;
176
+ let status;
177
+ const seen = new Set();
178
+ for (let index = 0; index < argv.length; index += 1) {
179
+ const { name, inline } = argvPart(argv[index]);
180
+ if (![
181
+ "--format",
182
+ "--limit",
183
+ "--cursor",
184
+ "--status",
185
+ ].includes(name)) {
186
+ return { class: "unrecognized_argument", message: "unrecognized review-record argument", syntax: listSyntax(value) };
187
+ }
188
+ if (seen.has(name))
189
+ return { class: "mutually_exclusive", message: `${name} may only be supplied once`, syntax: listSyntax(value) };
190
+ seen.add(name);
191
+ const option = inline ?? argv[++index];
192
+ if (!option || option.startsWith("--"))
193
+ return { class: "missing_argument", message: `${name} requires a value`, syntax: `${name} VALUE` };
194
+ if (name === "--format") {
195
+ if (option !== "json")
196
+ return { class: "invalid_choice", message: "--format must be json", valid_values: ["json"] };
197
+ continue;
198
+ }
199
+ if (name === "--limit") {
200
+ if (!/^[1-9]\d*$/u.test(option))
201
+ return { class: "invalid_int", message: "--limit must be a positive integer", syntax: listSyntax(value) };
202
+ const parsed = Number(option);
203
+ if (parsed > value.maximumLimit)
204
+ return { class: "invalid_choice", message: `--limit must be from 1 to ${value.maximumLimit}`, valid_values: [`1..${value.maximumLimit}`], syntax: listSyntax(value) };
205
+ limit = parsed;
206
+ continue;
207
+ }
208
+ if (name === "--cursor") {
209
+ if (!boundedText(option, MAX_CURSOR_UTF8_BYTES))
210
+ return { class: "invalid_request", message: `--cursor must be a non-empty value within ${MAX_CURSOR_UTF8_BYTES} UTF-8 bytes`, syntax: listSyntax(value) };
211
+ cursor = option;
212
+ continue;
213
+ }
214
+ if (!value.statuses.includes(option)) {
215
+ return { class: "invalid_choice", message: "--status must be pending or terminal", valid_values: value.statuses, syntax: listSyntax(value) };
216
+ }
217
+ status = option;
218
+ }
219
+ return { limit, cursor, status };
220
+ }
221
+ function parseExact(argv, value) {
222
+ const names = {
223
+ "--review-id": "reviewId",
224
+ "--candidate-id": "candidateId",
225
+ "--candidate-revision": "candidateRevision",
226
+ "--generation": "generation",
227
+ "--policy-version": "policyVersion",
228
+ };
229
+ const fields = {};
230
+ const seen = new Set();
231
+ for (let index = 0; index < argv.length; index += 1) {
232
+ const { name, inline } = argvPart(argv[index]);
233
+ if (name !== "--format" && !(name in names))
234
+ return { class: "unrecognized_argument", message: "unrecognized review-record argument", syntax: exactSyntax(value) };
235
+ if (seen.has(name))
236
+ return { class: "mutually_exclusive", message: `${name} may only be supplied once`, syntax: exactSyntax(value) };
237
+ seen.add(name);
238
+ const option = inline ?? argv[++index];
239
+ if (!option || option.startsWith("--"))
240
+ return { class: "missing_argument", message: `${name} requires a value`, syntax: `${name} VALUE` };
241
+ if (name === "--format") {
242
+ if (option !== "json")
243
+ return { class: "invalid_choice", message: "--format must be json", valid_values: ["json"] };
244
+ continue;
245
+ }
246
+ fields[names[name]] = option;
247
+ }
248
+ for (const [flag, field] of Object.entries(names)) {
249
+ if (!fields[field])
250
+ return { class: "missing_argument", message: `${flag} is required`, syntax: exactSyntax(value) };
251
+ }
252
+ for (const [flag, field] of [
253
+ ["--review-id", "reviewId"],
254
+ ["--candidate-id", "candidateId"],
255
+ ["--candidate-revision", "candidateRevision"],
256
+ ]) {
257
+ if (!SHA256.test(fields[field])) {
258
+ return { class: "invalid_request", message: `${flag} must be a lowercase SHA-256 identity`, valid_values: ["64 lowercase hexadecimal characters"], syntax: exactSyntax(value) };
259
+ }
260
+ }
261
+ for (const [flag, field, valid] of [
262
+ ["--generation", "generation", validPersonalGlossaryReviewGenerationBinding],
263
+ ["--policy-version", "policyVersion", validPersonalGlossaryReviewMetadataBinding],
264
+ ]) {
265
+ if (!valid(fields[field])) {
266
+ return {
267
+ class: "invalid_request",
268
+ message: `${flag} must be a non-secret, non-path value within 256 UTF-8 bytes`,
269
+ syntax: exactSyntax(value),
270
+ };
271
+ }
272
+ }
273
+ return fields;
274
+ }
275
+ function readBoundedDescriptor(fd, maxBytes) {
276
+ const bytes = Buffer.allocUnsafe(maxBytes + 1);
277
+ let offset = 0;
278
+ while (offset < bytes.length) {
279
+ const count = fs.readSync(fd, bytes, offset, bytes.length - offset, null);
280
+ if (count === 0)
281
+ break;
282
+ offset += count;
283
+ }
284
+ if (offset > maxBytes)
285
+ throw new Error("over bound");
286
+ return bytes.subarray(0, offset);
287
+ }
288
+ function sameIdentity(left, right) {
289
+ return left.dev === right.dev && left.ino === right.ino;
290
+ }
291
+ function readBoundedFile(source, maxBytes) {
292
+ const observed = fs.lstatSync(source, { bigint: true });
293
+ if (observed.isSymbolicLink() || !observed.isFile() || observed.size > BigInt(maxBytes)) {
294
+ throw new Error("over bound or unreadable");
295
+ }
296
+ let descriptor = null;
297
+ try {
298
+ descriptor = fs.openSync(source, fs.constants.O_RDONLY | (fs.constants.O_NOFOLLOW ?? 0));
299
+ const opened = fs.fstatSync(descriptor, { bigint: true });
300
+ if (!opened.isFile() || !sameIdentity(observed, opened) || opened.size > BigInt(maxBytes)) {
301
+ throw new Error("over bound or unreadable");
302
+ }
303
+ return readBoundedDescriptor(descriptor, maxBytes);
304
+ }
305
+ finally {
306
+ if (descriptor !== null)
307
+ fs.closeSync(descriptor);
308
+ }
309
+ }
310
+ function readBoundedStdin(maxBytes, io) {
311
+ if (io.stdin) {
312
+ const value = io.stdin();
313
+ if (Buffer.byteLength(value, "utf8") > maxBytes)
314
+ throw new Error("over bound");
315
+ return Buffer.from(value, "utf8");
316
+ }
317
+ if (process.stdin.isTTY)
318
+ return Buffer.alloc(0);
319
+ return readBoundedDescriptor(0, maxBytes);
320
+ }
321
+ function readRequest(source, maxBytes, io) {
322
+ const bytes = source === "-" ? readBoundedStdin(maxBytes, io) : readBoundedFile(source, maxBytes);
323
+ const text = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
324
+ const value = loadYamlMapping(text);
325
+ if (!mapping(value))
326
+ throw new Error("not a mapping");
327
+ return value;
328
+ }
329
+ function validateQueueRequest(request, value) {
330
+ const keys = Object.keys(request);
331
+ if (keys.some((key) => !value.queueRequestFields.includes(key)) ||
332
+ value.queueRequestFields.some((field) => !(field in request))) {
333
+ return { error: { class: "schema_violation", message: "personal glossary review queue request fields are invalid", valid_values: value.queueRequestFields } };
334
+ }
335
+ if (request.schema_version !== value.queueRequestSchemaVersion || !mapping(request.receipt)) {
336
+ return { error: { class: "schema_violation", message: `request requires ${value.queueRequestSchemaVersion} and one receipt mapping`, valid_values: [value.queueRequestSchemaVersion, "receipt"] } };
337
+ }
338
+ return { receipt: request.receipt };
339
+ }
340
+ function validateDispositionRequest(request, value) {
341
+ const keys = Object.keys(request);
342
+ if (keys.some((key) => !value.dispositionRequestFields.includes(key)) ||
343
+ value.dispositionRequestFields.some((field) => !(field in request))) {
344
+ return {
345
+ error: {
346
+ class: "schema_violation",
347
+ message: "personal glossary review disposition request fields are invalid",
348
+ valid_values: value.dispositionRequestFields,
349
+ },
350
+ };
351
+ }
352
+ if (request.schema_version !== value.dispositionRequestSchemaVersion ||
353
+ typeof request.review_id !== "string" ||
354
+ !SHA256.test(request.review_id) ||
355
+ !mapping(request.receipt) ||
356
+ !mapping(request.approval)) {
357
+ return {
358
+ error: {
359
+ class: "schema_violation",
360
+ message: `request requires ${value.dispositionRequestSchemaVersion}, one review identity, receipt, and approval mappings`,
361
+ valid_values: [value.dispositionRequestSchemaVersion, "review_id", "receipt", "approval"],
362
+ },
363
+ };
364
+ }
365
+ return { reviewId: request.review_id, receipt: request.receipt, approval: request.approval };
366
+ }
367
+ function reviewSummary(record) {
368
+ return {
369
+ review_id: record.review_id,
370
+ candidate_id: record.candidate_id,
371
+ candidate_revision: record.candidate_revision,
372
+ candidate_capsule_sha256: record.candidate_capsule_sha256,
373
+ candidate_projection_sha256: record.candidate_projection_sha256,
374
+ host_receipt_sha256: record.host_receipt_sha256,
375
+ cli_decision_sha256: record.cli_decision_sha256,
376
+ semantic_fingerprint: record.semantic_fingerprint,
377
+ generation: record.generation,
378
+ policy_version: record.policy_version,
379
+ scope: record.scope,
380
+ reason: record.reason,
381
+ status: record.status,
382
+ disposition: record.disposition,
383
+ reopen_reason: record.reopen_reason,
384
+ queued_at: record.queued_at,
385
+ terminal_at: record.terminal_at,
386
+ expires_at: record.expires_at,
387
+ record_sha256: record.record_sha256,
388
+ };
389
+ }
390
+ function serializedBytes(value) {
391
+ return Buffer.byteLength(`${JSON.stringify(value, null, 2)}\n`, "utf8");
392
+ }
393
+ function emitQueueSuccess(io, value, result) {
394
+ const response = {
395
+ schemaVersion: value.queueResultSchemaVersion,
396
+ command: `${value.command} queue`,
397
+ status: result.status,
398
+ reason: result.reason,
399
+ reopen_reason: result.reopen_reason,
400
+ owner: value.owner,
401
+ record: reviewSummary(result.record),
402
+ effects: {
403
+ review_metadata: result.status === "queued" || result.status === "reopened"
404
+ ? "created"
405
+ : result.status === "suppressed"
406
+ ? "unchanged_suppressed"
407
+ : "unchanged_replay",
408
+ profile_entry: "unchanged",
409
+ project_state: "unchanged",
410
+ candidate_projection: "unchanged",
411
+ publication: "unchanged",
412
+ },
413
+ };
414
+ if (serializedBytes(response) > value.queueResultMaxUtf8Bytes) {
415
+ return failure(io, value.queueResultSchemaVersion, `${value.command} queue`, queueSyntax(value), queueSyntax(value), {
416
+ class: "output_bound_exceeded",
417
+ message: `review queue response exceeds its ${value.queueResultMaxUtf8Bytes}-byte bound`,
418
+ recovery: "Repair the private review records before retrying; no partial review metadata was returned.",
419
+ });
420
+ }
421
+ emitStructured(response, "json", io.out ?? ((text) => process.stdout.write(text)));
422
+ return 0;
423
+ }
424
+ function queueReview(io, input, value) {
425
+ let request;
426
+ try {
427
+ request = readRequest(input, value.queueMaxRequestUtf8Bytes, io);
428
+ }
429
+ catch {
430
+ return invalid(io, { class: "invalid_format", message: "--input must be one readable bounded UTF-8 YAML or JSON mapping" });
431
+ }
432
+ const validated = validateQueueRequest(request, value);
433
+ if ("error" in validated)
434
+ return invalid(io, validated.error);
435
+ let result;
436
+ try {
437
+ result = queuePersonalGlossaryReviewRecord({ receipt: validated.receipt });
438
+ }
439
+ catch {
440
+ return failure(io, value.queueResultSchemaVersion, `${value.command} queue`, queueSyntax(value), queueSyntax(value), {
441
+ class: "review_records_unavailable",
442
+ message: "the private current-user review records could not be read or written",
443
+ recovery: "Repair the configured profile path or its permissions, then retry; no profile, project, or candidate-projection bytes were changed.",
444
+ });
445
+ }
446
+ if (["queued", "unchanged_replay", "suppressed", "reopened"].includes(result.status)) {
447
+ return emitQueueSuccess(io, value, result);
448
+ }
449
+ const failures = {
450
+ decision_not_review_required: {
451
+ class: "review_not_required",
452
+ message: "the current deterministic decision is not review_required",
453
+ recovery: "Use the current decision outcome without queueing, or submit one current review-required receipt; no review metadata was changed.",
454
+ },
455
+ current_binding_mismatch: {
456
+ class: "current_binding_mismatch",
457
+ message: "the review receipt or decision does not bind the current candidate projection",
458
+ recovery: "Read the current candidate projection, create one matching host receipt, and retry; no review metadata was changed.",
459
+ },
460
+ records_unavailable: {
461
+ class: "review_records_unavailable",
462
+ message: "the private current-user review records are unavailable or invalid",
463
+ recovery: "Repair the private review-record file, then retry; no profile, project, or candidate-projection bytes were changed.",
464
+ },
465
+ record_capacity_exceeded: {
466
+ class: "review_record_capacity_exceeded",
467
+ message: "the bounded private review-record store is full",
468
+ recovery: "Run authenticated review maintenance or purge expired terminal metadata, then retry; no review metadata was changed.",
469
+ },
470
+ already_terminal: {
471
+ class: "review_already_terminal",
472
+ message: "the matching review record is terminal and cannot be silently replaced",
473
+ recovery: "Use the existing terminal review lifecycle or queue a distinct current binding; no review metadata was changed.",
474
+ },
475
+ };
476
+ return failure(io, value.queueResultSchemaVersion, `${value.command} queue`, queueSyntax(value), queueSyntax(value), failures[result.status]);
477
+ }
478
+ function emitDispositionSuccess(io, value, result) {
479
+ const response = {
480
+ schemaVersion: value.dispositionResultSchemaVersion,
481
+ command: `${value.command} disposition`,
482
+ status: result.status,
483
+ owner: value.owner,
484
+ record: reviewSummary(result.record),
485
+ publication_authorization: result.publication_authorization,
486
+ effects: {
487
+ review_metadata: result.status === "disposed" ? "changed" : "unchanged_replay",
488
+ profile_entry: "unchanged",
489
+ project_state: "unchanged",
490
+ candidate_projection: "unchanged",
491
+ publication: "unchanged",
492
+ },
493
+ };
494
+ if (serializedBytes(response) > value.dispositionResultMaxUtf8Bytes) {
495
+ return failure(io, value.dispositionResultSchemaVersion, `${value.command} disposition`, dispositionSyntax(value), dispositionSyntax(value), {
496
+ class: "output_bound_exceeded",
497
+ message: `review disposition response exceeds its ${value.dispositionResultMaxUtf8Bytes}-byte bound`,
498
+ recovery: "Repair the private review records before retrying; no partial review metadata was returned.",
499
+ });
500
+ }
501
+ emitStructured(response, "json", io.out ?? ((text) => process.stdout.write(text)));
502
+ return 0;
503
+ }
504
+ function dispositionReview(io, input, value) {
505
+ let request;
506
+ try {
507
+ request = readRequest(input, value.dispositionMaxRequestUtf8Bytes, io);
508
+ }
509
+ catch {
510
+ return invalid(io, { class: "invalid_format", message: "--input must be one readable bounded UTF-8 YAML or JSON mapping" });
511
+ }
512
+ const validated = validateDispositionRequest(request, value);
513
+ if ("error" in validated)
514
+ return invalid(io, validated.error);
515
+ let result;
516
+ try {
517
+ result = dispositionPersonalGlossaryReviewRecord({
518
+ review_id: validated.reviewId,
519
+ receipt: validated.receipt,
520
+ approval: validated.approval,
521
+ });
522
+ }
523
+ catch {
524
+ return failure(io, value.dispositionResultSchemaVersion, `${value.command} disposition`, dispositionSyntax(value), dispositionSyntax(value), {
525
+ class: "review_records_unavailable",
526
+ message: "the private current-user review records could not be read or written",
527
+ recovery: "Repair the configured profile path or its permissions, then retry; no profile, project, or candidate-projection bytes were changed.",
528
+ });
529
+ }
530
+ if (result.status === "disposed" || result.status === "unchanged_replay") {
531
+ return emitDispositionSuccess(io, value, result);
532
+ }
533
+ const failures = {
534
+ review_not_found: {
535
+ class: "not_found",
536
+ message: "the requested current review record was not found",
537
+ recovery: "List current private review records and copy one current review identity; no review metadata was changed.",
538
+ },
539
+ review_not_pending: {
540
+ class: "review_not_pending",
541
+ message: "the requested review is terminal and cannot receive another disposition",
542
+ recovery: "Use its existing publication authorization or queue a current distinct review; no review metadata was changed.",
543
+ },
544
+ current_binding_mismatch: {
545
+ class: "current_binding_mismatch",
546
+ message: "the review receipt or deterministic decision no longer binds the current candidate projection",
547
+ recovery: "Read the current candidate, create matching host classification evidence, and retry; no review metadata was changed.",
548
+ },
549
+ approval_invalid: {
550
+ class: "review_approval_invalid",
551
+ message: "the current-user review approval is invalid or no longer fresh",
552
+ recovery: "Request a new signed current-user local-host approval for this current review, then retry; no review metadata was changed.",
553
+ },
554
+ approval_conflicting_replay: {
555
+ class: "review_approval_replayed",
556
+ message: "the review approval nonce was already used with different signed content",
557
+ recovery: "Request a new signed current-user local-host approval with a new nonce, then retry; no review metadata was changed.",
558
+ },
559
+ approval_unavailable: {
560
+ class: "review_approval_unavailable",
561
+ message: "the configured trusted local-host key is unavailable or invalid",
562
+ recovery: "Repair the user-local trusted host key configuration, then retry; no review metadata was changed.",
563
+ },
564
+ records_unavailable: {
565
+ class: "review_records_unavailable",
566
+ message: "the private current-user review records are unavailable or invalid",
567
+ recovery: "Repair the private review-record file, then retry; no profile, project, or candidate-projection bytes were changed.",
568
+ },
569
+ replay_capacity_exceeded: {
570
+ class: "review_replay_capacity_exceeded",
571
+ message: "the bounded review approval replay index is full",
572
+ recovery: "Run authenticated review maintenance after receipt expiry, then retry; no review metadata was changed.",
573
+ },
574
+ };
575
+ return failure(io, value.dispositionResultSchemaVersion, `${value.command} disposition`, dispositionSyntax(value), dispositionSyntax(value), failures[result.status]);
576
+ }
577
+ /** Run the private, noninteractive review queue, disposition, and read operations. */
578
+ export function runPersonalGlossaryReviewRecordsCommand(argv, io) {
579
+ let value;
580
+ try {
581
+ value = contract();
582
+ }
583
+ catch {
584
+ return failure(io, "agentera.personalGlossaryReviewRetrieval.v1", "agentera report personal-glossary-reviews", "agentera report personal-glossary-reviews {queue,disposition,list,get} --format json", "agentera report personal-glossary-reviews list --limit 20 --format json", {
585
+ class: "unsupported_state",
586
+ message: "personal glossary review-record contract is unavailable",
587
+ recovery: "Restore the bundled glossary authority, then retry; no review metadata was changed.",
588
+ });
589
+ }
590
+ const operation = argv[0];
591
+ if (operation !== "queue" && operation !== "disposition" && operation !== "list" && operation !== "get") {
592
+ return invalid(io, {
593
+ class: operation ? "unsupported_target" : "missing_argument",
594
+ message: operation ? "unsupported personal glossary review operation" : "review operation is required",
595
+ valid_values: ["queue", "disposition", "list", "get"],
596
+ syntax: `${value.command} {queue,disposition,list,get} --format json`,
597
+ example: `${value.command} list --limit ${value.defaultLimit} --format json`,
598
+ recovery: "Choose queue, disposition, list, or get and retry; no review metadata was changed.",
599
+ });
600
+ }
601
+ if (operation === "queue") {
602
+ const parsed = parseQueue(argv.slice(1), value);
603
+ if ("class" in parsed)
604
+ return invalid(io, { ...parsed, recovery: "Correct the bounded queue request and retry; no review metadata was changed." });
605
+ return queueReview(io, parsed.input, value);
606
+ }
607
+ if (operation === "disposition") {
608
+ const parsed = parseDisposition(argv.slice(1), value);
609
+ if ("class" in parsed)
610
+ return invalid(io, { ...parsed, recovery: "Correct the bounded disposition request and retry; no review metadata was changed." });
611
+ return dispositionReview(io, parsed.input, value);
612
+ }
613
+ if (operation === "list") {
614
+ const parsed = parseList(argv.slice(1), value);
615
+ if ("class" in parsed)
616
+ return invalid(io, { ...parsed, recovery: "Correct the bounded list request and retry; no review metadata was changed." });
617
+ return listPersonalGlossaryReviewRecords(io, parsed, value);
618
+ }
619
+ const parsed = parseExact(argv.slice(1), value);
620
+ if ("class" in parsed)
621
+ return invalid(io, { ...parsed, recovery: "Correct the exact review binding and retry; no review metadata was changed." });
622
+ return getPersonalGlossaryReviewRecord(io, parsed, value);
623
+ }