@tacuchi/agent-workflow-cli 20.26.0 → 21.1.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.
Files changed (220) hide show
  1. package/README.md +2 -1
  2. package/dist/adapters/node-file-system.js +3 -0
  3. package/dist/adapters/node-file-system.js.map +1 -1
  4. package/dist/application/capability/compose.js +161 -0
  5. package/dist/application/capability/compose.js.map +1 -0
  6. package/dist/application/capability/descriptor-loader.js +109 -0
  7. package/dist/application/capability/descriptor-loader.js.map +1 -0
  8. package/dist/application/capability/design-handler.js +299 -0
  9. package/dist/application/capability/design-handler.js.map +1 -0
  10. package/dist/application/capability/dispatcher.js +312 -0
  11. package/dist/application/capability/dispatcher.js.map +1 -0
  12. package/dist/application/capability/durable-effect.js +220 -0
  13. package/dist/application/capability/durable-effect.js.map +1 -0
  14. package/dist/application/capability/installed-inventory.js +168 -0
  15. package/dist/application/capability/installed-inventory.js.map +1 -0
  16. package/dist/application/capability/readiness.js +204 -0
  17. package/dist/application/capability/readiness.js.map +1 -0
  18. package/dist/application/capability/resolution.js +264 -0
  19. package/dist/application/capability/resolution.js.map +1 -0
  20. package/dist/application/capability/wrapper.js +187 -0
  21. package/dist/application/capability/wrapper.js.map +1 -0
  22. package/dist/application/context/manifest.js +11 -5
  23. package/dist/application/context/manifest.js.map +1 -1
  24. package/dist/application/context/measure.js +5 -2
  25. package/dist/application/context/measure.js.map +1 -1
  26. package/dist/application/context/plan-service.js +6 -6
  27. package/dist/application/context/plan-service.js.map +1 -1
  28. package/dist/application/design/design-bundle-service.js +148 -0
  29. package/dist/application/design/design-bundle-service.js.map +1 -0
  30. package/dist/application/design/design-gate-service.js +246 -0
  31. package/dist/application/design/design-gate-service.js.map +1 -0
  32. package/dist/application/design/design-graph-service.js +85 -0
  33. package/dist/application/design/design-graph-service.js.map +1 -0
  34. package/dist/application/design/design-index-service.js +132 -0
  35. package/dist/application/design/design-index-service.js.map +1 -0
  36. package/dist/application/design/design-proposal-service.js +69 -0
  37. package/dist/application/design/design-proposal-service.js.map +1 -0
  38. package/dist/application/design/design-publish-service.js +756 -0
  39. package/dist/application/design/design-publish-service.js.map +1 -0
  40. package/dist/application/design/design-record-service.js +100 -0
  41. package/dist/application/design/design-record-service.js.map +1 -0
  42. package/dist/application/design/design-resolver-service.js +165 -0
  43. package/dist/application/design/design-resolver-service.js.map +1 -0
  44. package/dist/application/flow/advance.js +533 -0
  45. package/dist/application/flow/advance.js.map +1 -0
  46. package/dist/application/flow/flow-service.js +65 -0
  47. package/dist/application/flow/flow-service.js.map +1 -0
  48. package/dist/application/flow/run-projection.js +80 -0
  49. package/dist/application/flow/run-projection.js.map +1 -0
  50. package/dist/application/flow/run-state-service.js +107 -0
  51. package/dist/application/flow/run-state-service.js.map +1 -0
  52. package/dist/application/flow/submit.js +423 -0
  53. package/dist/application/flow/submit.js.map +1 -0
  54. package/dist/application/resume-service.js +46 -14
  55. package/dist/application/resume-service.js.map +1 -1
  56. package/dist/application/self/install-skill.js +11 -0
  57. package/dist/application/self/install-skill.js.map +1 -1
  58. package/dist/application/self/uninstall.js +21 -0
  59. package/dist/application/self/uninstall.js.map +1 -1
  60. package/dist/application/semantic-operation/protocol.js +18 -12
  61. package/dist/application/semantic-operation/protocol.js.map +1 -1
  62. package/dist/application/semantic-operation/publish.js +56 -9
  63. package/dist/application/semantic-operation/publish.js.map +1 -1
  64. package/dist/application/skills-resolver-service.js +27 -6
  65. package/dist/application/skills-resolver-service.js.map +1 -1
  66. package/dist/application/status-service.js +5 -1
  67. package/dist/application/status-service.js.map +1 -1
  68. package/dist/application/workline-index-service.js +6 -0
  69. package/dist/application/workline-index-service.js.map +1 -1
  70. package/dist/application/workspace-init-service.js +22 -2
  71. package/dist/application/workspace-init-service.js.map +1 -1
  72. package/dist/cli/commands/capability.js +182 -0
  73. package/dist/cli/commands/capability.js.map +1 -0
  74. package/dist/cli/commands/designs.js +110 -0
  75. package/dist/cli/commands/designs.js.map +1 -0
  76. package/dist/cli/commands/flow.js +71 -0
  77. package/dist/cli/commands/flow.js.map +1 -0
  78. package/dist/cli/commands/index.js +6 -0
  79. package/dist/cli/commands/index.js.map +1 -1
  80. package/dist/cli/commands/resume.js +8 -0
  81. package/dist/cli/commands/resume.js.map +1 -1
  82. package/dist/cli/commands/skills.js +55 -2
  83. package/dist/cli/commands/skills.js.map +1 -1
  84. package/dist/cli/commands/status.js +50 -0
  85. package/dist/cli/commands/status.js.map +1 -1
  86. package/dist/cli/help-groups.js +9 -0
  87. package/dist/cli/help-groups.js.map +1 -1
  88. package/dist/cli/parser.js +7 -0
  89. package/dist/cli/parser.js.map +1 -1
  90. package/dist/cli/tui/data/workflow-content.js +5 -0
  91. package/dist/cli/tui/data/workflow-content.js.map +1 -1
  92. package/dist/cli/tui/tabs/workflow-tab.js +5 -1
  93. package/dist/cli/tui/tabs/workflow-tab.js.map +1 -1
  94. package/dist/domain/capability/descriptor.js +553 -0
  95. package/dist/domain/capability/descriptor.js.map +1 -0
  96. package/dist/domain/capability/effects.js +90 -0
  97. package/dist/domain/capability/effects.js.map +1 -0
  98. package/dist/domain/capability/protocol.js +494 -0
  99. package/dist/domain/capability/protocol.js.map +1 -0
  100. package/dist/domain/contract-reader.js +84 -0
  101. package/dist/domain/contract-reader.js.map +1 -0
  102. package/dist/domain/design/adapter.js +161 -0
  103. package/dist/domain/design/adapter.js.map +1 -0
  104. package/dist/domain/design/artifact-body.js +223 -0
  105. package/dist/domain/design/artifact-body.js.map +1 -0
  106. package/dist/domain/design/artifact.js +750 -0
  107. package/dist/domain/design/artifact.js.map +1 -0
  108. package/dist/domain/design/baseline.js +359 -0
  109. package/dist/domain/design/baseline.js.map +1 -0
  110. package/dist/domain/design/capability.js +310 -0
  111. package/dist/domain/design/capability.js.map +1 -0
  112. package/dist/domain/design/closure.js +161 -0
  113. package/dist/domain/design/closure.js.map +1 -0
  114. package/dist/domain/design/direct.js +108 -0
  115. package/dist/domain/design/direct.js.map +1 -0
  116. package/dist/domain/design/external-send.js +65 -0
  117. package/dist/domain/design/external-send.js.map +1 -0
  118. package/dist/domain/design/governance.js +298 -0
  119. package/dist/domain/design/governance.js.map +1 -0
  120. package/dist/domain/design/identity.js +145 -0
  121. package/dist/domain/design/identity.js.map +1 -0
  122. package/dist/domain/design/manifest.js +552 -0
  123. package/dist/domain/design/manifest.js.map +1 -0
  124. package/dist/domain/design/maturity.js +305 -0
  125. package/dist/domain/design/maturity.js.map +1 -0
  126. package/dist/domain/design/naming.js +117 -0
  127. package/dist/domain/design/naming.js.map +1 -0
  128. package/dist/domain/design/offline.js +92 -0
  129. package/dist/domain/design/offline.js.map +1 -0
  130. package/dist/domain/design/profiles.js +212 -0
  131. package/dist/domain/design/profiles.js.map +1 -0
  132. package/dist/domain/design/projections.js +165 -0
  133. package/dist/domain/design/projections.js.map +1 -0
  134. package/dist/domain/design/proposal.js +234 -0
  135. package/dist/domain/design/proposal.js.map +1 -0
  136. package/dist/domain/design/reference.js +234 -0
  137. package/dist/domain/design/reference.js.map +1 -0
  138. package/dist/domain/design/render-bundle.js +0 -0
  139. package/dist/domain/design/render-bundle.js.map +1 -0
  140. package/dist/domain/design/rendition.js +472 -0
  141. package/dist/domain/design/rendition.js.map +1 -0
  142. package/dist/domain/design/retired.js +61 -0
  143. package/dist/domain/design/retired.js.map +1 -0
  144. package/dist/domain/design/revision.js +219 -0
  145. package/dist/domain/design/revision.js.map +1 -0
  146. package/dist/domain/design/secrets.js +86 -0
  147. package/dist/domain/design/secrets.js.map +1 -0
  148. package/dist/domain/design/sources.js +180 -0
  149. package/dist/domain/design/sources.js.map +1 -0
  150. package/dist/domain/design/validation.js +19 -0
  151. package/dist/domain/design/validation.js.map +1 -0
  152. package/dist/domain/design/visual-evidence.js +155 -0
  153. package/dist/domain/design/visual-evidence.js.map +1 -0
  154. package/dist/domain/design/yaml-subset.js +390 -0
  155. package/dist/domain/design/yaml-subset.js.map +1 -0
  156. package/dist/domain/flow/answer.js +459 -0
  157. package/dist/domain/flow/answer.js.map +1 -0
  158. package/dist/domain/flow/authority.js +2276 -0
  159. package/dist/domain/flow/authority.js.map +1 -0
  160. package/dist/domain/flow/authorization.js +62 -0
  161. package/dist/domain/flow/authorization.js.map +1 -0
  162. package/dist/domain/flow/directive.js +421 -0
  163. package/dist/domain/flow/directive.js.map +1 -0
  164. package/dist/domain/flow/rules.js +140 -0
  165. package/dist/domain/flow/rules.js.map +1 -0
  166. package/dist/domain/flow/run-state.js +317 -0
  167. package/dist/domain/flow/run-state.js.map +1 -0
  168. package/dist/domain/harnesses.js +21 -0
  169. package/dist/domain/harnesses.js.map +1 -1
  170. package/dist/domain/safe-path.js +23 -0
  171. package/dist/domain/safe-path.js.map +1 -0
  172. package/dist/domain/skills.js +26 -3
  173. package/dist/domain/skills.js.map +1 -1
  174. package/package.json +1 -1
  175. package/skills/w/SKILL.md +15 -13
  176. package/skills/w/artifacts/README.md +3 -4
  177. package/skills/w/artifacts/artifacts-core/CHECKPOINT.md +2 -2
  178. package/skills/w/commands/plan-exec.md +11 -5
  179. package/skills/w/commands/plan-new.md +6 -2
  180. package/skills/w/commands/plan-refine.md +6 -3
  181. package/skills/w/commands/quick.md +2 -2
  182. package/skills/w/commands/spec-new.md +2 -2
  183. package/skills/w/commands/spec-refine.md +1 -0
  184. package/skills/w/context/MANIFEST.json +57 -9
  185. package/skills/w/harness/HARNESS.md +20 -0
  186. package/skills/w/loops/CHASSIS.md +11 -20
  187. package/skills/w/loops/CODE-POLICIES.md +18 -9
  188. package/skills/w/loops/README.md +1 -1
  189. package/skills/w/loops/plan-exec-loop/LOOP.md +126 -62
  190. package/skills/w/loops/plan-new-loop/LOOP.md +71 -73
  191. package/skills/w/loops/plan-refine-loop/LOOP.md +20 -10
  192. package/skills/w/loops/quick-loop/LOOP.md +18 -26
  193. package/skills/w/loops/spec-refine-loop/LOOP.md +28 -30
  194. package/skills/w/modules/COMPACTION.md +2 -6
  195. package/skills/w/modules/DB-SCRIPTS-ONLY.md +2 -0
  196. package/skills/w/modules/DESIGN-REFERENCES.md +113 -0
  197. package/skills/w/modules/IDEATION-GATE.md +3 -3
  198. package/skills/w/modules/PERSIST-ROUTING.md +1 -0
  199. package/skills/w/modules/PLAN-EXECUTION-BATCHES.md +65 -0
  200. package/skills/w/modules/PLAN-INPUT.md +5 -3
  201. package/skills/w/modules/PLAN-REFINE-SPLIT.md +3 -1
  202. package/skills/w/modules/PLAN-SPLIT-GATE.md +7 -5
  203. package/skills/w/modules/PROMPT-CONTINUITY.md +3 -1
  204. package/skills/w/modules/SPEC-CHANGE-SHAPE.md +4 -4
  205. package/skills/w/roles/README.md +9 -5
  206. package/skills/w/roles/design/CONTRACT.md +80 -0
  207. package/skills/w/roles/design/ROLE.md +224 -0
  208. package/skills/w/schemas/capability-descriptor.schema.json +260 -0
  209. package/skills/w/schemas/design/design-baseline.v1.schema.json +71 -0
  210. package/skills/w/schemas/design/design-manifest.v1.schema.json +322 -0
  211. package/skills/w/schemas/design/design-render-bundle.v1.schema.json +191 -0
  212. package/skills/w/schemas/design/design-rendition.v1.schema.json +232 -0
  213. package/skills/w/schemas/design/design-review.v1.schema.json +72 -0
  214. package/skills/w/schemas/design/design-revocation.v1.schema.json +54 -0
  215. package/skills/w/schemas/design/ui-flow.v1.schema.json +211 -0
  216. package/skills/w/schemas/design/ui-screen.v1.schema.json +242 -0
  217. package/skills/w/artifacts/artifacts-design/SPEC.md +0 -42
  218. package/skills/w/modules/PLAN-DESIGN-SPECS.md +0 -12
  219. package/skills/w/modules/PLAN-REFINE-DESIGN-SPECS.md +0 -7
  220. package/skills/w/roles/ui-spec/ROLE.md +0 -147
@@ -0,0 +1,756 @@
1
+ import { createHash } from "node:crypto";
2
+ import { join } from "node:path";
3
+ import { validateDesignArtifact } from "../../domain/design/artifact.js";
4
+ import { DESIGN_BASELINE_SCHEMA_ID, checkBaselineAuthority, computeBaselineDigest, validateDesignBaseline, } from "../../domain/design/baseline.js";
5
+ import { ARTIFACT_PREFIX, parseArtifactId } from "../../domain/design/identity.js";
6
+ import { validateDesignManifest } from "../../domain/design/manifest.js";
7
+ import { gateDesignDocument } from "../../domain/design/maturity.js";
8
+ import { NAMING, baselinePath, checkNaming } from "../../domain/design/naming.js";
9
+ import { checkOfflineHtml } from "../../domain/design/offline.js";
10
+ import { checkProviderLocator } from "../../domain/design/profiles.js";
11
+ import { currentRevisions, renderDesignMd, renderPackageMd, } from "../../domain/design/projections.js";
12
+ import { checkDataAuthorization } from "../../domain/design/render-bundle.js";
13
+ import { validateDesignRendition } from "../../domain/design/rendition.js";
14
+ import { KIND_LIST } from "../../domain/design/revision.js";
15
+ import { crossVisualEvidence } from "../../domain/design/visual-evidence.js";
16
+ import { checkSafeRelativePath } from "../../domain/safe-path.js";
17
+ import { publishArtifacts } from "../semantic-operation/publish.js";
18
+ import { readDesignIndex } from "./design-index-service.js";
19
+ export async function publishDesignRevision(fs, workspace, input) {
20
+ const unsafe = [...input.files, ...(input.documents ?? [])].flatMap((f) => pathFailure(f.path));
21
+ if (unsafe.length > 0)
22
+ return { ok: false, failures: unsafe };
23
+ const index = await readDesignIndex(fs, workspace);
24
+ const located = locate(index, input.packageId);
25
+ if ("failures" in located)
26
+ return { ok: false, failures: located.failures };
27
+ const { manifest, packagePath } = located;
28
+ const current = manifest.current_baseline;
29
+ const actual = current === null ? null : `${manifest.id}@r${current.revision}`;
30
+ if (input.expectedBase !== actual) {
31
+ return {
32
+ ok: false,
33
+ failures: [
34
+ {
35
+ code: "DESIGN_BASE_STALE",
36
+ artifact: `${packagePath}/design-manifest.json`,
37
+ message: `preparaste sobre ${input.expectedBase ?? "ninguna revisión"} y la vigente es ${actual ?? "ninguna"}`,
38
+ action: "alguien publicó mientras preparabas esta: releé el package y rehacé la revisión sobre la base nueva",
39
+ },
40
+ ],
41
+ };
42
+ }
43
+ const catalog = mergeCatalog(manifest, input.files, input.dataAuthorization);
44
+ if ("failures" in catalog)
45
+ return { ok: false, failures: catalog.failures };
46
+ // Los archivos locales de una rendition se comprueban antes que las citas: una
47
+ // preview que no está, que no es la que su rendition declara o que se cuelga de
48
+ // una URL remota no puede respaldar nada, y decirlo así nombra la causa real.
49
+ const local = await checkLocalEvidence(fs, workspace, packagePath, catalog.value, input.files);
50
+ if (local.length > 0)
51
+ return { ok: false, failures: local };
52
+ // La evidencia visual se cruza acá, con el catálogo YA fusionado: una rendition
53
+ // publicada en esta misma revisión tiene que poder respaldar a la screen que la
54
+ // cita, y una cita que no se sostiene se rechaza ANTES de sellar nada.
55
+ const evidence = await crossPublishedEvidence(fs, workspace, packagePath, catalog.value, input.files);
56
+ if (evidence.length > 0)
57
+ return { ok: false, failures: evidence };
58
+ const revision = (manifest.current_baseline?.revision ?? 0) + 1;
59
+ const selection = await buildSelection(fs, workspace, packagePath, catalog.value, input.files);
60
+ if ("failures" in selection)
61
+ return { ok: false, failures: selection.failures };
62
+ const baseline = sealBaseline(manifest, revision, input.published, selection.value);
63
+ const nextManifest = nextManifestOf(manifest, catalog.value, baseline);
64
+ const failures = validateCandidate(nextManifest, baseline, packagePath);
65
+ if (failures.length > 0)
66
+ return { ok: false, failures };
67
+ const artifacts = candidateArtifacts(packagePath, nextManifest, baseline, input.files);
68
+ // The documents go LAST, after the manifest switched the package to the new
69
+ // revision: a reference is only ever visible pointing at a baseline that is
70
+ // already there. `publishArtifacts` rolls the whole batch back either way.
71
+ const published = await publishArtifacts(fs, workspace, [
72
+ ...artifacts,
73
+ ...(input.documents ?? []).map((d) => ({ ...d, overwrite: true })),
74
+ ]);
75
+ if (!published.ok)
76
+ return { ok: false, failures: [publishFailure(published.failure, packagePath)] };
77
+ return { ok: true, value: { revision, baseline, written: published.value.written } };
78
+ }
79
+ /**
80
+ * Every published path, checked at the ENTRANCE.
81
+ *
82
+ * Not inside the per-kind branches: an asset whose digest was already
83
+ * catalogued used to skip the catalog — and with it the only path check there
84
+ * was — while still being written, and `join(root, path)` normalizes `..`.
85
+ * A file escaped the workspace through the one branch that had no guard.
86
+ */
87
+ function pathFailure(path) {
88
+ const check = checkSafeRelativePath(path);
89
+ if (check.ok)
90
+ return [];
91
+ return [
92
+ {
93
+ code: "DESIGN_PATH_UNSAFE",
94
+ artifact: path,
95
+ message: `'${path}' ${check.why}`,
96
+ action: "una publicación escribe DENTRO de su package: usá una ruta relativa sin '..'",
97
+ },
98
+ ];
99
+ }
100
+ /** Last resort: a package with no manifest and no diagnosis of its own. */
101
+ function brokenManifest(packagePath) {
102
+ return {
103
+ code: "DESIGN_MANIFEST_MISSING",
104
+ artifact: `${packagePath}/design-manifest.json`,
105
+ message: "el package no tiene un manifest legible",
106
+ action: "reparalo antes de publicar sobre él",
107
+ };
108
+ }
109
+ /**
110
+ * A publication failure said in this domain's terms.
111
+ *
112
+ * The generic layer offers «confirmá la sobrescritura, o publicá en otro
113
+ * destino»: here both are ILLEGAL — overwriting a published baseline destroys
114
+ * an immutable revision, and a revision has no other destination. And the
115
+ * artifact named must be the file to fix, not the dossier it lives in.
116
+ */
117
+ function publishFailure(failure, packagePath) {
118
+ // `publishArtifacts` ya nombra la ruta relativa al workspace entre comillas.
119
+ const quoted = /'([^']+)'/.exec(failure.message);
120
+ const artifact = quoted?.[1] ?? packagePath;
121
+ if (failure.code !== "PUBLISH_TARGET_EXISTS") {
122
+ return { ...failure, artifact };
123
+ }
124
+ return {
125
+ code: "DESIGN_BASE_STALE",
126
+ artifact,
127
+ message: `'${artifact}' ya existe: alguien publicó esta revisión mientras preparabas`,
128
+ action: "releé el package y rehacé la revisión sobre la base nueva: una publicada no se reescribe",
129
+ };
130
+ }
131
+ function locate(index, packageId) {
132
+ const found = index.packages.filter((p) => p.id === packageId || p.declared_id === packageId);
133
+ if (found.length > 1) {
134
+ return {
135
+ failures: [
136
+ {
137
+ code: "DESIGN_REFERENCE_AMBIGUOUS",
138
+ artifact: index.root,
139
+ message: `${packageId} está declarado por ${found.length} packages`,
140
+ action: "dos packages no pueden reclamar la misma identidad: renombrá uno",
141
+ },
142
+ ],
143
+ };
144
+ }
145
+ const entry = found[0];
146
+ if (entry === undefined) {
147
+ return {
148
+ failures: [
149
+ {
150
+ code: "DESIGN_REFERENCE_MISSING",
151
+ artifact: index.root,
152
+ message: `no hay un package ${packageId} bajo ${index.root}/`,
153
+ action: "revisá 'aw designs': publicar exige un package existente",
154
+ },
155
+ ],
156
+ };
157
+ }
158
+ // Un package ROTO no es un package inexistente: son problemas muy distintos
159
+ // para quien tiene que arreglar uno, así que se devuelven sus fallos reales.
160
+ if (entry.manifest == null) {
161
+ return { failures: entry.failures.length > 0 ? entry.failures : [brokenManifest(entry.path)] };
162
+ }
163
+ return { manifest: entry.manifest, packagePath: entry.path };
164
+ }
165
+ /**
166
+ * The catalog this revision will have: the current one plus what it introduces.
167
+ *
168
+ * Two rules the first draft got backwards. A published document is VALIDATED
169
+ * before it is sealed — sealing bytes that do not satisfy their own contract
170
+ * makes the seal a lie. And for a flow or a screen the **frontmatter** is the
171
+ * authority for identity, revision and maturity (spec § *Canonical authority*):
172
+ * the file name must AGREE with it, never define it.
173
+ */
174
+ function mergeCatalog(manifest, files, authorization) {
175
+ const catalog = structuredClone(manifest.catalog);
176
+ const failures = [];
177
+ for (const file of files) {
178
+ if (file.path.startsWith("assets/")) {
179
+ failures.push(...addAsset(catalog, file));
180
+ continue;
181
+ }
182
+ // Una preview NO se cataloga: la autoridad sobre los archivos de una
183
+ // rendition es su propio documento, que los declara con su digest. Darle una
184
+ // entrada de catálogo le daría una identidad y una revisión que no tiene, y
185
+ // ningún baseline puede sellar un path que el catálogo no nombre.
186
+ if (isRenditionCompanion(file.path))
187
+ continue;
188
+ failures.push(...addArtifact(catalog, manifest.id, file, authorization));
189
+ }
190
+ return failures.length > 0 ? { failures } : { value: catalog };
191
+ }
192
+ /** A file inside a rendition's folder that is not the rendition document itself. */
193
+ function isRenditionCompanion(path) {
194
+ return path.startsWith(`${NAMING.rendition.dir}/`) && !path.endsWith(NAMING.rendition.suffix);
195
+ }
196
+ /** The directory a rendition's own files live in: `renditions/VIS-001-r001-x/`. */
197
+ function dirOf(renditionPath) {
198
+ return renditionPath.slice(0, renditionPath.length - "rendition.json".length);
199
+ }
200
+ /** One flow, screen, rule, token or rendition into the catalog it belongs to. */
201
+ function addArtifact(catalog, packageId, file, authorization) {
202
+ const described = describeFile(file, packageId, authorization);
203
+ if ("failures" in described)
204
+ return described.failures;
205
+ const { kind, id, revision, maturity, states, supersedes } = described.value;
206
+ const list = catalog[KIND_LIST[kind]];
207
+ if (list.some((e) => e.id === id && e.revision === revision)) {
208
+ return [
209
+ {
210
+ code: "DESIGN_ID_DUPLICATE",
211
+ artifact: file.path,
212
+ message: `${id}@r${revision} ya está publicada`,
213
+ action: "una revisión publicada no se reescribe: numerá la siguiente",
214
+ },
215
+ ];
216
+ }
217
+ const previous = list.filter((e) => e.id === id).reduce((max, e) => Math.max(max, e.revision), 0);
218
+ list.push({
219
+ id,
220
+ revision,
221
+ path: file.path,
222
+ // El documento declara a quién supersede y F2 ya lo validó; acuñarlo desde
223
+ // el catálogo publicaría un manifest que contradice lo que sella.
224
+ supersedes: supersedes === undefined
225
+ ? previous === 0
226
+ ? null
227
+ : `${packageId}/${id}@r${previous}`
228
+ : supersedes,
229
+ ...(maturity === undefined ? {} : { maturity }),
230
+ ...(states === undefined ? {} : { states }),
231
+ });
232
+ return [];
233
+ }
234
+ /** An asset is content-addressed: its name has to be the digest of its bytes. */
235
+ function addAsset(catalog, file) {
236
+ const digest = digestOf(new TextEncoder().encode(file.content));
237
+ const expected = `assets/${digest.slice("sha256:".length)}-`;
238
+ if (!file.path.startsWith(expected)) {
239
+ return [
240
+ {
241
+ code: "DESIGN_FIELD_INVALID",
242
+ artifact: file.path,
243
+ message: "el nombre del asset no es el digest de su contenido",
244
+ action: `renombralo a '${expected}<nombre>.<ext>'`,
245
+ },
246
+ ];
247
+ }
248
+ const twin = catalog.assets.find((a) => a.digest === digest);
249
+ if (twin !== undefined) {
250
+ // Escribirlo sin catalogarlo dejaría bytes dentro del package que ningún
251
+ // baseline sella: un asset es su contenido, y ya tiene un nombre.
252
+ return [
253
+ {
254
+ code: "DESIGN_ID_DUPLICATE",
255
+ artifact: file.path,
256
+ message: `ese contenido ya está publicado como '${twin.path}'`,
257
+ action: "un asset se direcciona por su contenido: referenciá el que ya existe",
258
+ },
259
+ ];
260
+ }
261
+ catalog.assets.push({ digest, path: file.path });
262
+ return [];
263
+ }
264
+ /**
265
+ * What a published file IS. For a flow or a screen the frontmatter decides and
266
+ * the path must match it; for the kinds F2 gave no frontmatter contract, the
267
+ * name is the only declaration there is — and that is stated, not hidden.
268
+ */
269
+ function describeFile(file, packageId, authorization) {
270
+ const kind = kindOfPath(file.path);
271
+ if (kind === null) {
272
+ return {
273
+ failures: [
274
+ {
275
+ code: "DESIGN_FIELD_INVALID",
276
+ artifact: file.path,
277
+ message: `'${file.path}' no está en ninguna carpeta de artefactos normativos`,
278
+ action: `publicá bajo: ${[...Object.values(NAMING).map((r) => r.dir), "assets"].join(", ")}`,
279
+ },
280
+ ],
281
+ };
282
+ }
283
+ if (kind === "flow" || kind === "screen")
284
+ return describeDocument(file, packageId, kind);
285
+ if (kind === "rendition")
286
+ return describeRendition(file, packageId, authorization);
287
+ const name = file.path.slice(NAMING[kind].dir.length + 1);
288
+ const parsed = /^((?:RUL|TOK)-[0-9]+)-r([0-9]{3,})-/.exec(name);
289
+ if (parsed === null) {
290
+ return {
291
+ failures: [
292
+ {
293
+ code: "DESIGN_FIELD_INVALID",
294
+ artifact: file.path,
295
+ message: `'${file.path}' no lleva su identidad y revisión en el nombre`,
296
+ action: `usá '${NAMING[kind].dir}/${ARTIFACT_PREFIX[kind]}-NNN-rNNN-<slug>${NAMING[kind].suffix}'`,
297
+ },
298
+ ],
299
+ };
300
+ }
301
+ return { value: { kind, id: parsed[1], revision: Number(parsed[2]) } };
302
+ }
303
+ /**
304
+ * A rendition: validated, and its identity read from its own JSON.
305
+ *
306
+ * Until F4 gave it a content contract, the file NAME was the only declaration a
307
+ * rendition had — so a picture could be filed as `VIS-003@r1` while claiming
308
+ * something else inside, and nothing looked. Now the document is the authority
309
+ * here too, exactly as it is for a flow or a screen, and the name must agree.
310
+ */
311
+ function describeRendition(file, packageId, authorization) {
312
+ const parsed = parseJson(file.content);
313
+ if (parsed === null) {
314
+ return {
315
+ failures: [
316
+ {
317
+ code: "DESIGN_RENDITION_UNREADABLE",
318
+ artifact: file.path,
319
+ message: "la rendition no es un JSON legible",
320
+ action: "reparala como un único objeto JSON válido",
321
+ },
322
+ ],
323
+ };
324
+ }
325
+ const validation = validateDesignRendition(parsed, file.path);
326
+ if (!validation.ok || validation.value === null)
327
+ return { failures: validation.failures };
328
+ const rendition = validation.value;
329
+ const identity = parseArtifactId(rendition.id);
330
+ if (identity === null || identity.package !== packageId) {
331
+ return {
332
+ failures: [
333
+ {
334
+ code: "DESIGN_AUTHORITY_CONFLICT",
335
+ artifact: file.path,
336
+ message: `la rendition declara '${rendition.id}' y el package es ${packageId}`,
337
+ action: "una rendition se publica dentro del package que declara su id",
338
+ },
339
+ ],
340
+ };
341
+ }
342
+ const naming = checkNaming("rendition", file.path, identity.artifact, rendition.revision);
343
+ if (!naming.ok) {
344
+ return {
345
+ failures: [
346
+ {
347
+ code: "DESIGN_AUTHORITY_CONFLICT",
348
+ artifact: file.path,
349
+ message: `la rendition declara ${identity.artifact}@r${rendition.revision} y ${naming.why}`,
350
+ action: `el documento manda: renombralo a '${naming.expected}'`,
351
+ },
352
+ ],
353
+ };
354
+ }
355
+ // Lo que ya no se puede deshacer una vez publicado: un dato personal queda en el
356
+ // historial del repo para siempre, y un locator incompleto deja una evidencia que
357
+ // apunta a nada.
358
+ const policy = [
359
+ ...checkDataAuthorization(rendition.data_classification, authorization, file.path),
360
+ ...checkProviderLocator(rendition.provider, file.path),
361
+ ];
362
+ if (policy.length > 0)
363
+ return { failures: policy };
364
+ return {
365
+ value: {
366
+ kind: "rendition",
367
+ id: identity.artifact,
368
+ revision: rendition.revision,
369
+ supersedes: rendition.supersedes,
370
+ },
371
+ };
372
+ }
373
+ /** A flow or a screen: validated, and its identity read from the frontmatter. */
374
+ function describeDocument(file, packageId, kind) {
375
+ const document = validateDesignArtifact(file.content, kind, file.path);
376
+ if (!document.ok || document.value === null)
377
+ return { failures: document.failures };
378
+ const parsed = parseArtifactId(document.value.id);
379
+ if (parsed === null || parsed.package !== packageId) {
380
+ return {
381
+ failures: [
382
+ {
383
+ code: "DESIGN_AUTHORITY_CONFLICT",
384
+ artifact: file.path,
385
+ message: `el frontmatter declara '${document.value.id}' y el package es ${packageId}`,
386
+ action: "un artefacto se publica dentro del package que declara su id",
387
+ },
388
+ ],
389
+ };
390
+ }
391
+ const naming = checkNaming(kind, file.path, parsed.artifact, document.value.revision);
392
+ if (!naming.ok) {
393
+ return {
394
+ failures: [
395
+ {
396
+ code: "DESIGN_AUTHORITY_CONFLICT",
397
+ artifact: file.path,
398
+ message: `el frontmatter declara ${parsed.artifact}@r${document.value.revision} y ${naming.why}`,
399
+ action: `el frontmatter manda: renombralo a '${naming.expected}'`,
400
+ },
401
+ ],
402
+ };
403
+ }
404
+ // La madurez que el documento RECLAMA se comprueba antes de sellarla —después
405
+ // de la identidad, porque un artefacto en el package equivocado se diagnostica
406
+ // por eso y no por su completitud.
407
+ const gate = gateDesignDocument(file.content, kind, file.path);
408
+ if (gate.failures.length > 0)
409
+ return { failures: gate.failures };
410
+ return {
411
+ value: {
412
+ kind,
413
+ id: parsed.artifact,
414
+ revision: document.value.revision,
415
+ maturity: document.value.maturity,
416
+ supersedes: document.value.supersedes,
417
+ ...(document.value.kind === "screen"
418
+ ? { states: document.value.states.map((s) => s.anchor) }
419
+ : {}),
420
+ },
421
+ };
422
+ }
423
+ /**
424
+ * The elevated `handoff` gate, at the only place that can run it.
425
+ *
426
+ * A screen's classification matrix cites renditions, and whether those citations
427
+ * hold is a question about OTHER files — so it cannot be answered by the document
428
+ * validator. It is answered here, where the merged catalog says which renditions
429
+ * exist and the incoming batch plus the disk say what they contain.
430
+ *
431
+ * Only the screens this revision publishes are crossed. The ones already sealed
432
+ * were judged by the gate in force when they were published, and re-judging them
433
+ * would rewrite history; the elevation reaches them the next time somebody
434
+ * publishes a revision of them, which is exactly when it can be acted on.
435
+ */
436
+ async function crossPublishedEvidence(fs, workspace, packagePath, catalog, files) {
437
+ const screens = files.filter((f) => kindOfPath(f.path) === "screen");
438
+ if (screens.length === 0)
439
+ return [];
440
+ const renditions = await readRenditions(fs, workspace, packagePath, catalog.renditions, files);
441
+ const failures = [];
442
+ for (const file of screens) {
443
+ const parsed = validateDesignArtifact(file.content, "screen", file.path);
444
+ // Un documento que no valida ya se reportó en `mergeCatalog`: acá no se
445
+ // vuelve a decir, porque su clasificación tampoco se pudo leer.
446
+ if (!parsed.ok || parsed.value === null)
447
+ continue;
448
+ failures.push(...crossVisualEvidence(catalog.renditions, parsed.value, file.path, (path) => renditions.get(path) ?? null));
449
+ }
450
+ return failures;
451
+ }
452
+ /**
453
+ * The seal chain of a local preview, and its offline guarantee.
454
+ *
455
+ * A preview file is not catalogued and no baseline selects it — it cannot be, since
456
+ * a baseline only seals catalogued paths and a preview has no identity of its own.
457
+ * What seals it is its rendition: `files[].sha256` names the exact bytes, and the
458
+ * rendition document IS sealed. So the chain holds only if somebody checks that
459
+ * link at publication time, which is what this does.
460
+ *
461
+ * Three things, all about the revision being published — the already-sealed ones
462
+ * were judged by the gate in force then, same doctrine as the evidence cross:
463
+ *
464
+ * 1. every file a published rendition declares exists, with the declared digest;
465
+ * 2. a companion file belongs to a rendition the catalog knows and that
466
+ * rendition declares it — otherwise it is a stray file inside the package;
467
+ * 3. an `.html` export is self-sufficient (AC-REN-07).
468
+ */
469
+ async function checkLocalEvidence(fs, workspace, packagePath, catalog, files) {
470
+ const incoming = new Map(files.map((f) => [f.path, f.content]));
471
+ const failures = [];
472
+ const declared = new Set();
473
+ for (const file of files) {
474
+ if (!file.path.endsWith(NAMING.rendition.suffix))
475
+ continue;
476
+ const validation = validateDesignRendition(parseJson(file.content), file.path);
477
+ // Una rendition que no valida ya se reportó en `mergeCatalog`; sus archivos no
478
+ // se pueden juzgar porque no se pudo leer qué archivos declara.
479
+ if (!validation.ok || validation.value === null)
480
+ continue;
481
+ const dir = dirOf(file.path);
482
+ for (const entry of validation.value.files) {
483
+ const path = `${dir}${entry.path}`;
484
+ declared.add(path);
485
+ const bytes = await bytesOf(fs, workspace, packagePath, path, incoming);
486
+ failures.push(...sealFailures(file.path, entry, path, bytes));
487
+ }
488
+ }
489
+ for (const file of files) {
490
+ if (!isRenditionCompanion(file.path))
491
+ continue;
492
+ failures.push(...companionFailures(file, catalog, declared));
493
+ if (file.path.endsWith(".html"))
494
+ failures.push(...checkOfflineHtml(file.content, file.path));
495
+ }
496
+ return failures;
497
+ }
498
+ /** One declared file against the bytes that are really there. */
499
+ function sealFailures(document, entry, path, bytes) {
500
+ if (bytes === null) {
501
+ return [
502
+ {
503
+ code: "DESIGN_EVIDENCE_INSUFFICIENT",
504
+ artifact: document,
505
+ message: `declara '${entry.path}' y el archivo no está en el package`,
506
+ action: `publicá '${path}' junto con la rendition: la evidencia local es el archivo, no la promesa`,
507
+ },
508
+ ];
509
+ }
510
+ const actual = digestOf(bytes);
511
+ if (actual === entry.sha256)
512
+ return [];
513
+ return [
514
+ {
515
+ code: "DESIGN_DIGEST_MISMATCH",
516
+ artifact: path,
517
+ message: `'${entry.path}' no son los bytes que la rendition declara (declara ${entry.sha256}, calcula ${actual})`,
518
+ action: "recalculá el digest sobre el archivo publicado: el documento de la rendition es lo único que sella su preview",
519
+ },
520
+ ];
521
+ }
522
+ /**
523
+ * A companion file has to belong to a rendition, and be one it declares.
524
+ *
525
+ * Declaration is checked FIRST because it is the stronger fact: a storyboard may
526
+ * keep `frames/step-1.svg` in a subfolder of its own directory, and asking "which
527
+ * rendition sits in this file's immediate directory" would call that an orphan. The
528
+ * owner lookup exists only to produce the better of the two diagnostics.
529
+ */
530
+ function companionFailures(file, catalog, declared) {
531
+ if (declared.has(file.path))
532
+ return [];
533
+ const owner = catalog.renditions.find((e) => file.path.startsWith(dirOf(e.path)));
534
+ if (owner === undefined) {
535
+ const dir = file.path.slice(0, file.path.lastIndexOf("/") + 1);
536
+ return [
537
+ {
538
+ code: "DESIGN_REFERENCE_MISSING",
539
+ artifact: file.path,
540
+ message: `'${file.path}' no pertenece a ninguna rendition del package`,
541
+ action: `publicá '${dir}rendition.json', o movelo: dentro de 'renditions/' solo viven los archivos de una rendition`,
542
+ },
543
+ ];
544
+ }
545
+ return [
546
+ {
547
+ code: "DESIGN_EVIDENCE_INSUFFICIENT",
548
+ artifact: file.path,
549
+ message: `'${file.path}' se publica y ${owner.id}@r${owner.revision} no lo declara en 'files'`,
550
+ action: "declaralo en 'files' con su digest, o no lo publiques: un archivo que ninguna rendition sella es evidencia que nadie puede verificar",
551
+ },
552
+ ];
553
+ }
554
+ /**
555
+ * Every catalogued rendition, pre-read: the cross walks a graph synchronously and
556
+ * cannot await mid-traversal. An unreadable or invalid one is simply absent — the
557
+ * citation then reports as dangling, which is what it is.
558
+ */
559
+ async function readRenditions(fs, workspace, packagePath, entries, files) {
560
+ const incoming = new Map(files.map((f) => [f.path, f.content]));
561
+ const out = new Map();
562
+ for (const entry of entries) {
563
+ const inline = incoming.get(entry.path);
564
+ let text = inline;
565
+ if (text === undefined) {
566
+ const absolute = join(workspace, packagePath, entry.path);
567
+ if (!(await fs.exists(absolute)))
568
+ continue;
569
+ text = await fs.readText(absolute);
570
+ }
571
+ const parsed = parseJson(text);
572
+ if (parsed === null)
573
+ continue;
574
+ const validation = validateDesignRendition(parsed, entry.path);
575
+ if (validation.ok && validation.value !== null)
576
+ out.set(entry.path, validation.value);
577
+ }
578
+ return out;
579
+ }
580
+ function parseJson(text) {
581
+ try {
582
+ return JSON.parse(text);
583
+ }
584
+ catch {
585
+ return null;
586
+ }
587
+ }
588
+ /** Which artifact kind lives in the directory a path sits in. */
589
+ function kindOfPath(path) {
590
+ const kinds = Object.entries(NAMING);
591
+ const match = kinds
592
+ .sort(([, a], [, b]) => b.dir.length - a.dir.length)
593
+ .find(([, rule]) => path.startsWith(`${rule.dir}/`));
594
+ return match === undefined ? null : match[0];
595
+ }
596
+ /**
597
+ * What the revision seals: the CURRENT revision of every artifact plus every
598
+ * asset. A baseline says what the package IS at this revision, not what changed.
599
+ */
600
+ async function buildSelection(fs, workspace, packagePath, catalog, files) {
601
+ const incoming = new Map(files.map((f) => [f.path, f.content]));
602
+ const entries = [];
603
+ const failures = [];
604
+ for (const key of ["flows", "screens", "rules", "tokens", "renditions"]) {
605
+ for (const entry of currentRevisions(catalog[key])) {
606
+ const bytes = await bytesOf(fs, workspace, packagePath, entry.path, incoming);
607
+ if (bytes === null) {
608
+ failures.push(missingFile(entry.path, packagePath));
609
+ continue;
610
+ }
611
+ entries.push({ path: entry.path, revision: entry.revision, sha256: digestOf(bytes) });
612
+ }
613
+ }
614
+ for (const asset of catalog.assets) {
615
+ const bytes = await bytesOf(fs, workspace, packagePath, asset.path, incoming);
616
+ if (bytes === null) {
617
+ failures.push(missingFile(asset.path, packagePath));
618
+ continue;
619
+ }
620
+ entries.push({ path: asset.path, revision: null, sha256: digestOf(bytes) });
621
+ }
622
+ return failures.length > 0 ? { failures } : { value: entries };
623
+ }
624
+ /** Bytes of a file this revision publishes, or of one already on disk. */
625
+ async function bytesOf(fs, workspace, packagePath, path, incoming) {
626
+ const content = incoming.get(path);
627
+ if (content !== undefined)
628
+ return new TextEncoder().encode(content);
629
+ const absolute = join(workspace, packagePath, path);
630
+ if (!(await fs.exists(absolute)))
631
+ return null;
632
+ return fs.readBytes(absolute);
633
+ }
634
+ function missingFile(path, packagePath) {
635
+ return {
636
+ code: "DESIGN_REFERENCE_FILE_MISSING",
637
+ artifact: `${packagePath}/${path}`,
638
+ message: `el catálogo declara '${path}' y el archivo no está`,
639
+ action: "restauralo o quitalo del catálogo: un baseline sella bytes que existen",
640
+ };
641
+ }
642
+ function digestOf(bytes) {
643
+ return `sha256:${createHash("sha256").update(bytes).digest("hex")}`;
644
+ }
645
+ function sealBaseline(manifest, revision, published, selection) {
646
+ const current = manifest.current_baseline;
647
+ const sealed = {
648
+ // Del registro, no a mano: este es el ÚNICO sitio del sistema que ESCRIBE
649
+ // un id canónico, y un literal aquí puede discrepar del que valida. Cuando
650
+ // eso pasa el floor emite output que su propio validador rechaza y ninguna
651
+ // revisión se publica — un formato paralelo de un solo carácter.
652
+ schema: DESIGN_BASELINE_SCHEMA_ID,
653
+ package: manifest.id,
654
+ revision,
655
+ parent_baseline: current === null ? null : `${manifest.id}@r${current.revision}`,
656
+ published,
657
+ selection,
658
+ };
659
+ return { ...sealed, digest: computeBaselineDigest(sealed) };
660
+ }
661
+ function nextManifestOf(manifest, catalog, baseline) {
662
+ const path = baselinePath(manifest.id, baseline.revision);
663
+ return {
664
+ ...manifest,
665
+ catalog,
666
+ currentness: deriveCurrentness(manifest.id, catalog),
667
+ baselines: [
668
+ ...manifest.baselines,
669
+ {
670
+ revision: baseline.revision,
671
+ path,
672
+ digest: baseline.digest,
673
+ parent_baseline: baseline.parent_baseline,
674
+ published: baseline.published,
675
+ },
676
+ ],
677
+ current_baseline: { revision: baseline.revision, path, digest: baseline.digest },
678
+ };
679
+ }
680
+ /**
681
+ * Currentness is DERIVED, never authored: the highest revision of each artifact
682
+ * is current and the rest are superseded. Minting `supersedes` and leaving the
683
+ * index empty would publish a manifest that contradicts itself.
684
+ */
685
+ function deriveCurrentness(packageId, catalog) {
686
+ const out = [];
687
+ for (const key of ["flows", "screens", "rules", "tokens", "renditions"]) {
688
+ const highest = new Map();
689
+ for (const entry of catalog[key]) {
690
+ highest.set(entry.id, Math.max(highest.get(entry.id) ?? 0, entry.revision));
691
+ }
692
+ for (const entry of catalog[key]) {
693
+ out.push({
694
+ ref: `${packageId}/${entry.id}@r${entry.revision}`,
695
+ state: entry.revision === highest.get(entry.id) ? "current" : "superseded",
696
+ });
697
+ }
698
+ }
699
+ return out;
700
+ }
701
+ /**
702
+ * Everything, before anything is written. The compare-and-swap runs against the
703
+ * manifest as it was READ, which is what makes it a swap: if the line moved
704
+ * since, the candidate's parent no longer matches and the publication stops.
705
+ */
706
+ function validateCandidate(after, baseline, packagePath) {
707
+ // Sin compare-and-swap acá: el candidato DERIVA su parent y su revisión del
708
+ // mismo manifest contra el que se compararía, así que la guarda no podía
709
+ // fallar nunca. El swap real es `expectedBase`, que el que llama declara.
710
+ const manifestPath = `${packagePath}/design-manifest.json`;
711
+ const baselineDoc = `${packagePath}/${baselinePath(after.id, baseline.revision)}`;
712
+ const manifestCheck = validateDesignManifest(JSON.parse(JSON.stringify(after)), manifestPath);
713
+ if (!manifestCheck.ok)
714
+ return manifestCheck.failures;
715
+ const baselineCheck = validateDesignBaseline(JSON.parse(JSON.stringify(baseline)), baselineDoc);
716
+ if (!baselineCheck.ok)
717
+ return baselineCheck.failures;
718
+ return checkBaselineAuthority(manifestCheck.value, baseline, baselineDoc);
719
+ }
720
+ /**
721
+ * The batch. The new revision's files and its baseline are written EXCLUSIVELY —
722
+ * a concurrent publication must lose the race, not overwrite an immutable
723
+ * revision. The index and the projections are meant to be rewritten.
724
+ */
725
+ function candidateArtifacts(packagePath, manifest, baseline, files) {
726
+ const at = (path) => `${packagePath}/${path}`;
727
+ return [
728
+ ...files.map((f) => ({ path: at(f.path), content: f.content, overwrite: false })),
729
+ {
730
+ path: at(baselinePath(manifest.id, baseline.revision)),
731
+ content: `${JSON.stringify(baseline, null, 2)}\n`,
732
+ overwrite: false,
733
+ },
734
+ { path: at("PACKAGE.md"), content: renderPackageMd(manifest), overwrite: true },
735
+ // `design-system/` es CONDICIONAL: crear la carpeta en un package sin reglas
736
+ // ni tokens inventaría estructura que la spec no pide.
737
+ ...(manifest.catalog.rules.length + manifest.catalog.tokens.length > 0
738
+ ? [
739
+ {
740
+ path: at("design-system/DESIGN.md"),
741
+ content: renderDesignMd(manifest),
742
+ overwrite: true,
743
+ },
744
+ ]
745
+ : []),
746
+ // ÚLTIMO, a propósito: el manifest es el interruptor que conmuta el package
747
+ // a la revisión nueva. Escribirlo antes que las proyecciones deja una
748
+ // ventana en la que un rollback fallido publica un baseline colgante.
749
+ {
750
+ path: at("design-manifest.json"),
751
+ content: `${JSON.stringify(manifest, null, 2)}\n`,
752
+ overwrite: true,
753
+ },
754
+ ];
755
+ }
756
+ //# sourceMappingURL=design-publish-service.js.map