onto-mcp 0.4.11 → 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.
- package/.onto/authority/core-lexicon.yaml +1 -1
- package/.onto/authority/supported-models.yaml +153 -0
- package/.onto/domains/accounting-kr/competency_qs.md +153 -153
- package/.onto/processes/reconstruct/obligation-coverage-ledger.yaml +842 -0
- package/.onto/processes/reconstruct/obligation-coverage-recorded.yaml +225 -0
- package/.onto/processes/reconstruct/ontology-seeding-and-maturation-design.md +93 -54
- package/.onto/processes/reconstruct/reconstruct-contract-registry.yaml +202 -49
- package/.onto/processes/reconstruct/reconstruct-execution-ux-contract.md +72 -1
- package/.onto/processes/reconstruct/source-profiles/spreadsheet.md +35 -0
- package/.onto/processes/review/nesting-batch-worker-contract.md +1 -1
- package/.onto/processes/review/productized-live-path.md +9 -5
- package/.onto/processes/review/prompt-execution-runner-contract.md +32 -8
- package/.onto/processes/review/review-target-profile-contract.md +47 -4
- package/.onto/processes/shared/pipeline-execution-ledger-contract.md +11 -0
- package/.onto/processes/shared/target-material-kind-contract.md +1 -1
- package/README.md +64 -1
- package/dist/cli.js +11 -1
- package/dist/core-api/reconstruct-api.js +679 -18
- package/dist/core-api/review-api.js +70 -5
- package/dist/core-api/review-progress.js +7 -0
- package/dist/core-api/runtime-observation.js +7 -0
- package/dist/core-runtime/artifact-io.js +131 -0
- package/dist/core-runtime/cli/claude-code-review-unit-executor.js +21 -9
- package/dist/core-runtime/cli/claude-nesting-batch-worker.js +4 -2
- package/dist/core-runtime/cli/materialize-review-prompt-packets.js +68 -14
- package/dist/core-runtime/cli/prepare-review-session.js +34 -1
- package/dist/core-runtime/cli/render-review-final-output.js +2 -1
- package/dist/core-runtime/cli/run-review-prompt-execution.js +758 -48
- package/dist/core-runtime/cli/run-seats.js +18 -0
- package/dist/core-runtime/cli/runtime-submit-context.js +10 -2
- package/dist/core-runtime/cli/unit-resubmit.js +269 -0
- package/dist/core-runtime/cli/worker-structured-output.js +80 -2
- package/dist/core-runtime/discovery/host-detection.js +71 -22
- package/dist/core-runtime/discovery/review-cert-assemble.js +208 -0
- package/dist/core-runtime/discovery/review-cert-record.js +442 -0
- package/dist/core-runtime/discovery/review-execution-units.js +22 -0
- package/dist/core-runtime/discovery/seat-inventory.js +167 -0
- package/dist/core-runtime/discovery/settings-chain.js +311 -52
- package/dist/core-runtime/discovery/supported-models.js +289 -25
- package/dist/core-runtime/discovery/synthesize-cert-assemble.js +197 -0
- package/dist/core-runtime/discovery/synthesize-cert-capsule.js +399 -0
- package/dist/core-runtime/discovery/synthesize-cert-judge.js +340 -0
- package/dist/core-runtime/discovery/synthesize-cert-loop.js +234 -0
- package/dist/core-runtime/discovery/synthesize-cert-mutation.js +117 -0
- package/dist/core-runtime/discovery/synthesize-cert-packet.js +182 -0
- package/dist/core-runtime/discovery/synthesize-cert-record.js +831 -0
- package/dist/core-runtime/discovery/synthesize-cert-sampler.js +336 -0
- package/dist/core-runtime/effort-calibration-ingest.js +326 -0
- package/dist/core-runtime/effort-calibration-reconstruct.js +47 -0
- package/dist/core-runtime/effort-calibration-review.js +22 -0
- package/dist/core-runtime/effort-calibration-sweep.js +110 -0
- package/dist/core-runtime/effort-frontier.js +134 -0
- package/dist/core-runtime/llm/claude-bin.js +77 -0
- package/dist/core-runtime/llm/dispatch-breaker.js +412 -0
- package/dist/core-runtime/llm/dispatch-fallback-adapter-capabilities.js +33 -0
- package/dist/core-runtime/llm/llm-caller.js +316 -35
- package/dist/core-runtime/llm/mock-llm-realization.js +35 -4
- package/dist/core-runtime/llm/model-switcher.js +1 -1
- package/dist/core-runtime/llm/openai-responses-incomplete-error.js +46 -0
- package/dist/core-runtime/llm/sealed-dispatch-capability.js +359 -0
- package/dist/core-runtime/llm/structured-dispatch-error.js +15 -0
- package/dist/core-runtime/onboard/claude-profile-scan.js +65 -0
- package/dist/core-runtime/onboard/cli-host.js +5 -2
- package/dist/core-runtime/onboard/configure-provider.js +33 -1
- package/dist/core-runtime/onboard/host-target.js +28 -3
- package/dist/core-runtime/onboard/register.js +54 -12
- package/dist/core-runtime/pipeline-execution-ledger.js +10 -0
- package/dist/core-runtime/reconstruct/artifact-types.js +37 -0
- package/dist/core-runtime/reconstruct/benchmark-evidence.js +16 -0
- package/dist/core-runtime/reconstruct/claim-projection-validation.js +19 -5
- package/dist/core-runtime/reconstruct/competency-projection-contract.js +63 -0
- package/dist/core-runtime/reconstruct/comprehension-artifact.js +321 -0
- package/dist/core-runtime/reconstruct/comprehension-reduce.js +367 -0
- package/dist/core-runtime/reconstruct/comprehension-semantic-map.js +771 -0
- package/dist/core-runtime/reconstruct/contract-registry.js +20 -0
- package/dist/core-runtime/reconstruct/directive-validation.js +4 -3
- package/dist/core-runtime/reconstruct/dispatch-fallback-artifacts.js +640 -0
- package/dist/core-runtime/reconstruct/execution-telemetry.js +84 -9
- package/dist/core-runtime/reconstruct/final-output-sections.js +89 -0
- package/dist/core-runtime/reconstruct/governing-snapshot.js +36 -1
- package/dist/core-runtime/reconstruct/leaf-reader.js +305 -0
- package/dist/core-runtime/reconstruct/llm-dispatch-failure.js +270 -0
- package/dist/core-runtime/reconstruct/llm-touch-fingerprint.js +94 -0
- package/dist/core-runtime/reconstruct/material-admission-validation.js +20 -5
- package/dist/core-runtime/reconstruct/material-profile-validation.js +24 -6
- package/dist/core-runtime/reconstruct/materialize-preparation.js +380 -23
- package/dist/core-runtime/reconstruct/maturation-validation.js +1522 -67
- package/dist/core-runtime/reconstruct/mock-llm-realization.js +120 -1
- package/dist/core-runtime/reconstruct/obligation-assertion.js +18 -0
- package/dist/core-runtime/reconstruct/ontology-seed-validation.js +39 -7
- package/dist/core-runtime/reconstruct/output-budget.js +6 -0
- package/dist/core-runtime/reconstruct/pipeline-execution-ledger.js +112 -5
- package/dist/core-runtime/reconstruct/post-seed-validation.js +122 -11
- package/dist/core-runtime/reconstruct/proof-authority-validation.js +2 -6
- package/dist/core-runtime/reconstruct/purpose-authority-validation.js +119 -7
- package/dist/core-runtime/reconstruct/record.js +49 -3
- package/dist/core-runtime/reconstruct/registry-verification-validation.js +41 -5
- package/dist/core-runtime/reconstruct/run-control-validation.js +1019 -37
- package/dist/core-runtime/reconstruct/run.js +5409 -517
- package/dist/core-runtime/reconstruct/seed-authoring-readiness-validation.js +56 -8
- package/dist/core-runtime/reconstruct/semantic-quality-gate.js +112 -7
- package/dist/core-runtime/reconstruct/source-observation-delta-validation.js +53 -5
- package/dist/core-runtime/reconstruct/source-observations.js +166 -0
- package/dist/core-runtime/reconstruct/source-safety-validation.js +77 -188
- package/dist/core-runtime/reconstruct/source-scout-pack-validation.js +50 -6
- package/dist/core-runtime/reconstruct/terminal-validation.js +145 -13
- package/dist/core-runtime/review/continuation-plan.js +12 -5
- package/dist/core-runtime/review/issue-artifact-runtime.js +4 -1
- package/dist/core-runtime/review/materializers.js +100 -11
- package/dist/core-runtime/review/nesting-batch.js +6 -2
- package/dist/core-runtime/review/obligation-shardability.js +138 -0
- package/dist/core-runtime/review/pipeline-execution-ledger.js +42 -2
- package/dist/core-runtime/review/review-artifact-utils.js +219 -7
- package/dist/core-runtime/review/review-execution-steps.js +32 -8
- package/dist/core-runtime/review/review-progress-contract.js +8 -0
- package/dist/core-runtime/review/review-prompt-budget.js +97 -0
- package/dist/core-runtime/review/semantic-quality-gate.js +33 -7
- package/dist/core-runtime/review/spreadsheet-review-disposition.js +94 -0
- package/dist/core-runtime/route-identity.js +182 -0
- package/dist/core-runtime/spreadsheet-header-escalation.js +204 -0
- package/dist/core-runtime/spreadsheet-structure-observer.js +2689 -0
- package/dist/core-runtime/target-material-kind.js +58 -0
- package/dist/mcp/server.js +27 -5
- package/dist/mcp/tool-schemas.js +8 -0
- package/dist/tui/app.js +167 -0
- package/dist/tui/data/event-follower.js +116 -0
- package/dist/tui/data/node-detail.js +42 -0
- package/dist/tui/data/projection-poll.js +26 -0
- package/dist/tui/data/session-discovery.js +65 -0
- package/dist/tui/index.js +108 -0
- package/dist/tui/screens/log.js +25 -0
- package/dist/tui/screens/node-detail.js +17 -0
- package/dist/tui/screens/session-selector.js +26 -0
- package/dist/tui/screens/workflow-tree.js +90 -0
- package/dist/tui/view-model/reconstruct-adapter.js +162 -0
- package/dist/tui/view-model/review-adapter.js +270 -0
- package/dist/tui/view-model/tree-view-model.js +19 -0
- package/package.json +20 -3
- package/settings.example.json +77 -16
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import crypto from "node:crypto";
|
|
2
2
|
import fs from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
|
-
import { parse as parseYaml
|
|
4
|
+
import { parse as parseYaml } from "yaml";
|
|
5
|
+
import { assertArrayField, atomicWriteYamlDocument as writeYamlDocument } from "../artifact-io.js";
|
|
6
|
+
import { assertObligation } from "./obligation-assertion.js";
|
|
5
7
|
const SOURCE_SAFETY_CANONICAL_AXES = [
|
|
6
8
|
"lifecycle_state",
|
|
7
9
|
"authorization_state",
|
|
8
|
-
"privacy_state",
|
|
9
|
-
"redaction_state",
|
|
10
10
|
"proof_sufficiency_state",
|
|
11
11
|
"replay_state",
|
|
12
12
|
];
|
|
@@ -27,26 +27,13 @@ const AUTHORIZATION_STATES = [
|
|
|
27
27
|
"unknown",
|
|
28
28
|
"not_required",
|
|
29
29
|
];
|
|
30
|
-
const PRIVACY_STATES = [
|
|
31
|
-
"non_sensitive",
|
|
32
|
-
"privacy_sensitive",
|
|
33
|
-
"unknown",
|
|
34
|
-
];
|
|
35
|
-
const REDACTION_STATES = [
|
|
36
|
-
"none",
|
|
37
|
-
"redacted",
|
|
38
|
-
"required",
|
|
39
|
-
"insufficient",
|
|
40
|
-
];
|
|
41
30
|
const PROOF_SUFFICIENCY_STATES = [
|
|
42
31
|
"sufficient_for_claim",
|
|
43
32
|
"insufficient_for_claim",
|
|
44
|
-
"trace_only",
|
|
45
33
|
"unavailable",
|
|
46
34
|
];
|
|
47
35
|
const REPLAY_STATES = [
|
|
48
36
|
"replay_allowed",
|
|
49
|
-
"replay_with_redaction",
|
|
50
37
|
"no_replay_use",
|
|
51
38
|
"unknown",
|
|
52
39
|
];
|
|
@@ -60,32 +47,9 @@ const INTENDED_CONSUMPTIONS = [
|
|
|
60
47
|
const VISIBILITY_TIERS = [
|
|
61
48
|
"consumption_allowed",
|
|
62
49
|
"internal_only",
|
|
63
|
-
"redacted_output_only",
|
|
64
50
|
"no_prompt_use",
|
|
65
51
|
"no_replay_use",
|
|
66
52
|
];
|
|
67
|
-
const ALLOWED_PROOF_FORMS = [
|
|
68
|
-
"raw_value",
|
|
69
|
-
"hash",
|
|
70
|
-
"bounded_summary",
|
|
71
|
-
"source_ref_only",
|
|
72
|
-
"unavailable",
|
|
73
|
-
];
|
|
74
|
-
const SAFE_REDACTED_PROOF_FORMS = new Set([
|
|
75
|
-
"hash",
|
|
76
|
-
"bounded_summary",
|
|
77
|
-
"source_ref_only",
|
|
78
|
-
]);
|
|
79
|
-
const SENSITIVE_SOURCE_PATTERNS = [
|
|
80
|
-
/-----BEGIN (?:RSA |EC |OPENSSH |DSA |PGP )?PRIVATE KEY-----/,
|
|
81
|
-
/\b(?:api[_-]?key|secret|password|passwd|pwd|token)\b\s*[:=]\s*['"]?[^'"\s]{8,}/i,
|
|
82
|
-
/\bAuthorization:\s*Bearer\s+[A-Za-z0-9._~+/=-]{12,}/i,
|
|
83
|
-
/\b(?:AKIA|ASIA)[A-Z0-9]{16}\b/,
|
|
84
|
-
/\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/i,
|
|
85
|
-
/\b\d{3}-\d{2}-\d{4}\b/,
|
|
86
|
-
/\b\d{6}-[1-4]\d{6}\b/,
|
|
87
|
-
/\b(?:\+?\d{1,3}[-.\s]?)?(?:\(?\d{2,4}\)?[-.\s]?)\d{3,4}[-.\s]?\d{4}\b/,
|
|
88
|
-
];
|
|
89
53
|
function isoNow() {
|
|
90
54
|
return new Date().toISOString();
|
|
91
55
|
}
|
|
@@ -95,10 +59,6 @@ function isRecord(value) {
|
|
|
95
59
|
async function readYamlDocument(filePath) {
|
|
96
60
|
return parseYaml(await fs.readFile(filePath, "utf8"));
|
|
97
61
|
}
|
|
98
|
-
async function writeYamlDocument(filePath, value) {
|
|
99
|
-
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
100
|
-
await fs.writeFile(filePath, stringifyYaml(value), "utf8");
|
|
101
|
-
}
|
|
102
62
|
function violation(args) {
|
|
103
63
|
return {
|
|
104
64
|
code: args.code,
|
|
@@ -110,24 +70,9 @@ function violation(args) {
|
|
|
110
70
|
function inList(value, allowed) {
|
|
111
71
|
return typeof value === "string" && allowed.includes(value);
|
|
112
72
|
}
|
|
113
|
-
function arrayValues(value, allowed) {
|
|
114
|
-
if (!Array.isArray(value))
|
|
115
|
-
return [];
|
|
116
|
-
return value.filter((item) => inList(item, allowed));
|
|
117
|
-
}
|
|
118
73
|
export function sourceSafetyRowIdForObservation(observation, intendedConsumption = "prompt_context") {
|
|
119
74
|
return `source_safety:${observation.observation_id}:${intendedConsumption}`;
|
|
120
75
|
}
|
|
121
|
-
function observationContentExcerpt(observation) {
|
|
122
|
-
const excerpt = observation.structural_data.content_excerpt;
|
|
123
|
-
return typeof excerpt === "string" ? excerpt : null;
|
|
124
|
-
}
|
|
125
|
-
function hasSensitiveSourceEvidence(observation) {
|
|
126
|
-
const excerpt = observationContentExcerpt(observation);
|
|
127
|
-
if (!excerpt)
|
|
128
|
-
return false;
|
|
129
|
-
return SENSITIVE_SOURCE_PATTERNS.some((pattern) => pattern.test(excerpt));
|
|
130
|
-
}
|
|
131
76
|
function explicitConsumptionAuthorizations(observation) {
|
|
132
77
|
const rawAuthorizations = observation.structural_data.source_safety_consumption_authorizations;
|
|
133
78
|
if (!Array.isArray(rawAuthorizations))
|
|
@@ -147,9 +92,6 @@ function stableRuleRef(row) {
|
|
|
147
92
|
.slice(0, 16);
|
|
148
93
|
return `source-safety-visibility-v1:${digest}`;
|
|
149
94
|
}
|
|
150
|
-
function hasSafeRedactedProofForm(row) {
|
|
151
|
-
return row.redaction_evidence.allowed_proof_forms.some((form) => SAFE_REDACTED_PROOF_FORMS.has(form));
|
|
152
|
-
}
|
|
153
95
|
export function deriveSourceSafetyVisibilityTier(row) {
|
|
154
96
|
const intendedConsumption = row.visibility_derivation.intended_consumption;
|
|
155
97
|
if (intendedConsumption === "replay" &&
|
|
@@ -162,9 +104,7 @@ export function deriveSourceSafetyVisibilityTier(row) {
|
|
|
162
104
|
row.authorization_state === "unknown") {
|
|
163
105
|
return "no_prompt_use";
|
|
164
106
|
}
|
|
165
|
-
if (row.proof_sufficiency_state === "unavailable"
|
|
166
|
-
row.redaction_state === "insufficient" ||
|
|
167
|
-
row.redaction_evidence.allowed_proof_forms.includes("unavailable")) {
|
|
107
|
+
if (row.proof_sufficiency_state === "unavailable") {
|
|
168
108
|
return "no_prompt_use";
|
|
169
109
|
}
|
|
170
110
|
if (intendedConsumption === "material_claim" &&
|
|
@@ -175,15 +115,6 @@ export function deriveSourceSafetyVisibilityTier(row) {
|
|
|
175
115
|
row.proof_sufficiency_state !== "sufficient_for_claim") {
|
|
176
116
|
return "internal_only";
|
|
177
117
|
}
|
|
178
|
-
if (row.redaction_state === "redacted" ||
|
|
179
|
-
row.redaction_state === "required" ||
|
|
180
|
-
row.replay_state === "replay_with_redaction" ||
|
|
181
|
-
row.privacy_state === "privacy_sensitive") {
|
|
182
|
-
return hasSafeRedactedProofForm(row) ? "redacted_output_only" : "internal_only";
|
|
183
|
-
}
|
|
184
|
-
if (row.privacy_state === "unknown") {
|
|
185
|
-
return hasSafeRedactedProofForm(row) ? "redacted_output_only" : "internal_only";
|
|
186
|
-
}
|
|
187
118
|
if (intendedConsumption === "public_output" ||
|
|
188
119
|
intendedConsumption === "prompt_context" ||
|
|
189
120
|
intendedConsumption === "material_claim") {
|
|
@@ -192,40 +123,29 @@ export function deriveSourceSafetyVisibilityTier(row) {
|
|
|
192
123
|
return "internal_only";
|
|
193
124
|
}
|
|
194
125
|
function buildSafetyRowForObservation(observation, intendedConsumption) {
|
|
195
|
-
const sensitive = hasSensitiveSourceEvidence(observation);
|
|
196
|
-
const excerpt = observationContentExcerpt(observation);
|
|
197
126
|
const explicitlyAuthorized = explicitConsumptionAuthorizations(observation)
|
|
198
127
|
.has(intendedConsumption);
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
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;
|
|
207
139
|
const rowBase = {
|
|
208
140
|
safety_row_id: sourceSafetyRowIdForObservation(observation, intendedConsumption),
|
|
209
141
|
subject_ref: observation.source_ref,
|
|
210
142
|
subject_kind: "source_ref",
|
|
211
143
|
lifecycle_state: "active",
|
|
212
144
|
authorization_state: consumptionAuthorized ? "authorized" : "unknown",
|
|
213
|
-
|
|
214
|
-
? "
|
|
215
|
-
:
|
|
216
|
-
|
|
217
|
-
: "unknown",
|
|
218
|
-
redaction_state: sensitive ? "required" : consumptionAuthorized ? "none" : "insufficient",
|
|
219
|
-
proof_sufficiency_state: sensitive
|
|
220
|
-
? "trace_only"
|
|
221
|
-
: consumptionAuthorized
|
|
222
|
-
? "sufficient_for_claim"
|
|
223
|
-
: "insufficient_for_claim",
|
|
224
|
-
replay_state: sensitive
|
|
225
|
-
? "replay_with_redaction"
|
|
226
|
-
: consumptionAuthorized
|
|
227
|
-
? "replay_allowed"
|
|
228
|
-
: "unknown",
|
|
145
|
+
proof_sufficiency_state: consumptionAuthorized
|
|
146
|
+
? "sufficient_for_claim"
|
|
147
|
+
: "insufficient_for_claim",
|
|
148
|
+
replay_state: consumptionAuthorized ? "replay_allowed" : "unknown",
|
|
229
149
|
visibility_tier: "internal_only",
|
|
230
150
|
visibility_derivation: {
|
|
231
151
|
intended_consumption: intendedConsumption,
|
|
@@ -240,11 +160,6 @@ function buildSafetyRowForObservation(observation, intendedConsumption) {
|
|
|
240
160
|
? "source_safety_explicit_consumption_authorization"
|
|
241
161
|
: "runtime_target_ref_read_scope"
|
|
242
162
|
: null,
|
|
243
|
-
redaction_evidence: {
|
|
244
|
-
raw_value_available: excerpt !== null,
|
|
245
|
-
allowed_proof_forms: allowedProofForms,
|
|
246
|
-
redaction_rule_ref: sensitive ? "source-safety-sensitive-source-pattern-v1" : null,
|
|
247
|
-
},
|
|
248
163
|
tombstone: {
|
|
249
164
|
tombstone_ref: null,
|
|
250
165
|
reason: null,
|
|
@@ -252,9 +167,6 @@ function buildSafetyRowForObservation(observation, intendedConsumption) {
|
|
|
252
167
|
downstream_refs: [],
|
|
253
168
|
},
|
|
254
169
|
limitation_refs: [
|
|
255
|
-
...(sensitive
|
|
256
|
-
? [`source-safety-sensitive-source:${observation.observation_id}`]
|
|
257
|
-
: []),
|
|
258
170
|
...(!consumptionAuthorized
|
|
259
171
|
? [
|
|
260
172
|
`source-safety-consumption-authorization-gap:${observation.observation_id}:${intendedConsumption}`,
|
|
@@ -271,6 +183,7 @@ function buildSafetyRowsForObservation(observation) {
|
|
|
271
183
|
return INTENDED_CONSUMPTIONS.map((intendedConsumption) => buildSafetyRowForObservation(observation, intendedConsumption));
|
|
272
184
|
}
|
|
273
185
|
export function buildSourceSafetyLedgerFromSourceObservations(args) {
|
|
186
|
+
assertArrayField(args.sourceObservations.observations, "source-observations", "observations");
|
|
274
187
|
return {
|
|
275
188
|
schema_version: "1",
|
|
276
189
|
session_id: args.sourceObservations.session_id,
|
|
@@ -280,9 +193,11 @@ export function buildSourceSafetyLedgerFromSourceObservations(args) {
|
|
|
280
193
|
};
|
|
281
194
|
}
|
|
282
195
|
function sourceObservationSubjectRefs(sourceObservations) {
|
|
196
|
+
assertArrayField(sourceObservations.observations, "source-observations", "observations");
|
|
283
197
|
return new Set(sourceObservations.observations.map((observation) => observation.source_ref));
|
|
284
198
|
}
|
|
285
199
|
function sourceObservationBindingBySafetyRowId(sourceObservations) {
|
|
200
|
+
assertArrayField(sourceObservations.observations, "source-observations", "observations");
|
|
286
201
|
return new Map(sourceObservations.observations.flatMap((observation) => INTENDED_CONSUMPTIONS.map((intendedConsumption) => [
|
|
287
202
|
sourceSafetyRowIdForObservation(observation, intendedConsumption),
|
|
288
203
|
observation,
|
|
@@ -296,67 +211,17 @@ 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
|
|
214
|
+
message: "visibility_derivation.derived_from_axes must contain exactly the four canonical source-safety axes",
|
|
300
215
|
subjectId: args.row.safety_row_id,
|
|
301
216
|
}));
|
|
302
217
|
}
|
|
303
218
|
}
|
|
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",
|
|
328
|
-
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
|
-
}));
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
219
|
function normalizeSafetyRow(rawRow) {
|
|
352
220
|
if (!isRecord(rawRow))
|
|
353
221
|
return null;
|
|
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
|
-
|
|
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,
|
|
@@ -418,7 +276,25 @@ function normalizeSafetyRow(rawRow) {
|
|
|
418
276
|
};
|
|
419
277
|
}
|
|
420
278
|
export function validateSourceSafetyLedger(args) {
|
|
279
|
+
assertArrayField(args.sourceObservations.observations, "source-observations", "observations");
|
|
421
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");
|
|
422
298
|
const rawLedger = args.sourceSafetyLedger;
|
|
423
299
|
if (!isRecord(rawLedger) || !Array.isArray(rawLedger.safety_rows)) {
|
|
424
300
|
violations.push(violation({
|
|
@@ -509,13 +385,6 @@ export function validateSourceSafetyLedger(args) {
|
|
|
509
385
|
allowed: AUTHORIZATION_STATES,
|
|
510
386
|
axis: "authorization_state",
|
|
511
387
|
},
|
|
512
|
-
{ field: "privacy_state", value: row.privacy_state, allowed: PRIVACY_STATES, axis: "privacy_state" },
|
|
513
|
-
{
|
|
514
|
-
field: "redaction_state",
|
|
515
|
-
value: row.redaction_state,
|
|
516
|
-
allowed: REDACTION_STATES,
|
|
517
|
-
axis: "redaction_state",
|
|
518
|
-
},
|
|
519
388
|
{
|
|
520
389
|
field: "proof_sufficiency_state",
|
|
521
390
|
value: row.proof_sufficiency_state,
|
|
@@ -542,15 +411,7 @@ export function validateSourceSafetyLedger(args) {
|
|
|
542
411
|
subjectId: row.safety_row_id,
|
|
543
412
|
}));
|
|
544
413
|
}
|
|
545
|
-
if (row.redaction_evidence.allowed_proof_forms.length === 0) {
|
|
546
|
-
violations.push(violation({
|
|
547
|
-
code: "missing_required_field",
|
|
548
|
-
message: "source safety row must name at least one allowed proof form",
|
|
549
|
-
subjectId: row.safety_row_id,
|
|
550
|
-
}));
|
|
551
|
-
}
|
|
552
414
|
validateCanonicalAxes({ row, violations });
|
|
553
|
-
validateSupportingDetailConsistency({ row, violations });
|
|
554
415
|
if (VISIBILITY_TIERS.includes(row.visibility_tier)) {
|
|
555
416
|
const expected = deriveSourceSafetyVisibilityTier(row);
|
|
556
417
|
if (row.visibility_tier !== expected) {
|
|
@@ -561,6 +422,34 @@ export function validateSourceSafetyLedger(args) {
|
|
|
561
422
|
}));
|
|
562
423
|
}
|
|
563
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
|
+
}
|
|
564
453
|
}
|
|
565
454
|
for (const requiredSafetyRowId of requiredSafetyRowIds) {
|
|
566
455
|
if (!rowIds.has(requiredSafetyRowId)) {
|
|
@@ -582,10 +471,10 @@ export function validateSourceSafetyLedger(args) {
|
|
|
582
471
|
safety_row_count: rows.length,
|
|
583
472
|
no_prompt_use_count: rows.filter((row) => row.visibility_tier === "no_prompt_use")
|
|
584
473
|
.length,
|
|
585
|
-
redacted_output_only_count: rows.filter((row) => row.visibility_tier === "redacted_output_only").length,
|
|
586
474
|
validation_results: violations.length === 0
|
|
587
475
|
? ["source_safety_ledger_valid"]
|
|
588
476
|
: ["source_safety_ledger_invalid"],
|
|
477
|
+
asserted_obligation_ids: assertedObligationIds,
|
|
589
478
|
violations,
|
|
590
479
|
};
|
|
591
480
|
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import crypto from "node:crypto";
|
|
2
2
|
import fs from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
|
-
import { parse as parseYaml
|
|
4
|
+
import { parse as parseYaml } from "yaml";
|
|
5
|
+
import { assertArrayField, atomicWriteYamlDocument as writeYamlDocument } from "../artifact-io.js";
|
|
6
|
+
import { assertObligation } from "./obligation-assertion.js";
|
|
5
7
|
import { sourceSafetyRowIdForObservation } from "./source-safety-validation.js";
|
|
6
8
|
const SIGNAL_AXES = [
|
|
7
9
|
"actor",
|
|
@@ -111,10 +113,6 @@ function inList(value, allowed) {
|
|
|
111
113
|
async function readYamlDocument(filePath) {
|
|
112
114
|
return parseYaml(await fs.readFile(filePath, "utf8"));
|
|
113
115
|
}
|
|
114
|
-
async function writeYamlDocument(filePath, value) {
|
|
115
|
-
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
116
|
-
await fs.writeFile(filePath, stringifyYaml(value), "utf8");
|
|
117
|
-
}
|
|
118
116
|
async function sha256File(filePath) {
|
|
119
117
|
if (!filePath)
|
|
120
118
|
return null;
|
|
@@ -139,12 +137,15 @@ function violation(args) {
|
|
|
139
137
|
};
|
|
140
138
|
}
|
|
141
139
|
function safetyRowById(sourceSafetyLedger) {
|
|
140
|
+
assertArrayField(sourceSafetyLedger.safety_rows, "source-safety-ledger", "safety_rows");
|
|
142
141
|
return new Map(sourceSafetyLedger.safety_rows.map((row) => [row.safety_row_id, row]));
|
|
143
142
|
}
|
|
144
143
|
function selectedProfileRefForObservation(args) {
|
|
144
|
+
assertArrayField(args.targetMaterialProfile.selected_source_profiles, "target-material-profile", "selected_source_profiles");
|
|
145
145
|
return args.targetMaterialProfile.selected_source_profiles.find((profile) => profile.target_material_kind === args.observation.target_material_kind)?.profile_ref ?? null;
|
|
146
146
|
}
|
|
147
147
|
function scoutScopeState(args) {
|
|
148
|
+
assertArrayField(args.targetMaterialProfile.target_material_kind_candidates, "target-material-profile", "target_material_kind_candidates");
|
|
148
149
|
if (args.targetMaterialProfile.target_material_kind === "mixed" &&
|
|
149
150
|
args.targetMaterialProfile.target_material_kind_candidates.some((kind) => kind === "code" || kind === "document")) {
|
|
150
151
|
return "member_scoped_composite";
|
|
@@ -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":
|
|
@@ -315,6 +315,10 @@ function buildCoverageSlots(args) {
|
|
|
315
315
|
});
|
|
316
316
|
}
|
|
317
317
|
export function buildSourceScoutPackFromArtifacts(args) {
|
|
318
|
+
assertArrayField(args.sourceSafetyLedger.safety_rows, "source-safety-ledger", "safety_rows");
|
|
319
|
+
assertArrayField(args.sourceObservations.observations, "source-observations", "observations");
|
|
320
|
+
assertArrayField(args.targetMaterialProfile.selected_source_profiles, "target-material-profile", "selected_source_profiles");
|
|
321
|
+
assertArrayField(args.targetMaterialProfile.target_refs, "target-material-profile", "target_refs");
|
|
318
322
|
const scopeState = scoutScopeState({
|
|
319
323
|
targetMaterialProfile: args.targetMaterialProfile,
|
|
320
324
|
targetMaterialProfileValidation: args.targetMaterialProfileValidation,
|
|
@@ -398,7 +402,46 @@ function validateNoSelectedPurposeLeak(sourceScoutPack, violations) {
|
|
|
398
402
|
}
|
|
399
403
|
}
|
|
400
404
|
export async function validateSourceScoutPack(args) {
|
|
405
|
+
assertArrayField(args.sourceObservations.observations, "source-observations", "observations");
|
|
406
|
+
assertArrayField(args.sourceScoutPack.profile_scout_coverage_slots, "source-scout-pack", "profile_scout_coverage_slots");
|
|
401
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");
|
|
443
|
+
// signal_rows is this validator's primary subject and is already shape-checked
|
|
444
|
+
// gracefully below (→ schema_shape_invalid violation), so it is NOT asserted.
|
|
402
445
|
const rawPack = args.sourceScoutPack;
|
|
403
446
|
if (!isRecord(rawPack) || !Array.isArray(rawPack.signal_rows)) {
|
|
404
447
|
violations.push(violation({
|
|
@@ -645,6 +688,7 @@ export async function validateSourceScoutPack(args) {
|
|
|
645
688
|
validation_results: violations.length === 0
|
|
646
689
|
? ["source_scout_pack_valid"]
|
|
647
690
|
: ["source_scout_pack_invalid"],
|
|
691
|
+
asserted_obligation_ids: assertedObligationIds,
|
|
648
692
|
violations,
|
|
649
693
|
};
|
|
650
694
|
}
|