create-principles-disciple 1.146.8 → 1.146.9

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.
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
3
  "productVersion": "2.8.0",
4
- "sourceCommit": "9ef8d607d51cea885249077634df3b4126919a0d"
4
+ "sourceCommit": "8e0cdc607ee5d25e0b66a0655a6e0ce033a1fe99"
5
5
  }
@@ -0,0 +1,159 @@
1
+ import type { ReceiptEvidenceCoverage } from '@principles/core/runtime-v2';
2
+ import type { PromptInjectionRouteSummaryWire } from '../../shared/prompt-injection-contract.js';
3
+ import type { PrincipleReceipts } from './ReceiptsConsoleModel.js';
4
+ export interface ExaminerEvidenceDeliverySample {
5
+ evidenceId: string;
6
+ recordedAt: string;
7
+ sourceKind: string;
8
+ confirmation: string;
9
+ outcome: string;
10
+ targetKind: string;
11
+ }
12
+ export interface ExaminerEvidenceApplicationSample {
13
+ evidenceId: string;
14
+ recordedAt: string;
15
+ sourceKind: string;
16
+ proofMethod: string;
17
+ action: string;
18
+ }
19
+ export interface ExaminerEvidenceRecordSample {
20
+ evidenceId: string;
21
+ recordedAt: string;
22
+ sourceKind: string;
23
+ summary: string;
24
+ }
25
+ /** P1-3: per-kind pagination truth, passed through verbatim from the audit. */
26
+ export interface ExaminerEvidenceKindPage {
27
+ hasMore: boolean;
28
+ nextCursor: {
29
+ recordedAt: string;
30
+ evidenceId: string;
31
+ } | null;
32
+ }
33
+ export interface ExaminerEvidenceAuditProjection {
34
+ status: 'ok' | 'degraded';
35
+ reason?: string;
36
+ nextAction?: string;
37
+ /** P1-3: LOADED counts (the audit page window) — never presented as totals. */
38
+ counts: {
39
+ deliveries: number;
40
+ applications: number;
41
+ episodes: number;
42
+ effects: number;
43
+ outcomes: number;
44
+ };
45
+ /** P1-3: pagination truth per kind — hasMore=true means MORE records exist. */
46
+ pages: Record<'delivery' | 'application' | 'behavior_episode' | 'effect' | 'outcome', ExaminerEvidenceKindPage>;
47
+ /** True when ANY kind reports hasMore — the UI shows a "more exist" band. */
48
+ anyHasMore: boolean;
49
+ /** Delivery confirmations observed in the audit window (never promoted). */
50
+ deliveryConfirmations: {
51
+ prepared: number;
52
+ submitted: number;
53
+ hostAccepted: number;
54
+ contextPresent: number;
55
+ runtimeLoaded: number;
56
+ };
57
+ /** Application proof split — agent claims are NOT runtime verification. */
58
+ applicationProof: {
59
+ agentClaimed: number;
60
+ runtimeVerified: number;
61
+ };
62
+ unresolvedReferences: number;
63
+ capabilityDeclarations: {
64
+ hostKind: string;
65
+ capability: string;
66
+ status: string;
67
+ maxConfirmation?: string;
68
+ note?: string;
69
+ }[];
70
+ samples: {
71
+ deliveries: ExaminerEvidenceDeliverySample[];
72
+ applications: ExaminerEvidenceApplicationSample[];
73
+ episodes: ExaminerEvidenceRecordSample[];
74
+ effects: ExaminerEvidenceRecordSample[];
75
+ outcomes: ExaminerEvidenceRecordSample[];
76
+ };
77
+ asOf: string | null;
78
+ coverage: ReceiptEvidenceCoverage | null;
79
+ }
80
+ export interface ExaminerPromptActivationRow {
81
+ activationId: string;
82
+ action: string;
83
+ status: string;
84
+ activatedAt: string | null;
85
+ deactivatedAt: string | null;
86
+ enforcement?: string;
87
+ warning?: string;
88
+ }
89
+ export interface ExaminerRuleCodeRow {
90
+ activationId: string;
91
+ action: string;
92
+ mode?: string;
93
+ status: string;
94
+ enforcement?: string;
95
+ targetRef: string;
96
+ warning?: string;
97
+ }
98
+ export interface ExaminerActivationProjection {
99
+ status: 'ok' | 'degraded';
100
+ reason?: string;
101
+ nextAction?: string;
102
+ prompt: ExaminerPromptActivationRow[];
103
+ ruleCode: ExaminerRuleCodeRow[];
104
+ promptInjectionRoute?: PromptInjectionRouteSummaryWire;
105
+ }
106
+ export interface ExaminerInjectionProjection {
107
+ status: 'available' | 'degraded';
108
+ /** Present when no status could be computed at all (fail-loud, rc-9). */
109
+ globalReason?: string;
110
+ statuses: {
111
+ activationId: string;
112
+ status: string;
113
+ inCurrentWindow?: boolean;
114
+ costChars?: number;
115
+ budget?: number;
116
+ route?: string;
117
+ reason?: string;
118
+ }[];
119
+ budgetProvenance?: {
120
+ capacitySource?: string;
121
+ capacityStatus?: string;
122
+ contextWindowTokens?: number;
123
+ policyVersion?: string;
124
+ fallbackReason?: string;
125
+ };
126
+ warnings: string[];
127
+ }
128
+ export interface ExaminerPrincipleEvidence {
129
+ asOf: string;
130
+ principleId: string;
131
+ principle: {
132
+ id: string;
133
+ text: string;
134
+ status: string;
135
+ ruleCount: number;
136
+ derivedFromPainIds: string[];
137
+ evaluability: string;
138
+ painPreventedCount: number;
139
+ adherenceRate: number;
140
+ valueScore: number;
141
+ runtimeDeactivated?: boolean;
142
+ /** Asset status ≠ runtime activation — pinned for the UI (SPEC §4.1). */
143
+ ledgerStatusNote: string;
144
+ };
145
+ activation: ExaminerActivationProjection;
146
+ injection: ExaminerInjectionProjection;
147
+ receipts: PrincipleReceipts;
148
+ evidenceAudit: ExaminerEvidenceAuditProjection;
149
+ }
150
+ export declare class ExaminerEvidenceModel {
151
+ private readonly workspaceDir;
152
+ constructor(workspaceDir: string);
153
+ /**
154
+ * One principle's full production-evidence aggregate. The caller has
155
+ * already validated the principle id charset; a principle that is not in
156
+ * the ledger returns null (route answers 404 — never a fabricated entry).
157
+ */
158
+ getPrincipleEvidence(principleId: string): Promise<ExaminerPrincipleEvidence | null>;
159
+ }
@@ -0,0 +1,274 @@
1
+ /**
2
+ * SPEC-PD-EXAMINER-001 v0.10.1 §4/§5 — Examiner production-evidence read model.
3
+ *
4
+ * A pure DERIVED projection: composes the existing console models and the
5
+ * host-runtime production projection into ONE per-principle evidence
6
+ * aggregate for the Examiner Principle Evaluation page. It owns no storage,
7
+ * writes nothing, and creates no second evidence truth (P4) — every fact is
8
+ * read through the authority that already owns it:
9
+ *
10
+ * ledger PrinciplesConsoleModel (principle_training_state.json)
11
+ * activations ActivationsConsoleModel (state.db activations + artifacts)
12
+ * prompt inject. readPromptActivationInjectionStatuses (host-runtime,
13
+ * built on the SAME production projection the runtime uses)
14
+ * receipts ReceiptsConsoleModel (principle_applications + coverage)
15
+ * evidence audit ReceiptsConsoleModel.getEvidenceAudit
16
+ * (SqliteInterventionEvidenceStore.readAuditRelations)
17
+ *
18
+ * Honesty contract (SPEC §4.3/§5.5):
19
+ * - Projection / Submitted / Presence / Context Present / Runtime Loaded
20
+ * stay DISTINCT — nothing is promoted.
21
+ * - `agent_claimed` never becomes `runtime_verified`.
22
+ * - Unavailable evidence is reported as DEGRADED/UNAVAILABLE with reason
23
+ * and nextAction — empty data is never presented as "zero events".
24
+ * - RuleHost evidence is surfaced as its own lane; it is never counted as
25
+ * prompt-consumption evidence.
26
+ */
27
+ import { readPromptActivationInjectionStatuses } from '@principles/host-runtime';
28
+ import { ReceiptsConsoleModel } from './ReceiptsConsoleModel.js';
29
+ import { PrinciplesConsoleModel, CorePrincipleNotGovernedError } from './PrinciplesConsoleModel.js';
30
+ import { ActivationsConsoleModel } from './ActivationsConsoleModel.js';
31
+ /** Bounded per-kind sample shown on the evaluation page (audit stays paged). */
32
+ const AUDIT_SAMPLE_LIMIT = 5;
33
+ function isRecord(value) {
34
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
35
+ }
36
+ function stringOrUndefined(value) {
37
+ return typeof value === 'string' ? value : undefined;
38
+ }
39
+ /**
40
+ * Bounded episode/effect/outcome sample summary from the payload (untrusted).
41
+ * Shortened by CODE POINTS — a UTF-16 slice can split a surrogate pair and
42
+ * render U+FFFD in the Owner-facing panel (EP-11).
43
+ */
44
+ function recordSummary(payload) {
45
+ if (!isRecord(payload))
46
+ return '';
47
+ const candidate = payload.observationSummary ?? payload.actionSummary ?? payload.feedbackText ?? payload.resultSummary;
48
+ const text = typeof candidate === 'string' ? candidate : '';
49
+ const codePoints = Array.from(text);
50
+ return codePoints.length > 160 ? `${codePoints.slice(0, 157).join('')}…` : text;
51
+ }
52
+ export class ExaminerEvidenceModel {
53
+ workspaceDir;
54
+ constructor(workspaceDir) {
55
+ this.workspaceDir = workspaceDir;
56
+ }
57
+ /**
58
+ * One principle's full production-evidence aggregate. The caller has
59
+ * already validated the principle id charset; a principle that is not in
60
+ * the ledger returns null (route answers 404 — never a fabricated entry).
61
+ */
62
+ async getPrincipleEvidence(principleId) {
63
+ const asOf = new Date().toISOString();
64
+ // 1. Ledger facts (the authoritative principle asset source).
65
+ const principlesModel = new PrinciplesConsoleModel(this.workspaceDir);
66
+ let detailOutput;
67
+ try {
68
+ detailOutput = await principlesModel.getPrincipleDetail(principleId);
69
+ }
70
+ catch (error) {
71
+ // Core principles are not workspace-governed objects: they have no
72
+ // production-evidence aggregate here, so the route answers the same
73
+ // 404 as any other principle absent from the workspace ledger.
74
+ if (error instanceof CorePrincipleNotGovernedError)
75
+ return null;
76
+ throw error;
77
+ }
78
+ if (!detailOutput)
79
+ return null;
80
+ const detail = detailOutput.principle;
81
+ // 2. Activation lifecycle rows (prompt + RuleCode lanes) — full reuse of
82
+ // the activations read model, filtered to this principle. A read error
83
+ // degrades the ACTIVATION lane only: the ledger facts below still
84
+ // answer, and the lane carries reason + nextAction (never "zero").
85
+ const activationsModel = new ActivationsConsoleModel(this.workspaceDir);
86
+ let activations;
87
+ try {
88
+ activations = await activationsModel.getActivations();
89
+ }
90
+ catch (error) {
91
+ activations = {
92
+ activations: [],
93
+ status: 'degraded',
94
+ reason: `activation read failed: ${error instanceof Error ? error.message : String(error)}`,
95
+ nextAction: 'check workspace .pd/state.db readability and the activation schema',
96
+ };
97
+ }
98
+ const rows = activations.activations.filter((row) => row.principleId === principleId);
99
+ const activation = {
100
+ status: activations.status,
101
+ reason: activations.reason,
102
+ nextAction: activations.nextAction,
103
+ promptInjectionRoute: activations.promptInjectionRoute,
104
+ prompt: rows
105
+ .filter((row) => row.channel === 'prompt')
106
+ .map((row) => ({
107
+ activationId: row.activationId,
108
+ action: row.action,
109
+ status: row.status,
110
+ activatedAt: row.activatedAt,
111
+ deactivatedAt: row.deactivatedAt,
112
+ enforcement: row.enforcement,
113
+ warning: row.warning,
114
+ })),
115
+ ruleCode: rows
116
+ .filter((row) => row.channel === 'code_tool_hook')
117
+ .map((row) => ({
118
+ activationId: row.activationId,
119
+ action: row.action,
120
+ mode: row.mode,
121
+ status: row.status,
122
+ enforcement: row.enforcement,
123
+ targetRef: row.targetRef,
124
+ warning: row.warning,
125
+ })),
126
+ };
127
+ // 3. Injection statuses from the SAME production projection the runtime
128
+ // uses (route + budget provenance + per-activation single-item facts).
129
+ const injectionReport = await readPromptActivationInjectionStatuses({ workspaceDir: this.workspaceDir });
130
+ const targetStatuses = injectionReport.statuses.filter((row) => row.principleId === principleId);
131
+ const injection = {
132
+ status: injectionReport.globalReason ? 'degraded' : 'available',
133
+ globalReason: injectionReport.globalReason,
134
+ statuses: targetStatuses.map((row) => ({
135
+ activationId: row.activationId,
136
+ status: row.status,
137
+ inCurrentWindow: row.inCurrentWindow,
138
+ costChars: row.costChars,
139
+ budget: row.budget,
140
+ route: row.route,
141
+ reason: row.reason,
142
+ })),
143
+ budgetProvenance: injectionReport.budgetProvenance
144
+ ? {
145
+ capacitySource: injectionReport.budgetProvenance.capacitySource,
146
+ capacityStatus: injectionReport.budgetProvenance.capacityStatus,
147
+ contextWindowTokens: injectionReport.budgetProvenance.contextWindowTokens,
148
+ policyVersion: injectionReport.budgetProvenance.policyVersion,
149
+ fallbackReason: injectionReport.budgetProvenance.fallbackReason,
150
+ }
151
+ : undefined,
152
+ warnings: injectionReport.warnings.slice(0, 20),
153
+ };
154
+ // 4. Receipts + 5. evidence audit through the existing receipts model.
155
+ const receiptsModel = new ReceiptsConsoleModel(this.workspaceDir);
156
+ const receipts = await receiptsModel.getPrincipleReceipts(principleId);
157
+ const audit = await receiptsModel.getEvidenceAudit({ type: 'principle', principleId });
158
+ const deliveryConfirmations = { prepared: 0, submitted: 0, hostAccepted: 0, contextPresent: 0, runtimeLoaded: 0 };
159
+ for (const record of audit.deliveries) {
160
+ const confirmation = stringOrUndefined(isRecord(record.payload) ? record.payload.confirmation : undefined);
161
+ if (confirmation === 'prepared')
162
+ deliveryConfirmations.prepared += 1;
163
+ else if (confirmation === 'submitted')
164
+ deliveryConfirmations.submitted += 1;
165
+ else if (confirmation === 'host_accepted')
166
+ deliveryConfirmations.hostAccepted += 1;
167
+ else if (confirmation === 'context_present')
168
+ deliveryConfirmations.contextPresent += 1;
169
+ else if (confirmation === 'runtime_loaded')
170
+ deliveryConfirmations.runtimeLoaded += 1;
171
+ }
172
+ const applicationProof = { agentClaimed: 0, runtimeVerified: 0 };
173
+ for (const record of audit.applications) {
174
+ const proof = stringOrUndefined(isRecord(record.payload) ? record.payload.proofMethod : undefined);
175
+ if (proof === 'runtime_verified')
176
+ applicationProof.runtimeVerified += 1;
177
+ else if (proof === 'agent_claimed')
178
+ applicationProof.agentClaimed += 1;
179
+ }
180
+ // P1-3: pagination truth rides through verbatim — the counts above are
181
+ // LOADED-window sizes, and hasMore per kind tells the Owner more records
182
+ // exist beyond this read.
183
+ const pages = {
184
+ delivery: { hasMore: audit.pages.delivery.hasMore, nextCursor: audit.pages.delivery.nextCursor },
185
+ application: { hasMore: audit.pages.application.hasMore, nextCursor: audit.pages.application.nextCursor },
186
+ behavior_episode: { hasMore: audit.pages.behavior_episode.hasMore, nextCursor: audit.pages.behavior_episode.nextCursor },
187
+ effect: { hasMore: audit.pages.effect.hasMore, nextCursor: audit.pages.effect.nextCursor },
188
+ outcome: { hasMore: audit.pages.outcome.hasMore, nextCursor: audit.pages.outcome.nextCursor },
189
+ };
190
+ const anyHasMore = Object.values(pages).some((page) => page.hasMore);
191
+ const evidenceAudit = {
192
+ status: audit.status,
193
+ reason: audit.reason,
194
+ nextAction: audit.nextAction,
195
+ counts: {
196
+ deliveries: audit.deliveries.length,
197
+ applications: audit.applications.length,
198
+ episodes: audit.episodes.length,
199
+ effects: audit.effects.length,
200
+ outcomes: audit.outcomes.length,
201
+ },
202
+ pages,
203
+ anyHasMore,
204
+ deliveryConfirmations,
205
+ applicationProof,
206
+ unresolvedReferences: audit.unresolvedReferences.length,
207
+ capabilityDeclarations: audit.capabilityDeclarations.slice(0, 12).map((declaration) => ({
208
+ hostKind: String(declaration.hostKind),
209
+ capability: String(declaration.capability),
210
+ status: String(declaration.status),
211
+ maxConfirmation: stringOrUndefined(declaration.maxConfirmation),
212
+ note: stringOrUndefined(declaration.note),
213
+ })),
214
+ samples: {
215
+ deliveries: audit.deliveries.slice(0, AUDIT_SAMPLE_LIMIT).map((record) => ({
216
+ evidenceId: record.evidenceId,
217
+ recordedAt: record.recordedAt,
218
+ sourceKind: String(record.sourceKind),
219
+ confirmation: stringOrUndefined(isRecord(record.payload) ? record.payload.confirmation : undefined) ?? 'unknown',
220
+ outcome: stringOrUndefined(isRecord(record.payload) ? record.payload.outcome : undefined) ?? 'unknown',
221
+ targetKind: stringOrUndefined(isRecord(record.payload) ? record.payload.targetKind : undefined) ?? 'unknown',
222
+ })),
223
+ applications: audit.applications.slice(0, AUDIT_SAMPLE_LIMIT).map((record) => ({
224
+ evidenceId: record.evidenceId,
225
+ recordedAt: record.recordedAt,
226
+ sourceKind: String(record.sourceKind),
227
+ proofMethod: stringOrUndefined(isRecord(record.payload) ? record.payload.proofMethod : undefined) ?? 'unknown',
228
+ action: stringOrUndefined(isRecord(record.payload) ? record.payload.action : undefined) ?? 'unknown',
229
+ })),
230
+ episodes: audit.episodes.slice(0, AUDIT_SAMPLE_LIMIT).map((record) => ({
231
+ evidenceId: record.evidenceId,
232
+ recordedAt: record.recordedAt,
233
+ sourceKind: String(record.sourceKind),
234
+ summary: recordSummary(record.payload),
235
+ })),
236
+ effects: audit.effects.slice(0, AUDIT_SAMPLE_LIMIT).map((record) => ({
237
+ evidenceId: record.evidenceId,
238
+ recordedAt: record.recordedAt,
239
+ sourceKind: String(record.sourceKind),
240
+ summary: recordSummary(record.payload),
241
+ })),
242
+ outcomes: audit.outcomes.slice(0, AUDIT_SAMPLE_LIMIT).map((record) => ({
243
+ evidenceId: record.evidenceId,
244
+ recordedAt: record.recordedAt,
245
+ sourceKind: String(record.sourceKind),
246
+ summary: recordSummary(record.payload),
247
+ })),
248
+ },
249
+ asOf: audit.asOf,
250
+ coverage: audit.coverage ?? null,
251
+ };
252
+ return {
253
+ asOf,
254
+ principleId,
255
+ principle: {
256
+ id: detail.id,
257
+ text: detail.text,
258
+ status: detail.status,
259
+ ruleCount: detail.ruleCount,
260
+ derivedFromPainIds: detail.derivedFromPainIds,
261
+ evaluability: detail.evaluability,
262
+ painPreventedCount: detail.painPreventedCount,
263
+ adherenceRate: detail.adherenceRate,
264
+ valueScore: detail.valueScore,
265
+ runtimeDeactivated: detail.runtimeDeactivated,
266
+ ledgerStatusNote: 'ledger status is the ASSET lifecycle — it is not runtime activation; the runtime rows below are the activation truth',
267
+ },
268
+ activation,
269
+ injection,
270
+ receipts,
271
+ evidenceAudit,
272
+ };
273
+ }
274
+ }
@@ -171,7 +171,21 @@ export class ReceiptsConsoleModel {
171
171
  coverage: unreadCoverage('unavailable', 'ledger_table_missing', 'update_plugin'),
172
172
  };
173
173
  }
174
- throw err;
174
+ // An unreadable state.db (permissions, corruption) must degrade, not
175
+ // throw: the documented read-path contract is degraded-over-500, so the
176
+ // evidence route keeps its ledger facts and shows WHY receipts are absent.
177
+ const message = err instanceof Error ? err.message : String(err);
178
+ return {
179
+ status: 'degraded',
180
+ reason: `receipt ledger read failed: ${message}`,
181
+ nextAction: 'Run pd runtime diagnostics to check workspace .pd/state.db readability',
182
+ principleId,
183
+ effectCount: 0,
184
+ presenceCount: 0,
185
+ lastEffectAt: null,
186
+ events: [],
187
+ coverage: unreadCoverage('unavailable', 'receipt_read_failed', 'run_runtime_diagnostics'),
188
+ };
175
189
  }
176
190
  finally {
177
191
  conn.close();
@@ -149,8 +149,6 @@ export declare function writableRootProblem(roots: StudyRoots): string | null;
149
149
  * mutation — a check done only at startup is not evidence about now.
150
150
  */
151
151
  export declare function writableStudyDirProblem(roots: StudyRoots, studyDir: string): string | null;
152
- /** Study status projection (manifest + ledger settlement counts + report status). */
153
- export declare function studyStatusPayload(studyDir: string): Record<string, unknown>;
154
152
  /** Read-only Report V2 payload + replay verification. */
155
153
  export declare function studyReportPayload(modules: LabModules, studyDir: string): Record<string, unknown> | null;
156
154
  export declare class ExaminerPreflightBlockedError extends Error {
@@ -179,7 +177,9 @@ export declare function preflightCheck(modules: LabModules, studyDir: string, co
179
177
  * before RUN_STARTED — but a blocker here means the runner is not called at
180
178
  * all, so no attempt is reserved and no actor is created.
181
179
  */
182
- export declare function offlineStudyPass(modules: LabModules, studyDir: string, context?: StudyDirContext): Promise<{
180
+ export declare function offlineStudyPass(modules: LabModules, studyDir: string, context?: StudyDirContext, admissionCheck?: (input: {
181
+ studyDir: string;
182
+ }) => void | Promise<void>): Promise<{
183
183
  units: Record<string, unknown>[];
184
184
  }>;
185
185
  export declare function reportMaterialize(modules: LabModules, studyDir: string, now: string, context: StudyDirContext | undefined, canWrite: () => {
@@ -217,6 +217,26 @@ export type CreateStudyInputs = {
217
217
  }[];
218
218
  repetitionsPerCase: number;
219
219
  };
220
+ /**
221
+ * v0.10.1 host-backed inputs (SPEC §4/§6/§7): the frozen production snapshot,
222
+ * the runtime-derived RuleCode facts and the real pain lineage. All gathered
223
+ * by the caller (examiner-study-inputs) from existing authorities; this layer
224
+ * only freezes them into the study directory. Suite CASES are compiled here
225
+ * with the minted study handle (case ids are study-scoped).
226
+ */
227
+ export type HostBackedStudyInputs = {
228
+ productionSnapshot: Record<string, unknown>;
229
+ ruleCodeFacts: {
230
+ ruleCodes: {
231
+ ruleCodeId: string;
232
+ principleId: string;
233
+ baselineState: 'ON' | 'OFF';
234
+ }[];
235
+ principleRuleCodeIndependence: boolean | null;
236
+ targetRuleCodeCount: number;
237
+ };
238
+ painRefs: string[];
239
+ };
220
240
  /** Server-minted study handle + directory (crypto-random; never request-derived). */
221
241
  export declare function mintStudyHandle(roots: StudyRoots): {
222
242
  studyId: string;
@@ -241,6 +261,10 @@ export declare function materializeConsoleStudy(modules: LabModules, labRoot: st
241
261
  ruleCount?: number;
242
262
  }[];
243
263
  repetitionsPerCase: number;
264
+ /** v0.10.1: catalog suite key (validated by the caller against the fixed list). */
265
+ suiteKey?: string;
266
+ /** v0.10.1: host-backed inputs (production snapshot / RuleCode facts / pain refs). */
267
+ hostBacked?: HostBackedStudyInputs;
244
268
  }): Promise<{
245
269
  studyId: string;
246
270
  targetPrincipleId: string;
@@ -253,8 +277,20 @@ export declare function materializeConsoleStudy(modules: LabModules, labRoot: st
253
277
  * receives already-validated server data only. Synthetic calibration cases
254
278
  * with SYNTHETIC_OFFLINE gold provenance — offline contract validation,
255
279
  * never real-host behavior.
280
+ *
281
+ * v0.10.1: when `hostBacked` inputs are supplied, the study freezes the
282
+ * caller-selected suite cases, the RUNTIME-derived RuleCode facts and the
283
+ * real pain lineage, and `production-snapshot.json` records where the A/B
284
+ * starting point differs from production (SPEC §4.4/§6.4). The frozen
285
+ * binding files the lab verifies are unchanged — the snapshot is an
286
+ * additional frozen fact, hashed by nothing that the runner recomputes, so
287
+ * appending it after writeExaminerStudyArtifacts keeps every existing
288
+ * verification intact (events.jsonl rides the same discipline).
256
289
  */
257
- export declare function materializeOfflineStudy(modules: LabModules, labRoot: string, inputs: CreateStudyInputs): Promise<{
290
+ export declare function materializeOfflineStudy(modules: LabModules, labRoot: string, inputs: CreateStudyInputs & {
291
+ suiteKey?: string;
292
+ hostBacked?: HostBackedStudyInputs;
293
+ }): Promise<{
258
294
  manifest: Record<string, unknown>;
259
295
  pairAssignment: Record<string, unknown>;
260
296
  }>;