agentera 3.0.0-dev.79 → 3.0.0-dev.80

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.
@@ -2915,51 +2915,58 @@ mutation_grammar:
2915
2915
  verb: create
2916
2916
  class: record_payload
2917
2917
  selectors: []
2918
- preconditions: [entity_mode_marker_is_valid, todo_reconciliation_is_valid, full_typed_record_is_valid, readiness_is_complete_or_needs_triage, dependency_graph_is_valid]
2918
+ preconditions: [entity_mode_marker_is_valid, todo_reconciliation_is_valid, singleton_or_versioned_batch_shape_is_valid, full_typed_records_are_valid, duplicate_local_references_are_absent, all_ids_are_allocated_before_local_reference_resolution, dependency_graph_is_valid, batch_effect_is_previewed_and_confirmed]
2919
2919
  owned_fields: [id, artifact, status, public_order, lifecycle]
2920
2920
  input:
2921
2921
  mode: structured
2922
- root: full typed TODO record
2922
+ root: full typed TODO record or agentera.todoCreateBatch.v1 envelope
2923
2923
  sources: [file, stdin]
2924
2924
  structured_sources: [file, stdin]
2925
2925
  cli_owned_fields: [id, artifact, status, public_order, lifecycle]
2926
- recovery: Run npx -y agentera@next state todo explain --verb create --format json, remove CLI-owned fields, provide the full typed TODO record, and retry.
2926
+ recovery: For one item, preserve the existing full-record form. For a batch, supply agentera.todoCreateBatch.v1, preview with --dry-run, then repeat the same input with its effect SHA-256 and --yes.
2927
2927
  examples: ['npx -y agentera@next state todo create --input todo.yaml --format json']
2928
- bounds: { max_input_utf8_bytes: 32768, max_collection_items: 100 }
2929
- fields: []
2928
+ bounds: { max_input_utf8_bytes: 32768, max_collection_items: 256 }
2929
+ fields:
2930
+ - { flag: --effect-sha256, field: effect_sha256, kind: string }
2931
+ - { flag: --yes, field: confirmed, kind: boolean }
2930
2932
 
2931
2933
  - artifact: todo
2932
2934
  verb: update
2933
2935
  class: record_payload
2934
2936
  selectors: [--id]
2935
- preconditions: [entity_mode_marker_is_valid, todo_reconciliation_is_valid, target_id_is_bare_ten_letters, typed_patch_is_valid, dependency_graph_is_valid]
2937
+ preconditions: [entity_mode_marker_is_valid, todo_reconciliation_is_valid, singleton_or_versioned_batch_shape_is_valid, target_ids_are_bare_ten_letters, duplicate_targets_are_absent, typed_patches_are_valid, dependency_graph_is_valid, batch_effect_is_previewed_and_confirmed]
2936
2938
  owned_fields: [id, artifact, status, public_order, lifecycle]
2937
2939
  input:
2938
2940
  mode: structured
2939
- root: TODO record patch
2941
+ root: TODO record patch or agentera.todoUpdateBatch.v1 envelope
2940
2942
  sources: [file, stdin]
2941
2943
  structured_sources: [file, stdin]
2942
2944
  cli_owned_fields: [id, artifact, status, public_order, lifecycle]
2943
- recovery: Reread the TODO item, use its bare ID, supply only typed patch fields, and use null or an empty list only for declared clearable fields.
2945
+ recovery: For one item, preserve the existing --id and patch form. For a batch, supply agentera.todoUpdateBatch.v1, preview with --dry-run, then repeat the same input with its effect SHA-256 and --yes.
2944
2946
  examples: ['npx -y agentera@next state todo update --id qjtrmnpvka --input todo-patch.yaml --format json']
2945
- bounds: { max_input_utf8_bytes: 32768, max_collection_items: 1 }
2946
- fields: [{ flag: --id, field: id, kind: string, required: true }]
2947
+ bounds: { max_input_utf8_bytes: 32768, max_collection_items: 256 }
2948
+ fields:
2949
+ - { flag: --id, field: id, kind: string, required: true }
2950
+ - { flag: --effect-sha256, field: effect_sha256, kind: string }
2951
+ - { flag: --yes, field: confirmed, kind: boolean }
2947
2952
 
2948
2953
  - artifact: todo
2949
2954
  verb: set-severity
2950
2955
  class: simple_transition
2951
2956
  selectors: [--id]
2952
- preconditions: [entity_mode_marker_is_valid, todo_reconciliation_is_valid, target_id_is_bare_ten_letters, severity_is_valid, reason_and_date_are_present]
2957
+ preconditions: [entity_mode_marker_is_valid, todo_reconciliation_is_valid, singleton_or_agentera_todoSetSeverityBatch_v1_shape_is_valid, target_ids_are_bare_ten_letters, duplicate_targets_are_absent, severity_is_valid, reason_and_date_are_present, batch_effect_is_previewed_and_confirmed]
2953
2958
  owned_fields: [id, artifact, severity, lifecycle]
2954
- input: { mode: none, sources: [], cli_owned_fields: [] }
2955
- recovery: Use the bare TODO ID, one immediate-impact severity, a reason, and a YYYY-MM-DD date; no record input is accepted.
2959
+ input: { mode: structured, root: agentera.todoSetSeverityBatch.v1 envelope, optional: true, sources: [file, stdin], structured_sources: [file, stdin], cli_owned_fields: [] }
2960
+ recovery: Use singleton flags unchanged, or supply agentera.todoSetSeverityBatch.v1 through --input, preview it, then exact-apply it with the effect SHA-256 and --yes.
2956
2961
  examples: ['npx -y agentera@next state todo set-severity --id qjtrmnpvka --severity degraded --reason "Impact changed" --date 2026-07-31 --format json']
2957
- bounds: { max_input_utf8_bytes: 0, max_collection_items: 1, max_reason_code_points: 500 }
2962
+ bounds: { max_input_utf8_bytes: 32768, max_collection_items: 256, max_reason_code_points: 500 }
2958
2963
  fields:
2959
2964
  - { flag: --id, field: id, kind: string, required: true }
2960
2965
  - { flag: --severity, field: severity, kind: string, required: true, valid_values: [critical, degraded, normal, annoying] }
2961
2966
  - { flag: --reason, field: lifecycle.reason, kind: string, required: true }
2962
2967
  - { flag: --date, field: lifecycle.date, kind: date, required: true }
2968
+ - { flag: --effect-sha256, field: effect_sha256, kind: string }
2969
+ - { flag: --yes, field: confirmed, kind: boolean }
2963
2970
 
2964
2971
  - artifact: todo
2965
2972
  verb: supersede
@@ -2981,16 +2988,18 @@ mutation_grammar:
2981
2988
  verb: resolve
2982
2989
  class: simple_transition
2983
2990
  selectors: [--id]
2984
- preconditions: [entity_mode_marker_is_valid, todo_reconciliation_is_valid, target_id_is_bare_ten_letters, item_is_open, reason_and_date_are_present]
2991
+ preconditions: [entity_mode_marker_is_valid, todo_reconciliation_is_valid, singleton_or_agentera_todoResolveBatch_v1_shape_is_valid, target_ids_are_bare_ten_letters, duplicate_targets_are_absent, every_item_is_open, reason_and_date_are_present, batch_effect_is_previewed_and_confirmed]
2985
2992
  owned_fields: [id, artifact, status, lifecycle]
2986
- input: { mode: none, sources: [], cli_owned_fields: [] }
2987
- recovery: Use the bare TODO ID, a reason, and a YYYY-MM-DD date; no record input is accepted.
2993
+ input: { mode: structured, root: agentera.todoResolveBatch.v1 envelope, optional: true, sources: [file, stdin], structured_sources: [file, stdin], cli_owned_fields: [] }
2994
+ recovery: Use singleton flags unchanged, or supply agentera.todoResolveBatch.v1 through --input, preview it, then exact-apply it with the effect SHA-256 and --yes.
2988
2995
  examples: ['npx -y agentera@next state todo resolve --id qjtrmnpvka --reason "Shipped" --date 2026-07-31 --format json']
2989
- bounds: { max_input_utf8_bytes: 0, max_collection_items: 1, max_reason_code_points: 500 }
2996
+ bounds: { max_input_utf8_bytes: 32768, max_collection_items: 256, max_reason_code_points: 500 }
2990
2997
  fields:
2991
2998
  - { flag: --id, field: id, kind: string, required: true }
2992
2999
  - { flag: --reason, field: lifecycle.reason, kind: string, required: true }
2993
3000
  - { flag: --date, field: lifecycle.date, kind: date, required: true }
3001
+ - { flag: --effect-sha256, field: effect_sha256, kind: string }
3002
+ - { flag: --yes, field: confirmed, kind: boolean }
2994
3003
 
2995
3004
  - artifact: todo
2996
3005
  verb: reopen
@@ -134,10 +134,13 @@ LIFECYCLE:
134
134
  description: >-
135
135
  activate is a flag-only reconciliation transition that requires the
136
136
  preview effect SHA-256 and explicit confirmation. Item lifecycle transitions
137
- set-severity, supersede, resolve, and reopen require a reason and date;
137
+ Singleton set-severity, supersede, resolve, and reopen require flag-only
138
+ reason and date values. Set-severity and resolve also accept strict
139
+ agentera.todoSetSeverityBatch.v1 and agentera.todoResolveBatch.v1 envelopes;
140
+ batches require dry-run preview and exact effect-confirmed apply.
138
141
  supersede additionally requires a distinct existing replacement ID. These
139
- operations never accept structured record content or use Markdown
140
- strikethrough.
142
+ These operations never accept unversioned structured record content or use
143
+ Markdown strikethrough.
141
144
  clearable_patch_fields: [target_version, requirements, acceptance, readiness]
142
145
  cli_owned_fields: [id, artifact, status, public_order, lifecycle]
143
146
  transition_fields: [reason, date, replacement]
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schemaVersion": "agentera.generatedBuildSource.v1",
3
- "commit": "b22922ec963df96099033595c72d26aa0ef45a0b",
4
- "tree": "9711c04dcf28117032a46f83f628a3e8abe00ce0",
5
- "files": 6835,
6
- "workingTreeSha256": "09d0c5b86327910ad828a654ea6eb6679dda41a085ed03a452187d1d0c3265d7",
7
- "identitySha256": "32f7d71e8664b15a675406df2cfb669a2d3dac0ffdfefaa026d25debbc590347"
3
+ "commit": "296cb7e29c2521b95985f933b08f1a5d7724b4f0",
4
+ "tree": "732d6174b4bbf04834ff7fde86172e24ad69689c",
5
+ "files": 6856,
6
+ "workingTreeSha256": "0c148b6e30d19f0945267efa03f12d71e92587c80f8744b1cc1e0541de2ab9bd",
7
+ "identitySha256": "aec00264c492a72d7501ee6e58d69fa6fd1f10e0b3d5d75b0feff257e5874914"
8
8
  }
@@ -20,10 +20,11 @@ export function progressVerificationSummary(progress) {
20
20
  }
21
21
  const latest = progress.latest && typeof progress.latest === "object" && !Array.isArray(progress.latest) ? progress.latest : {};
22
22
  const verified = latest.verified;
23
- const latestVerification = progress.latest_verification && typeof progress.latest_verification === "object" && !Array.isArray(progress.latest_verification)
24
- ? progress.latest_verification
25
- : {};
26
- const projectedVerifiedPresent = latest.verified_present === true || latestVerification.present === true;
23
+ const latestVerification = progress.latest_verification;
24
+ const projectedVerifiedPresent = latest.verified_present === true ||
25
+ (latestVerification && typeof latestVerification === "object" && !Array.isArray(latestVerification)
26
+ ? latestVerification.present === true
27
+ : hasRecordedValue(latestVerification));
27
28
  const verifiedPresent = hasRecordedValue(verified) || projectedVerifiedPresent;
28
29
  const cycle = {};
29
30
  for (const key of ["number", "timestamp", "type", "phase"]) {
@@ -40,8 +41,8 @@ export function progressVerificationSummary(progress) {
40
41
  verified_present: verifiedPresent,
41
42
  non_empty_evidence_present: verifiedPresent,
42
43
  non_empty_evidence_fields: evidenceFields,
43
- verified: hasRecordedValue(verified) ? verified : null,
44
- verification_summary: hasRecordedValue(verified) ? verified : projectedVerifiedPresent ? { present: true } : null,
44
+ verified: hasRecordedValue(verified) ? verified : projectedVerifiedPresent ? latestVerification : null,
45
+ verification_summary: hasRecordedValue(verified) ? verified : projectedVerifiedPresent ? latestVerification : null,
45
46
  latest_progress_verification_pointer: pointer,
46
47
  caveats,
47
48
  };
@@ -59,6 +59,19 @@ export function cmdPrime(args, io = {}) {
59
59
  err("Error: prime --dashboard/--orientation and prime --guidance are mutually exclusive\n");
60
60
  return 2;
61
61
  }
62
+ if (dashboard && args.fields !== undefined) {
63
+ const migrationCommand = preCutoverCommand("prime --context status --format json");
64
+ return rejectInput({
65
+ class: "mutually_exclusive",
66
+ message: "Deprecated prime --dashboard --fields selectors are not supported during status-capsule migration",
67
+ example: migrationCommand,
68
+ diagnosis: {
69
+ migration_command: migrationCommand,
70
+ consumer_path: "capability_context.context.status_context",
71
+ },
72
+ recovery: `Run '${migrationCommand}' and read capability_context.context.status_context; no state was changed.`,
73
+ });
74
+ }
62
75
  const retiredFieldRejection = rejectRetiredPrimeFields(command, format, args.fields, out, err);
63
76
  if (retiredFieldRejection !== null)
64
77
  return retiredFieldRejection;
@@ -5,6 +5,8 @@ import { emitStructured } from "../../structured.js";
5
5
  import { StateRetrievalFailure } from "../../../state/directRetrieval.js";
6
6
  import { normalizeArtifactProtocolId, VALIDATE_ARTIFACT_PROTOCOL_IDS, } from "../../../registries/artifactProtocolIds.js";
7
7
  import { buildExplain, buildExplainAll, assertMutationGrammarParity, exampleFor, executeStateWrite, isWritableArtifact, loadStructuredInput, operationSpec, projectedFields, renderExplainText, StateWriteInputError, verbsForArtifact, WRITABLE_ARTIFACTS, } from "../../../state/write/index.js";
8
+ import { inspectTodoUpdateBatch } from "../../../state/todoUpdateBatch.js";
9
+ import { inspectTodoCreateBatch } from "../../../state/todoCreateBatch.js";
8
10
  function formatFromArgv(argv) {
9
11
  for (let i = 0; i < argv.length; i++) {
10
12
  if (argv[i] === "--format" && argv[i + 1] === "json")
@@ -17,6 +19,14 @@ function formatFromArgv(argv) {
17
19
  function invalid(body) {
18
20
  throw new StateWriteInputError(body);
19
21
  }
22
+ function missingTodoUpdateId() {
23
+ invalid({
24
+ class: "missing_argument",
25
+ message: "--id is required for todo update",
26
+ syntax: "--id VALUE",
27
+ example: exampleFor("todo", "update"),
28
+ });
29
+ }
20
30
  function emitRetrievalFailure(error, format, io) {
21
31
  if (format === "json") {
22
32
  emitStructured(error.body, "json", io.out ?? ((text) => process.stdout.write(text)));
@@ -168,10 +178,16 @@ function parseWrite(artifactRaw, argv) {
168
178
  let dryRun = false;
169
179
  let force = false;
170
180
  let inputSource = null;
181
+ const transitionBatchMode = artifact === "todo"
182
+ && (verb === "set-severity" || verb === "resolve")
183
+ && argv.slice(1).some((token) => token === "--input" || token.startsWith("--input="));
171
184
  for (let i = 1; i < argv.length;) {
172
185
  const token = argv[i];
173
186
  if (!token.startsWith("--"))
174
187
  invalid({ class: "unrecognized_argument", message: `unrecognized arguments: ${token}` });
188
+ const tokenName = token.split("=", 1)[0];
189
+ if (!transitionBatchMode && artifact === "todo" && (verb === "set-severity" || verb === "resolve") && (tokenName === "--effect-sha256" || tokenName === "--yes"))
190
+ invalid({ class: "unrecognized_argument", message: `unrecognized arguments: ${tokenName}`, example: exampleFor(artifact, verb) });
175
191
  if (token === "--dry-run") {
176
192
  dryRun = true;
177
193
  i += 1;
@@ -280,15 +296,19 @@ function parseWrite(artifactRaw, argv) {
280
296
  example: exampleFor(artifact, verb),
281
297
  });
282
298
  const selectorFields = new Set(spec.fields.filter((field) => spec.selectors.includes(field.flag)).map((field) => field.field));
283
- const correctionApplyFields = artifact === "todo" && verb === "correct-owners"
299
+ const correctionApplyFields = artifact === "todo" && (verb === "correct-owners" || verb === "update" || verb === "create" || verb === "set-severity" || verb === "resolve")
284
300
  ? new Set(["effect_sha256", "confirmed"])
285
301
  : new Set();
286
- if (spec.inputRoot && Object.keys(values).some((field) => !selectorFields.has(field) && !correctionApplyFields.has(field)))
302
+ if (artifact === "todo" && ["set-severity", "resolve"].includes(verb) && inputSource && Object.keys(values).some((field) => !correctionApplyFields.has(field)))
303
+ invalid({ class: "mutually_exclusive", message: `${artifact} ${verb} accepts field flags, not --input`, example: exampleFor(artifact, verb) });
304
+ if (spec.inputRoot && inputSource && Object.keys(values).some((field) => !selectorFields.has(field) && !correctionApplyFields.has(field)))
287
305
  invalid({
288
306
  class: "mutually_exclusive",
289
307
  message: `--input cannot be combined with field flags for ${artifact} ${verb}`,
290
308
  });
291
309
  for (const field of fields.filter((candidate) => candidate.required)) {
310
+ if (artifact === "todo" && ["update", "set-severity", "resolve"].includes(verb) && inputSource && ["id", "severity", "lifecycle.reason", "lifecycle.date"].includes(field.field))
311
+ continue;
292
312
  if (mappingPath(values, field.field) === undefined) {
293
313
  invalid({
294
314
  class: "missing_argument",
@@ -389,6 +409,8 @@ export function runStateWrite(artifactRaw, argv, io) {
389
409
  input = loadStructuredInput(parsed.inputSource, io.stdin ?? (() => fsStdin()), parsed.spec.inputMaxBytes);
390
410
  }
391
411
  catch (error) {
412
+ if (parsed.artifact === "todo" && parsed.spec.verb === "update" && !parsed.values.id)
413
+ missingTodoUpdateId();
392
414
  const message = error.message;
393
415
  invalid({
394
416
  class: message.startsWith("input file") ? "unsupported_target" : message.includes("UTF-8 limit") ? "schema_violation" : "invalid_format",
@@ -398,7 +420,11 @@ export function runStateWrite(artifactRaw, argv, io) {
398
420
  ...(message.includes("UTF-8 limit") ? { violations: [message] } : {}),
399
421
  });
400
422
  }
401
- for (const owned of parsed.spec.cliOwnedFields ?? []) {
423
+ const updateBatch = parsed.artifact === "todo" && parsed.spec.verb === "update" ? inspectTodoUpdateBatch(input) : null;
424
+ const createBatch = parsed.artifact === "todo" && parsed.spec.verb === "create" ? inspectTodoCreateBatch(input) : null;
425
+ if (parsed.artifact === "todo" && parsed.spec.verb === "update" && !parsed.values.id && !updateBatch?.strictEnvelope)
426
+ missingTodoUpdateId();
427
+ for (const owned of createBatch ? [] : parsed.spec.cliOwnedFields ?? []) {
402
428
  if (hasNested(input, owned))
403
429
  invalid({
404
430
  class: "schema_violation",
package/dist/cli/help.js CHANGED
@@ -216,16 +216,21 @@ export function printStateHelp(sub) {
216
216
  " agentera state todo activate|repair --dry-run|--effect-sha256 SHA256 --yes --format json",
217
217
  " agentera state todo correct-owners --input OWNER_MAPPING.yaml --dry-run|--effect-sha256 SHA256 --yes --format json",
218
218
  " agentera state todo create --input TODO.yaml --format json",
219
+ " agentera state todo create --input TODO-CREATE-BATCH.yaml --dry-run --format json",
220
+ " agentera state todo create --input TODO-CREATE-BATCH.yaml --effect-sha256 SHA256 --yes --format json",
219
221
  " agentera state todo update --id ID --input TODO-PATCH.yaml --format json",
220
222
  " agentera state todo set-severity --id ID --severity LEVEL --reason TEXT --date YYYY-MM-DD --format json",
223
+ " agentera state todo set-severity --input TODO-SEVERITY-BATCH.yaml --dry-run|--effect-sha256 SHA256 --yes --format json",
221
224
  " agentera state todo supersede --id ID --replacement ID --reason TEXT --date YYYY-MM-DD --format json",
222
225
  " agentera state todo resolve|reopen --id ID --reason TEXT --date YYYY-MM-DD --format json",
226
+ " agentera state todo resolve --input TODO-RESOLVE-BATCH.yaml --dry-run|--effect-sha256 SHA256 --yes --format json",
223
227
  "",
224
228
  "Each TODO item is one independently mutable canonical entity. IDs are bare ten-letter project-wide identities.",
225
229
  "TODO views are bounded in severity/status and Markdown public order; exact get returns complete detail.",
226
- "Create accepts a full typed YAML/JSON record; update is a patch, so omitted fields preserve state and null/empty-list clears apply only to declared clearable fields.",
230
+ "Singleton create accepts a full typed YAML/JSON record. Batch create accepts one strict agentera.todoCreateBatch.v1 envelope: preview it with --dry-run, then apply the same input with the returned --effect-sha256 and --yes.",
231
+ "Update is a patch, so omitted fields preserve state and null/empty-list clears apply only to declared clearable fields.",
227
232
  "Readiness is Agentera-owned operational state; public fields are owned by TODO.md and divergent public values fail before effects.",
228
- "Lifecycle verbs are flag-only typed transitions and cannot be supplied as record content.",
233
+ "Singleton lifecycle verbs remain flag-only. Set-severity and resolve also accept strict verb-specific batch envelopes that require preview and exact confirmed apply.",
229
234
  "Marker-absent repositories must complete migration before ordinary TODO access.",
230
235
  "",
231
236
  "Discover writes: agentera state todo explain --format json",
@@ -494,10 +499,11 @@ export function printPrimeHelp() {
494
499
  " --context CAPABILITY Emit startup context for a capability (e.g. plan)",
495
500
  " --input FILE|- Transient agentera.buildExecutionRequest.v1 input; valid only with --context build",
496
501
  " --guidance Emit static routing guidance",
497
- " --dashboard Emit the prime orientation dashboard",
498
- " --orientation Emit orientation briefing sections",
502
+ " --dashboard Deprecated status-capsule alias; incompatible with --fields",
503
+ " --orientation Deprecated dashboard alias; incompatible with --fields",
499
504
  " --fields FIELDS Comma-separated field filter for JSON/YAML output",
500
505
  "",
506
+ "Dashboard consumers must use `npx -y agentera@next prime --context status --format json` and read capability_context.context.status_context.",
501
507
  "JSON output uses bounded surfaces: bare prime is at most 12000 UTF-8 bytes and status context at most 22500; startup contains one availability projection and aggregate outcome, while schema discovery owns writer detail.",
502
508
  ].join("\n");
503
509
  }
@@ -0,0 +1,69 @@
1
+ import { createHash } from "node:crypto";
2
+ import { canonicalRecordJson } from "./archiveDiscovery.js";
3
+ import { todoInputViolations } from "./todoDocsEntityValidation.js";
4
+ import { reject } from "./write/errors.js";
5
+ export const TODO_CREATE_BATCH_VERSION = "agentera.todoCreateBatch.v1";
6
+ function mapping(value) { return value !== null && typeof value === "object" && !Array.isArray(value); }
7
+ export function inspectTodoCreateBatch(input) {
8
+ if (!input || input.schema_version !== TODO_CREATE_BATCH_VERSION)
9
+ return null;
10
+ const strictEnvelope = Object.keys(input).sort().join(",") === "creates,schema_version" && Array.isArray(input.creates);
11
+ const violations = [];
12
+ if (!strictEnvelope)
13
+ violations.push("batch envelope must contain exactly schema_version and creates");
14
+ if (!Array.isArray(input.creates) || input.creates.length < 1 || input.creates.length > 256)
15
+ violations.push("creates must contain 1 to 256 entries");
16
+ const creates = (Array.isArray(input.creates) ? input.creates : []).map((value, index) => {
17
+ if (!mapping(value) || Object.keys(value).sort().join(",") !== "local_ref,record" || typeof value.local_ref !== "string" || !/^[a-z][a-z0-9_-]{0,63}$/.test(value.local_ref) || !mapping(value.record)) {
18
+ violations.push(`creates[${index}] must contain exactly one valid local_ref and one record mapping`);
19
+ return { local_ref: "", record: {} };
20
+ }
21
+ const validationRecord = structuredClone(value.record);
22
+ if (mapping(validationRecord.readiness) && Array.isArray(validationRecord.readiness.dependencies))
23
+ validationRecord.readiness.dependencies = validationRecord.readiness.dependencies.map((dependency) => mapping(dependency) && typeof dependency.local_ref === "string" ? { artifact: "todo", id: "aaaaaaaaaa" } : dependency);
24
+ violations.push(...todoInputViolations(validationRecord, "create").map((item) => `creates[${index}].record: ${item}`));
25
+ return { local_ref: value.local_ref, record: value.record };
26
+ });
27
+ const refs = new Set();
28
+ for (const { local_ref } of creates) {
29
+ if (local_ref && refs.has(local_ref))
30
+ violations.push(`duplicate local reference '${local_ref}'`);
31
+ refs.add(local_ref);
32
+ }
33
+ for (const [index, { record }] of creates.entries()) {
34
+ const dependencies = mapping(record.readiness) ? record.readiness.dependencies : undefined;
35
+ if (!Array.isArray(dependencies))
36
+ continue;
37
+ for (const dependency of dependencies) {
38
+ if (mapping(dependency) && Object.keys(dependency).sort().join(",") === "artifact,id" && dependency.artifact === "todo" && typeof dependency.id === "string" && /^[a-z]{10}$/.test(dependency.id))
39
+ continue;
40
+ if (mapping(dependency) && Object.keys(dependency).join(",") === "local_ref" && typeof dependency.local_ref === "string" && refs.has(dependency.local_ref))
41
+ continue;
42
+ violations.push(`creates[${index}].record.readiness.dependencies contains a missing or invalid local reference`);
43
+ }
44
+ }
45
+ const edges = new Map(creates.map(({ local_ref, record }) => [local_ref, (mapping(record.readiness) && Array.isArray(record.readiness.dependencies) ? record.readiness.dependencies : []).flatMap((value) => mapping(value) && typeof value.local_ref === "string" ? [value.local_ref] : [])]));
46
+ const visiting = new Set();
47
+ const visited = new Set();
48
+ const cyclic = (ref) => { if (visiting.has(ref))
49
+ return true; if (visited.has(ref))
50
+ return false; visiting.add(ref); const found = (edges.get(ref) ?? []).some(cyclic); visiting.delete(ref); visited.add(ref); return found; };
51
+ if ([...edges.keys()].some(cyclic))
52
+ violations.push("request-local dependency graph contains a cycle");
53
+ return { strictEnvelope, creates, violations: [...new Set(violations)] };
54
+ }
55
+ export function parseTodoCreateBatch(input) {
56
+ const inspected = inspectTodoCreateBatch(input);
57
+ if (!inspected)
58
+ return null;
59
+ if (inspected.violations.length)
60
+ reject({ class: "schema_violation", message: "todo create batch input is invalid", violations: inspected.violations, recovery: "Correct the strict agentera.todoCreateBatch.v1 envelope, then preview it again; no state was changed." });
61
+ return inspected.creates;
62
+ }
63
+ export function resolveTodoCreateBatchRecords(entries, ids) {
64
+ return entries.map(({ local_ref, record }) => { const copy = structuredClone(record); if (mapping(copy.readiness) && Array.isArray(copy.readiness.dependencies))
65
+ copy.readiness.dependencies = copy.readiness.dependencies.map((value) => mapping(value) && "local_ref" in value ? { artifact: "todo", id: ids.get(String(value.local_ref)) } : value); return { local_ref, record: copy }; });
66
+ }
67
+ export function todoCreateBatchEffectSha256(input, mappingSha256, localRefs, targets) {
68
+ return createHash("sha256").update(canonicalRecordJson({ schema_version: TODO_CREATE_BATCH_VERSION, input, mapping_sha256: mappingSha256, local_refs: localRefs, targets })).digest("hex");
69
+ }