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.
- package/bundle/.agentera-build-source.json +5 -5
- package/bundle/CHANGELOG.md +9 -0
- package/bundle/references/adapters/package-registry.yaml +147 -145
- package/bundle/references/artifacts/state-storage-authority.yaml +27 -18
- package/bundle/skills/agentera/schemas/artifacts/todo.yaml +6 -3
- package/dist/.agentera-build-source.json +5 -5
- package/dist/cli/capabilityContext/progress.js +7 -6
- package/dist/cli/commands/prime.js +13 -0
- package/dist/cli/commands/state/write.js +29 -3
- package/dist/cli/help.js +10 -4
- package/dist/state/todoCreateBatch.js +69 -0
- package/dist/state/todoDocsEntities.js +212 -2
- package/dist/state/todoReconciliationTransaction.js +41 -8
- package/dist/state/todoTransitionBatch.js +65 -0
- package/dist/state/todoUpdateBatch.js +50 -0
- package/dist/state/write/explain.js +6 -3
- package/dist/state/write/runtimeOperations.js +8 -8
- package/package.json +2 -2
|
@@ -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,
|
|
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:
|
|
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:
|
|
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,
|
|
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:
|
|
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:
|
|
2946
|
-
fields:
|
|
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,
|
|
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:
|
|
2955
|
-
recovery: Use
|
|
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:
|
|
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,
|
|
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:
|
|
2987
|
-
recovery: Use
|
|
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:
|
|
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
|
|
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
|
|
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": "
|
|
4
|
-
"tree": "
|
|
5
|
-
"files":
|
|
6
|
-
"workingTreeSha256": "
|
|
7
|
-
"identitySha256": "
|
|
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
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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 ?
|
|
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 (
|
|
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
|
-
|
|
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
|
-
"
|
|
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
|
-
"
|
|
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
|
|
498
|
-
" --orientation
|
|
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
|
+
}
|