@alphazede/bearing-lite 0.2.2 → 1.0.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 (57) hide show
  1. package/CONTRIBUTING.md +11 -6
  2. package/README.md +224 -131
  3. package/com.github.copilot/hooks/hooks.json +33 -0
  4. package/hooks/assurance-budget.cjs +33 -11
  5. package/hooks/com.anthropic.claude-code/mapping.md +35 -16
  6. package/hooks/plan-package.cjs +5 -4
  7. package/hooks/policy.cjs +10 -4
  8. package/hooks/profiles.cjs +119 -0
  9. package/hooks/te-host.cjs +40 -5
  10. package/hooks/transition-order.cjs +12 -4
  11. package/hooks/verification.cjs +358 -0
  12. package/package.json +6 -3
  13. package/plugin.json +2 -34
  14. package/profiles.json +1 -0
  15. package/schemas/implementation.schema.json +204 -5
  16. package/schemas/journey.schema.json +3 -3
  17. package/schemas/profiles.schema.json +359 -0
  18. package/schemas/verification.schema.json +126 -0
  19. package/skills/{set-bearings → architectural-alignment}/SKILL.md +17 -13
  20. package/skills/{set-bearings → architectural-alignment}/templates/workspace.md +1 -1
  21. package/skills/bearing-lite/SKILL.md +22 -22
  22. package/skills/bearing-lite/references/assurance-policy.md +32 -16
  23. package/skills/bearing-lite/references/lineups.md +7 -110
  24. package/skills/bearing-lite/references/owner-stops.md +13 -13
  25. package/skills/bearing-lite/references/peer-synthesis.md +11 -12
  26. package/skills/bearing-lite/references/profiles.md +149 -0
  27. package/skills/bearing-lite/references/resume.md +30 -0
  28. package/skills/bearing-lite/references/review-policy.md +3 -3
  29. package/skills/bearing-lite/references/role-routing.mmd +14 -14
  30. package/skills/bearing-lite/references/task-state.md +10 -10
  31. package/skills/bearing-lite/references/task-state.mmd +2 -2
  32. package/skills/bearing-lite/references/verification.md +52 -0
  33. package/skills/bearing-lite/templates/task.md +29 -28
  34. package/skills/{explorer → coordinator}/SKILL.md +16 -16
  35. package/skills/{crewmate → implementer}/SKILL.md +17 -15
  36. package/skills/{repository-fit → intake}/SKILL.md +10 -9
  37. package/skills/integration-engineer/SKILL.md +17 -12
  38. package/skills/light-implementer/SKILL.md +6 -5
  39. package/skills/onboard-bearing/SKILL.md +46 -0
  40. package/skills/plan-integrator/SKILL.md +14 -14
  41. package/skills/{map-the-route → planning-and-design}/SKILL.md +27 -27
  42. package/skills/{map-the-route → planning-and-design}/references/artifact-grammar.md +29 -27
  43. package/skills/prompt/SKILL.md +245 -0
  44. package/skills/requirements-engineer/SKILL.md +11 -11
  45. package/skills/{park-ranger → reviewer}/SKILL.md +16 -13
  46. package/skills/{gather-supplies → scope-definition}/SKILL.md +13 -13
  47. package/skills/scribe/SKILL.md +8 -8
  48. package/skills/systems-modeler/SKILL.md +5 -3
  49. package/skills/test-engineer/SKILL.md +16 -13
  50. package/templates/dod-manifest-v1.html +648 -0
  51. package/tools/render-dod-manifest.mjs +1743 -0
  52. package/lineups.json +0 -1
  53. package/schemas/lineups.schema.json +0 -145
  54. package/skills/navigator/SKILL.md +0 -36
  55. package/skills/surveyor/SKILL.md +0 -48
  56. package/skills/validator/SKILL.md +0 -37
  57. package/skills/validator/references/grading-rubric.md +0 -40
@@ -0,0 +1,358 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * Deterministic verification adapter (DES-BDL-008 / AC-BDL-009 / SEIT-BDL-004).
5
+ * Pure evaluator: no HOOK_CLASS, no host event, no download, no role dispatch.
6
+ * Reverify is one optional backend; availability never selects it for a task.
7
+ */
8
+
9
+ const STATUSES = Object.freeze(["VERIFIED", "REFUTED", "INCONCLUSIVE", "ERROR"]);
10
+ const AUTHORITIES = Object.freeze(["diagnostic", "assurance"]);
11
+ const STAGES = Object.freeze([
12
+ "implementation",
13
+ "integration",
14
+ "assurance",
15
+ "review",
16
+ "post_repair_closure",
17
+ ]);
18
+ const EXPECTED = Object.freeze(["VERIFIED", "REFUTED"]);
19
+ const SHA256 = /^[0-9a-f]{64}$/;
20
+ const CANDIDATE_FIELDS = Object.freeze([
21
+ "candidate_ref",
22
+ "candidate_revision",
23
+ "candidate_digest",
24
+ ]);
25
+
26
+ const isPlainObject = (value) =>
27
+ value !== null && typeof value === "object" && !Array.isArray(value);
28
+
29
+ const nonempty = (value) => typeof value === "string" && value.length > 0;
30
+
31
+ function deepEqual(a, b) {
32
+ if (a === b) return true;
33
+ if (typeof a !== typeof b) return false;
34
+ if (Array.isArray(a) && Array.isArray(b)) {
35
+ return a.length === b.length && a.every((item, i) => deepEqual(item, b[i]));
36
+ }
37
+ if (isPlainObject(a) && isPlainObject(b)) {
38
+ const keys = Object.keys(a);
39
+ return keys.length === Object.keys(b).length && keys.every((key) => deepEqual(a[key], b[key]));
40
+ }
41
+ return false;
42
+ }
43
+
44
+ function result(outcome, reason, extra = {}) {
45
+ return {
46
+ outcome,
47
+ reason,
48
+ status: extra.status ?? null,
49
+ authority: extra.authority ?? null,
50
+ gate_eligible: extra.gate_eligible === true,
51
+ closure_eligible: extra.closure_eligible === true,
52
+ rereview: false,
53
+ download_attempted: false,
54
+ backend_is_role: false,
55
+ };
56
+ }
57
+
58
+ function present(value) {
59
+ return !(value === undefined || value === null || value === "");
60
+ }
61
+
62
+ function sameCandidate(left, right) {
63
+ if (!isPlainObject(left) || !isPlainObject(right)) return false;
64
+ for (const field of CANDIDATE_FIELDS) {
65
+ const a = left[field];
66
+ const b = right[field];
67
+ if (field === "candidate_digest" && !present(a) && !present(b)) continue;
68
+ if (!present(a) || !present(b) || a !== b) return false;
69
+ }
70
+ return true;
71
+ }
72
+
73
+ function requestErrors(request) {
74
+ if (!isPlainObject(request)) return "verification_request_missing";
75
+ if (request.schema_version !== "1" || request.kind !== "request") return "verification_request_invalid";
76
+ for (const field of [
77
+ "candidate_ref",
78
+ "candidate_revision",
79
+ "claim_id",
80
+ "claim_type",
81
+ "backend",
82
+ "stage",
83
+ "authority",
84
+ "expected_result",
85
+ ]) {
86
+ if (!nonempty(request[field])) return "verification_request_unbound";
87
+ }
88
+ if (!STAGES.includes(request.stage)) return "verification_request_invalid";
89
+ if (!AUTHORITIES.includes(request.authority)) return "verification_request_invalid";
90
+ if (!EXPECTED.includes(request.expected_result)) return "verification_request_invalid";
91
+ if (!isPlainObject(request.command_configuration) || Object.keys(request.command_configuration).length === 0) {
92
+ return "verification_request_unbound";
93
+ }
94
+ if (typeof request.selected !== "boolean" || typeof request.required !== "boolean") {
95
+ return "verification_request_unbound";
96
+ }
97
+ if (request.candidate_digest !== undefined && !SHA256.test(request.candidate_digest)) {
98
+ return "verification_request_invalid";
99
+ }
100
+ return null;
101
+ }
102
+
103
+ function receiptErrors(receipt) {
104
+ if (!isPlainObject(receipt)) return "verification_receipt_missing";
105
+ if (receipt.schema_version !== "1" || receipt.kind !== "receipt") return "verification_receipt_invalid";
106
+ for (const field of [
107
+ "status",
108
+ "candidate_ref",
109
+ "candidate_revision",
110
+ "claim_id",
111
+ "backend",
112
+ "backend_version",
113
+ "evidence_digest",
114
+ "authority",
115
+ ]) {
116
+ if (!nonempty(receipt[field])) return "verification_receipt_unbound";
117
+ }
118
+ if (!STATUSES.includes(receipt.status)) return "verification_receipt_invalid";
119
+ if (!AUTHORITIES.includes(receipt.authority)) return "verification_receipt_invalid";
120
+ if (!SHA256.test(receipt.evidence_digest)) return "verification_receipt_unbound";
121
+ if (!isPlainObject(receipt.command_configuration) || Object.keys(receipt.command_configuration).length === 0) {
122
+ return "verification_receipt_unbound";
123
+ }
124
+ const who = receipt.produced_by;
125
+ if (!isPlainObject(who) || !nonempty(who.role) || !nonempty(who.identity) || !nonempty(who.session)) {
126
+ return "verification_receipt_unbound";
127
+ }
128
+ if (receipt.candidate_digest !== undefined && !SHA256.test(receipt.candidate_digest)) {
129
+ return "verification_receipt_invalid";
130
+ }
131
+ if (receipt.stage !== undefined && !STAGES.includes(receipt.stage)) return "verification_receipt_invalid";
132
+ return null;
133
+ }
134
+
135
+ function backendState(input, request) {
136
+ const backend = isPlainObject(input.backend) ? input.backend : {};
137
+ const available = backend.available === true;
138
+ const enabled = backend.enabled;
139
+ const name = nonempty(backend.name) ? backend.name : request.backend;
140
+ return {
141
+ name,
142
+ selected: request.selected === true,
143
+ required: request.required === true,
144
+ available,
145
+ enabled: enabled === false ? false : enabled === true ? true : available,
146
+ };
147
+ }
148
+
149
+ /**
150
+ * @param {unknown} input verification evaluation request
151
+ * @returns {{
152
+ * outcome: string,
153
+ * reason: string,
154
+ * status: string|null,
155
+ * authority: string|null,
156
+ * gate_eligible: boolean,
157
+ * closure_eligible: boolean,
158
+ * rereview: false,
159
+ * download_attempted: false,
160
+ * backend_is_role: false
161
+ * }}
162
+ */
163
+ function evaluateVerification(input) {
164
+ try {
165
+ if (!isPlainObject(input)) {
166
+ return result("NEEDS_MORE_EVIDENCE", "verification_request_missing");
167
+ }
168
+ // Reverify/download is never performed here. A caller hint is ignored.
169
+ if (input.download === true || input.install === true || typeof input.download_fn === "function") {
170
+ // Fall through without invoking anything.
171
+ }
172
+
173
+ const requestErr = requestErrors(input.request);
174
+ if (requestErr) return result("NEEDS_MORE_EVIDENCE", requestErr);
175
+
176
+ const request = input.request;
177
+ const current = isPlainObject(input.candidate) ? input.candidate : request;
178
+ if (!sameCandidate(request, current)) {
179
+ return result("REJECT", "candidate_mismatch");
180
+ }
181
+
182
+ const backend = backendState(input, request);
183
+ const activated = backend.selected || backend.required;
184
+ if (!activated) {
185
+ if (!isPlainObject(input.receipt)) {
186
+ return result("INACTIVE", "backend_unselected_unrequired");
187
+ }
188
+ } else if (backend.enabled === false || backend.available !== true) {
189
+ return result("ERROR", "backend_unavailable", { status: "ERROR" });
190
+ }
191
+
192
+ if (!Object.hasOwn(input, "receipt") || input.receipt === undefined || input.receipt === null) {
193
+ return result(activated ? "NEEDS_MORE_EVIDENCE" : "INACTIVE", activated ? "verification_receipt_missing" : "backend_unselected_unrequired");
194
+ }
195
+
196
+ const receiptErr = receiptErrors(input.receipt);
197
+ if (receiptErr) return result("NEEDS_MORE_EVIDENCE", receiptErr);
198
+ const receipt = input.receipt;
199
+
200
+ if (
201
+ receipt.claim_id !== request.claim_id ||
202
+ receipt.backend !== request.backend ||
203
+ !deepEqual(receipt.command_configuration, request.command_configuration)
204
+ ) {
205
+ return result("REJECT", "claim_or_backend_mismatch", {
206
+ status: receipt.status,
207
+ authority: receipt.authority,
208
+ });
209
+ }
210
+ if (!sameCandidate(receipt, request) || !sameCandidate(receipt, current)) {
211
+ return result("REJECT", "candidate_mismatch", {
212
+ status: receipt.status,
213
+ authority: receipt.authority,
214
+ });
215
+ }
216
+
217
+ const repair = isPlainObject(input.repair) ? input.repair : {};
218
+ if (
219
+ nonempty(repair.prior_evidence_digest) &&
220
+ repair.prior_evidence_digest === receipt.evidence_digest &&
221
+ (repair.post_repair === true || nonempty(repair.prior_candidate_revision))
222
+ ) {
223
+ return result("REJECT", "stale_evidence", {
224
+ status: receipt.status,
225
+ authority: receipt.authority,
226
+ });
227
+ }
228
+ if (
229
+ nonempty(repair.prior_candidate_revision) &&
230
+ receipt.candidate_revision === repair.prior_candidate_revision &&
231
+ current.candidate_revision !== repair.prior_candidate_revision
232
+ ) {
233
+ return result("REJECT", "stale_evidence", {
234
+ status: receipt.status,
235
+ authority: receipt.authority,
236
+ });
237
+ }
238
+
239
+ if (receipt.status === "ERROR") {
240
+ return result("ERROR", "backend_error", {
241
+ status: "ERROR",
242
+ authority: receipt.authority,
243
+ });
244
+ }
245
+ if (receipt.status === "INCONCLUSIVE") {
246
+ return result("INCONCLUSIVE", "inconclusive_cannot_satisfy_gate", {
247
+ status: "INCONCLUSIVE",
248
+ authority: receipt.authority,
249
+ });
250
+ }
251
+
252
+ const author = isPlainObject(input.author) ? input.author : null;
253
+ const who = receipt.produced_by;
254
+ const sameIdentity = author && nonempty(author.identity) && who.identity === author.identity;
255
+ const sameSession = author && nonempty(author.session) && who.session === author.session;
256
+ const selfCert = sameIdentity || sameSession;
257
+ const gate = input.gate === "post_repair_closure" ? "post_repair_closure" : input.gate === "assurance" ? "assurance" : "none";
258
+
259
+ if (gate === "assurance") {
260
+ if (!author || !nonempty(author.identity) || !nonempty(author.session)) {
261
+ return result("NEEDS_MORE_EVIDENCE", "author_identity_missing", {
262
+ status: receipt.status,
263
+ authority: receipt.authority,
264
+ });
265
+ }
266
+ if (receipt.authority === "diagnostic") {
267
+ return result("REJECT", "diagnostic_cannot_satisfy_assurance_gate", {
268
+ status: receipt.status,
269
+ authority: "diagnostic",
270
+ });
271
+ }
272
+ if (selfCert) {
273
+ return result("REJECT", "self_certification", {
274
+ status: receipt.status,
275
+ authority: receipt.authority,
276
+ });
277
+ }
278
+ if (receipt.status !== request.expected_result) {
279
+ return result("REJECT", "expected_result_mismatch", {
280
+ status: receipt.status,
281
+ authority: receipt.authority,
282
+ });
283
+ }
284
+ return result("PASS", "independent_assurance_verified", {
285
+ status: receipt.status,
286
+ authority: "assurance",
287
+ gate_eligible: true,
288
+ });
289
+ }
290
+
291
+ if (gate === "post_repair_closure") {
292
+ if (input.review_after_repair === true || input.automatic_rereview_requested === true) {
293
+ return result("REJECT", "automatic_rereview_prohibited", {
294
+ status: receipt.status,
295
+ authority: receipt.authority,
296
+ });
297
+ }
298
+ if (!author || !nonempty(author.identity) || !nonempty(author.session)) {
299
+ return result("NEEDS_MORE_EVIDENCE", "author_identity_missing", {
300
+ status: receipt.status,
301
+ authority: receipt.authority,
302
+ });
303
+ }
304
+ if (selfCert) {
305
+ return result("REJECT", "self_certification", {
306
+ status: receipt.status,
307
+ authority: receipt.authority,
308
+ });
309
+ }
310
+ const closureStage =
311
+ request.stage === "post_repair_closure" || receipt.stage === "post_repair_closure";
312
+ if (receipt.authority === "diagnostic" && !closureStage) {
313
+ return result("REJECT", "diagnostic_cannot_satisfy_closure_gate", {
314
+ status: receipt.status,
315
+ authority: "diagnostic",
316
+ });
317
+ }
318
+ if (!closureStage) {
319
+ return result("REJECT", "closure_stage_required", {
320
+ status: receipt.status,
321
+ authority: receipt.authority,
322
+ });
323
+ }
324
+ if (receipt.status !== request.expected_result) {
325
+ return result("REJECT", "expected_result_mismatch", {
326
+ status: receipt.status,
327
+ authority: receipt.authority,
328
+ });
329
+ }
330
+ return result("PASS", "post_repair_deterministic_closure", {
331
+ status: receipt.status,
332
+ authority: receipt.authority,
333
+ closure_eligible: true,
334
+ });
335
+ }
336
+
337
+ if (receipt.authority === "diagnostic") {
338
+ return result("PASS", "diagnostic_receipt_recorded", {
339
+ status: receipt.status,
340
+ authority: "diagnostic",
341
+ });
342
+ }
343
+ return result("PASS", "receipt_recorded", {
344
+ status: receipt.status,
345
+ authority: receipt.authority,
346
+ });
347
+ } catch {
348
+ return result("NEEDS_MORE_EVIDENCE", "verification_adapter_unavailable");
349
+ }
350
+ }
351
+
352
+ module.exports = {
353
+ evaluateVerification,
354
+ STATUSES,
355
+ AUTHORITIES,
356
+ STAGES,
357
+ CANDIDATE_FIELDS,
358
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alphazede/bearing-lite",
3
- "version": "0.2.2",
3
+ "version": "1.0.0",
4
4
  "description": "Skills-first Agent Plugin for planning, routing, bounded execution, and independent review of repository work—without CLI, MCP, server, or hidden runtime state.",
5
5
  "keywords": [
6
6
  "agent-plugins",
@@ -38,8 +38,11 @@
38
38
  "CONTRIBUTING.md",
39
39
  "SECURITY.md",
40
40
  "LICENSE-APACHE",
41
- "lineups.json",
42
- "schemas/"
41
+ "profiles.json",
42
+ "schemas/",
43
+ "templates/",
44
+ "tools/",
45
+ "com.github.copilot/"
43
46
  ],
44
47
  "packageManager": "pnpm@10.33.0+sha512.10568bb4a6afb58c9eb3630da90cc9516417abebd3fabbe6739f0ae795728da1491e9db5a544c76ad8eb7570f5c4bb3d6c637b2cb41bfdcdb47fa823c8649319"
45
48
  }
package/plugin.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
3
3
  "name": "bearing-lite",
4
- "version": "0.2.2",
4
+ "version": "1.0.0",
5
5
  "description": "Skills-first portable plugin that routes repository work through the smallest valid planning stages and agent roles, using project Markdown as the only task record.",
6
6
  "author": {
7
7
  "name": "William Rumph / AlphaZede",
@@ -18,37 +18,5 @@
18
18
  "routing",
19
19
  "developer-tools",
20
20
  "skills-first"
21
- ],
22
- "hooks": {
23
- "com.anthropic.claude-code.activation": {
24
- "path": "./hooks/com.anthropic.claude-code/host.cjs"
25
- },
26
- "com.anthropic.claude-code.closeout": {
27
- "path": "./hooks/com.anthropic.claude-code/host.cjs"
28
- },
29
- "com.openai.codex.activation": {
30
- "path": "./hooks/com.anthropic.claude-code/host.cjs"
31
- },
32
- "com.openai.codex.closeout": {
33
- "path": "./hooks/com.anthropic.claude-code/host.cjs"
34
- },
35
- "com.xai.grok-build.activation": {
36
- "path": "./hooks/com.anthropic.claude-code/host.cjs"
37
- },
38
- "com.xai.grok-build.closeout": {
39
- "path": "./hooks/com.anthropic.claude-code/host.cjs"
40
- },
41
- "com.cursor.ide.activation": {
42
- "path": "./hooks/com.anthropic.claude-code/host.cjs"
43
- },
44
- "com.cursor.ide.closeout": {
45
- "path": "./hooks/com.anthropic.claude-code/host.cjs"
46
- },
47
- "com.moonshotai.kimi-code.activation": {
48
- "path": "./hooks/com.anthropic.claude-code/host.cjs"
49
- },
50
- "com.moonshotai.kimi-code.closeout": {
51
- "path": "./hooks/com.anthropic.claude-code/host.cjs"
52
- }
53
- }
21
+ ]
54
22
  }
package/profiles.json ADDED
@@ -0,0 +1 @@
1
+ {"schema_version":1,"profiles":{}}
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
3
  "$id": "https://github.com/alphazede/bearing-lite/schemas/implementation.schema.json",
4
4
  "title": "Bearing Lite implementation.json",
5
- "description": "Nested execution authority. XLSX is never authority. Owner-configured n, k, and c have no assistant default integers. Every slice requires CONTRACT-EMV-008 fields including reasoning_level (object), not a sibling reasoning string. human_decision may be a string, object, or null. authority_id is a nonempty string on dispatchable slices and may be null only when dispatchable is false or slice_status is publication-blocked. Extra lifecycle metadata is allowed.",
5
+ "description": "Nested execution authority. XLSX is never authority. Owner-configured n, k, and c have no assistant default integers. Every slice requires CONTRACT-EMV-008 fields including reasoning_level (object), not a sibling reasoning string. human_decision may be a string, object, or null. authority_id is a nonempty string on dispatchable slices and may be null only when dispatchable is false or slice_status is publication-blocked. Extra lifecycle metadata is allowed. lineup_freeze is not a live field; profile_freeze is required.",
6
6
  "type": "object",
7
7
  "additionalProperties": true,
8
8
  "required": [
@@ -10,7 +10,7 @@
10
10
  "artifact",
11
11
  "source_baseline",
12
12
  "journey_settings",
13
- "lineup_freeze",
13
+ "profile_freeze",
14
14
  "waves",
15
15
  "dependencies",
16
16
  "slices",
@@ -108,6 +108,174 @@
108
108
  }
109
109
  }
110
110
  ]
111
+ },
112
+ "dodNa": {
113
+ "type": "object",
114
+ "additionalProperties": true,
115
+ "properties": {
116
+ "not_applicable": { "type": "boolean" },
117
+ "na": { "type": "boolean" },
118
+ "reason": { "type": "string", "minLength": 1 },
119
+ "status": { "type": "string" }
120
+ }
121
+ },
122
+ "dodView": {
123
+ "type": "object",
124
+ "additionalProperties": false,
125
+ "properties": {
126
+ "format": { "type": "string", "enum": ["svg", "png"] },
127
+ "path": { "type": "string", "minLength": 1 },
128
+ "inline": { "type": "string", "minLength": 1 },
129
+ "alt": { "type": "string" }
130
+ }
131
+ },
132
+ "dodModel": {
133
+ "type": "object",
134
+ "additionalProperties": true,
135
+ "required": ["id"],
136
+ "properties": {
137
+ "id": { "type": "string", "minLength": 1 },
138
+ "mode": { "type": "string" },
139
+ "source": { "type": "string" },
140
+ "revision": { "type": "string" },
141
+ "digest": { "type": "string" },
142
+ "viewpoint": { "type": "string" },
143
+ "trace": { "type": "string" },
144
+ "limitations": { "type": "string" },
145
+ "render_status": { "type": "string" },
146
+ "required": { "type": "boolean" },
147
+ "selected": { "type": "boolean" },
148
+ "not_applicable": { "type": "boolean" },
149
+ "reason": { "type": "string" },
150
+ "view": { "$ref": "#/$defs/dodView" },
151
+ "views": {
152
+ "type": "array",
153
+ "items": { "$ref": "#/$defs/dodView" }
154
+ }
155
+ }
156
+ },
157
+ "dodManifest": {
158
+ "type": "object",
159
+ "additionalProperties": true,
160
+ "description": "Structured rendering input for the versioned DoD Manifest template. Planning fields are the immutable projection; closeout is append-only. Activated models (selected OR required, including render_status READY/MISSING/STALE) must bind digest plus view.format svg|png and view.path or svg view.inline, either on the planning row or via closeout.model_views. The renderer never invents views or evidence and never rewrites planning model rows.",
161
+ "required": ["template_version", "output_name", "title"],
162
+ "properties": {
163
+ "template_version": { "type": "string", "const": "1.0.1" },
164
+ "output_name": { "type": "string", "minLength": 1, "pattern": "^[A-Za-z0-9._-]+-dod-manifest\\.html$" },
165
+ "state": { "type": "string", "minLength": 1 },
166
+ "title": { "type": "string", "minLength": 1 },
167
+ "lede": { "type": "string" },
168
+ "bluf": { "type": "string" },
169
+ "identity": {
170
+ "type": "array",
171
+ "items": {
172
+ "type": "object",
173
+ "additionalProperties": true,
174
+ "properties": {
175
+ "label": { "type": "string" },
176
+ "value": { "type": ["string", "number"] }
177
+ }
178
+ }
179
+ },
180
+ "outcome": { "type": "string" },
181
+ "scope": { "type": "array", "items": { "type": "string" } },
182
+ "exclusions": { "type": "array", "items": { "type": "string" } },
183
+ "definition_of_done": {
184
+ "oneOf": [
185
+ { "$ref": "#/$defs/dodNa" },
186
+ { "type": "array", "items": { "type": "object", "additionalProperties": true } }
187
+ ]
188
+ },
189
+ "requirements": {
190
+ "oneOf": [
191
+ { "$ref": "#/$defs/dodNa" },
192
+ { "type": "array", "items": { "type": "object", "additionalProperties": true } }
193
+ ]
194
+ },
195
+ "architecture": {
196
+ "oneOf": [
197
+ { "$ref": "#/$defs/dodNa" },
198
+ { "type": "array", "items": { "type": "object", "additionalProperties": true } }
199
+ ]
200
+ },
201
+ "models": {
202
+ "oneOf": [
203
+ { "$ref": "#/$defs/dodNa" },
204
+ { "type": "array", "items": { "$ref": "#/$defs/dodModel" } }
205
+ ]
206
+ },
207
+ "lineup": {
208
+ "oneOf": [
209
+ { "$ref": "#/$defs/dodNa" },
210
+ { "type": "array", "items": { "type": "object", "additionalProperties": true } }
211
+ ]
212
+ },
213
+ "tasks": {
214
+ "oneOf": [
215
+ { "$ref": "#/$defs/dodNa" },
216
+ { "type": "array", "items": { "type": "object", "additionalProperties": true } }
217
+ ]
218
+ },
219
+ "vv": {
220
+ "oneOf": [
221
+ { "$ref": "#/$defs/dodNa" },
222
+ { "type": "array", "items": { "type": "object", "additionalProperties": true } }
223
+ ]
224
+ },
225
+ "documentation": {
226
+ "oneOf": [
227
+ { "$ref": "#/$defs/dodNa" },
228
+ { "type": "array", "items": { "type": "object", "additionalProperties": true } }
229
+ ]
230
+ },
231
+ "risks": {
232
+ "oneOf": [
233
+ { "$ref": "#/$defs/dodNa" },
234
+ { "type": "array", "items": { "type": "object", "additionalProperties": true } }
235
+ ]
236
+ },
237
+ "authority": {
238
+ "oneOf": [
239
+ { "$ref": "#/$defs/dodNa" },
240
+ { "type": "array", "items": { "type": "object", "additionalProperties": true } }
241
+ ]
242
+ },
243
+ "closeout": {
244
+ "type": "object",
245
+ "additionalProperties": true,
246
+ "description": "Append-only actuals. Must not rewrite planning projection fields.",
247
+ "properties": {
248
+ "candidate": { "type": ["string", "null"] },
249
+ "changed_paths": { "type": "array" },
250
+ "evidence": { "type": "array" },
251
+ "open_gaps": { "type": "array" },
252
+ "owner_acceptance": { "type": ["string", "null"] },
253
+ "slice_receipts": { "type": "array" },
254
+ "amendments": { "type": "array" },
255
+ "owner_amendments": { "type": "array" },
256
+ "documentation_completion": true,
257
+ "anomalies": true,
258
+ "repairs": true,
259
+ "recovery_used": true,
260
+ "residual_gaps": { "type": "array" },
261
+ "completion_status": { "type": "string" },
262
+ "model_views": {
263
+ "type": "array",
264
+ "description": "Append-only supplemental rendering bindings keyed by planning model_id. Each entry may carry digest and view only. Duplicate, unknown, semantically overriding, or already-planned bindings are rejected.",
265
+ "items": {
266
+ "type": "object",
267
+ "additionalProperties": false,
268
+ "required": ["model_id", "digest", "view"],
269
+ "properties": {
270
+ "model_id": { "type": "string", "minLength": 1 },
271
+ "digest": { "type": "string", "minLength": 1 },
272
+ "view": { "$ref": "#/$defs/dodView" }
273
+ }
274
+ }
275
+ }
276
+ }
277
+ }
278
+ }
111
279
  }
112
280
  },
113
281
  "properties": {
@@ -129,10 +297,40 @@
129
297
  "k": { "type": "integer", "minimum": 0 },
130
298
  "c": { "type": "integer", "minimum": 0 }
131
299
  }
132
- }
300
+ },
301
+ "development_strategy": {
302
+ "type": "object",
303
+ "additionalProperties": true,
304
+ "required": ["mode"],
305
+ "properties": {
306
+ "mode": { "type": "string", "enum": ["single_implementer", "tdd"] }
307
+ }
308
+ },
309
+ "assurance_cadence": {
310
+ "type": "object",
311
+ "additionalProperties": true,
312
+ "properties": {
313
+ "test_engineer": {
314
+ "type": "object",
315
+ "additionalProperties": true,
316
+ "properties": {
317
+ "assurance": { "type": "string", "enum": ["slice", "phase", "lifecycle"] }
318
+ }
319
+ },
320
+ "reviewer": { "type": "string", "enum": ["slice", "phase", "lifecycle"] },
321
+ "integration_engineer": {
322
+ "type": "object",
323
+ "additionalProperties": true,
324
+ "properties": {
325
+ "execution": { "type": "string", "enum": ["slice", "phase", "lifecycle"] }
326
+ }
327
+ }
328
+ }
329
+ },
330
+ "planning_to_implementation_clean_session": { "type": "boolean" }
133
331
  }
134
332
  },
135
- "lineup_freeze": { "$ref": "#/$defs/contentfulObject" },
333
+ "profile_freeze": { "$ref": "#/$defs/contentfulObject" },
136
334
  "waves": {
137
335
  "type": "array",
138
336
  "items": { "type": "object", "additionalProperties": true }
@@ -145,6 +343,7 @@
145
343
  "type": "array",
146
344
  "items": { "$ref": "#/$defs/slice" }
147
345
  },
148
- "traceability": { "$ref": "#/$defs/contentfulObject" }
346
+ "traceability": { "$ref": "#/$defs/contentfulObject" },
347
+ "dod_manifest": { "$ref": "#/$defs/dodManifest" }
149
348
  }
150
349
  }