onto-mcp 0.4.12 → 0.4.13

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 (128) hide show
  1. package/.onto/authority/core-lexicon.yaml +1 -1
  2. package/.onto/authority/supported-models.yaml +137 -0
  3. package/.onto/domains/accounting-kr/competency_qs.md +153 -153
  4. package/.onto/processes/reconstruct/obligation-coverage-ledger.yaml +842 -0
  5. package/.onto/processes/reconstruct/obligation-coverage-recorded.yaml +225 -0
  6. package/.onto/processes/reconstruct/ontology-seeding-and-maturation-design.md +93 -54
  7. package/.onto/processes/reconstruct/reconstruct-contract-registry.yaml +169 -8
  8. package/.onto/processes/reconstruct/reconstruct-execution-ux-contract.md +72 -1
  9. package/.onto/processes/reconstruct/source-profiles/spreadsheet.md +35 -0
  10. package/.onto/processes/review/productized-live-path.md +9 -5
  11. package/.onto/processes/review/prompt-execution-runner-contract.md +32 -8
  12. package/.onto/processes/review/review-target-profile-contract.md +47 -4
  13. package/.onto/processes/shared/pipeline-execution-ledger-contract.md +11 -0
  14. package/.onto/processes/shared/target-material-kind-contract.md +1 -1
  15. package/README.md +64 -1
  16. package/dist/cli.js +11 -1
  17. package/dist/core-api/reconstruct-api.js +575 -24
  18. package/dist/core-api/review-api.js +70 -5
  19. package/dist/core-api/review-progress.js +7 -0
  20. package/dist/core-api/runtime-observation.js +7 -0
  21. package/dist/core-runtime/artifact-io.js +75 -3
  22. package/dist/core-runtime/cli/claude-code-review-unit-executor.js +19 -8
  23. package/dist/core-runtime/cli/materialize-review-prompt-packets.js +68 -14
  24. package/dist/core-runtime/cli/prepare-review-session.js +34 -1
  25. package/dist/core-runtime/cli/render-review-final-output.js +2 -1
  26. package/dist/core-runtime/cli/run-review-prompt-execution.js +758 -48
  27. package/dist/core-runtime/cli/run-seats.js +18 -0
  28. package/dist/core-runtime/cli/runtime-submit-context.js +10 -2
  29. package/dist/core-runtime/cli/unit-resubmit.js +269 -0
  30. package/dist/core-runtime/cli/worker-structured-output.js +80 -2
  31. package/dist/core-runtime/discovery/host-detection.js +71 -22
  32. package/dist/core-runtime/discovery/review-cert-assemble.js +208 -0
  33. package/dist/core-runtime/discovery/review-cert-record.js +442 -0
  34. package/dist/core-runtime/discovery/review-execution-units.js +22 -0
  35. package/dist/core-runtime/discovery/seat-inventory.js +167 -0
  36. package/dist/core-runtime/discovery/settings-chain.js +311 -52
  37. package/dist/core-runtime/discovery/supported-models.js +282 -29
  38. package/dist/core-runtime/discovery/synthesize-cert-assemble.js +197 -0
  39. package/dist/core-runtime/discovery/synthesize-cert-capsule.js +399 -0
  40. package/dist/core-runtime/discovery/synthesize-cert-judge.js +340 -0
  41. package/dist/core-runtime/discovery/synthesize-cert-loop.js +234 -0
  42. package/dist/core-runtime/discovery/synthesize-cert-mutation.js +117 -0
  43. package/dist/core-runtime/discovery/synthesize-cert-packet.js +182 -0
  44. package/dist/core-runtime/discovery/synthesize-cert-record.js +831 -0
  45. package/dist/core-runtime/discovery/synthesize-cert-sampler.js +336 -0
  46. package/dist/core-runtime/effort-calibration-ingest.js +135 -0
  47. package/dist/core-runtime/llm/dispatch-breaker.js +412 -0
  48. package/dist/core-runtime/llm/dispatch-fallback-adapter-capabilities.js +33 -0
  49. package/dist/core-runtime/llm/llm-caller.js +188 -47
  50. package/dist/core-runtime/llm/mock-llm-realization.js +35 -4
  51. package/dist/core-runtime/llm/model-switcher.js +1 -1
  52. package/dist/core-runtime/llm/openai-responses-incomplete-error.js +46 -0
  53. package/dist/core-runtime/llm/sealed-dispatch-capability.js +359 -0
  54. package/dist/core-runtime/llm/structured-dispatch-error.js +15 -0
  55. package/dist/core-runtime/onboard/claude-profile-scan.js +65 -0
  56. package/dist/core-runtime/onboard/cli-host.js +5 -2
  57. package/dist/core-runtime/onboard/configure-provider.js +33 -1
  58. package/dist/core-runtime/onboard/host-target.js +28 -3
  59. package/dist/core-runtime/onboard/register.js +54 -12
  60. package/dist/core-runtime/pipeline-execution-ledger.js +10 -0
  61. package/dist/core-runtime/reconstruct/artifact-types.js +35 -0
  62. package/dist/core-runtime/reconstruct/claim-projection-validation.js +17 -0
  63. package/dist/core-runtime/reconstruct/competency-projection-contract.js +63 -0
  64. package/dist/core-runtime/reconstruct/comprehension-artifact.js +321 -0
  65. package/dist/core-runtime/reconstruct/comprehension-reduce.js +367 -0
  66. package/dist/core-runtime/reconstruct/comprehension-semantic-map.js +771 -0
  67. package/dist/core-runtime/reconstruct/contract-registry.js +20 -0
  68. package/dist/core-runtime/reconstruct/dispatch-fallback-artifacts.js +640 -0
  69. package/dist/core-runtime/reconstruct/execution-telemetry.js +83 -9
  70. package/dist/core-runtime/reconstruct/final-output-sections.js +89 -0
  71. package/dist/core-runtime/reconstruct/governing-snapshot.js +36 -1
  72. package/dist/core-runtime/reconstruct/leaf-reader.js +305 -0
  73. package/dist/core-runtime/reconstruct/llm-dispatch-failure.js +270 -0
  74. package/dist/core-runtime/reconstruct/llm-touch-fingerprint.js +94 -0
  75. package/dist/core-runtime/reconstruct/material-admission-validation.js +18 -0
  76. package/dist/core-runtime/reconstruct/material-profile-validation.js +14 -0
  77. package/dist/core-runtime/reconstruct/materialize-preparation.js +304 -18
  78. package/dist/core-runtime/reconstruct/maturation-validation.js +1231 -53
  79. package/dist/core-runtime/reconstruct/mock-llm-realization.js +107 -3
  80. package/dist/core-runtime/reconstruct/obligation-assertion.js +18 -0
  81. package/dist/core-runtime/reconstruct/ontology-seed-validation.js +27 -0
  82. package/dist/core-runtime/reconstruct/output-budget.js +6 -0
  83. package/dist/core-runtime/reconstruct/pipeline-execution-ledger.js +87 -3
  84. package/dist/core-runtime/reconstruct/post-seed-validation.js +118 -5
  85. package/dist/core-runtime/reconstruct/purpose-authority-validation.js +117 -2
  86. package/dist/core-runtime/reconstruct/record.js +42 -0
  87. package/dist/core-runtime/reconstruct/registry-verification-validation.js +16 -0
  88. package/dist/core-runtime/reconstruct/run-control-validation.js +1011 -34
  89. package/dist/core-runtime/reconstruct/run.js +5198 -536
  90. package/dist/core-runtime/reconstruct/seed-authoring-readiness-validation.js +54 -3
  91. package/dist/core-runtime/reconstruct/semantic-quality-gate.js +105 -2
  92. package/dist/core-runtime/reconstruct/source-observation-delta-validation.js +41 -0
  93. package/dist/core-runtime/reconstruct/source-observations.js +166 -0
  94. package/dist/core-runtime/reconstruct/source-safety-validation.js +71 -183
  95. package/dist/core-runtime/reconstruct/source-scout-pack-validation.js +37 -1
  96. package/dist/core-runtime/reconstruct/terminal-validation.js +132 -2
  97. package/dist/core-runtime/review/continuation-plan.js +12 -5
  98. package/dist/core-runtime/review/issue-artifact-runtime.js +4 -1
  99. package/dist/core-runtime/review/materializers.js +100 -11
  100. package/dist/core-runtime/review/nesting-batch.js +6 -2
  101. package/dist/core-runtime/review/obligation-shardability.js +138 -0
  102. package/dist/core-runtime/review/pipeline-execution-ledger.js +42 -2
  103. package/dist/core-runtime/review/review-artifact-utils.js +217 -5
  104. package/dist/core-runtime/review/review-execution-steps.js +32 -8
  105. package/dist/core-runtime/review/review-progress-contract.js +8 -0
  106. package/dist/core-runtime/review/review-prompt-budget.js +97 -0
  107. package/dist/core-runtime/review/semantic-quality-gate.js +33 -7
  108. package/dist/core-runtime/review/spreadsheet-review-disposition.js +94 -0
  109. package/dist/core-runtime/route-identity.js +182 -0
  110. package/dist/core-runtime/spreadsheet-header-escalation.js +204 -0
  111. package/dist/core-runtime/spreadsheet-structure-observer.js +2689 -0
  112. package/dist/core-runtime/target-material-kind.js +58 -0
  113. package/dist/mcp/server.js +12 -5
  114. package/dist/tui/app.js +167 -0
  115. package/dist/tui/data/event-follower.js +116 -0
  116. package/dist/tui/data/node-detail.js +42 -0
  117. package/dist/tui/data/projection-poll.js +26 -0
  118. package/dist/tui/data/session-discovery.js +65 -0
  119. package/dist/tui/index.js +108 -0
  120. package/dist/tui/screens/log.js +25 -0
  121. package/dist/tui/screens/node-detail.js +17 -0
  122. package/dist/tui/screens/session-selector.js +26 -0
  123. package/dist/tui/screens/workflow-tree.js +90 -0
  124. package/dist/tui/view-model/reconstruct-adapter.js +162 -0
  125. package/dist/tui/view-model/review-adapter.js +270 -0
  126. package/dist/tui/view-model/tree-view-model.js +19 -0
  127. package/package.json +18 -3
  128. package/settings.example.json +77 -16
@@ -3,11 +3,10 @@ import fs from "node:fs/promises";
3
3
  import path from "node:path";
4
4
  import { parse as parseYaml } from "yaml";
5
5
  import { assertArrayField, atomicWriteYamlDocument as writeYamlDocument } from "../artifact-io.js";
6
+ import { assertObligation } from "./obligation-assertion.js";
6
7
  const SOURCE_SAFETY_CANONICAL_AXES = [
7
8
  "lifecycle_state",
8
9
  "authorization_state",
9
- "privacy_state",
10
- "redaction_state",
11
10
  "proof_sufficiency_state",
12
11
  "replay_state",
13
12
  ];
@@ -28,26 +27,13 @@ const AUTHORIZATION_STATES = [
28
27
  "unknown",
29
28
  "not_required",
30
29
  ];
31
- const PRIVACY_STATES = [
32
- "non_sensitive",
33
- "privacy_sensitive",
34
- "unknown",
35
- ];
36
- const REDACTION_STATES = [
37
- "none",
38
- "redacted",
39
- "required",
40
- "insufficient",
41
- ];
42
30
  const PROOF_SUFFICIENCY_STATES = [
43
31
  "sufficient_for_claim",
44
32
  "insufficient_for_claim",
45
- "trace_only",
46
33
  "unavailable",
47
34
  ];
48
35
  const REPLAY_STATES = [
49
36
  "replay_allowed",
50
- "replay_with_redaction",
51
37
  "no_replay_use",
52
38
  "unknown",
53
39
  ];
@@ -61,32 +47,9 @@ const INTENDED_CONSUMPTIONS = [
61
47
  const VISIBILITY_TIERS = [
62
48
  "consumption_allowed",
63
49
  "internal_only",
64
- "redacted_output_only",
65
50
  "no_prompt_use",
66
51
  "no_replay_use",
67
52
  ];
68
- const ALLOWED_PROOF_FORMS = [
69
- "raw_value",
70
- "hash",
71
- "bounded_summary",
72
- "source_ref_only",
73
- "unavailable",
74
- ];
75
- const SAFE_REDACTED_PROOF_FORMS = new Set([
76
- "hash",
77
- "bounded_summary",
78
- "source_ref_only",
79
- ]);
80
- const SENSITIVE_SOURCE_PATTERNS = [
81
- /-----BEGIN (?:RSA |EC |OPENSSH |DSA |PGP )?PRIVATE KEY-----/,
82
- /\b(?:api[_-]?key|secret|password|passwd|pwd|token)\b\s*[:=]\s*['"]?[^'"\s]{8,}/i,
83
- /\bAuthorization:\s*Bearer\s+[A-Za-z0-9._~+/=-]{12,}/i,
84
- /\b(?:AKIA|ASIA)[A-Z0-9]{16}\b/,
85
- /\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/i,
86
- /\b\d{3}-\d{2}-\d{4}\b/,
87
- /\b\d{6}-[1-4]\d{6}\b/,
88
- /\b(?:\+?\d{1,3}[-.\s]?)?(?:\(?\d{2,4}\)?[-.\s]?)\d{3,4}[-.\s]?\d{4}\b/,
89
- ];
90
53
  function isoNow() {
91
54
  return new Date().toISOString();
92
55
  }
@@ -107,24 +70,9 @@ function violation(args) {
107
70
  function inList(value, allowed) {
108
71
  return typeof value === "string" && allowed.includes(value);
109
72
  }
110
- function arrayValues(value, allowed) {
111
- if (!Array.isArray(value))
112
- return [];
113
- return value.filter((item) => inList(item, allowed));
114
- }
115
73
  export function sourceSafetyRowIdForObservation(observation, intendedConsumption = "prompt_context") {
116
74
  return `source_safety:${observation.observation_id}:${intendedConsumption}`;
117
75
  }
118
- function observationContentExcerpt(observation) {
119
- const excerpt = observation.structural_data.content_excerpt;
120
- return typeof excerpt === "string" ? excerpt : null;
121
- }
122
- function hasSensitiveSourceEvidence(observation) {
123
- const excerpt = observationContentExcerpt(observation);
124
- if (!excerpt)
125
- return false;
126
- return SENSITIVE_SOURCE_PATTERNS.some((pattern) => pattern.test(excerpt));
127
- }
128
76
  function explicitConsumptionAuthorizations(observation) {
129
77
  const rawAuthorizations = observation.structural_data.source_safety_consumption_authorizations;
130
78
  if (!Array.isArray(rawAuthorizations))
@@ -144,9 +92,6 @@ function stableRuleRef(row) {
144
92
  .slice(0, 16);
145
93
  return `source-safety-visibility-v1:${digest}`;
146
94
  }
147
- function hasSafeRedactedProofForm(row) {
148
- return row.redaction_evidence.allowed_proof_forms.some((form) => SAFE_REDACTED_PROOF_FORMS.has(form));
149
- }
150
95
  export function deriveSourceSafetyVisibilityTier(row) {
151
96
  const intendedConsumption = row.visibility_derivation.intended_consumption;
152
97
  if (intendedConsumption === "replay" &&
@@ -159,9 +104,7 @@ export function deriveSourceSafetyVisibilityTier(row) {
159
104
  row.authorization_state === "unknown") {
160
105
  return "no_prompt_use";
161
106
  }
162
- if (row.proof_sufficiency_state === "unavailable" ||
163
- row.redaction_state === "insufficient" ||
164
- row.redaction_evidence.allowed_proof_forms.includes("unavailable")) {
107
+ if (row.proof_sufficiency_state === "unavailable") {
165
108
  return "no_prompt_use";
166
109
  }
167
110
  if (intendedConsumption === "material_claim" &&
@@ -172,15 +115,6 @@ export function deriveSourceSafetyVisibilityTier(row) {
172
115
  row.proof_sufficiency_state !== "sufficient_for_claim") {
173
116
  return "internal_only";
174
117
  }
175
- if (row.redaction_state === "redacted" ||
176
- row.redaction_state === "required" ||
177
- row.replay_state === "replay_with_redaction" ||
178
- row.privacy_state === "privacy_sensitive") {
179
- return hasSafeRedactedProofForm(row) ? "redacted_output_only" : "internal_only";
180
- }
181
- if (row.privacy_state === "unknown") {
182
- return hasSafeRedactedProofForm(row) ? "redacted_output_only" : "internal_only";
183
- }
184
118
  if (intendedConsumption === "public_output" ||
185
119
  intendedConsumption === "prompt_context" ||
186
120
  intendedConsumption === "material_claim") {
@@ -189,40 +123,29 @@ export function deriveSourceSafetyVisibilityTier(row) {
189
123
  return "internal_only";
190
124
  }
191
125
  function buildSafetyRowForObservation(observation, intendedConsumption) {
192
- const sensitive = hasSensitiveSourceEvidence(observation);
193
- const excerpt = observationContentExcerpt(observation);
194
126
  const explicitlyAuthorized = explicitConsumptionAuthorizations(observation)
195
127
  .has(intendedConsumption);
196
- const consumptionAuthorized = runtimeInternalConsumption(intendedConsumption) || explicitlyAuthorized;
197
- const allowedProofForms = sensitive
198
- ? ["hash", "bounded_summary", "source_ref_only"]
199
- : !consumptionAuthorized
200
- ? ["hash", "bounded_summary", "source_ref_only"]
201
- : excerpt
202
- ? ["raw_value", "hash", "bounded_summary", "source_ref_only"]
203
- : ["hash", "bounded_summary", "source_ref_only"];
128
+ // Defect-3 basis A (runtime-target provenance): a user-provided reconstruct
129
+ // runtime-target source authorizes the outward tiers (material_claim,
130
+ // public_output) by provenance. Scoped to the two upper tiers only — the
131
+ // internal tiers are already covered by runtimeInternalConsumption, and a
132
+ // non-target observation (is_runtime_target_source absent/false) never gets
133
+ // basis A, so this cannot leak to frontier-discovered sources.
134
+ const provenanceAuthorized = observation.is_runtime_target_source === true &&
135
+ (intendedConsumption === "material_claim" ||
136
+ intendedConsumption === "public_output");
137
+ const consumptionAuthorized = runtimeInternalConsumption(intendedConsumption) || explicitlyAuthorized ||
138
+ provenanceAuthorized;
204
139
  const rowBase = {
205
140
  safety_row_id: sourceSafetyRowIdForObservation(observation, intendedConsumption),
206
141
  subject_ref: observation.source_ref,
207
142
  subject_kind: "source_ref",
208
143
  lifecycle_state: "active",
209
144
  authorization_state: consumptionAuthorized ? "authorized" : "unknown",
210
- privacy_state: sensitive
211
- ? "privacy_sensitive"
212
- : consumptionAuthorized
213
- ? "non_sensitive"
214
- : "unknown",
215
- redaction_state: sensitive ? "required" : consumptionAuthorized ? "none" : "insufficient",
216
- proof_sufficiency_state: sensitive
217
- ? "trace_only"
218
- : consumptionAuthorized
219
- ? "sufficient_for_claim"
220
- : "insufficient_for_claim",
221
- replay_state: sensitive
222
- ? "replay_with_redaction"
223
- : consumptionAuthorized
224
- ? "replay_allowed"
225
- : "unknown",
145
+ proof_sufficiency_state: consumptionAuthorized
146
+ ? "sufficient_for_claim"
147
+ : "insufficient_for_claim",
148
+ replay_state: consumptionAuthorized ? "replay_allowed" : "unknown",
226
149
  visibility_tier: "internal_only",
227
150
  visibility_derivation: {
228
151
  intended_consumption: intendedConsumption,
@@ -237,11 +160,6 @@ function buildSafetyRowForObservation(observation, intendedConsumption) {
237
160
  ? "source_safety_explicit_consumption_authorization"
238
161
  : "runtime_target_ref_read_scope"
239
162
  : null,
240
- redaction_evidence: {
241
- raw_value_available: excerpt !== null,
242
- allowed_proof_forms: allowedProofForms,
243
- redaction_rule_ref: sensitive ? "source-safety-sensitive-source-pattern-v1" : null,
244
- },
245
163
  tombstone: {
246
164
  tombstone_ref: null,
247
165
  reason: null,
@@ -249,9 +167,6 @@ function buildSafetyRowForObservation(observation, intendedConsumption) {
249
167
  downstream_refs: [],
250
168
  },
251
169
  limitation_refs: [
252
- ...(sensitive
253
- ? [`source-safety-sensitive-source:${observation.observation_id}`]
254
- : []),
255
170
  ...(!consumptionAuthorized
256
171
  ? [
257
172
  `source-safety-consumption-authorization-gap:${observation.observation_id}:${intendedConsumption}`,
@@ -296,55 +211,8 @@ function validateCanonicalAxes(args) {
296
211
  SOURCE_SAFETY_CANONICAL_AXES.some((axis) => !axisSet.has(axis))) {
297
212
  args.violations.push(violation({
298
213
  code: "visibility_axis_set_invalid",
299
- message: "visibility_derivation.derived_from_axes must contain exactly the six canonical source-safety axes",
300
- subjectId: args.row.safety_row_id,
301
- }));
302
- }
303
- }
304
- function validateSupportingDetailConsistency(args) {
305
- if (args.row.redaction_state !== "none" &&
306
- args.row.redaction_evidence.allowed_proof_forms.includes("raw_value")) {
307
- args.violations.push(violation({
308
- code: "supporting_detail_contradiction",
309
- message: "redaction_evidence.allowed_proof_forms cannot grant raw_value when top-level redaction_state requires or applies redaction",
310
- subjectId: args.row.safety_row_id,
311
- axis: "redaction_state",
312
- }));
313
- }
314
- if (args.row.redaction_state === "none" &&
315
- args.row.redaction_evidence.redaction_rule_ref) {
316
- args.violations.push(violation({
317
- code: "supporting_detail_contradiction",
318
- message: "redaction_evidence.redaction_rule_ref must be null when top-level redaction_state is none",
319
- subjectId: args.row.safety_row_id,
320
- axis: "redaction_state",
321
- }));
322
- }
323
- if (args.row.proof_sufficiency_state === "unavailable" &&
324
- !args.row.redaction_evidence.allowed_proof_forms.includes("unavailable")) {
325
- args.violations.push(violation({
326
- code: "supporting_detail_contradiction",
327
- message: "proof_sufficiency_state unavailable must be supported by allowed_proof_forms including unavailable",
214
+ message: "visibility_derivation.derived_from_axes must contain exactly the four canonical source-safety axes",
328
215
  subjectId: args.row.safety_row_id,
329
- axis: "proof_sufficiency_state",
330
- }));
331
- }
332
- if (args.row.redaction_evidence.allowed_proof_forms.includes("unavailable") &&
333
- args.row.proof_sufficiency_state !== "unavailable") {
334
- args.violations.push(violation({
335
- code: "supporting_detail_contradiction",
336
- message: "allowed_proof_forms unavailable contradicts a top-level proof_sufficiency_state that is not unavailable",
337
- subjectId: args.row.safety_row_id,
338
- axis: "proof_sufficiency_state",
339
- }));
340
- }
341
- if (args.row.redaction_evidence.raw_value_available === false &&
342
- args.row.redaction_evidence.allowed_proof_forms.includes("raw_value")) {
343
- args.violations.push(violation({
344
- code: "supporting_detail_contradiction",
345
- message: "redaction_evidence cannot allow raw_value when raw_value_available is false",
346
- subjectId: args.row.safety_row_id,
347
- axis: "redaction_state",
348
216
  }));
349
217
  }
350
218
  }
@@ -354,9 +222,6 @@ function normalizeSafetyRow(rawRow) {
354
222
  const derivation = isRecord(rawRow.visibility_derivation)
355
223
  ? rawRow.visibility_derivation
356
224
  : {};
357
- const redactionEvidence = isRecord(rawRow.redaction_evidence)
358
- ? rawRow.redaction_evidence
359
- : {};
360
225
  const tombstone = isRecord(rawRow.tombstone) ? rawRow.tombstone : {};
361
226
  return {
362
227
  safety_row_id: typeof rawRow.safety_row_id === "string" ? rawRow.safety_row_id : "",
@@ -370,12 +235,6 @@ function normalizeSafetyRow(rawRow) {
370
235
  authorization_state: inList(rawRow.authorization_state, AUTHORIZATION_STATES)
371
236
  ? rawRow.authorization_state
372
237
  : "",
373
- privacy_state: inList(rawRow.privacy_state, PRIVACY_STATES)
374
- ? rawRow.privacy_state
375
- : "",
376
- redaction_state: inList(rawRow.redaction_state, REDACTION_STATES)
377
- ? rawRow.redaction_state
378
- : "",
379
238
  proof_sufficiency_state: inList(rawRow.proof_sufficiency_state, PROOF_SUFFICIENCY_STATES)
380
239
  ? rawRow.proof_sufficiency_state
381
240
  : "",
@@ -389,7 +248,13 @@ function normalizeSafetyRow(rawRow) {
389
248
  intended_consumption: inList(derivation.intended_consumption, INTENDED_CONSUMPTIONS)
390
249
  ? derivation.intended_consumption
391
250
  : "",
392
- derived_from_axes: arrayValues(derivation.derived_from_axes, SOURCE_SAFETY_CANONICAL_AXES),
251
+ // Keep every string entry as-is (do NOT filter to the canonical set): a stale
252
+ // or tampered row carrying retired axes (e.g. privacy_state/redaction_state)
253
+ // must reach validateCanonicalAxes and fail `visibility_axis_set_invalid`,
254
+ // not be silently laundered down to exactly the four canonical axes.
255
+ derived_from_axes: (Array.isArray(derivation.derived_from_axes)
256
+ ? derivation.derived_from_axes.filter((axis) => typeof axis === "string")
257
+ : []),
393
258
  derivation_rule_ref: typeof derivation.derivation_rule_ref === "string"
394
259
  ? derivation.derivation_rule_ref
395
260
  : "",
@@ -397,13 +262,6 @@ function normalizeSafetyRow(rawRow) {
397
262
  authorization_scope_ref: typeof rawRow.authorization_scope_ref === "string"
398
263
  ? rawRow.authorization_scope_ref
399
264
  : null,
400
- redaction_evidence: {
401
- raw_value_available: redactionEvidence.raw_value_available === true,
402
- allowed_proof_forms: arrayValues(redactionEvidence.allowed_proof_forms, ALLOWED_PROOF_FORMS),
403
- redaction_rule_ref: typeof redactionEvidence.redaction_rule_ref === "string"
404
- ? redactionEvidence.redaction_rule_ref
405
- : null,
406
- },
407
265
  tombstone: {
408
266
  tombstone_ref: typeof tombstone.tombstone_ref === "string" ? tombstone.tombstone_ref : null,
409
267
  reason: typeof tombstone.reason === "string" ? tombstone.reason : null,
@@ -420,6 +278,23 @@ function normalizeSafetyRow(rawRow) {
420
278
  export function validateSourceSafetyLedger(args) {
421
279
  assertArrayField(args.sourceObservations.observations, "source-observations", "observations");
422
280
  const violations = [];
281
+ // G(a) deferred-7 slice 1: record the three obligations this validator fully enforces. Stamped here,
282
+ // before the per-row loop, so they fire on zero-row input (the enforcement sites exist unconditionally).
283
+ // asserted_obligation_ids is in-memory-only telemetry (Stage 0 #145): it is stripped at the write
284
+ // boundary and excluded from reuseMatchArtifactHash, so stamping this reuse-hashed validation artifact
285
+ // does not rotate reuse provenance. PARKED (see obligation-coverage-ledger.yaml):
286
+ // - preserve_..._consumption_boundaries: no independent "no substitution" enforcer (the per-consumption
287
+ // required rows are the only related check), so it cannot bind non-overlappingly.
288
+ // - validate_every_observation_has_source_safety_rows_for_each_intended_consumption (codex #147 P1):
289
+ // the required-row pass only proves each source_safety:<obs>:<consumption> ID STRING is present; it
290
+ // never binds that ID suffix to the row's visibility_derivation.intended_consumption (the field
291
+ // deriveSourceSafetyVisibilityTier uses). So a public_output-ID row can carry prompt_context
292
+ // derivation and a tier derived for the wrong consumption, pass, and mislead downstream lookup-by-ID.
293
+ // PARK pending an ID-suffix ↔ intended_consumption binding.
294
+ const assertedObligationIds = [];
295
+ assertObligation(assertedObligationIds, "validate_exactly_four_canonical_source_safety_axes");
296
+ assertObligation(assertedObligationIds, "validate_source_safety_subject_refs_against_observed_source_refs");
297
+ assertObligation(assertedObligationIds, "validate_visibility_tier_is_derived_not_independent_authority");
423
298
  const rawLedger = args.sourceSafetyLedger;
424
299
  if (!isRecord(rawLedger) || !Array.isArray(rawLedger.safety_rows)) {
425
300
  violations.push(violation({
@@ -510,13 +385,6 @@ export function validateSourceSafetyLedger(args) {
510
385
  allowed: AUTHORIZATION_STATES,
511
386
  axis: "authorization_state",
512
387
  },
513
- { field: "privacy_state", value: row.privacy_state, allowed: PRIVACY_STATES, axis: "privacy_state" },
514
- {
515
- field: "redaction_state",
516
- value: row.redaction_state,
517
- allowed: REDACTION_STATES,
518
- axis: "redaction_state",
519
- },
520
388
  {
521
389
  field: "proof_sufficiency_state",
522
390
  value: row.proof_sufficiency_state,
@@ -543,15 +411,7 @@ export function validateSourceSafetyLedger(args) {
543
411
  subjectId: row.safety_row_id,
544
412
  }));
545
413
  }
546
- if (row.redaction_evidence.allowed_proof_forms.length === 0) {
547
- violations.push(violation({
548
- code: "missing_required_field",
549
- message: "source safety row must name at least one allowed proof form",
550
- subjectId: row.safety_row_id,
551
- }));
552
- }
553
414
  validateCanonicalAxes({ row, violations });
554
- validateSupportingDetailConsistency({ row, violations });
555
415
  if (VISIBILITY_TIERS.includes(row.visibility_tier)) {
556
416
  const expected = deriveSourceSafetyVisibilityTier(row);
557
417
  if (row.visibility_tier !== expected) {
@@ -562,6 +422,34 @@ export function validateSourceSafetyLedger(args) {
562
422
  }));
563
423
  }
564
424
  }
425
+ // Defect-3 D3 (basis-attribution enforcement): an outward consumption tier
426
+ // (material_claim/public_output) that actually REACHES consumption_allowed MUST
427
+ // be justified by a canonical basis — A (the observation is the runtime target,
428
+ // is_runtime_target_source) or B (explicit source self-declaration). Without
429
+ // this, a forged/replayed row could reach consumption_allowed on a
430
+ // frontier-discovered/non-target source (the four axes alone don't bind to the
431
+ // basis). The trigger keys on the DERIVED outcome, not a single
432
+ // authorization_state literal: both "authorized" AND "not_required" derive to
433
+ // consumption_allowed for these tiers, so checking only "authorized" left a
434
+ // "not_required" bypass. Internal tiers are exempt (runtime read scope), and a
435
+ // legit non-target row derives to no_prompt_use (producer emits "unknown"), so
436
+ // D3 stays silent there.
437
+ const consumption = row.visibility_derivation.intended_consumption;
438
+ if ((consumption === "material_claim" || consumption === "public_output") &&
439
+ deriveSourceSafetyVisibilityTier(row) === "consumption_allowed" &&
440
+ expectedObservation) {
441
+ const basisA = expectedObservation.is_runtime_target_source === true;
442
+ const basisB = explicitConsumptionAuthorizations(expectedObservation)
443
+ .has(consumption);
444
+ if (!basisA && !basisB) {
445
+ violations.push(violation({
446
+ code: "unjustified_consumption_authorization",
447
+ message: `source safety row ${row.safety_row_id} reaches consumption_allowed for ${consumption} without a runtime-target-provenance (A) or explicit-source (B) authorization basis`,
448
+ subjectId: row.safety_row_id,
449
+ axis: "authorization_state",
450
+ }));
451
+ }
452
+ }
565
453
  }
566
454
  for (const requiredSafetyRowId of requiredSafetyRowIds) {
567
455
  if (!rowIds.has(requiredSafetyRowId)) {
@@ -583,10 +471,10 @@ export function validateSourceSafetyLedger(args) {
583
471
  safety_row_count: rows.length,
584
472
  no_prompt_use_count: rows.filter((row) => row.visibility_tier === "no_prompt_use")
585
473
  .length,
586
- redacted_output_only_count: rows.filter((row) => row.visibility_tier === "redacted_output_only").length,
587
474
  validation_results: violations.length === 0
588
475
  ? ["source_safety_ledger_valid"]
589
476
  : ["source_safety_ledger_invalid"],
477
+ asserted_obligation_ids: assertedObligationIds,
590
478
  violations,
591
479
  };
592
480
  }
@@ -3,6 +3,7 @@ import fs from "node:fs/promises";
3
3
  import path from "node:path";
4
4
  import { parse as parseYaml } from "yaml";
5
5
  import { assertArrayField, atomicWriteYamlDocument as writeYamlDocument } from "../artifact-io.js";
6
+ import { assertObligation } from "./obligation-assertion.js";
6
7
  import { sourceSafetyRowIdForObservation } from "./source-safety-validation.js";
7
8
  const SIGNAL_AXES = [
8
9
  "actor",
@@ -172,7 +173,6 @@ function visibilityStateForSafetyRow(row) {
172
173
  case "consumption_allowed":
173
174
  return "prompt_visible";
174
175
  case "internal_only":
175
- case "redacted_output_only":
176
176
  return "redacted";
177
177
  case "no_prompt_use":
178
178
  case "no_replay_use":
@@ -405,6 +405,41 @@ export async function validateSourceScoutPack(args) {
405
405
  assertArrayField(args.sourceObservations.observations, "source-observations", "observations");
406
406
  assertArrayField(args.sourceScoutPack.profile_scout_coverage_slots, "source-scout-pack", "profile_scout_coverage_slots");
407
407
  const violations = [];
408
+ // G(a) deferred-7 slice 5 (LAST): ONE mode-agnostic fn is shared by THREE registry validator_ids
409
+ // (source-scout-pack[-pre-seed|-post-maturation]-validator) — no mode arg, one uniform check set. The harvest
410
+ // attributes this asserted set to ALL 3 validator_ids; recording only an obligation registered for all 3
411
+ // (no registry-absent pair). asserted_obligation_ids is in-memory-only telemetry (Stage 0 #145) on a
412
+ // reuse-hashed (run.ts source_scout_pack_validation_sha256) + scout-captured (sha256File raw-file) artifact,
413
+ // so stamping rotates neither channel. Stamped before the per-row loop so it fires on zero signal rows.
414
+ //
415
+ // RECORD 1 (shared across all 3 modes, uniformly enforced):
416
+ // - validate_signal_rows_resolve_to_source_observations → signal_observation_missing (per-row, UNCONDITIONAL:
417
+ // every signal row's observation_id must resolve in source-observations; +per-row content-sha match). The
418
+ // passed observations are bound to the chain via source_observations_hash_mismatch (pack snapshot hash vs
419
+ // sha256File(ref)); the resolution check itself does not depend on that pinning.
420
+ //
421
+ // PARKED 8 (ledger notes carry detail):
422
+ // - validate_prompt_visible_rows_have_source_safety_validation_refs (×3): the named per-row field
423
+ // source_safety_ledger_validation_ref is BUILT but never read by the validator — it checks source_safety_
424
+ // row_id (ledger row) + global validation_status + tier instead, so the named validation-ref field is
425
+ // unenforced (a row with valid row_id but null/wrong validation_ref passes).
426
+ // - validate_group_and_coverage_refs_resolve (base): "group" has no enforcement surface (no group structure);
427
+ // only coverage_slot→signal_row resolution (coverage_slot_signal_missing) is checked → compound, half missing.
428
+ // - validate_scout_pack_is_profile_local_and_contains_no_selected_purpose_required_element_refs (base):
429
+ // validateNoSelectedPurposeLeak is a JSON substring search (no-purpose-leak half, fragile); "profile-local"
430
+ // is never checked → compound, half missing.
431
+ // - validate_scout_scope_derives_from_target_material_profile_validation (base) /
432
+ // validate_snapshot_scope_derives_from_target_material_profile_validation (pre-seed+post-maturation):
433
+ // scout_scope is copied from the pack; only internal consistency (unsupported_scope_overclaimed) is checked,
434
+ // never recomputed/derived from the target-material-profile-validation content (scoutScopeState() unused here).
435
+ // - validate_snapshot_lineage_validation_ref_and_hash_match_current_lineage_authority (pre-seed+post-maturation):
436
+ // slice-3 null-read + caller-authority — sha256File(null)=null so a null lineage ref + null pack hash passes,
437
+ // and the "current lineage authority" is the caller-supplied ref, not derived from the chain.
438
+ // - validate_pre_seed_scout_snapshot_is_immutable_consumed_authority (pre-seed) /
439
+ // validate_post_maturation_scout_snapshot_is_immutable_audit_authority (post-maturation): only INPUT-artifact
440
+ // hashes (freshness) are checked; snapshot immutability / consumed-or-audit authority state is not enforced.
441
+ const assertedObligationIds = [];
442
+ assertObligation(assertedObligationIds, "validate_signal_rows_resolve_to_source_observations");
408
443
  // signal_rows is this validator's primary subject and is already shape-checked
409
444
  // gracefully below (→ schema_shape_invalid violation), so it is NOT asserted.
410
445
  const rawPack = args.sourceScoutPack;
@@ -653,6 +688,7 @@ export async function validateSourceScoutPack(args) {
653
688
  validation_results: violations.length === 0
654
689
  ? ["source_scout_pack_valid"]
655
690
  : ["source_scout_pack_invalid"],
691
+ asserted_obligation_ids: assertedObligationIds,
656
692
  violations,
657
693
  };
658
694
  }