@tacuchi/agent-workflow-cli 21.15.0 → 21.17.0
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/dist/application/capability/design-handler.js +416 -167
- package/dist/application/capability/design-handler.js.map +1 -1
- package/dist/application/design/design-content-gate-service.js +9 -1
- package/dist/application/design/design-content-gate-service.js.map +1 -1
- package/dist/application/design/design-publish-service.js +17 -130
- package/dist/application/design/design-publish-service.js.map +1 -1
- package/dist/application/design/design-simple-service.js +4 -2
- package/dist/application/design/design-simple-service.js.map +1 -1
- package/dist/application/export-service.js +183 -19
- package/dist/application/export-service.js.map +1 -1
- package/dist/application/semantic-operation/protocol.js +54 -4
- package/dist/application/semantic-operation/protocol.js.map +1 -1
- package/dist/cli/commands/capability.js +38 -3
- package/dist/cli/commands/capability.js.map +1 -1
- package/dist/cli/commands/export.js +78 -4
- package/dist/cli/commands/export.js.map +1 -1
- package/dist/domain/design/capability.js +19 -3
- package/dist/domain/design/capability.js.map +1 -1
- package/dist/domain/design/direct.js.map +1 -1
- package/dist/domain/design/simple.js +33 -1
- package/dist/domain/design/simple.js.map +1 -1
- package/package.json +1 -1
- package/skills/w/commands/export-diagrams.md +1 -1
- package/skills/w/commands/export-manuals.md +1 -1
- package/skills/w/commands/export-reports.md +1 -1
- package/skills/w/commands/export-scripts.md +1 -1
|
@@ -20,11 +20,13 @@ import { DESIGN_DESCRIPTOR, DESIGN_OPERATIONS } from "../../domain/design/capabi
|
|
|
20
20
|
import { attainedMaturity, isIndexable, resolveOutputRoot, } from "../../domain/design/direct.js";
|
|
21
21
|
import { deriveStructuralSignals, judgeExpansion, } from "../../domain/design/expansion.js";
|
|
22
22
|
import { DESIGN_MANIFEST_FILE, DESIGN_MANIFEST_SCHEMA_ID, } from "../../domain/design/manifest.js";
|
|
23
|
+
import { PROJECTIONS } from "../../domain/design/naming.js";
|
|
23
24
|
import { DESIGN_ADAPTERS } from "../../domain/design/profiles.js";
|
|
24
|
-
import { SIMPLE_CORE_SECTIONS, SIMPLE_DESIGN_FILE, SIMPLE_SECTIONS, designFolder, designSlug, nextPackageId, } from "../../domain/design/simple.js";
|
|
25
|
+
import { SIMPLE_CORE_SECTIONS, SIMPLE_DESIGN_FILE, SIMPLE_SECTIONS, designFolder, designSlug, nextPackageId, simpleMaturity, validateSimpleDesign, } from "../../domain/design/simple.js";
|
|
25
26
|
import { classifySource, reportSources, } from "../../domain/design/sources.js";
|
|
26
27
|
import { baseDigest } from "../../domain/proposal.js";
|
|
27
28
|
import { localDateIso } from "../dates.js";
|
|
29
|
+
import { currentEntries, gatePackageContent } from "../design/design-content-gate-service.js";
|
|
28
30
|
import { readDesignIndex, resolveDesignPackage, } from "../design/design-index-service.js";
|
|
29
31
|
import { buildPackageCandidate, } from "../design/design-publish-service.js";
|
|
30
32
|
import { checkRecordPrecondition } from "../design/design-record-service.js";
|
|
@@ -78,25 +80,42 @@ async function validatePackage(ctx) {
|
|
|
78
80
|
},
|
|
79
81
|
};
|
|
80
82
|
}
|
|
83
|
+
// The SAME content gate `aw designs` runs. Judging a package with the
|
|
84
|
+
// structural check alone answered `handoff` for a tree the listing then
|
|
85
|
+
// rejected — the verdict and the listing have to be the same verdict.
|
|
86
|
+
const content = await gatePackageContent(ctx.fs, ctx.workspace, found);
|
|
87
|
+
const failures = [...found.failures, ...content];
|
|
88
|
+
const ok = found.ok && content.length === 0;
|
|
81
89
|
const validations = [
|
|
82
90
|
{
|
|
83
91
|
id: "design-manifest",
|
|
84
92
|
passed: found.ok,
|
|
85
93
|
detail: found.ok ? null : found.failures.map((f) => `${f.code}: ${f.message}`).join("; "),
|
|
86
94
|
},
|
|
95
|
+
{
|
|
96
|
+
id: "design-content",
|
|
97
|
+
passed: content.length === 0,
|
|
98
|
+
detail: content.length === 0 ? null : content.map((f) => `${f.code}: ${f.message}`).join("; "),
|
|
99
|
+
},
|
|
87
100
|
];
|
|
88
101
|
const report = reportSources([], `${id}`);
|
|
89
102
|
const simple = found.mode === "simple";
|
|
90
|
-
const
|
|
103
|
+
const gate = ok
|
|
104
|
+
? await publishedMaturity(ctx.fs, ctx.workspace, found)
|
|
105
|
+
: { attained: "outline", reasons: failures.map((f) => f.message) };
|
|
106
|
+
const maturity = attainedMaturity(requestedMaturity(ctx), gate.attained, report);
|
|
91
107
|
const fields = {
|
|
92
108
|
package: found.id,
|
|
93
109
|
baseline: found.current_baseline === null
|
|
94
110
|
? null
|
|
95
111
|
: { revision: found.current_baseline.revision, digest: found.current_baseline.digest },
|
|
112
|
+
// A verdict publishes nothing, so there is nothing it could have failed to
|
|
113
|
+
// seal: the package's own baseline above is the whole answer.
|
|
114
|
+
unsealed: null,
|
|
96
115
|
path: found.path,
|
|
97
116
|
root: "workspace",
|
|
98
117
|
indexable: true,
|
|
99
|
-
maturity: { requested: requestedMaturity(ctx), attained:
|
|
118
|
+
maturity: { requested: requestedMaturity(ctx), attained: maturity.attained },
|
|
100
119
|
sources: [],
|
|
101
120
|
renditions: [],
|
|
102
121
|
// Judging an existing design reports the route it IS, not one this attempt
|
|
@@ -108,7 +127,7 @@ async function validatePackage(ctx) {
|
|
|
108
127
|
kind: "completed",
|
|
109
128
|
validations,
|
|
110
129
|
output: {
|
|
111
|
-
value: { design: fields, ok
|
|
130
|
+
value: { design: fields, ok, failures, gaps: [...gate.reasons, ...maturity.gaps] },
|
|
112
131
|
reference: found.id === null || found.current_baseline === null
|
|
113
132
|
? null
|
|
114
133
|
: {
|
|
@@ -146,6 +165,25 @@ async function authoring(ctx) {
|
|
|
146
165
|
},
|
|
147
166
|
};
|
|
148
167
|
}
|
|
168
|
+
// Outside a workspace every destination this route could declare is ABSOLUTE,
|
|
169
|
+
// and the write boundary only admits workspace-relative paths inside the
|
|
170
|
+
// declared ones: an absolute answer is refused for being absolute and a
|
|
171
|
+
// relative one for falling outside them. No answer exists, so publishing the
|
|
172
|
+
// contract burns an authoring round over a question with no valid reply — and
|
|
173
|
+
// `apply` demands a workspace anyway, so it could never land either. The
|
|
174
|
+
// refusal belongs here, where the root is decided, and not one stage later
|
|
175
|
+
// where it reads as the author's mistake.
|
|
176
|
+
if (ctx.workspace === null) {
|
|
177
|
+
return {
|
|
178
|
+
kind: "blocked",
|
|
179
|
+
failure: {
|
|
180
|
+
code: "DESIGN_WORKSPACE_ABSENT",
|
|
181
|
+
message: `fuera de un workspace los destinos de '${ctx.operation.name}' son absolutos y ninguna respuesta puede caer dentro de ellos`,
|
|
182
|
+
action: "corré la operación dentro del workspace donde debe quedar el diseño: 'target' acota la carpeta DENTRO del workspace, no publica fuera de él",
|
|
183
|
+
},
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
const workspace = ctx.workspace;
|
|
149
187
|
const precondition = await operationPrecondition(ctx);
|
|
150
188
|
if (precondition !== null)
|
|
151
189
|
return { kind: "blocked", failure: precondition };
|
|
@@ -166,7 +204,7 @@ async function authoring(ctx) {
|
|
|
166
204
|
// what a valid answer looks like, and where it may land, are different on the
|
|
167
205
|
// two routes, so asking first and classifying afterwards would publish a
|
|
168
206
|
// contract for a route the run is not on.
|
|
169
|
-
const route = await decideRoute(ctx, sources, root.value);
|
|
207
|
+
const route = await decideRoute(ctx, workspace, sources, root.value);
|
|
170
208
|
if (!route.ok)
|
|
171
209
|
return { kind: "blocked", failure: route.failure };
|
|
172
210
|
const request = buildSemanticRequest({
|
|
@@ -204,9 +242,14 @@ async function authoring(ctx) {
|
|
|
204
242
|
if (!parsed.ok)
|
|
205
243
|
return { kind: "blocked", failure: parsed.failure };
|
|
206
244
|
const answered = parsed.value.artifacts ?? [];
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
245
|
+
const { target } = route.value;
|
|
246
|
+
if (target.mode === "simple") {
|
|
247
|
+
return simpleProposal(ctx, workspace, report, route.value, target.simple, answered);
|
|
248
|
+
}
|
|
249
|
+
if (target.mode === "package") {
|
|
250
|
+
return packageProposal(ctx, workspace, report, route.value, target.package, answered);
|
|
251
|
+
}
|
|
252
|
+
return projectionProposal(ctx, workspace, report, route.value, target.projection, answered);
|
|
210
253
|
}
|
|
211
254
|
/**
|
|
212
255
|
* Which route this attempt runs on, and everything that follows from it.
|
|
@@ -217,13 +260,17 @@ async function authoring(ctx) {
|
|
|
217
260
|
* or a structural one somebody typed comes back as a rejection in the verdict
|
|
218
261
|
* rather than as an expansion nobody can explain.
|
|
219
262
|
*/
|
|
220
|
-
async function decideRoute(ctx, sources, root) {
|
|
221
|
-
|
|
263
|
+
async function decideRoute(ctx, workspace, sources, root) {
|
|
264
|
+
// Always readable, never null: a workspace with no `docs/designs/` yet answers
|
|
265
|
+
// an EMPTY index, and outside a workspace `authoring` already refused. A
|
|
266
|
+
// nullable index here used to carry a third state that every branch below had
|
|
267
|
+
// to restate and that no invocation could reach.
|
|
268
|
+
const index = await readDesignIndex(ctx.fs, workspace);
|
|
222
269
|
// By IDENTITY and only when one was named. `find(p => p.id === null)` would
|
|
223
270
|
// match the first package whose manifest does not validate — an entry that has
|
|
224
271
|
// no identity is not the one this invocation continues.
|
|
225
272
|
const named = packageInput(ctx);
|
|
226
|
-
const targeted =
|
|
273
|
+
const targeted = named === null ? null : (index.packages.find((p) => p.id === named) ?? null);
|
|
227
274
|
const verdict = judgeExpansion(declaredExpansionSignals(ctx), deriveStructuralSignals({
|
|
228
275
|
sensitiveSources: ctx.request.policy.sensitive_sources === true,
|
|
229
276
|
externalTransmission: ctx.request.policy.external_transmission === true,
|
|
@@ -232,9 +279,9 @@ async function decideRoute(ctx, sources, root) {
|
|
|
232
279
|
(targeted?.manifest?.governance.revocations.length ?? 0),
|
|
233
280
|
publishedRevisions: targeted?.manifest?.baselines.length ?? 0,
|
|
234
281
|
}));
|
|
235
|
-
// The package route is also the only one available outside a
|
|
236
|
-
//
|
|
237
|
-
//
|
|
282
|
+
// The package route is also the only one available outside `docs/designs/`: a
|
|
283
|
+
// simple design derives its identity from the index, and a root the index does
|
|
284
|
+
// not cover has none to derive from.
|
|
238
285
|
//
|
|
239
286
|
// `render` and `record` are package operations whatever the signals say —
|
|
240
287
|
// projecting revisions and sealing governance decisions are things a catalog
|
|
@@ -242,13 +289,13 @@ async function decideRoute(ctx, sources, root) {
|
|
|
242
289
|
// simple design for a maturity and a rendition it never had.
|
|
243
290
|
if (verdict.mode === "package" ||
|
|
244
291
|
!AUTHORING_OPERATIONS.includes(ctx.operation.name) ||
|
|
245
|
-
index === null ||
|
|
246
292
|
!isIndexable(root)) {
|
|
247
293
|
return packageRoute(ctx, verdict, index, root);
|
|
248
294
|
}
|
|
249
295
|
const resolved = resolveSimpleTarget(index, ctx.operation.name, {
|
|
250
296
|
title: textInput(ctx, "title"),
|
|
251
297
|
packageId: packageInput(ctx),
|
|
298
|
+
root: root.root,
|
|
252
299
|
});
|
|
253
300
|
if (!resolved.ok) {
|
|
254
301
|
const { code, message, action } = resolved.failure;
|
|
@@ -269,49 +316,32 @@ async function decideRoute(ctx, sources, root) {
|
|
|
269
316
|
// The exact file, not its folder: on the simple route the CLI already knows
|
|
270
317
|
// the one destination, so anything else is not a design it can publish.
|
|
271
318
|
destinations: [`${resolved.value.path}/${SIMPLE_DESIGN_FILE}`],
|
|
272
|
-
target: resolved.value,
|
|
273
|
-
packageTarget: null,
|
|
319
|
+
target: { mode: "simple", simple: resolved.value },
|
|
274
320
|
root,
|
|
275
321
|
},
|
|
276
322
|
};
|
|
277
323
|
}
|
|
278
324
|
/**
|
|
279
|
-
* The package route
|
|
325
|
+
* The package route. It seals, or it does not run.
|
|
280
326
|
*
|
|
281
|
-
*
|
|
282
|
-
*
|
|
283
|
-
*
|
|
327
|
+
* There used to be a third outcome, declared in the code as a known limitation:
|
|
328
|
+
* when the target could not be derived the answer was written EXACTLY as
|
|
329
|
+
* authored — no baseline, no manifest, no gate — and the receipt said
|
|
330
|
+
* `completed` anyway. The measured effect was a tree `aw designs` refuses right
|
|
331
|
+
* afterwards, which only a hand check ever discovers. An operation that did not
|
|
332
|
+
* happen is a better outcome than a dossier nobody can read, so what used to be
|
|
333
|
+
* a silent half-write is now either a sealed publication or a refusal that names
|
|
334
|
+
* what is missing.
|
|
284
335
|
*/
|
|
285
336
|
function packageRoute(ctx, verdict, index, root) {
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
action: "pasá 'base' con la revisión vigente (por ejemplo DES-001@r3), o null si el package no publicó ninguna",
|
|
293
|
-
},
|
|
294
|
-
};
|
|
337
|
+
// `render` and `record` author no normative content: a projection is derived
|
|
338
|
+
// from the manifest and a governance decision decides ON a baseline. Minting
|
|
339
|
+
// no revision is their NATURE, not a defect, so they get their own route
|
|
340
|
+
// instead of the refusal that briefly made both unreachable.
|
|
341
|
+
if (!AUTHORING_OPERATIONS.includes(ctx.operation.name)) {
|
|
342
|
+
return projectionRoute(ctx, verdict, index, root);
|
|
295
343
|
}
|
|
296
|
-
|
|
297
|
-
// derive from (outside a workspace or outside `docs/designs/`) — still run
|
|
298
|
-
// the verbatim path: the answer is published as authored, with no derived
|
|
299
|
-
// manifest, baseline or gate. Only create/update over the index seal.
|
|
300
|
-
if (index === null || !AUTHORING_OPERATIONS.includes(ctx.operation.name) || !isIndexable(root)) {
|
|
301
|
-
return {
|
|
302
|
-
ok: true,
|
|
303
|
-
value: {
|
|
304
|
-
verdict,
|
|
305
|
-
contract: contractFor(ctx.operation.name),
|
|
306
|
-
inventory: { root: root.root, mode: root.kind },
|
|
307
|
-
destinations: [root.root],
|
|
308
|
-
target: null,
|
|
309
|
-
packageTarget: null,
|
|
310
|
-
root,
|
|
311
|
-
},
|
|
312
|
-
};
|
|
313
|
-
}
|
|
314
|
-
const resolved = resolvePackageTarget(ctx, index);
|
|
344
|
+
const resolved = resolvePackageTarget(ctx, index, root);
|
|
315
345
|
if (!resolved.ok)
|
|
316
346
|
return { ok: false, failure: resolved.failure };
|
|
317
347
|
const target = resolved.value;
|
|
@@ -330,67 +360,160 @@ function packageRoute(ctx, verdict, index, root) {
|
|
|
330
360
|
// The package folder, not the taxonomy root: the destination check is
|
|
331
361
|
// segment-based, so every artifact lands INSIDE this package or nowhere.
|
|
332
362
|
destinations: [target.path],
|
|
333
|
-
target:
|
|
334
|
-
packageTarget: target,
|
|
363
|
+
target: { mode: "package", package: target },
|
|
335
364
|
root,
|
|
336
365
|
},
|
|
337
366
|
};
|
|
338
367
|
}
|
|
368
|
+
/** Why each non-authoring operation mints no revision, said in its own receipt. */
|
|
369
|
+
const UNSEALED_CAUSE = {
|
|
370
|
+
render: "'render' regenera proyecciones: las deriva el CLI del manifest y ningún baseline las selecciona, así que no hay revisión que acuñar",
|
|
371
|
+
record: "'record' decide SOBRE una revisión que ya existe: sella una decisión de gobierno y no acuña una línea base nueva",
|
|
372
|
+
};
|
|
339
373
|
/**
|
|
340
|
-
* The
|
|
374
|
+
* The route of an operation that publishes WITHOUT minting a revision.
|
|
341
375
|
*
|
|
342
|
-
*
|
|
343
|
-
*
|
|
344
|
-
*
|
|
345
|
-
*
|
|
346
|
-
*
|
|
376
|
+
* It writes inside a package the index already carries, and refuses when there
|
|
377
|
+
* is none. The two halves are one rule: with a manifest already there the tree
|
|
378
|
+
* stays readable — `aw designs` accepts afterwards exactly what it accepted
|
|
379
|
+
* before, because nothing sealed moves — and without one the files would land in
|
|
380
|
+
* a folder the listing then rejects for having no manifest, which is the
|
|
381
|
+
* illegible tree this plan exists to stop.
|
|
347
382
|
*/
|
|
348
|
-
function
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
return {
|
|
353
|
-
ok: false,
|
|
354
|
-
failure: {
|
|
355
|
-
code: "DESIGN_FIELD_INVALID",
|
|
356
|
-
message: "un package nuevo necesita un título",
|
|
357
|
-
action: "pasá 'title' con el nombre humano del diseño: de ahí salen la carpeta y el id",
|
|
358
|
-
},
|
|
359
|
-
};
|
|
360
|
-
}
|
|
361
|
-
const packageId = nextPackageId(index.packages.map((p) => p.id ?? p.declared_id));
|
|
383
|
+
function projectionRoute(ctx, verdict, index, root) {
|
|
384
|
+
const operation = ctx.operation.name;
|
|
385
|
+
const named = packageInput(ctx);
|
|
386
|
+
if (named === null) {
|
|
362
387
|
return {
|
|
363
|
-
ok:
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
manifest: initialPackageManifest(packageId, title, localDateIso(new Date())),
|
|
388
|
+
ok: false,
|
|
389
|
+
failure: {
|
|
390
|
+
code: "DESIGN_FIELD_INVALID",
|
|
391
|
+
message: `'${operation}' escribe dentro de un package que ya existe y no se declaró cuál`,
|
|
392
|
+
action: "pasá 'package' con su id, por ejemplo DES-007",
|
|
369
393
|
},
|
|
370
394
|
};
|
|
371
395
|
}
|
|
372
|
-
const
|
|
373
|
-
if (
|
|
396
|
+
const located = locatePackage(index, named);
|
|
397
|
+
if (!located.ok)
|
|
398
|
+
return { ok: false, failure: located.failure };
|
|
399
|
+
const entry = located.value;
|
|
400
|
+
const target = {
|
|
401
|
+
entry,
|
|
402
|
+
unsealed: UNSEALED_CAUSE[operation] ?? `'${operation}' no acuña una revisión`,
|
|
403
|
+
};
|
|
404
|
+
return {
|
|
405
|
+
ok: true,
|
|
406
|
+
value: {
|
|
407
|
+
verdict,
|
|
408
|
+
contract: projectionContract(operation, target),
|
|
409
|
+
inventory: {
|
|
410
|
+
root: root.root,
|
|
411
|
+
mode: entry.manifest.mode,
|
|
412
|
+
package: entry.manifest.id,
|
|
413
|
+
path: entry.path,
|
|
414
|
+
// Consultative and load-bearing: an author who does not know the answer
|
|
415
|
+
// will not be sealed writes a revision nobody asked for.
|
|
416
|
+
seals: false,
|
|
417
|
+
},
|
|
418
|
+
// The package folder the INDEX reports, not the root the invocation named:
|
|
419
|
+
// this operation writes into a package that already exists, wherever it is.
|
|
420
|
+
destinations: [entry.path],
|
|
421
|
+
target: { mode: "projection", projection: target },
|
|
422
|
+
root,
|
|
423
|
+
},
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
function resolvePackageTarget(ctx, index, root) {
|
|
427
|
+
return ctx.operation.name === "create"
|
|
428
|
+
? mintPackageTarget(ctx, index, root)
|
|
429
|
+
: continuePackageTarget(ctx, index);
|
|
430
|
+
}
|
|
431
|
+
/** A brand-new package: the identity and the folder, from the title and the root. */
|
|
432
|
+
function mintPackageTarget(ctx, index, root) {
|
|
433
|
+
const title = textInput(ctx, "title");
|
|
434
|
+
if (title === null) {
|
|
374
435
|
return {
|
|
375
436
|
ok: false,
|
|
376
437
|
failure: {
|
|
377
438
|
code: "DESIGN_FIELD_INVALID",
|
|
378
|
-
message: "
|
|
379
|
-
action: "pasá '
|
|
439
|
+
message: "un package nuevo necesita un título",
|
|
440
|
+
action: "pasá 'title' con el nombre humano del diseño: de ahí salen la carpeta y el id",
|
|
380
441
|
},
|
|
381
442
|
};
|
|
382
443
|
}
|
|
383
|
-
const
|
|
384
|
-
|
|
444
|
+
const packageId = nextPackageId(index.packages.map((p) => p.id ?? p.declared_id));
|
|
445
|
+
return {
|
|
446
|
+
ok: true,
|
|
447
|
+
value: {
|
|
448
|
+
packageId,
|
|
449
|
+
// The DECLARED root, not the index's: a `target` that narrows where the
|
|
450
|
+
// package lands has to be where it lands, or the folder and the
|
|
451
|
+
// destination allowlist the request publishes disagree.
|
|
452
|
+
path: designFolder(root.root, packageId, designSlug(title)),
|
|
453
|
+
revision: 1,
|
|
454
|
+
manifest: initialPackageManifest(packageId, title, localDateIso(new Date())),
|
|
455
|
+
},
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
function locatePackage(index, named) {
|
|
459
|
+
// By identity, and never by the FIRST match. Two packages claiming one id
|
|
460
|
+
// break every reference to it, and picking whichever the walk reached first
|
|
461
|
+
// would write into one of them at random.
|
|
462
|
+
const claiming = index.packages.filter((p) => p.id === named || p.declared_id === named);
|
|
463
|
+
if (claiming.length > 1) {
|
|
464
|
+
return {
|
|
465
|
+
ok: false,
|
|
466
|
+
failure: {
|
|
467
|
+
code: "DESIGN_REFERENCE_AMBIGUOUS",
|
|
468
|
+
message: `${named} está declarado por ${claiming.length} packages: ${claiming.map((p) => p.path).join(", ")}`,
|
|
469
|
+
action: "dos packages no pueden reclamar la misma identidad: renombrá uno y volvé a intentar",
|
|
470
|
+
},
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
const found = claiming[0];
|
|
474
|
+
if (found === undefined) {
|
|
385
475
|
return {
|
|
386
476
|
ok: false,
|
|
387
477
|
failure: {
|
|
388
478
|
code: "DESIGN_PACKAGE_NOT_FOUND",
|
|
389
|
-
message: `no hay ningún package ${named}
|
|
479
|
+
message: `no hay ningún package ${named} bajo ${index.root}/`,
|
|
390
480
|
action: `revisá 'aw designs' para ver las identidades publicadas bajo ${index.root}/`,
|
|
391
481
|
},
|
|
392
482
|
};
|
|
393
483
|
}
|
|
484
|
+
// A BROKEN package is not a missing one: they are very different problems for
|
|
485
|
+
// whoever has to fix one, so what comes back is the manifest's own diagnosis
|
|
486
|
+
// and not «no existe».
|
|
487
|
+
if (found.manifest === null) {
|
|
488
|
+
const first = found.failures[0];
|
|
489
|
+
return {
|
|
490
|
+
ok: false,
|
|
491
|
+
failure: {
|
|
492
|
+
code: first?.code ?? "DESIGN_MANIFEST_MISSING",
|
|
493
|
+
message: `${found.manifest_path}: ${first?.message ?? "el package no tiene un manifest legible"}`,
|
|
494
|
+
action: first?.action ?? "reparalo antes de publicar sobre él",
|
|
495
|
+
},
|
|
496
|
+
};
|
|
497
|
+
}
|
|
498
|
+
return { ok: true, value: { ...found, manifest: found.manifest } };
|
|
499
|
+
}
|
|
500
|
+
/** The next revision of a package that exists: located by identity, base checked. */
|
|
501
|
+
function continuePackageTarget(ctx, index) {
|
|
502
|
+
const named = packageInput(ctx);
|
|
503
|
+
if (named === null) {
|
|
504
|
+
return {
|
|
505
|
+
ok: false,
|
|
506
|
+
failure: {
|
|
507
|
+
code: "DESIGN_FIELD_INVALID",
|
|
508
|
+
message: "actualizar un package necesita la identidad del que se continúa",
|
|
509
|
+
action: "pasá 'package' con su id, por ejemplo DES-007",
|
|
510
|
+
},
|
|
511
|
+
};
|
|
512
|
+
}
|
|
513
|
+
const located = locatePackage(index, named);
|
|
514
|
+
if (!located.ok)
|
|
515
|
+
return { ok: false, failure: located.failure };
|
|
516
|
+
const found = located.value;
|
|
394
517
|
const manifest = found.manifest;
|
|
395
518
|
const current = manifest.current_baseline;
|
|
396
519
|
const actual = current === null ? null : `${manifest.id}@r${current.revision}`;
|
|
@@ -452,8 +575,7 @@ function initialPackageManifest(packageId, title, created) {
|
|
|
452
575
|
};
|
|
453
576
|
}
|
|
454
577
|
/** The simple route's durable step: one authored document, everything else derived. */
|
|
455
|
-
async function simpleProposal(ctx, report, route, answered) {
|
|
456
|
-
const target = route.target;
|
|
578
|
+
async function simpleProposal(ctx, workspace, report, route, target, answered) {
|
|
457
579
|
const documentPath = `${target.path}/${SIMPLE_DESIGN_FILE}`;
|
|
458
580
|
const document = answered.find((a) => a.path === documentPath);
|
|
459
581
|
if (answered.length !== 1 || document === undefined) {
|
|
@@ -466,9 +588,7 @@ async function simpleProposal(ctx, report, route, answered) {
|
|
|
466
588
|
},
|
|
467
589
|
};
|
|
468
590
|
}
|
|
469
|
-
|
|
470
|
-
// inside a workspace: the simple route and a null workspace cannot coexist.
|
|
471
|
-
const built = await buildSimpleProposal(ctx.fs, ctx.workspace, {
|
|
591
|
+
const built = await buildSimpleProposal(ctx.fs, workspace, {
|
|
472
592
|
target,
|
|
473
593
|
document: document.content,
|
|
474
594
|
published: localDateIso(new Date()),
|
|
@@ -484,14 +604,19 @@ async function simpleProposal(ctx, report, route, answered) {
|
|
|
484
604
|
},
|
|
485
605
|
};
|
|
486
606
|
}
|
|
607
|
+
// The gate's verdict over the document, exactly as the package route takes it
|
|
608
|
+
// from the gate over its own: what the design attains cannot depend on which
|
|
609
|
+
// route wrote it.
|
|
610
|
+
const gate = built.value.maturity;
|
|
611
|
+
const maturity = attainedMaturity(requestedMaturity(ctx), gate.attained, report);
|
|
487
612
|
const fields = {
|
|
488
613
|
package: built.value.packageId,
|
|
489
614
|
baseline: { revision: built.value.revision, digest: built.value.digest },
|
|
615
|
+
unsealed: null,
|
|
490
616
|
path: target.path,
|
|
491
617
|
root: route.root.kind,
|
|
492
618
|
indexable: true,
|
|
493
|
-
|
|
494
|
-
maturity: { requested: requestedMaturity(ctx), attained: null },
|
|
619
|
+
maturity: { requested: requestedMaturity(ctx), attained: maturity.attained },
|
|
495
620
|
sources: report.sources,
|
|
496
621
|
renditions: [],
|
|
497
622
|
route: routeOf(route.verdict),
|
|
@@ -507,7 +632,7 @@ async function simpleProposal(ctx, report, route, answered) {
|
|
|
507
632
|
value: {
|
|
508
633
|
design: fields,
|
|
509
634
|
artifacts: built.value.artifacts.map((a) => a.path),
|
|
510
|
-
gaps: [],
|
|
635
|
+
gaps: [...gate.reasons, ...maturity.gaps],
|
|
511
636
|
},
|
|
512
637
|
reference: null,
|
|
513
638
|
completeness: "partial",
|
|
@@ -516,47 +641,14 @@ async function simpleProposal(ctx, report, route, answered) {
|
|
|
516
641
|
};
|
|
517
642
|
}
|
|
518
643
|
/**
|
|
519
|
-
* The expanded route's durable step.
|
|
520
|
-
*
|
|
521
|
-
* With a derived target (create/update over the index) the CLI owns the seal:
|
|
522
|
-
* the authored artifacts are candidate files, and the manifest, the baseline
|
|
523
|
-
* and `PACKAGE.md` are DERIVED here — the same candidate `publishDesignRevision`
|
|
524
|
-
* publishes, so the gate verdict the `012` computes runs NOW, inside `validate`,
|
|
525
|
-
* and an invalid tree is blocked before the first byte moves.
|
|
644
|
+
* The expanded route's durable step. The CLI owns the seal, always.
|
|
526
645
|
*
|
|
527
|
-
*
|
|
528
|
-
*
|
|
529
|
-
*
|
|
646
|
+
* The authored artifacts are candidate files, and the manifest, the baseline
|
|
647
|
+
* and `PACKAGE.md` are DERIVED here — the one candidate the whole system
|
|
648
|
+
* publishes, so the gate verdict runs NOW, inside `validate`, and an invalid
|
|
649
|
+
* tree is blocked before the first byte moves.
|
|
530
650
|
*/
|
|
531
|
-
async function packageProposal(ctx, report, route, answered) {
|
|
532
|
-
const target = route.packageTarget;
|
|
533
|
-
if (target === null) {
|
|
534
|
-
const artifacts = answered.map((a) => ({ path: a.path, content: a.content }));
|
|
535
|
-
const maturity = attainedMaturity(requestedMaturity(ctx), "outline", report);
|
|
536
|
-
const fields = {
|
|
537
|
-
package: packageInput(ctx),
|
|
538
|
-
baseline: null,
|
|
539
|
-
path: route.root.root,
|
|
540
|
-
root: route.root.kind,
|
|
541
|
-
indexable: isIndexable(route.root),
|
|
542
|
-
maturity: { requested: requestedMaturity(ctx), attained: maturity.attained },
|
|
543
|
-
sources: report.sources,
|
|
544
|
-
renditions: [],
|
|
545
|
-
route: routeOf(route.verdict),
|
|
546
|
-
};
|
|
547
|
-
return {
|
|
548
|
-
kind: "durable",
|
|
549
|
-
artifacts,
|
|
550
|
-
output: {
|
|
551
|
-
value: { design: fields, artifacts: artifacts.map((a) => a.path), gaps: maturity.gaps },
|
|
552
|
-
reference: null,
|
|
553
|
-
// The durable step has not run yet, so nothing is published. Claiming
|
|
554
|
-
// `complete` here would let a gate accept a proposal as a package.
|
|
555
|
-
completeness: "partial",
|
|
556
|
-
},
|
|
557
|
-
base: null,
|
|
558
|
-
};
|
|
559
|
-
}
|
|
651
|
+
async function packageProposal(ctx, workspace, report, route, target, answered) {
|
|
560
652
|
// From workspace-relative to package-relative, which is the vocabulary the
|
|
561
653
|
// candidate builder speaks. The destination check already confined every
|
|
562
654
|
// answer to the package folder, so the prefix always strips.
|
|
@@ -564,7 +656,7 @@ async function packageProposal(ctx, report, route, answered) {
|
|
|
564
656
|
const files = [];
|
|
565
657
|
for (const artifact of answered) {
|
|
566
658
|
const relative = artifact.path.slice(prefix.length);
|
|
567
|
-
if (
|
|
659
|
+
if (owns(DERIVED_PATHS, relative)) {
|
|
568
660
|
return {
|
|
569
661
|
kind: "blocked",
|
|
570
662
|
failure: {
|
|
@@ -576,9 +668,7 @@ async function packageProposal(ctx, report, route, answered) {
|
|
|
576
668
|
}
|
|
577
669
|
files.push({ path: relative, content: artifact.content });
|
|
578
670
|
}
|
|
579
|
-
|
|
580
|
-
// does inside a workspace.
|
|
581
|
-
const candidate = await buildPackageCandidate(ctx.fs, ctx.workspace, {
|
|
671
|
+
const candidate = await buildPackageCandidate(ctx.fs, workspace, {
|
|
582
672
|
manifest: target.manifest,
|
|
583
673
|
packagePath: target.path,
|
|
584
674
|
files,
|
|
@@ -597,13 +687,17 @@ async function packageProposal(ctx, report, route, answered) {
|
|
|
597
687
|
},
|
|
598
688
|
};
|
|
599
689
|
}
|
|
600
|
-
// The
|
|
601
|
-
//
|
|
602
|
-
//
|
|
603
|
-
|
|
690
|
+
// The verdict over the catalog this revision LEAVES, not over the files it
|
|
691
|
+
// brings. A revision of a single token introduces no document that could
|
|
692
|
+
// object, and judging only what it introduces answered `handoff` for a package
|
|
693
|
+
// whose current flow was still `outline` — a receipt the `validate` right
|
|
694
|
+
// afterwards contradicted about the same tree.
|
|
695
|
+
const gate = catalogMaturity(candidate.value.manifest);
|
|
696
|
+
const maturity = attainedMaturity(requestedMaturity(ctx), gate.attained, report);
|
|
604
697
|
const fields = {
|
|
605
698
|
package: target.packageId,
|
|
606
699
|
baseline: { revision: candidate.value.revision, digest: candidate.value.baseline.digest },
|
|
700
|
+
unsealed: null,
|
|
607
701
|
path: target.path,
|
|
608
702
|
root: route.root.kind,
|
|
609
703
|
indexable: isIndexable(route.root),
|
|
@@ -619,39 +713,184 @@ async function packageProposal(ctx, report, route, answered) {
|
|
|
619
713
|
value: {
|
|
620
714
|
design: fields,
|
|
621
715
|
artifacts: candidate.value.artifacts.map((a) => a.path),
|
|
622
|
-
gaps: maturity.gaps,
|
|
716
|
+
gaps: [...gate.reasons, ...maturity.gaps],
|
|
623
717
|
},
|
|
624
718
|
reference: null,
|
|
625
719
|
// The durable step has not run yet, so nothing is published. Claiming
|
|
626
720
|
// `complete` here would let a gate accept a proposal as a package.
|
|
627
721
|
completeness: "partial",
|
|
628
722
|
},
|
|
629
|
-
base: await packageManifestBase(ctx, target),
|
|
723
|
+
base: await packageManifestBase(ctx, workspace, target),
|
|
724
|
+
};
|
|
725
|
+
}
|
|
726
|
+
/**
|
|
727
|
+
* The durable step of a publication that mints NO revision.
|
|
728
|
+
*
|
|
729
|
+
* What it refuses is the only way one of these operations can leave the package
|
|
730
|
+
* unreadable: a hand-authored manifest or baseline seals the tree with something
|
|
731
|
+
* nobody derived, and `aw designs` rejects it right afterwards. Everything else
|
|
732
|
+
* lands as authored — a projection replaces the one it regenerates, anything
|
|
733
|
+
* else is additive — and the receipt says, in words, that nothing was sealed.
|
|
734
|
+
*/
|
|
735
|
+
async function projectionProposal(ctx, workspace, report, route, target, answered) {
|
|
736
|
+
const entry = target.entry;
|
|
737
|
+
const prefix = `${entry.path}/`;
|
|
738
|
+
const artifacts = [];
|
|
739
|
+
for (const artifact of answered) {
|
|
740
|
+
const relative = artifact.path.slice(prefix.length);
|
|
741
|
+
if (owns(SEALED_PATHS, relative)) {
|
|
742
|
+
return {
|
|
743
|
+
kind: "blocked",
|
|
744
|
+
failure: {
|
|
745
|
+
code: "DESIGN_FIELD_INVALID",
|
|
746
|
+
message: `'${artifact.path}' es lo que sella el package, y '${ctx.operation.name}' no acuña revisión`,
|
|
747
|
+
action: `quitalo de 'artifacts': ${DESIGN_MANIFEST_FILE} y 'baselines/' los deriva y sella una publicación de contenido normativo, con 'create' o 'update'`,
|
|
748
|
+
},
|
|
749
|
+
};
|
|
750
|
+
}
|
|
751
|
+
artifacts.push({
|
|
752
|
+
path: artifact.path,
|
|
753
|
+
content: artifact.content,
|
|
754
|
+
// Regenerating a projection REPLACES it — that is what regenerating means,
|
|
755
|
+
// and `render` declares `mutate_overwrite` for exactly this. Nothing else
|
|
756
|
+
// is regenerable: a governance record decides on bytes that already exist,
|
|
757
|
+
// so publishing over one would rewrite a decision somebody made.
|
|
758
|
+
overwrite: PROJECTIONS.includes(relative),
|
|
759
|
+
});
|
|
760
|
+
}
|
|
761
|
+
// The design's own maturity, unchanged: this publication catalogues nothing,
|
|
762
|
+
// so reporting anything else would credit or blame it for a verdict it did
|
|
763
|
+
// not move. Through the same function `validate` uses, which is what keeps a
|
|
764
|
+
// simple design judged by its document instead of by an empty catalog.
|
|
765
|
+
const gate = await publishedMaturity(ctx.fs, workspace, entry);
|
|
766
|
+
const maturity = attainedMaturity(requestedMaturity(ctx), gate.attained, report);
|
|
767
|
+
const fields = {
|
|
768
|
+
package: entry.manifest.id,
|
|
769
|
+
// Null, and SAID: `unsealed` is what turns "no hay línea base" from an
|
|
770
|
+
// omission into a declaration.
|
|
771
|
+
baseline: null,
|
|
772
|
+
unsealed: target.unsealed,
|
|
773
|
+
path: entry.path,
|
|
774
|
+
root: route.root.kind,
|
|
775
|
+
// Resolved FROM the index, so it is indexed whatever root the invocation
|
|
776
|
+
// happened to name.
|
|
777
|
+
indexable: true,
|
|
778
|
+
maturity: { requested: requestedMaturity(ctx), attained: maturity.attained },
|
|
779
|
+
sources: report.sources,
|
|
780
|
+
renditions: [],
|
|
781
|
+
route: routeOf(route.verdict),
|
|
782
|
+
};
|
|
783
|
+
return {
|
|
784
|
+
kind: "durable",
|
|
785
|
+
artifacts,
|
|
786
|
+
output: {
|
|
787
|
+
value: {
|
|
788
|
+
design: fields,
|
|
789
|
+
artifacts: artifacts.map((a) => a.path),
|
|
790
|
+
gaps: [...gate.reasons, ...maturity.gaps],
|
|
791
|
+
},
|
|
792
|
+
reference: null,
|
|
793
|
+
// Nothing is on disk until the approval lands, here as everywhere else.
|
|
794
|
+
completeness: "partial",
|
|
795
|
+
},
|
|
796
|
+
// Nothing to compare and swap: this publication reads no manifest to derive
|
|
797
|
+
// its output, so there is no state it could have been computed against.
|
|
798
|
+
base: null,
|
|
630
799
|
};
|
|
631
800
|
}
|
|
632
|
-
/** Package-relative file paths the CLI derives and seals — authoring one is rejected. */
|
|
633
|
-
const CLI_DERIVED_FILES = [DESIGN_MANIFEST_FILE, "PACKAGE.md"];
|
|
634
801
|
/**
|
|
635
|
-
*
|
|
636
|
-
*
|
|
637
|
-
* `handoff` when none says otherwise — the kinds without a maturity ladder
|
|
638
|
-
* have no vote.
|
|
802
|
+
* Package-relative paths the CLI SEALS. Authoring one is refused on every route:
|
|
803
|
+
* a hand-written manifest or baseline is precisely the tree the listing rejects.
|
|
639
804
|
*/
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
return
|
|
805
|
+
const SEALED_PATHS = [DESIGN_MANIFEST_FILE, "baselines"];
|
|
806
|
+
/** What a SEALING publication also derives for itself: the projections it renders. */
|
|
807
|
+
const DERIVED_PATHS = [...SEALED_PATHS, ...PROJECTIONS];
|
|
808
|
+
/** Is this package-relative path one of `owned` — the entry itself, or under it? */
|
|
809
|
+
function owns(owned, relative) {
|
|
810
|
+
return owned.some((path) => relative === path || relative.startsWith(`${path}/`));
|
|
811
|
+
}
|
|
812
|
+
/**
|
|
813
|
+
* The maturity a catalog attains as a whole.
|
|
814
|
+
*
|
|
815
|
+
* `handoff` is a property of the WHOLE thing being published: a package is
|
|
816
|
+
* consumed as one dossier, so the weakest CURRENT document is what an
|
|
817
|
+
* implementer hits. The empty case is vacuously `handoff`, and that is only
|
|
818
|
+
* sound because every caller derives its claims from {@link currentEntries},
|
|
819
|
+
* which yields exactly one entry per catalogued id: no claims means the catalog
|
|
820
|
+
* has no flow and no screen — no ladder to climb — rather than a filter having
|
|
821
|
+
* eaten the ones it has.
|
|
822
|
+
*/
|
|
823
|
+
function ceilingOf(claims) {
|
|
824
|
+
const holding = claims.filter((c) => c.maturity !== "handoff");
|
|
825
|
+
if (holding.length === 0)
|
|
826
|
+
return { attained: "handoff", reasons: [] };
|
|
827
|
+
return {
|
|
828
|
+
attained: "outline",
|
|
829
|
+
reasons: holding.map((c) => `${c.subject} alcanza '${c.maturity}': una publicación vale lo que vale su artefacto más flojo`),
|
|
830
|
+
};
|
|
831
|
+
}
|
|
832
|
+
/**
|
|
833
|
+
* The maturity a package attains — ONE function, over the manifest that IS its
|
|
834
|
+
* catalog.
|
|
835
|
+
*
|
|
836
|
+
* The same question for the tree a publication will LEAVE (the candidate's
|
|
837
|
+
* manifest) and for the one already published (the entry's), so the receipt and
|
|
838
|
+
* the `validate` right after it cannot answer differently about the same tree.
|
|
839
|
+
* Which revision of each artifact answers is the content gate's own
|
|
840
|
+
* `currentEntries`, and reusing it is the point: reading `currentness` again
|
|
841
|
+
* here dropped every artifact it did not enumerate, and a manifest is allowed
|
|
842
|
+
* not to enumerate one.
|
|
843
|
+
*
|
|
844
|
+
* Reading the catalog rather than the files is not a shortcut: the manifest
|
|
845
|
+
* records the maturity each revision was sealed with — the publication gate
|
|
846
|
+
* refused it otherwise — and that IS the verdict in force.
|
|
847
|
+
*/
|
|
848
|
+
function catalogMaturity(manifest) {
|
|
849
|
+
const claims = [...currentEntries(manifest, "flows"), ...currentEntries(manifest, "screens")].map((entry) => ({
|
|
850
|
+
subject: `${entry.id}@r${entry.revision}`,
|
|
851
|
+
maturity: entry.maturity ?? "outline",
|
|
852
|
+
}));
|
|
853
|
+
return ceilingOf(claims);
|
|
854
|
+
}
|
|
855
|
+
/**
|
|
856
|
+
* The gate's verdict over a design that is ALREADY published.
|
|
857
|
+
*
|
|
858
|
+
* Two shapes, one question: a simple design is judged by its own document, a
|
|
859
|
+
* package by its catalog. This only runs once the content gate came back clean,
|
|
860
|
+
* which is what makes the catalog's recorded maturities trustworthy here.
|
|
861
|
+
*/
|
|
862
|
+
async function publishedMaturity(fs, workspace, entry) {
|
|
863
|
+
const manifest = entry.manifest;
|
|
864
|
+
if (manifest === null) {
|
|
865
|
+
return {
|
|
866
|
+
attained: "outline",
|
|
867
|
+
reasons: [`'${entry.manifest_path}' no valida: sin manifest no hay catálogo que juzgar`],
|
|
868
|
+
};
|
|
869
|
+
}
|
|
870
|
+
if (manifest.mode !== "simple")
|
|
871
|
+
return catalogMaturity(manifest);
|
|
872
|
+
const absolute = join(workspace, entry.path, SIMPLE_DESIGN_FILE);
|
|
873
|
+
if (!(await fs.exists(absolute))) {
|
|
874
|
+
return {
|
|
875
|
+
attained: "outline",
|
|
876
|
+
reasons: [`'${entry.path}/${SIMPLE_DESIGN_FILE}' no está: no hay documento que juzgar`],
|
|
877
|
+
};
|
|
878
|
+
}
|
|
879
|
+
const parsed = validateSimpleDesign(await fs.readText(absolute), SIMPLE_DESIGN_FILE);
|
|
880
|
+
if (!parsed.ok || parsed.value === null) {
|
|
881
|
+
return { attained: "outline", reasons: parsed.failures.map((f) => f.message) };
|
|
882
|
+
}
|
|
883
|
+
const verdict = simpleMaturity(parsed.value);
|
|
884
|
+
return { attained: verdict.attained, reasons: verdict.reasons };
|
|
646
885
|
}
|
|
647
886
|
/**
|
|
648
887
|
* The compare-and-swap base of a package proposal: the manifest as it stood
|
|
649
888
|
* when the candidate was computed. Null when there is nothing on disk to have
|
|
650
889
|
* moved — a create is protected by its destinations not existing.
|
|
651
890
|
*/
|
|
652
|
-
async function packageManifestBase(ctx, target) {
|
|
891
|
+
async function packageManifestBase(ctx, workspace, target) {
|
|
653
892
|
const path = `${target.path}/${DESIGN_MANIFEST_FILE}`;
|
|
654
|
-
const absolute = join(
|
|
893
|
+
const absolute = join(workspace, path);
|
|
655
894
|
if (!(await ctx.fs.exists(absolute)))
|
|
656
895
|
return null;
|
|
657
896
|
return { path, digest: baseDigest(await ctx.fs.readText(absolute)) };
|
|
@@ -765,18 +1004,6 @@ function simpleContract(target) {
|
|
|
765
1004
|
"Respondé un único objeto JSON con 'version', 'operation', 'input_digest', 'state': 'proposed' y 'artifacts': [{path, content}] con ese único archivo.",
|
|
766
1005
|
].join(" ");
|
|
767
1006
|
}
|
|
768
|
-
function contractFor(operation) {
|
|
769
|
-
const shared = "Respondé un único objeto JSON con 'version', 'operation', 'input_digest', 'state': 'proposed' " +
|
|
770
|
-
"y 'artifacts': [{path, content}]. Cada 'path' es relativo al workspace y cae dentro de los " +
|
|
771
|
-
"destinos permitidos. Ningún artefacto inventa un formato: los del UI Design Package v1 son los únicos aceptados.";
|
|
772
|
-
const perOperation = {
|
|
773
|
-
create: "Autorá la PRIMERA revisión del package a partir de las fuentes declaradas.",
|
|
774
|
-
update: "Autorá la revisión SIGUIENTE sobre la base declarada. No reescribas revisiones ya selladas.",
|
|
775
|
-
render: "Regenerá las proyecciones de la revisión indicada. Una proyección no es normativa y nunca se sella.",
|
|
776
|
-
record: "Sellá la decisión de gobierno sobre la revisión indicada, sin tocar el contenido del package.",
|
|
777
|
-
};
|
|
778
|
-
return `${perOperation[operation] ?? ""} ${shared}`.trim();
|
|
779
|
-
}
|
|
780
1007
|
/**
|
|
781
1008
|
* What a valid answer is on the SEALED package route: the normative artifacts,
|
|
782
1009
|
* and nothing the CLI derives.
|
|
@@ -801,6 +1028,28 @@ function packageContract(operation, target) {
|
|
|
801
1028
|
.join(" ")
|
|
802
1029
|
.trim();
|
|
803
1030
|
}
|
|
1031
|
+
/**
|
|
1032
|
+
* What a valid answer is on the route that mints NO revision.
|
|
1033
|
+
*
|
|
1034
|
+
* It states that first, and states it before anything else: an author who thinks
|
|
1035
|
+
* the answer will be sealed writes a revision, and a revision is exactly what
|
|
1036
|
+
* this route does not publish.
|
|
1037
|
+
*/
|
|
1038
|
+
function projectionContract(operation, target) {
|
|
1039
|
+
const perOperation = {
|
|
1040
|
+
render: "Regenerá las proyecciones de la revisión vigente. Una proyección no es normativa: sale del manifest y ningún baseline la sella.",
|
|
1041
|
+
record: "Escribí la decisión de gobierno sobre la revisión indicada, sin tocar el contenido normativo del package.",
|
|
1042
|
+
};
|
|
1043
|
+
return [
|
|
1044
|
+
perOperation[operation] ?? "",
|
|
1045
|
+
`Esta operación NO acuña una revisión: ${target.unsealed}.`,
|
|
1046
|
+
`Se escribe DENTRO de '${target.entry.path}', el package ${target.entry.manifest.id} que ya está indexado.`,
|
|
1047
|
+
`NO autores '${DESIGN_MANIFEST_FILE}' ni nada bajo 'baselines/': son lo que sella el package y solo los deriva una publicación de contenido normativo.`,
|
|
1048
|
+
"Respondé un único objeto JSON con 'version', 'operation', 'input_digest', 'state': 'proposed' y 'artifacts': [{path, content}]. Cada 'path' es relativo al workspace y cae dentro de ese package.",
|
|
1049
|
+
]
|
|
1050
|
+
.join(" ")
|
|
1051
|
+
.trim();
|
|
1052
|
+
}
|
|
804
1053
|
function inputValue(ctx, name) {
|
|
805
1054
|
return ctx.request.inputs.find((i) => i.name === name)?.value;
|
|
806
1055
|
}
|