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,484 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { updatePersonalGlossaryProfile, } from "../../analytics/personalGlossaryProfile.js";
4
+ import { readCurrentPersonalGlossaryCandidateProjection } from "../../analytics/personalGlossaryCurrentGeneration.js";
5
+ import { containsPersonalGlossarySensitiveContent } from "../../analytics/personalGlossaryCandidateProjectionExcerpts.js";
6
+ import { decidePersonalGlossaryCandidate } from "../../analytics/personalGlossaryDecision.js";
7
+ import { personalGlossaryReviewPublicationAuthorization } from "../../analytics/personalGlossaryReviewRecords.js";
8
+ import { defaultProfileDir } from "../../analytics/extractCorpus/core.js";
9
+ import { loadYamlMapping } from "../../core/yaml.js";
10
+ import { createGlossaryPublicationResult, validateGlossaryAdmissionDecision, validateGlossaryHostClassificationReceipt, } from "../../registries/glossaryCandidateContracts.js";
11
+ import { personalGlossaryOutputContract, } from "../../registries/glossaryEntryContract.js";
12
+ import { emitInvalidInput } from "../errors.js";
13
+ import { emitStructured } from "../structured.js";
14
+ const REQUEST_FIELDS = ["schema_version", "receipt", "decision", "as_of"];
15
+ const REQUEST_OPTIONAL_FIELDS = ["review_authorization"];
16
+ const RESULT_FIELDS = [
17
+ "schema_version",
18
+ "owner",
19
+ "candidate_id",
20
+ "candidate_revision",
21
+ "candidate_capsule_sha256",
22
+ "decision_sha256",
23
+ "review_record_sha256",
24
+ "generation",
25
+ "policy_version",
26
+ "status",
27
+ "profile_section_sha256",
28
+ "published_at",
29
+ "result_sha256",
30
+ ];
31
+ const OUTPUT_STATUSES = ["changed", "unchanged_replay", "dry_run_candidate"];
32
+ const COMMAND = "agentera report personal-glossary-publish --input <file|-> [--dry-run] --format json";
33
+ const RECOVERY = "Reread the current personal candidate and authorized decision, then retry; no profile bytes were changed.";
34
+ function mapping(value) {
35
+ return value !== null && typeof value === "object" && !Array.isArray(value);
36
+ }
37
+ function sameStrings(actual, expected) {
38
+ return JSON.stringify(actual) === JSON.stringify(expected);
39
+ }
40
+ function calendarDate(value) {
41
+ if (typeof value !== "string" || !/^\d{4}-\d{2}-\d{2}$/u.test(value))
42
+ return false;
43
+ const date = new Date(`${value}T00:00:00.000Z`);
44
+ return !Number.isNaN(date.getTime()) && date.toISOString().slice(0, 10) === value;
45
+ }
46
+ function invalid(io, body) {
47
+ return emitInvalidInput(io, { format: "json", body: { ...body, recovery: body.recovery ?? RECOVERY } });
48
+ }
49
+ function failure(io, contract, failureClass) {
50
+ emitStructured({
51
+ schemaVersion: contract.resultSchemaVersion,
52
+ command: "report personal-glossary-publish",
53
+ status: "fail",
54
+ error: {
55
+ class: failureClass,
56
+ message: "Personal glossary publication was not applied.",
57
+ recovery: RECOVERY,
58
+ },
59
+ }, "json", io.out ?? ((text) => process.stdout.write(text)));
60
+ return 1;
61
+ }
62
+ function contract() {
63
+ const value = personalGlossaryOutputContract();
64
+ if (value.command !== "agentera report personal-glossary-publish" ||
65
+ value.requestSchemaVersion !== "agentera.personalGlossaryPublishRequest.v1" ||
66
+ !sameStrings(value.requestFields, REQUEST_FIELDS) ||
67
+ !sameStrings(value.requestOptionalFields, REQUEST_OPTIONAL_FIELDS) ||
68
+ value.maxRequestUtf8Bytes !== 16_384 ||
69
+ value.resultSchemaVersion !== "agentera.personalGlossaryPublicationResult.v1" ||
70
+ !sameStrings(value.resultFields, RESULT_FIELDS) ||
71
+ value.maxResultUtf8Bytes !== 4_096 ||
72
+ value.sectionSchemaVersion !== "agentera.personalGlossarySection.v1" ||
73
+ !sameStrings(value.outputStatuses, OUTPUT_STATUSES) ||
74
+ !sameStrings(value.reviewAuthorizationFields, ["review_id", "review_record_sha256"]) ||
75
+ !sameStrings(value.reviewAuthorizationDispositions, ["accept", "correct"])) {
76
+ throw new Error("personal glossary publication contract is unavailable");
77
+ }
78
+ return value;
79
+ }
80
+ function parseArgs(argv) {
81
+ let input;
82
+ let dryRun = false;
83
+ let format = false;
84
+ for (let index = 0; index < argv.length; index += 1) {
85
+ const [name, inline] = argv[index].split("=", 2);
86
+ if (name === "--dry-run") {
87
+ if (inline !== undefined || dryRun) {
88
+ return { class: "mutually_exclusive", message: "--dry-run may only be supplied once", syntax: COMMAND };
89
+ }
90
+ dryRun = true;
91
+ continue;
92
+ }
93
+ if (name !== "--input" && name !== "--format") {
94
+ return { class: "unrecognized_argument", message: "unrecognized personal publication argument", syntax: COMMAND };
95
+ }
96
+ const value = inline ?? argv[++index];
97
+ if (!value || value.startsWith("--")) {
98
+ return { class: "missing_argument", message: `${name} requires a value`, syntax: `${name} VALUE` };
99
+ }
100
+ if (name === "--format") {
101
+ if (format)
102
+ return { class: "mutually_exclusive", message: "--format may only be supplied once", syntax: COMMAND };
103
+ if (value !== "json")
104
+ return { class: "invalid_choice", message: "personal-glossary-publish requires --format json", valid_values: ["json"] };
105
+ format = true;
106
+ continue;
107
+ }
108
+ if (input !== undefined)
109
+ return { class: "mutually_exclusive", message: "--input may only be supplied once", syntax: COMMAND };
110
+ input = value;
111
+ }
112
+ return input ? { input, dryRun } : { class: "missing_argument", message: "--input is required", syntax: COMMAND };
113
+ }
114
+ function readBoundedDescriptor(fd, maxBytes) {
115
+ const bytes = Buffer.allocUnsafe(maxBytes + 1);
116
+ let offset = 0;
117
+ while (offset < bytes.length) {
118
+ const count = fs.readSync(fd, bytes, offset, bytes.length - offset, null);
119
+ if (count === 0)
120
+ break;
121
+ offset += count;
122
+ }
123
+ if (offset > maxBytes)
124
+ throw new Error("over bound");
125
+ return bytes.subarray(0, offset);
126
+ }
127
+ function sameIdentity(left, right) {
128
+ return left.dev === right.dev && left.ino === right.ino;
129
+ }
130
+ function readBoundedFile(source, maxBytes) {
131
+ const observed = fs.lstatSync(source, { bigint: true });
132
+ if (observed.isSymbolicLink() || !observed.isFile() || observed.size > BigInt(maxBytes)) {
133
+ throw new Error("over bound or unreadable");
134
+ }
135
+ let fd = null;
136
+ try {
137
+ fd = fs.openSync(source, fs.constants.O_RDONLY | (fs.constants.O_NOFOLLOW ?? 0));
138
+ const opened = fs.fstatSync(fd, { bigint: true });
139
+ if (!opened.isFile() || !sameIdentity(observed, opened) || opened.size > BigInt(maxBytes)) {
140
+ throw new Error("over bound or unreadable");
141
+ }
142
+ return readBoundedDescriptor(fd, maxBytes);
143
+ }
144
+ finally {
145
+ if (fd !== null)
146
+ fs.closeSync(fd);
147
+ }
148
+ }
149
+ function readBoundedStdin(maxBytes, io) {
150
+ if (io.stdin) {
151
+ const value = io.stdin();
152
+ if (Buffer.byteLength(value, "utf8") > maxBytes)
153
+ throw new Error("over bound");
154
+ return Buffer.from(value, "utf8");
155
+ }
156
+ if (process.stdin.isTTY)
157
+ return Buffer.alloc(0);
158
+ return readBoundedDescriptor(0, maxBytes);
159
+ }
160
+ function readRequest(source, maxBytes, io) {
161
+ const bytes = source === "-" ? readBoundedStdin(maxBytes, io) : readBoundedFile(source, maxBytes);
162
+ const text = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
163
+ const value = loadYamlMapping(text);
164
+ if (!mapping(value))
165
+ throw new Error("not a mapping");
166
+ return value;
167
+ }
168
+ function validateRequest(value, contractValue) {
169
+ const keys = Object.keys(value);
170
+ if (keys.some((key) => !contractValue.requestFields.includes(key) && !contractValue.requestOptionalFields.includes(key)) ||
171
+ contractValue.requestFields.some((field) => !(field in value))) {
172
+ return {
173
+ class: "schema_violation",
174
+ message: "personal glossary publish request fields are invalid",
175
+ valid_values: contractValue.requestFields,
176
+ };
177
+ }
178
+ if (value.schema_version !== contractValue.requestSchemaVersion ||
179
+ !mapping(value.receipt) ||
180
+ !mapping(value.decision) ||
181
+ !calendarDate(value.as_of)) {
182
+ return {
183
+ class: "schema_violation",
184
+ message: `request requires ${contractValue.requestSchemaVersion}, receipt, decision, and an ISO calendar date`,
185
+ valid_values: [contractValue.requestSchemaVersion, "receipt", "decision", "YYYY-MM-DD"],
186
+ };
187
+ }
188
+ if (!("review_authorization" in value)) {
189
+ return { receipt: value.receipt, decision: value.decision, reviewAuthorization: null, asOf: value.as_of };
190
+ }
191
+ if (!mapping(value.review_authorization) ||
192
+ !sameStrings(Object.keys(value.review_authorization).sort(), contractValue.reviewAuthorizationFields.sort()) ||
193
+ typeof value.review_authorization.review_id !== "string" ||
194
+ typeof value.review_authorization.review_record_sha256 !== "string" ||
195
+ !/^[a-f0-9]{64}$/u.test(value.review_authorization.review_id) ||
196
+ !/^[a-f0-9]{64}$/u.test(value.review_authorization.review_record_sha256)) {
197
+ return {
198
+ class: "schema_violation",
199
+ message: "review_authorization requires only one review identity and canonical review record digest",
200
+ valid_values: contractValue.reviewAuthorizationFields,
201
+ };
202
+ }
203
+ return {
204
+ receipt: value.receipt,
205
+ decision: value.decision,
206
+ reviewAuthorization: {
207
+ reviewId: value.review_authorization.review_id,
208
+ reviewRecordSha256: value.review_authorization.review_record_sha256,
209
+ },
210
+ asOf: value.as_of,
211
+ };
212
+ }
213
+ function currentCandidate(receipt) {
214
+ const current = readCurrentPersonalGlossaryCandidateProjection();
215
+ if (current.status !== "current" || current.projection === null)
216
+ return null;
217
+ const candidates = current.projection.candidates.filter(({ capsule }) => capsule.candidate_id === receipt.candidate_id &&
218
+ capsule.candidate_revision === receipt.candidate_revision &&
219
+ capsule.capsule_sha256 === receipt.candidate_capsule_sha256 &&
220
+ capsule.generation === receipt.generation &&
221
+ capsule.policy_version === receipt.policy_version);
222
+ return candidates.length === 1
223
+ ? { capsule: candidates[0].capsule, projectionSha256: current.projection.projection_sha256 }
224
+ : null;
225
+ }
226
+ function authorizedPublication(request) {
227
+ const selected = currentCandidate(request.receipt);
228
+ if (!selected)
229
+ return null;
230
+ if (validateGlossaryHostClassificationReceipt(request.receipt, selected.capsule, {
231
+ candidateProjectionSha256: selected.projectionSha256,
232
+ }).length > 0 ||
233
+ validateGlossaryAdmissionDecision(request.decision, selected.capsule, request.receipt).length > 0) {
234
+ return null;
235
+ }
236
+ const current = decidePersonalGlossaryCandidate(request.receipt);
237
+ if (current.decision === null || current.decision.decision_sha256 !== request.decision.decision_sha256) {
238
+ return null;
239
+ }
240
+ if (current.status === "automatic_admission" && current.reason === "explicit_current_authorized") {
241
+ if (request.reviewAuthorization !== null)
242
+ return null;
243
+ return {
244
+ capsule: selected.capsule,
245
+ receipt: request.receipt,
246
+ decision: request.decision,
247
+ review: null,
248
+ };
249
+ }
250
+ if (current.status !== "review_required" || request.reviewAuthorization === null)
251
+ return null;
252
+ const review = personalGlossaryReviewPublicationAuthorization({
253
+ review_id: request.reviewAuthorization.reviewId,
254
+ review_record_sha256: request.reviewAuthorization.reviewRecordSha256,
255
+ capsule: selected.capsule,
256
+ receipt: request.receipt,
257
+ decision: request.decision,
258
+ candidate_projection_sha256: selected.projectionSha256,
259
+ });
260
+ if (review.status !== "authorized" || review.review === null)
261
+ return null;
262
+ return {
263
+ capsule: selected.capsule,
264
+ receipt: request.receipt,
265
+ decision: request.decision,
266
+ review: review.review,
267
+ };
268
+ }
269
+ function entryFor(capsule, receipt, asOf, review) {
270
+ const classification = receipt.classification;
271
+ const evidence = capsule.evidence;
272
+ if (!["stable", "durable", "situational"].includes(classification.permanence) ||
273
+ !Number.isInteger(classification.confidence) ||
274
+ classification.confidence < 0 ||
275
+ classification.confidence > 100) {
276
+ return null;
277
+ }
278
+ let meaning = capsule.meaning;
279
+ if (review === null) {
280
+ if (evidence.length !== 1 ||
281
+ classification.term !== capsule.term ||
282
+ classification.meaning !== capsule.meaning ||
283
+ classification.scope !== "personal" ||
284
+ classification.consistency !== "consistent" ||
285
+ capsule.scope !== "personal")
286
+ return null;
287
+ }
288
+ else if (review.disposition === "accept") {
289
+ if (capsule.scope !== "personal" || classification.scope !== "personal")
290
+ return null;
291
+ }
292
+ else if (review.disposition === "correct") {
293
+ if (review.corrected_scope !== "personal" ||
294
+ !review.corrected_meaning ||
295
+ containsPersonalGlossarySensitiveContent(review.corrected_meaning) ||
296
+ capsule.scope === "project" ||
297
+ classification.scope === "project")
298
+ return null;
299
+ meaning = review.corrected_meaning;
300
+ }
301
+ else {
302
+ return null;
303
+ }
304
+ const retainedHistory = new Map();
305
+ let provenance;
306
+ if (capsule.provenance_kind === "personal_explicit_definition") {
307
+ if (evidence.length !== 1 || !mapping(evidence[0]) ||
308
+ typeof evidence[0].source_id !== "string" || typeof evidence[0].evidence_anchor !== "string" ||
309
+ typeof evidence[0].signal_type !== "string")
310
+ return null;
311
+ const source = evidence[0];
312
+ retainedHistory.set(source.evidence_anchor, {
313
+ sourceId: source.source_id,
314
+ sourceKind: "conversation_turn",
315
+ signalType: source.signal_type,
316
+ });
317
+ provenance = {
318
+ kind: "personal_explicit_definition",
319
+ evidence: [{
320
+ source_id: source.source_id,
321
+ evidence_anchor: source.evidence_anchor,
322
+ signal_type: source.signal_type,
323
+ }],
324
+ };
325
+ }
326
+ else if (capsule.provenance_kind === "personal_inferred_usage") {
327
+ if (evidence.length !== 2 || evidence.some((source) => !mapping(source) ||
328
+ typeof source.source_id !== "string" || typeof source.evidence_anchor !== "string" || typeof source.source_kind !== "string"))
329
+ return null;
330
+ const sources = evidence;
331
+ for (const source of sources)
332
+ retainedHistory.set(source.evidence_anchor, {
333
+ sourceId: source.source_id,
334
+ sourceKind: source.source_kind,
335
+ signalType: "instruction",
336
+ });
337
+ provenance = {
338
+ kind: "personal_inferred_usage",
339
+ evidence: sources.map((source) => ({
340
+ source_id: source.source_id,
341
+ evidence_anchor: source.evidence_anchor,
342
+ source_kind: source.source_kind,
343
+ })),
344
+ };
345
+ }
346
+ else if (capsule.provenance_kind === "personal_inferred_conversation") {
347
+ if (evidence.length < 3 || evidence.some((source) => !mapping(source) ||
348
+ ["source_id", "evidence_anchor", "source_kind", "signal_type", "session_id", "project_id", "content_fingerprint", "author_class"]
349
+ .some((field) => typeof source[field] !== "string")))
350
+ return null;
351
+ const sources = evidence;
352
+ for (const source of sources)
353
+ retainedHistory.set(source.evidence_anchor, {
354
+ sourceId: source.source_id,
355
+ sourceKind: source.source_kind,
356
+ signalType: source.signal_type,
357
+ sessionId: source.session_id,
358
+ projectId: source.project_id,
359
+ contentFingerprint: source.content_fingerprint,
360
+ authorClass: source.author_class,
361
+ });
362
+ provenance = {
363
+ kind: "personal_inferred_conversation",
364
+ evidence: sources.map((source) => ({ ...source })),
365
+ };
366
+ }
367
+ else {
368
+ return null;
369
+ }
370
+ return {
371
+ entry: {
372
+ term: capsule.term,
373
+ meaning,
374
+ confidence: classification.confidence,
375
+ permanence: classification.permanence,
376
+ temporal: { observed_at: asOf, last_confirmed_at: asOf },
377
+ provenance,
378
+ },
379
+ context: { retainedHistory },
380
+ };
381
+ }
382
+ function publishedAt(asOf) {
383
+ return `${asOf}T00:00:00.000Z`;
384
+ }
385
+ function resultText(contractValue, value) {
386
+ const text = `${JSON.stringify(value, null, 2)}\n`;
387
+ return Buffer.byteLength(text, "utf8") <= contractValue.maxResultUtf8Bytes ? text : null;
388
+ }
389
+ /** Publish one revalidated automatic personal admission to the owned profile section. */
390
+ export function runPersonalGlossaryPublishCommand(argv, io) {
391
+ let contractValue;
392
+ try {
393
+ contractValue = contract();
394
+ }
395
+ catch {
396
+ return invalid(io, {
397
+ class: "invalid_request",
398
+ message: "personal glossary publication contract is unavailable",
399
+ recovery: "Restore the bundled glossary authority, then retry; no profile bytes were changed.",
400
+ });
401
+ }
402
+ const parsedArgs = parseArgs(argv);
403
+ if ("class" in parsedArgs)
404
+ return invalid(io, parsedArgs);
405
+ let input;
406
+ try {
407
+ input = readRequest(parsedArgs.input, contractValue.maxRequestUtf8Bytes, io);
408
+ }
409
+ catch {
410
+ return invalid(io, {
411
+ class: "invalid_format",
412
+ message: "--input must be one readable bounded UTF-8 YAML or JSON mapping",
413
+ });
414
+ }
415
+ const request = validateRequest(input, contractValue);
416
+ if ("class" in request)
417
+ return invalid(io, request);
418
+ let authorized;
419
+ try {
420
+ authorized = authorizedPublication(request);
421
+ }
422
+ catch {
423
+ return failure(io, contractValue, "publication_unavailable");
424
+ }
425
+ if (!authorized)
426
+ return failure(io, contractValue, "publication_not_authorized");
427
+ const publication = entryFor(authorized.capsule, authorized.receipt, request.asOf, authorized.review);
428
+ if (!publication)
429
+ return failure(io, contractValue, "publication_not_authorized");
430
+ const profileInput = {
431
+ profilePath: path.join(defaultProfileDir(), "PROFILE.md"),
432
+ freshEntries: [publication.entry],
433
+ retainedHistory: publication.context,
434
+ asOf: request.asOf,
435
+ };
436
+ let preview;
437
+ try {
438
+ preview = updatePersonalGlossaryProfile({ ...profileInput, dryRun: true });
439
+ }
440
+ catch {
441
+ return failure(io, contractValue, "profile_unavailable");
442
+ }
443
+ let text;
444
+ try {
445
+ text = resultText(contractValue, createGlossaryPublicationResult({
446
+ capsule: authorized.capsule,
447
+ receipt: authorized.receipt,
448
+ decision: authorized.decision,
449
+ review: authorized.review,
450
+ status: parsedArgs.dryRun
451
+ ? "dry_run_candidate"
452
+ : preview.changed
453
+ ? "changed"
454
+ : "unchanged_replay",
455
+ profile_section_sha256: preview.profileSectionSha256,
456
+ published_at: parsedArgs.dryRun ? null : publishedAt(request.asOf),
457
+ }));
458
+ }
459
+ catch {
460
+ return failure(io, contractValue, "publication_unavailable");
461
+ }
462
+ if (text === null)
463
+ return failure(io, contractValue, "output_bound_exceeded");
464
+ if (!parsedArgs.dryRun && preview.changed) {
465
+ let beforeEffect;
466
+ try {
467
+ beforeEffect = authorizedPublication(request);
468
+ }
469
+ catch {
470
+ return failure(io, contractValue, "publication_unavailable");
471
+ }
472
+ if (!beforeEffect || beforeEffect.decision.decision_sha256 !== authorized.decision.decision_sha256) {
473
+ return failure(io, contractValue, "publication_not_authorized");
474
+ }
475
+ try {
476
+ updatePersonalGlossaryProfile(profileInput);
477
+ }
478
+ catch {
479
+ return failure(io, contractValue, "profile_unavailable");
480
+ }
481
+ }
482
+ (io.out ?? ((line) => process.stdout.write(line)))(text);
483
+ return 0;
484
+ }