forma-arch 0.10.1 → 0.11.1
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/README.md +50 -1
- package/lib/check.mjs +8 -1
- package/lib/docmap.mjs +43 -4
- package/lib/gen.mjs +13 -6
- package/lib/schema/c4-model.schema.json +314 -1
- package/lib/validate.mjs +219 -7
- package/lib/verify.mjs +10 -3
- package/lib/viewer/c4-hologram.html +205 -46
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -77,6 +77,53 @@ Column roles are detected by header name (`capability`/`feature`/`description`
|
|
|
77
77
|
|
|
78
78
|
The overlay is the authority: every field it sets wins over anything derived, and `check` stops re-deriving that field. You do not have to hand-edit that JSON — `forma gen --status-apply <file>` merges `{"nodes":{"<id>":{…}}}` into it, validating every patch **before** it touches disk, so a rejected fill leaves the committed overlay untouched. It is the state counterpart of `--enrich-apply`, for the fields no document can supply. To describe one box by hand without a table, put the sentence in `descriptions`, keyed `"<containerId>/<node-name-without-extension>"` — `"core/alpha"` for the leaf `alpha.js` in container `core`, `"core/core"` for the container itself, since a container is its own container. It outranks every other source.
|
|
79
79
|
|
|
80
|
+
**Future architecture can be a governed timeline.** Add an optional `timeline` to the curated
|
|
81
|
+
`c4-topology.json` when one binary target is not enough. The generated `nodes` and `edges` remain
|
|
82
|
+
the code-verified `AS-IS`; each checkpoint applies a compact typed patch over every checkpoint
|
|
83
|
+
before it. Forma never stores a second complete graph, never reads a board count as architecture,
|
|
84
|
+
and validates every cumulative state before replacing the last model.
|
|
85
|
+
|
|
86
|
+
```json
|
|
87
|
+
{
|
|
88
|
+
"timeline": {
|
|
89
|
+
"source": "docs/architecture/FUTURE.md",
|
|
90
|
+
"checkpoints": [{
|
|
91
|
+
"id": "g1", "label": "G1 · UAT", "badge": "9 board · 1 P0",
|
|
92
|
+
"patch": {
|
|
93
|
+
"nodes": {
|
|
94
|
+
"add": [{ "node": {
|
|
95
|
+
"id": "mcp", "level": "container", "parent": "system",
|
|
96
|
+
"kind": "container", "name": "MCP channel", "status": "planned"
|
|
97
|
+
}, "change": "Add the MCP surface." }],
|
|
98
|
+
"update": [{ "id": "engine", "set": {
|
|
99
|
+
"current": "Serves HTTP and MCP through one core."
|
|
100
|
+
}, "change": "Extend the existing engine." }]
|
|
101
|
+
},
|
|
102
|
+
"edges": {
|
|
103
|
+
"add": [{ "edge": {
|
|
104
|
+
"from": "mcp", "to": "engine", "label": "delegates",
|
|
105
|
+
"estatus": "to-build"
|
|
106
|
+
}, "change": "Connect the new surface to the engine." }]
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}]
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Node patches support `add`, non-structural `update`, and child-first `remove`; edge patches support
|
|
115
|
+
`add`, exact-match `rewire`, and `remove`. Every operation carries governed change prose; a live
|
|
116
|
+
node changed locally exposes it as “Change from previous”, while the graph accents changed nodes
|
|
117
|
+
and relations without keeping a second change log or drawing ghosts for removals. IDs, parents,
|
|
118
|
+
levels and kinds cannot be edited in place, ambiguous edge selectors fail, and a node cannot
|
|
119
|
+
disappear while children or incident relations survive. The source path must exist. `AS-IS` is
|
|
120
|
+
implicit and reserved; the last checkpoint is the target, so `target` is forbidden inside timeline
|
|
121
|
+
patches. With a timeline the viewer replaces CURRENT/TARGET with the checkpoints, supports
|
|
122
|
+
`?checkpoint=g1`, accents only the local patch, and keeps drill, layout, re-verification and export
|
|
123
|
+
working on the cumulative graph. A future checkpoint with no local patch says “no architecture
|
|
124
|
+
changes”; its display-only badge is not interpreted. Without a timeline, the legacy controls and
|
|
125
|
+
model are unchanged.
|
|
126
|
+
|
|
80
127
|
**Curated state, verified against reality.** `forma verify` asks your `gh` CLI for the state of every issue the model references (`--gh-repo owner/repo`, or `meta.ghRepo` in the topology), marks the nodes whose issues are closed as done, and prefixes their `current` with dated evidence. It touches state, never structure, and re-running it never stacks the evidence. It is opt-in and separate on purpose: `gen` and `check` never open a socket. In the served viewer, **RE-VERIFY** re-reads the model without losing your level, layout or mode.
|
|
81
128
|
|
|
82
129
|
**One source of truth.** `forma doc --attach docs/architecture/arc42.md` injects the generated diagrams/tables between `<!-- forma:begin -->` / `<!-- forma:end -->` markers in your existing doc; your prose lives outside them, and `forma check` fails if that block drifts. Attached files are recorded in `source.attachedDocs`, so the gate governs **every** doc you attach — not just the model's `docPath`; deleting the markers (or the file) from a registered doc fails the check rather than quietly un-governing it. That registry lives in `c4-model.json`, so **commit the model** — a lost model takes the registry with it. Where a repo lacks docs, `forma gen --enrich` can fill the remaining box holes with an LLM — opt-in, cached, never on the deterministic gate:
|
|
@@ -121,7 +168,9 @@ contract. The Claude skill in `adapters/` is a thin wrapper, not the product.
|
|
|
121
168
|
|
|
122
169
|
The viewer is a live C4 map, not a static picture:
|
|
123
170
|
|
|
124
|
-
- **Click any box** to read its explanation — what it does, current state
|
|
171
|
+
- **Click any box** to read its explanation — what it does, current state/target on legacy models,
|
|
172
|
+
or the state and local change at the selected checkpoint — at *every* level, from context down to
|
|
173
|
+
a leaf.
|
|
125
174
|
- **Double-click a box** (or its `[+] DRILL`) to descend into it; **BACK**, the breadcrumb, or `ESC` climb back out.
|
|
126
175
|
- **Drag boxes** to lay out the view your way; **RESET LAYOUT** restores the arrangement (your curated hints if the topology has them, the automatic one otherwise). To keep a layout, drag it, pick **Export layout JSON**, and paste the result under `"layout"` in the topology — `gen` carries it into `meta.layout` and the viewer pins those boxes, auto-arranging everything else clear of them.
|
|
127
176
|
- **Arrow labels** are painted on the diagram while the level stays readable (≤14 arrows) and turn off above that; **LABELS** forces them on or off, and hovering an arrow always reveals its label.
|
package/lib/check.mjs
CHANGED
|
@@ -10,7 +10,7 @@ import { containerOf } from './cluster.mjs'
|
|
|
10
10
|
import { renderBlock, extractBetween, norm } from './render.mjs'
|
|
11
11
|
import { descInputHash } from './enrich.mjs'
|
|
12
12
|
import { loadDocRows, indexByNode, describingRows, statusFor } from './docmap.mjs'
|
|
13
|
-
import { validateModel } from './validate.mjs'
|
|
13
|
+
import { materializeTimeline, validateModel } from './validate.mjs'
|
|
14
14
|
|
|
15
15
|
const HERE = dirname(fileURLToPath(import.meta.url))
|
|
16
16
|
const arg = (f, d) => { const i = process.argv.indexOf(f); return i > -1 ? process.argv[i + 1] : d }
|
|
@@ -26,6 +26,13 @@ if (!existsSync(TOPO)) { console.error('[check-c4] FAIL: topology missing: ' + T
|
|
|
26
26
|
const model = JSON.parse(readFileSync(MODEL, 'utf-8'))
|
|
27
27
|
const topo = JSON.parse(readFileSync(TOPO, 'utf-8'))
|
|
28
28
|
for (const err of validateModel(model)) errs.push('SCHEMA: ' + err)
|
|
29
|
+
// The topology is the curated architecture authority. The generated model carries the same compact
|
|
30
|
+
// patch programme for the browser; editing either side alone is drift even when both JSON values
|
|
31
|
+
// remain individually valid.
|
|
32
|
+
if (JSON.stringify(model.timeline || null) !== JSON.stringify(topo.timeline || null)) {
|
|
33
|
+
fail('TIMELINE DRIFT: model.timeline does not match c4-topology.json — regenerate c4-model.json.')
|
|
34
|
+
}
|
|
35
|
+
for (const err of materializeTimeline(model, { sourceExists: (rel) => existsSync(rp(rel)) }).errors) fail('TIMELINE: ' + err)
|
|
29
36
|
const byId = new Map((model.nodes || []).map((n) => [n.id, n]))
|
|
30
37
|
|
|
31
38
|
// 1) schemaVersion present + basic shape
|
package/lib/docmap.mjs
CHANGED
|
@@ -101,6 +101,7 @@ export function loadDocRows(repo, docSources, inventoryOnly = false) {
|
|
|
101
101
|
if (!existsSync(abs)) continue
|
|
102
102
|
let text
|
|
103
103
|
try { text = readFileSync(abs, 'utf-8') } catch { continue }
|
|
104
|
+
const before = out.length
|
|
104
105
|
for (const t of tables(text)) {
|
|
105
106
|
const iD = roleIndex(t.header, 'describe', src.describe)
|
|
106
107
|
const iM = roleIndex(t.header, 'ref', src.ref)
|
|
@@ -115,6 +116,15 @@ export function loadDocRows(repo, docSources, inventoryOnly = false) {
|
|
|
115
116
|
done: iS < 0 ? null : DONE.test(String(r[iS]).replace(/[*_`]+/g, '').trim()), from: rel })
|
|
116
117
|
}
|
|
117
118
|
}
|
|
119
|
+
// A status column that never once says "not done" is an INVENTORY, not a measure. `done /
|
|
120
|
+
// rows.length` is then pinned to 1 by arithmetic, for every node and every subset of rows:
|
|
121
|
+
// haben's feature matrix is 39 rows, 39 DONE, and all 27 nodes it reached came out at exactly
|
|
122
|
+
// 100 — a constant a stakeholder reads as a measurement. The document is still saying
|
|
123
|
+
// something (it declares those capabilities finished), so the VERDICT survives; the percentage
|
|
124
|
+
// does not. Per source, because that is the unit that either discriminates or does not.
|
|
125
|
+
const mine = out.slice(before)
|
|
126
|
+
const measures = mine.some((r) => r.done === false)
|
|
127
|
+
for (const r of mine) r.measures = measures
|
|
118
128
|
}
|
|
119
129
|
return out
|
|
120
130
|
}
|
|
@@ -147,7 +157,11 @@ export function indexByNode(rows, nodes) {
|
|
|
147
157
|
const paths = subtree(n.id)
|
|
148
158
|
if (!paths.length) continue
|
|
149
159
|
const hit = rows.filter((r) => r.refs.some((ref) => paths.some((p) => touches(ref, p))))
|
|
150
|
-
|
|
160
|
+
// `located`: does this node have evidence of its OWN? A curated grouping box has none — every
|
|
161
|
+
// path in its subtree belongs to a child — so no row ever names IT, only things inside it.
|
|
162
|
+
// Reporting what is underneath is the viewer's job (rollStatus); claiming it as the box's own
|
|
163
|
+
// verdict is not. See statusFor.
|
|
164
|
+
if (hit.length) idx.set(n.id, { rows: hit, cover: coverOf(n, byId, kids, hit, paths), located: (n.evidence || []).length > 0 })
|
|
151
165
|
}
|
|
152
166
|
return idx
|
|
153
167
|
}
|
|
@@ -176,19 +190,44 @@ export const describingRows = (idx, id) => {
|
|
|
176
190
|
const e = idx.get(id)
|
|
177
191
|
return e && e.rows.length <= MAX_ROWS ? e.rows : null
|
|
178
192
|
}
|
|
193
|
+
|
|
194
|
+
// The rows that RULE on a node: every row that names it, cap or no cap. MAX_ROWS is a limit on how
|
|
195
|
+
// much prose a box can carry honestly; counting how many named capabilities are finished is not
|
|
196
|
+
// prose, and inheriting the cap made the verdict scale backwards — the more rows named a module,
|
|
197
|
+
// the less it was judged. `internal_budget`, named by four DONE rows, came out unassessed next to
|
|
198
|
+
// modules nobody documented at all (#43).
|
|
199
|
+
export const rulingRows = (idx, id) => {
|
|
200
|
+
const e = idx.get(id)
|
|
201
|
+
return e && e.rows.length ? e.rows : null
|
|
202
|
+
}
|
|
179
203
|
export const coverageFor = (idx, id) => (idx.get(id) || {}).cover || null
|
|
180
204
|
|
|
181
205
|
// Programme state a document states outright: how many of the capabilities living in this node are
|
|
182
206
|
// finished. Derived, never curated — the c4-status.json overlay still overrides it (gen.mjs §WP-A1),
|
|
183
207
|
// and `check` re-derives it from the same document rather than trusting the committed model.
|
|
208
|
+
//
|
|
209
|
+
// A DECLARATION, and only sometimes a measurement. The verdict is what the document says; the
|
|
210
|
+
// percentage is only emitted when the document's status column can say no (see `measures` in
|
|
211
|
+
// loadDocRows). A number nobody could have varied is not a measurement, and on a board it is read
|
|
212
|
+
// as one — which is worse than no number at all.
|
|
184
213
|
export function statusFor(idx, id) {
|
|
185
|
-
const rows =
|
|
214
|
+
const rows = rulingRows(idx, id)
|
|
186
215
|
if (!rows || rows.some((r) => r.done == null)) return null // no status column: describe only
|
|
187
|
-
|
|
216
|
+
// A box the document cannot name did not get ruled on: the rows reached it only through its
|
|
217
|
+
// children. The demo's `fisco` and `accesso` are curated groupings with no evidence of their own,
|
|
218
|
+
// and both came out done/100 with coverage {named:0} — a verdict borrowed from below and printed
|
|
219
|
+
// as the box's own. Reporting what is underneath is the viewer's job (rollStatus), not gen's.
|
|
220
|
+
if (!(idx.get(id) || {}).located) return null
|
|
188
221
|
const c = coverageFor(idx, id)
|
|
222
|
+
const done = rows.filter((r) => r.done).length
|
|
223
|
+
// Past the cap the document TOUCHES the node rather than describing it (see MAX_ROWS), and the
|
|
224
|
+
// two halves of the answer part company: "of the rows that name this, N are done" is still
|
|
225
|
+
// something the document says, so the verdict stands — but a percentage over a reach nobody can
|
|
226
|
+
// state is the invented number this file exists to refuse. Verdict yes, percentage no.
|
|
227
|
+
const described = rows.length <= MAX_ROWS
|
|
189
228
|
return {
|
|
190
229
|
status2: done === rows.length ? 'done' : done === 0 ? 'planned' : 'in-progress',
|
|
191
|
-
completion: Math.round((done / rows.length) * 100),
|
|
230
|
+
...(described && rows.every((r) => r.measures) ? { completion: Math.round((done / rows.length) * 100) } : {}),
|
|
192
231
|
// The citation says what it is: a repo document declaring itself finished, not a verification.
|
|
193
232
|
source: `${rows[0].from} (${done}/${rows.length} declared done)`,
|
|
194
233
|
...(c ? { coverage: c } : {}),
|
package/lib/gen.mjs
CHANGED
|
@@ -11,14 +11,14 @@ import { isGo, goEdges } from './lang.mjs'
|
|
|
11
11
|
import { makeDescribeCtx, resolveDescription } from './describe.mjs'
|
|
12
12
|
import { loadDocRows, indexByNode, describingRows, statusFor } from './docmap.mjs'
|
|
13
13
|
import { loadCache, mergeCache, enrich, agentPlan, applyFills } from './enrich.mjs'
|
|
14
|
-
import { validateModel } from './validate.mjs'
|
|
14
|
+
import { materializeTimeline, validateModel } from './validate.mjs'
|
|
15
15
|
|
|
16
16
|
const HERE = dirname(fileURLToPath(import.meta.url))
|
|
17
17
|
const arg = (f, d) => { const i = process.argv.indexOf(f); return i > -1 ? process.argv[i + 1] : d }
|
|
18
18
|
const REPO = arg('--repo', process.cwd())
|
|
19
19
|
const TOPO = arg('--topology', join(REPO, 'docs/architecture/c4-topology.json'))
|
|
20
20
|
const OUT = arg('--out', join(REPO, 'docs/architecture/c4-model.json'))
|
|
21
|
-
const SCHEMA_VERSION = '1.
|
|
21
|
+
const SCHEMA_VERSION = '1.6.0' // +optional cumulative architecture timeline (additive)
|
|
22
22
|
const CLUSTER = !process.argv.includes('--no-cluster') // §2: auto-cluster flat containers; --no-cluster to disable
|
|
23
23
|
const ENRICH = process.argv.includes('--enrich') // §7: opt-in LLM prose for description holes
|
|
24
24
|
const ENRICHER = arg('--enricher', null)
|
|
@@ -168,7 +168,10 @@ for (const n of nodes) {
|
|
|
168
168
|
// `coverage` rides in `verify` (additionalProperties: true there, so no schema churn) and says how
|
|
169
169
|
// much of the node the citation actually reaches — the difference between "3 of 3 rows are done"
|
|
170
170
|
// and "this module is done".
|
|
171
|
-
|
|
171
|
+
// `ds.completion` is ABSENT when the document declares rather than measures (docmap §statusFor).
|
|
172
|
+
// Assigning it anyway would put an own `completion: undefined` on the node — invisible in the
|
|
173
|
+
// written JSON, but validateModel walks own properties and would reject it against `integer`.
|
|
174
|
+
if (ds) { n.status2 = ds.status2; if (n.completion == null && ds.completion != null) n.completion = ds.completion; if (!n.verify) n.verify = { source: ds.source, derived: true, ...(ds.coverage ? { coverage: ds.coverage } : {}) } }
|
|
172
175
|
// Code can prove a file EXISTS; it cannot prove the work behind it is finished. Marking every
|
|
173
176
|
// undecorated node done/100 turned a virgin repo into a board reading "10/10 complete" — the
|
|
174
177
|
// exact false green this tool exists to kill. No overlay (§WP-A1), no verdict: `unknown`, and
|
|
@@ -380,6 +383,7 @@ const model = {
|
|
|
380
383
|
schemaVersion: SCHEMA_VERSION,
|
|
381
384
|
generatedAt: new Date().toISOString(),
|
|
382
385
|
source: { repo: topo.meta.repo, commit, branch, docPath: topo.docPath, generator: 'gen-c4-model@' + SCHEMA_VERSION, ...(attachedDocs.length ? { attachedDocs } : {}), ...(statusPath ? { statusPath } : {}) },
|
|
386
|
+
...(topo.timeline ? { timeline: topo.timeline } : {}),
|
|
383
387
|
// topo.layout (optional curated coordinates, keyed by parent id) rides along verbatim: the
|
|
384
388
|
// viewer pins those nodes and auto-arranges the rest. `meta` is the model's free-form area.
|
|
385
389
|
meta: { ...topo.meta, ...(topo.layout ? { layout: topo.layout } : {}), verifyMethod: 'code+topology (gh re-verify optional)' },
|
|
@@ -402,11 +406,14 @@ if (process.argv.includes('--from-docs')) {
|
|
|
402
406
|
console.log(`[gen-c4] --from-docs: ${planned.length} planned milestone(s), ${accepted}/${adrs} ADR accepted`)
|
|
403
407
|
} catch (e) { model.meta.verifyError = 'from-docs: ' + String((e && e.message) || e) }
|
|
404
408
|
}
|
|
405
|
-
|
|
406
|
-
//
|
|
407
|
-
//
|
|
409
|
+
// topo.nodes and timeline patches are copied into the model, so validate both the static schema and
|
|
410
|
+
// EVERY cumulative checkpoint before touching OUT. A rejected projection must not corrupt the last
|
|
411
|
+
// committed model any more than a rejected status overlay does.
|
|
408
412
|
const schemaErrors = validateModel(model)
|
|
409
413
|
if (schemaErrors.length) fail('model does not validate against lib/schema/c4-model.schema.json:\n - ' + schemaErrors.join('\n - '))
|
|
414
|
+
const timelineErrors = materializeTimeline(model, { sourceExists: (rel) => existsSync(rp(rel)) }).errors
|
|
415
|
+
if (timelineErrors.length) fail('timeline is not a valid cumulative architecture projection:\n - ' + timelineErrors.join('\n - '))
|
|
416
|
+
writeFileSync(OUT, JSON.stringify(model, null, 2) + '\n')
|
|
410
417
|
const counts = { total: nodes.length, leaves: nodes.filter((n) => n.kind === 'leaf').length, planned: nodes.filter((n) => n.status === 'planned').length }
|
|
411
418
|
console.log(`[gen-c4] wrote ${OUT}`)
|
|
412
419
|
console.log(`[gen-c4] nodes=${counts.total} leaves=${counts.leaves} planned=${counts.planned} edges=${model.edges.length} commit=${commit.slice(0,8)}`)
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
3
|
"$id": "https://github.com/LucaDominici/forma/blob/main/lib/schema/c4-model.schema.json",
|
|
4
4
|
"title": "C4 model \u2014 stack-agnostic architecture graph",
|
|
5
|
-
"description": "Reusable contract for the C4 live dashboard. A generator conforms iff its output validates; the viewer renders
|
|
5
|
+
"description": "Reusable structural contract for the C4 live dashboard. A generator conforms iff its output validates and every optional timeline checkpoint materializes semantically; the viewer renders generator-conformant models. Stack-specific facts live only in tech/meta.",
|
|
6
6
|
"type": "object",
|
|
7
7
|
"required": [
|
|
8
8
|
"schemaVersion",
|
|
@@ -61,6 +61,319 @@
|
|
|
61
61
|
}
|
|
62
62
|
}
|
|
63
63
|
},
|
|
64
|
+
"timeline": {
|
|
65
|
+
"type": "object",
|
|
66
|
+
"description": "Optional governed future architecture. The generated nodes/edges remain the AS-IS baseline; ordered checkpoint patches are applied cumulatively.",
|
|
67
|
+
"required": [
|
|
68
|
+
"source",
|
|
69
|
+
"checkpoints"
|
|
70
|
+
],
|
|
71
|
+
"additionalProperties": false,
|
|
72
|
+
"properties": {
|
|
73
|
+
"source": {
|
|
74
|
+
"type": "string",
|
|
75
|
+
"description": "Repo-relative architecture document governing the future projection."
|
|
76
|
+
},
|
|
77
|
+
"checkpoints": {
|
|
78
|
+
"type": "array",
|
|
79
|
+
"minItems": 1,
|
|
80
|
+
"items": {
|
|
81
|
+
"type": "object",
|
|
82
|
+
"required": [
|
|
83
|
+
"id",
|
|
84
|
+
"label"
|
|
85
|
+
],
|
|
86
|
+
"additionalProperties": false,
|
|
87
|
+
"properties": {
|
|
88
|
+
"id": {
|
|
89
|
+
"type": "string",
|
|
90
|
+
"pattern": "^[a-z0-9][a-z0-9._-]*$"
|
|
91
|
+
},
|
|
92
|
+
"label": {
|
|
93
|
+
"type": "string"
|
|
94
|
+
},
|
|
95
|
+
"badge": {
|
|
96
|
+
"type": "string",
|
|
97
|
+
"description": "Display-only checkpoint annotation. Never interpreted as architecture operations."
|
|
98
|
+
},
|
|
99
|
+
"patch": {
|
|
100
|
+
"type": "object",
|
|
101
|
+
"additionalProperties": false,
|
|
102
|
+
"properties": {
|
|
103
|
+
"nodes": {
|
|
104
|
+
"type": "object",
|
|
105
|
+
"additionalProperties": false,
|
|
106
|
+
"properties": {
|
|
107
|
+
"add": {
|
|
108
|
+
"type": "array",
|
|
109
|
+
"items": {
|
|
110
|
+
"type": "object",
|
|
111
|
+
"required": [
|
|
112
|
+
"node",
|
|
113
|
+
"change"
|
|
114
|
+
],
|
|
115
|
+
"additionalProperties": false,
|
|
116
|
+
"properties": {
|
|
117
|
+
"node": {
|
|
118
|
+
"$ref": "#/properties/nodes/items"
|
|
119
|
+
},
|
|
120
|
+
"change": {
|
|
121
|
+
"type": "string"
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
"update": {
|
|
127
|
+
"type": "array",
|
|
128
|
+
"items": {
|
|
129
|
+
"type": "object",
|
|
130
|
+
"required": [
|
|
131
|
+
"id",
|
|
132
|
+
"set",
|
|
133
|
+
"change"
|
|
134
|
+
],
|
|
135
|
+
"additionalProperties": false,
|
|
136
|
+
"properties": {
|
|
137
|
+
"id": {
|
|
138
|
+
"type": "string"
|
|
139
|
+
},
|
|
140
|
+
"set": {
|
|
141
|
+
"type": "object",
|
|
142
|
+
"minProperties": 1,
|
|
143
|
+
"additionalProperties": false,
|
|
144
|
+
"properties": {
|
|
145
|
+
"name": {
|
|
146
|
+
"$ref": "#/properties/nodes/items/properties/name"
|
|
147
|
+
},
|
|
148
|
+
"drill": {
|
|
149
|
+
"$ref": "#/properties/nodes/items/properties/drill"
|
|
150
|
+
},
|
|
151
|
+
"tech": {
|
|
152
|
+
"$ref": "#/properties/nodes/items/properties/tech"
|
|
153
|
+
},
|
|
154
|
+
"description": {
|
|
155
|
+
"$ref": "#/properties/nodes/items/properties/description"
|
|
156
|
+
},
|
|
157
|
+
"status": {
|
|
158
|
+
"$ref": "#/properties/nodes/items/properties/status"
|
|
159
|
+
},
|
|
160
|
+
"link": {
|
|
161
|
+
"$ref": "#/properties/nodes/items/properties/link"
|
|
162
|
+
},
|
|
163
|
+
"evidence": {
|
|
164
|
+
"$ref": "#/properties/nodes/items/properties/evidence"
|
|
165
|
+
},
|
|
166
|
+
"meta": {
|
|
167
|
+
"$ref": "#/properties/nodes/items/properties/meta"
|
|
168
|
+
},
|
|
169
|
+
"category": {
|
|
170
|
+
"$ref": "#/properties/nodes/items/properties/category"
|
|
171
|
+
},
|
|
172
|
+
"status2": {
|
|
173
|
+
"$ref": "#/properties/nodes/items/properties/status2"
|
|
174
|
+
},
|
|
175
|
+
"completion": {
|
|
176
|
+
"$ref": "#/properties/nodes/items/properties/completion"
|
|
177
|
+
},
|
|
178
|
+
"statusWord": {
|
|
179
|
+
"$ref": "#/properties/nodes/items/properties/statusWord"
|
|
180
|
+
},
|
|
181
|
+
"current": {
|
|
182
|
+
"$ref": "#/properties/nodes/items/properties/current"
|
|
183
|
+
},
|
|
184
|
+
"issues": {
|
|
185
|
+
"$ref": "#/properties/nodes/items/properties/issues"
|
|
186
|
+
},
|
|
187
|
+
"verify": {
|
|
188
|
+
"$ref": "#/properties/nodes/items/properties/verify"
|
|
189
|
+
},
|
|
190
|
+
"func": {
|
|
191
|
+
"$ref": "#/properties/nodes/items/properties/func"
|
|
192
|
+
},
|
|
193
|
+
"descSource": {
|
|
194
|
+
"$ref": "#/properties/nodes/items/properties/descSource"
|
|
195
|
+
},
|
|
196
|
+
"descInputHash": {
|
|
197
|
+
"$ref": "#/properties/nodes/items/properties/descInputHash"
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
},
|
|
201
|
+
"change": {
|
|
202
|
+
"type": "string"
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
},
|
|
207
|
+
"remove": {
|
|
208
|
+
"type": "array",
|
|
209
|
+
"items": {
|
|
210
|
+
"type": "object",
|
|
211
|
+
"required": [
|
|
212
|
+
"id",
|
|
213
|
+
"change"
|
|
214
|
+
],
|
|
215
|
+
"additionalProperties": false,
|
|
216
|
+
"properties": {
|
|
217
|
+
"id": {
|
|
218
|
+
"type": "string"
|
|
219
|
+
},
|
|
220
|
+
"change": {
|
|
221
|
+
"type": "string"
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
},
|
|
228
|
+
"edges": {
|
|
229
|
+
"type": "object",
|
|
230
|
+
"additionalProperties": false,
|
|
231
|
+
"properties": {
|
|
232
|
+
"add": {
|
|
233
|
+
"type": "array",
|
|
234
|
+
"items": {
|
|
235
|
+
"type": "object",
|
|
236
|
+
"required": [
|
|
237
|
+
"edge",
|
|
238
|
+
"change"
|
|
239
|
+
],
|
|
240
|
+
"additionalProperties": false,
|
|
241
|
+
"properties": {
|
|
242
|
+
"edge": {
|
|
243
|
+
"$ref": "#/properties/edges/items"
|
|
244
|
+
},
|
|
245
|
+
"change": {
|
|
246
|
+
"type": "string"
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
},
|
|
251
|
+
"rewire": {
|
|
252
|
+
"type": "array",
|
|
253
|
+
"items": {
|
|
254
|
+
"type": "object",
|
|
255
|
+
"required": [
|
|
256
|
+
"match",
|
|
257
|
+
"set",
|
|
258
|
+
"change"
|
|
259
|
+
],
|
|
260
|
+
"additionalProperties": false,
|
|
261
|
+
"properties": {
|
|
262
|
+
"match": {
|
|
263
|
+
"type": "object",
|
|
264
|
+
"required": [
|
|
265
|
+
"from",
|
|
266
|
+
"to"
|
|
267
|
+
],
|
|
268
|
+
"additionalProperties": false,
|
|
269
|
+
"properties": {
|
|
270
|
+
"from": {
|
|
271
|
+
"type": "string"
|
|
272
|
+
},
|
|
273
|
+
"to": {
|
|
274
|
+
"type": "string"
|
|
275
|
+
},
|
|
276
|
+
"label": {
|
|
277
|
+
"type": "string"
|
|
278
|
+
},
|
|
279
|
+
"kind": {
|
|
280
|
+
"type": "string"
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
},
|
|
284
|
+
"set": {
|
|
285
|
+
"type": "object",
|
|
286
|
+
"minProperties": 1,
|
|
287
|
+
"additionalProperties": false,
|
|
288
|
+
"anyOf": [
|
|
289
|
+
{
|
|
290
|
+
"required": [
|
|
291
|
+
"from"
|
|
292
|
+
]
|
|
293
|
+
},
|
|
294
|
+
{
|
|
295
|
+
"required": [
|
|
296
|
+
"to"
|
|
297
|
+
]
|
|
298
|
+
}
|
|
299
|
+
],
|
|
300
|
+
"properties": {
|
|
301
|
+
"from": {
|
|
302
|
+
"$ref": "#/properties/edges/items/properties/from"
|
|
303
|
+
},
|
|
304
|
+
"to": {
|
|
305
|
+
"$ref": "#/properties/edges/items/properties/to"
|
|
306
|
+
},
|
|
307
|
+
"label": {
|
|
308
|
+
"$ref": "#/properties/edges/items/properties/label"
|
|
309
|
+
},
|
|
310
|
+
"kind": {
|
|
311
|
+
"$ref": "#/properties/edges/items/properties/kind"
|
|
312
|
+
},
|
|
313
|
+
"status": {
|
|
314
|
+
"$ref": "#/properties/edges/items/properties/status"
|
|
315
|
+
},
|
|
316
|
+
"evidence": {
|
|
317
|
+
"$ref": "#/properties/edges/items/properties/evidence"
|
|
318
|
+
},
|
|
319
|
+
"estatus": {
|
|
320
|
+
"$ref": "#/properties/edges/items/properties/estatus"
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
},
|
|
324
|
+
"change": {
|
|
325
|
+
"type": "string"
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
},
|
|
330
|
+
"remove": {
|
|
331
|
+
"type": "array",
|
|
332
|
+
"items": {
|
|
333
|
+
"type": "object",
|
|
334
|
+
"required": [
|
|
335
|
+
"match",
|
|
336
|
+
"change"
|
|
337
|
+
],
|
|
338
|
+
"additionalProperties": false,
|
|
339
|
+
"properties": {
|
|
340
|
+
"match": {
|
|
341
|
+
"type": "object",
|
|
342
|
+
"required": [
|
|
343
|
+
"from",
|
|
344
|
+
"to"
|
|
345
|
+
],
|
|
346
|
+
"additionalProperties": false,
|
|
347
|
+
"properties": {
|
|
348
|
+
"from": {
|
|
349
|
+
"type": "string"
|
|
350
|
+
},
|
|
351
|
+
"to": {
|
|
352
|
+
"type": "string"
|
|
353
|
+
},
|
|
354
|
+
"label": {
|
|
355
|
+
"type": "string"
|
|
356
|
+
},
|
|
357
|
+
"kind": {
|
|
358
|
+
"type": "string"
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
},
|
|
362
|
+
"change": {
|
|
363
|
+
"type": "string"
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
},
|
|
64
377
|
"meta": {
|
|
65
378
|
"type": "object",
|
|
66
379
|
"description": "Repo-level escape hatch (archetype, stack, overlays, version). Never required by the viewer.",
|