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.
- package/_release/product-identity.json +1 -1
- package/console/dist/server/models/ExaminerEvidenceModel.d.ts +159 -0
- package/console/dist/server/models/ExaminerEvidenceModel.js +274 -0
- package/console/dist/server/models/ReceiptsConsoleModel.js +15 -1
- package/console/dist/server/routes/examiner-executor.d.ts +40 -4
- package/console/dist/server/routes/examiner-executor.js +54 -45
- package/console/dist/server/routes/examiner-runs.d.ts +196 -0
- package/console/dist/server/routes/examiner-runs.js +938 -0
- package/console/dist/server/routes/examiner-study-inputs.d.ts +147 -0
- package/console/dist/server/routes/examiner-study-inputs.js +263 -0
- package/console/dist/server/routes/examiner.d.ts +26 -17
- package/console/dist/server/routes/examiner.js +261 -12
- package/console/dist/types.d.ts +1 -0
- package/console/dist/web/assets/app.css +92 -0
- package/console/dist/web/assets/app.js +38 -32
- package/console/lab/lib/challenge-openclaw-host.mjs +17 -0
- package/console/lab/lib/challenge-runner.mjs +9 -0
- package/console/lab/lib/examiner-study.mjs +55 -0
- package/package.json +2 -2
- package/release-manager/package.json +1 -1
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fixed server-side catalog. Each entry maps to a deterministic list of
|
|
3
|
+
* `makeExaminerV3Case` variants from the lab's authored calibration battery
|
|
4
|
+
* (`examiner-v07-cases.mjs`) — the same frozen assets the committed
|
|
5
|
+
* acceptance studies use. Case ids are minted per study by the executor.
|
|
6
|
+
*/
|
|
7
|
+
export declare const SUITE_CATALOG: readonly [{
|
|
8
|
+
readonly key: 'didactic-pair';
|
|
9
|
+
readonly title: 'Didactic pair (v0.9 default)';
|
|
10
|
+
readonly description: 'Two synthetic calibration cases: the canonical observe-then-report world plus the boundary (principle-not-applicable) variant. Same suite every v0.9 console study froze.';
|
|
11
|
+
readonly variants: readonly [{}, {
|
|
12
|
+
readonly boundary: true;
|
|
13
|
+
}];
|
|
14
|
+
readonly goldProvenance: 'SYNTHETIC_OFFLINE';
|
|
15
|
+
}, {
|
|
16
|
+
readonly key: 'calibration-core';
|
|
17
|
+
readonly title: 'Calibration core (six worlds)';
|
|
18
|
+
readonly description: 'Six distinct worlds from the authored v0.7 calibration battery: canonical, boundary, deploy-cycle (owner signoff), unknown-evidence, preseeded-evidence and stale-source variants. Richer applicability mix; still SYNTHETIC gold.';
|
|
19
|
+
readonly variants: readonly [{}, {
|
|
20
|
+
readonly boundary: true;
|
|
21
|
+
}, {
|
|
22
|
+
readonly deploy: true;
|
|
23
|
+
}, {
|
|
24
|
+
readonly unknown: true;
|
|
25
|
+
}, {
|
|
26
|
+
readonly preseed: true;
|
|
27
|
+
}, {
|
|
28
|
+
readonly staleRequiredSource: true;
|
|
29
|
+
}];
|
|
30
|
+
readonly goldProvenance: 'SYNTHETIC_OFFLINE';
|
|
31
|
+
}];
|
|
32
|
+
export type SuiteKey = (typeof SUITE_CATALOG)[number]['key'];
|
|
33
|
+
export declare const SUITE_KEYS: readonly string[];
|
|
34
|
+
export interface SuiteCatalogEntry {
|
|
35
|
+
key: string;
|
|
36
|
+
title: string;
|
|
37
|
+
description: string;
|
|
38
|
+
caseCount: number;
|
|
39
|
+
variantLabels: string[];
|
|
40
|
+
goldProvenance: string;
|
|
41
|
+
goldGate: string;
|
|
42
|
+
suiteHash: string | null;
|
|
43
|
+
}
|
|
44
|
+
export declare function listSuiteCatalog(labRoot: string): Promise<SuiteCatalogEntry[]>;
|
|
45
|
+
export declare function isSuiteKey(value: unknown): value is SuiteKey;
|
|
46
|
+
/**
|
|
47
|
+
* Compile the case definitions for one suite under a STUDY-MINTED id prefix.
|
|
48
|
+
* The case ids are server-minted (`<studyId>-CASE-NN`); the request never
|
|
49
|
+
* contributes a case id or a path.
|
|
50
|
+
*/
|
|
51
|
+
export declare function suiteCaseDefs(labRoot: string, studyId: string, suiteKey: SuiteKey): Promise<Record<string, unknown>[]>;
|
|
52
|
+
export interface ProductionSnapshot {
|
|
53
|
+
schema: 'examiner-production-snapshot.v1';
|
|
54
|
+
asOf: string;
|
|
55
|
+
targetPrincipleId: string;
|
|
56
|
+
promptRoute: {
|
|
57
|
+
status: 'confirmed' | 'unconfirmed';
|
|
58
|
+
routeName: string | null;
|
|
59
|
+
note: string;
|
|
60
|
+
};
|
|
61
|
+
budget: {
|
|
62
|
+
/** Production budget the projection actually resolved (UTF-16 chars). */
|
|
63
|
+
chars: number | null;
|
|
64
|
+
capacitySource: string | null;
|
|
65
|
+
capacityStatus: string | null;
|
|
66
|
+
policyVersion: string | null;
|
|
67
|
+
unit: 'utf16-chars';
|
|
68
|
+
};
|
|
69
|
+
runtimeActivation: {
|
|
70
|
+
/**
|
|
71
|
+
* The activation read model's own verdict ('ok' | 'degraded'). P1-2: a
|
|
72
|
+
* degraded read (missing state.db / table / unreadable) means the runtime
|
|
73
|
+
* truth is UNKNOWN — an empty row list here is NEVER "zero activations".
|
|
74
|
+
*/
|
|
75
|
+
status: 'ok' | 'degraded';
|
|
76
|
+
reason?: string;
|
|
77
|
+
nextAction?: string;
|
|
78
|
+
/** Principles with an ACTIVE prompt activation row (runtime truth). */
|
|
79
|
+
promptActivePrincipleIds: string[];
|
|
80
|
+
/** Principles the production projection would inject right now. */
|
|
81
|
+
projectedInjectedPrincipleIds: string[] | null;
|
|
82
|
+
/** Bounded display list — NEVER the statistics basis (see ruleCodeCount). */
|
|
83
|
+
ruleCodeActivations: {
|
|
84
|
+
activationId: string;
|
|
85
|
+
principleId: string;
|
|
86
|
+
mode: string | null;
|
|
87
|
+
status: string;
|
|
88
|
+
}[];
|
|
89
|
+
/**
|
|
90
|
+
* P1-2: the COMPLETE workspace-wide code_tool_hook row count, computed
|
|
91
|
+
* before any display truncation (the UI list above is capped at 50).
|
|
92
|
+
*/
|
|
93
|
+
ruleCodeActivationTotal: number;
|
|
94
|
+
};
|
|
95
|
+
ledgerActivePrincipleIds: string[];
|
|
96
|
+
divergence: {
|
|
97
|
+
/** Ledger-active principles with NO active runtime prompt activation. */
|
|
98
|
+
activeInLedgerNotRuntime: string[];
|
|
99
|
+
/** Ledger-active principles the current projection would NOT inject. */
|
|
100
|
+
activeInLedgerNotProjected: string[] | null;
|
|
101
|
+
};
|
|
102
|
+
targetPrinciple: {
|
|
103
|
+
runtimePromptActive: boolean;
|
|
104
|
+
projectedInjected: boolean | null;
|
|
105
|
+
ruleCodeActivationCount: number;
|
|
106
|
+
ledgerRuleCount: number;
|
|
107
|
+
};
|
|
108
|
+
experimentVsProduction: {
|
|
109
|
+
studyBudgetChars: number;
|
|
110
|
+
productionBudgetChars: number | null;
|
|
111
|
+
markers: string[];
|
|
112
|
+
note: string;
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
/** The single frozen marker for a configuration that is not the production injection path. */
|
|
116
|
+
export declare const EXPERIMENT_CONFIG_MARKER = "EXPERIMENT_CONFIGURATION_DIFFERS_FROM_PRODUCTION";
|
|
117
|
+
/**
|
|
118
|
+
* Gather the production facts a study must freeze at creation time. Every
|
|
119
|
+
* fact comes from an existing authority (projection / activation read model /
|
|
120
|
+
* ledger); nothing is inferred and nothing is written.
|
|
121
|
+
*/
|
|
122
|
+
export declare function gatherProductionSnapshot(workspaceDir: string, targetPrincipleId: string, studyBudgetChars: number): Promise<ProductionSnapshot>;
|
|
123
|
+
export interface StudyRuleCodeFacts {
|
|
124
|
+
ruleCodes: {
|
|
125
|
+
ruleCodeId: string;
|
|
126
|
+
principleId: string;
|
|
127
|
+
baselineState: 'ON' | 'OFF';
|
|
128
|
+
}[];
|
|
129
|
+
principleRuleCodeIndependence: boolean | null;
|
|
130
|
+
targetRuleCodeCount: number;
|
|
131
|
+
evidenceNote: string;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Derive the study's RuleCode facts from RUNTIME activation rows
|
|
135
|
+
* (authoritative) cross-checked against the ledger rule count.
|
|
136
|
+
*
|
|
137
|
+
* Honesty rules (SPEC §7.2):
|
|
138
|
+
* - a live-enforcing RuleCode bound to the target is a REAL coupling → the
|
|
139
|
+
* count resolves but independence is unproven → INTERVENTION_NOT_IDENTIFIABLE
|
|
140
|
+
* (the lab refuses creation; nothing is silently labelled principle_only);
|
|
141
|
+
* - shadow (non-enforcing) rows resolve to baseline OFF — still a coupling
|
|
142
|
+
* attribution, so independence stays unproven while any row exists;
|
|
143
|
+
* - ledger-attributed rules with no runtime row stay UNRESOLVED (v0.9
|
|
144
|
+
* semantics: unresolved baseline refuses, never assumed OFF).
|
|
145
|
+
*/
|
|
146
|
+
export declare function deriveStudyRuleCodeFacts(snapshot: ProductionSnapshot): StudyRuleCodeFacts;
|
|
147
|
+
export declare function readProductionSnapshot(studyDir: string): Record<string, unknown> | null;
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SPEC-PD-EXAMINER-001 v0.10.1 §6/§9/§4 — host-backed study inputs.
|
|
3
|
+
*
|
|
4
|
+
* Assembles the REAL inputs a v0.10.1 study freezes:
|
|
5
|
+
* - the Challenge Suite catalog (server-fixed keys over the lab's existing
|
|
6
|
+
* authored case variants — no new authoring system, no request strings
|
|
7
|
+
* crossing into the research pipeline: a request only SELECTS a key);
|
|
8
|
+
* - the production snapshot (runtime activation truth + the production
|
|
9
|
+
* prompt projection + budget provenance), frozen into the study as
|
|
10
|
+
* `production-snapshot.json` so the report can state exactly where the
|
|
11
|
+
* A/B starting point differs from production;
|
|
12
|
+
* - RuleCode facts derived from RUNTIME activation rows (stronger evidence
|
|
13
|
+
* than the ledger rule count alone; both are cross-checked and any
|
|
14
|
+
* unresolved attribution stays a refusal — never an assumed OFF).
|
|
15
|
+
*
|
|
16
|
+
* Read-only against production: gathering a snapshot writes nothing outside
|
|
17
|
+
* the study directory the executor already owns.
|
|
18
|
+
*/
|
|
19
|
+
import fs from 'node:fs';
|
|
20
|
+
import path from 'node:path';
|
|
21
|
+
import { pathToFileURL } from 'node:url';
|
|
22
|
+
import { resolveLivePromptInjectionProjection } from '@principles/host-runtime';
|
|
23
|
+
import { ActivationsConsoleModel } from '../models/ActivationsConsoleModel.js';
|
|
24
|
+
import { PrinciplesConsoleModel } from '../models/PrinciplesConsoleModel.js';
|
|
25
|
+
// ── Challenge Suite catalog (SPEC §9: reuse frozen research assets) ─────────
|
|
26
|
+
/**
|
|
27
|
+
* Fixed server-side catalog. Each entry maps to a deterministic list of
|
|
28
|
+
* `makeExaminerV3Case` variants from the lab's authored calibration battery
|
|
29
|
+
* (`examiner-v07-cases.mjs`) — the same frozen assets the committed
|
|
30
|
+
* acceptance studies use. Case ids are minted per study by the executor.
|
|
31
|
+
*/
|
|
32
|
+
export const SUITE_CATALOG = [
|
|
33
|
+
{
|
|
34
|
+
key: 'didactic-pair',
|
|
35
|
+
title: 'Didactic pair (v0.9 default)',
|
|
36
|
+
description: 'Two synthetic calibration cases: the canonical observe-then-report world plus the boundary (principle-not-applicable) variant. Same suite every v0.9 console study froze.',
|
|
37
|
+
variants: [{}, { boundary: true }],
|
|
38
|
+
goldProvenance: 'SYNTHETIC_OFFLINE',
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
key: 'calibration-core',
|
|
42
|
+
title: 'Calibration core (six worlds)',
|
|
43
|
+
description: 'Six distinct worlds from the authored v0.7 calibration battery: canonical, boundary, deploy-cycle (owner signoff), unknown-evidence, preseeded-evidence and stale-source variants. Richer applicability mix; still SYNTHETIC gold.',
|
|
44
|
+
variants: [{}, { boundary: true }, { deploy: true }, { unknown: true }, { preseed: true }, { staleRequiredSource: true }],
|
|
45
|
+
goldProvenance: 'SYNTHETIC_OFFLINE',
|
|
46
|
+
},
|
|
47
|
+
];
|
|
48
|
+
export const SUITE_KEYS = SUITE_CATALOG.map((suite) => suite.key);
|
|
49
|
+
/** Stable definition hash over the catalog entry (lab canonical hashing). */
|
|
50
|
+
async function suiteDefinitionHash(labRoot, entry) {
|
|
51
|
+
try {
|
|
52
|
+
const freezeUrl = pathToFileURL(path.join(labRoot, 'lib', 'challenge-freeze.mjs')).href;
|
|
53
|
+
const freeze = (await import(freezeUrl));
|
|
54
|
+
return freeze.hashOfCanonical({ key: entry.key, variants: entry.variants, goldProvenance: entry.goldProvenance });
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
function variantLabel(variant) {
|
|
61
|
+
const flags = Object.keys(variant).filter((key) => variant[key] === true);
|
|
62
|
+
return flags.length > 0 ? flags.join('+') : 'canonical';
|
|
63
|
+
}
|
|
64
|
+
export async function listSuiteCatalog(labRoot) {
|
|
65
|
+
const entries = [];
|
|
66
|
+
for (const suite of SUITE_CATALOG) {
|
|
67
|
+
entries.push({
|
|
68
|
+
key: suite.key,
|
|
69
|
+
title: suite.title,
|
|
70
|
+
description: suite.description,
|
|
71
|
+
caseCount: suite.variants.length,
|
|
72
|
+
variantLabels: suite.variants.map((variant) => variantLabel(variant)),
|
|
73
|
+
goldProvenance: suite.goldProvenance,
|
|
74
|
+
goldGate: 'SYNTHETIC gold validates the offline contract only — formal real-behavior conclusions stay blocked behind Gate 1 (independent human Gold review)',
|
|
75
|
+
suiteHash: await suiteDefinitionHash(labRoot, suite),
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
return entries;
|
|
79
|
+
}
|
|
80
|
+
export function isSuiteKey(value) {
|
|
81
|
+
return typeof value === 'string' && SUITE_KEYS.includes(value);
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Compile the case definitions for one suite under a STUDY-MINTED id prefix.
|
|
85
|
+
* The case ids are server-minted (`<studyId>-CASE-NN`); the request never
|
|
86
|
+
* contributes a case id or a path.
|
|
87
|
+
*/
|
|
88
|
+
export async function suiteCaseDefs(labRoot, studyId, suiteKey) {
|
|
89
|
+
const suite = SUITE_CATALOG.find((entry) => entry.key === suiteKey);
|
|
90
|
+
if (!suite)
|
|
91
|
+
throw new Error(`suiteCaseDefs: unknown suite key '${String(suiteKey)}'`);
|
|
92
|
+
const casesUrl = pathToFileURL(path.join(labRoot, 'lib', 'examiner-v07-cases.mjs')).href;
|
|
93
|
+
const mod = (await import(casesUrl));
|
|
94
|
+
return suite.variants.map((variant, index) => mod.makeExaminerV3Case({
|
|
95
|
+
...variant,
|
|
96
|
+
caseId: `${studyId}-CASE-${String(index + 1).padStart(2, '0')}`,
|
|
97
|
+
}));
|
|
98
|
+
}
|
|
99
|
+
/** The single frozen marker for a configuration that is not the production injection path. */
|
|
100
|
+
export const EXPERIMENT_CONFIG_MARKER = 'EXPERIMENT_CONFIGURATION_DIFFERS_FROM_PRODUCTION';
|
|
101
|
+
/**
|
|
102
|
+
* Gather the production facts a study must freeze at creation time. Every
|
|
103
|
+
* fact comes from an existing authority (projection / activation read model /
|
|
104
|
+
* ledger); nothing is inferred and nothing is written.
|
|
105
|
+
*/
|
|
106
|
+
export async function gatherProductionSnapshot(workspaceDir, targetPrincipleId, studyBudgetChars) {
|
|
107
|
+
const asOf = new Date().toISOString();
|
|
108
|
+
// Production prompt projection — the SAME computation the runtime performs
|
|
109
|
+
// (route, budget, injected ids, selection policy).
|
|
110
|
+
const resolution = await resolveLivePromptInjectionProjection({ workspaceDir });
|
|
111
|
+
const confirmed = resolution.status === 'confirmed';
|
|
112
|
+
const projection = confirmed ? resolution.projection : null;
|
|
113
|
+
const perHost = confirmed ? [] : resolution.perHost;
|
|
114
|
+
const routeName = projection?.route ?? null;
|
|
115
|
+
const budgetChars = typeof projection?.budget === 'number' ? projection.budget : null;
|
|
116
|
+
const projectedInjectedIds = projection?.injectedPrincipleIds ?? null;
|
|
117
|
+
const budgetProvenance = projection ?? null;
|
|
118
|
+
// Runtime activation truth + ledger truth.
|
|
119
|
+
const activationsModel = new ActivationsConsoleModel(workspaceDir);
|
|
120
|
+
const activations = await activationsModel.getActivations();
|
|
121
|
+
const promptActivePrincipleIds = [...new Set(activations.activations
|
|
122
|
+
.filter((row) => row.channel === 'prompt' && row.status === 'active')
|
|
123
|
+
.map((row) => row.principleId))].sort();
|
|
124
|
+
const ruleCodeActivations = activations.activations
|
|
125
|
+
.filter((row) => row.channel === 'code_tool_hook')
|
|
126
|
+
.map((row) => ({ activationId: row.activationId, principleId: row.principleId, mode: row.mode ?? null, status: row.status }));
|
|
127
|
+
const principlesModel = new PrinciplesConsoleModel(workspaceDir);
|
|
128
|
+
const list = await principlesModel.listPrinciples();
|
|
129
|
+
const ledgerActivePrincipleIds = (list.principles ?? []).filter((p) => p.status === 'active').map((p) => p.id).sort();
|
|
130
|
+
const targetRules = activations.activations.filter((row) => row.principleId === targetPrincipleId && row.channel === 'code_tool_hook');
|
|
131
|
+
const ledgerTarget = (list.principles ?? []).find((p) => p.id === targetPrincipleId);
|
|
132
|
+
const activeInLedgerNotRuntime = ledgerActivePrincipleIds.filter((id) => !promptActivePrincipleIds.includes(id));
|
|
133
|
+
const activeInLedgerNotProjected = projectedInjectedIds === null
|
|
134
|
+
? null
|
|
135
|
+
: ledgerActivePrincipleIds.filter((id) => !projectedInjectedIds.includes(id));
|
|
136
|
+
const markers = [];
|
|
137
|
+
if (budgetChars === null || budgetChars !== studyBudgetChars)
|
|
138
|
+
markers.push(EXPERIMENT_CONFIG_MARKER);
|
|
139
|
+
if (!confirmed)
|
|
140
|
+
markers.push('PROMPT_ROUTE_UNCONFIRMED_AT_FREEZE');
|
|
141
|
+
if (activeInLedgerNotProjected !== null && activeInLedgerNotProjected.length > 0) {
|
|
142
|
+
markers.push('PROJECTION_EVICTS_ACTIVE_LIBRARY_PRINCIPLES');
|
|
143
|
+
}
|
|
144
|
+
// P1-2: a degraded activation read is an Owner-visible freeze condition —
|
|
145
|
+
// the runtime truth is UNKNOWN, never "zero activations".
|
|
146
|
+
if (activations.status !== 'ok')
|
|
147
|
+
markers.push('ACTIVATION_DATA_DEGRADED');
|
|
148
|
+
return {
|
|
149
|
+
schema: 'examiner-production-snapshot.v1',
|
|
150
|
+
asOf,
|
|
151
|
+
targetPrincipleId,
|
|
152
|
+
promptRoute: {
|
|
153
|
+
status: confirmed ? 'confirmed' : 'unconfirmed',
|
|
154
|
+
routeName,
|
|
155
|
+
note: confirmed
|
|
156
|
+
? 'the production injection route was resolved from workspace host facts at freeze time'
|
|
157
|
+
: `the production route could not be confirmed (${perHost.length} candidate host(s)) — the study freezes an unconfirmed-route baseline`,
|
|
158
|
+
},
|
|
159
|
+
budget: {
|
|
160
|
+
chars: budgetChars,
|
|
161
|
+
capacitySource: budgetProvenance?.budgetCapacitySource ?? null,
|
|
162
|
+
capacityStatus: budgetProvenance?.budgetCapacityStatus ?? null,
|
|
163
|
+
policyVersion: budgetProvenance?.budgetPolicyVersion ?? null,
|
|
164
|
+
unit: 'utf16-chars',
|
|
165
|
+
},
|
|
166
|
+
runtimeActivation: {
|
|
167
|
+
status: activations.status,
|
|
168
|
+
reason: activations.reason,
|
|
169
|
+
nextAction: activations.nextAction,
|
|
170
|
+
promptActivePrincipleIds,
|
|
171
|
+
projectedInjectedPrincipleIds: projectedInjectedIds,
|
|
172
|
+
ruleCodeActivations: ruleCodeActivations.slice(0, 50),
|
|
173
|
+
ruleCodeActivationTotal: ruleCodeActivations.length,
|
|
174
|
+
},
|
|
175
|
+
ledgerActivePrincipleIds,
|
|
176
|
+
divergence: {
|
|
177
|
+
activeInLedgerNotRuntime,
|
|
178
|
+
activeInLedgerNotProjected,
|
|
179
|
+
},
|
|
180
|
+
targetPrinciple: {
|
|
181
|
+
runtimePromptActive: promptActivePrincipleIds.includes(targetPrincipleId),
|
|
182
|
+
projectedInjected: projectedInjectedIds === null ? null : projectedInjectedIds.includes(targetPrincipleId),
|
|
183
|
+
ruleCodeActivationCount: targetRules.length,
|
|
184
|
+
ledgerRuleCount: ledgerTarget?.ruleCount ?? 0,
|
|
185
|
+
},
|
|
186
|
+
experimentVsProduction: {
|
|
187
|
+
studyBudgetChars,
|
|
188
|
+
productionBudgetChars: budgetChars,
|
|
189
|
+
markers,
|
|
190
|
+
note: 'the experiment renders arms under the frozen study budget with the offline deterministic renderer; the production prompt hook computes its own budget and selection — a marker above states every detected difference',
|
|
191
|
+
},
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Derive the study's RuleCode facts from RUNTIME activation rows
|
|
196
|
+
* (authoritative) cross-checked against the ledger rule count.
|
|
197
|
+
*
|
|
198
|
+
* Honesty rules (SPEC §7.2):
|
|
199
|
+
* - a live-enforcing RuleCode bound to the target is a REAL coupling → the
|
|
200
|
+
* count resolves but independence is unproven → INTERVENTION_NOT_IDENTIFIABLE
|
|
201
|
+
* (the lab refuses creation; nothing is silently labelled principle_only);
|
|
202
|
+
* - shadow (non-enforcing) rows resolve to baseline OFF — still a coupling
|
|
203
|
+
* attribution, so independence stays unproven while any row exists;
|
|
204
|
+
* - ledger-attributed rules with no runtime row stay UNRESOLVED (v0.9
|
|
205
|
+
* semantics: unresolved baseline refuses, never assumed OFF).
|
|
206
|
+
*/
|
|
207
|
+
export function deriveStudyRuleCodeFacts(snapshot) {
|
|
208
|
+
// P1-2: a degraded activation read means the runtime truth is UNKNOWN — an
|
|
209
|
+
// empty row list is NEVER "zero activations". Independence may only be the
|
|
210
|
+
// evidenced TRUE when the read model reported status=ok.
|
|
211
|
+
const activationDegraded = snapshot.runtimeActivation.status !== 'ok';
|
|
212
|
+
// ONLY the rows bound to the TARGET principle count as its coupling — a
|
|
213
|
+
// RuleCode activation for a DIFFERENT principle is part of the frozen
|
|
214
|
+
// baseline, not the target's intervention variable (SPEC §7.2).
|
|
215
|
+
const targetRows = activationDegraded
|
|
216
|
+
? []
|
|
217
|
+
: snapshot.runtimeActivation.ruleCodeActivations
|
|
218
|
+
.filter((row) => row.principleId === snapshot.targetPrincipleId);
|
|
219
|
+
// Statistics come from the COMPLETE counts (never the 50-row display list):
|
|
220
|
+
// the snapshot freezes ruleCodeActivationTotal for the whole workspace and
|
|
221
|
+
// ruleCodeActivationCount for the target, both computed pre-truncation.
|
|
222
|
+
const targetRuleCodeCount = Math.max(snapshot.targetPrinciple.ruleCodeActivationCount, snapshot.targetPrinciple.ledgerRuleCount);
|
|
223
|
+
const resolved = targetRows.map((row) => ({
|
|
224
|
+
ruleCodeId: row.activationId,
|
|
225
|
+
principleId: snapshot.targetPrincipleId,
|
|
226
|
+
baselineState: row.mode === 'live' && row.status === 'active' ? 'ON' : 'OFF',
|
|
227
|
+
}));
|
|
228
|
+
const independence = !activationDegraded && targetRuleCodeCount === 0 ? true : null;
|
|
229
|
+
const degradedNote = activationDegraded
|
|
230
|
+
? `RUNTIME ACTIVATION DATA DEGRADED (${snapshot.runtimeActivation.reason ?? 'unknown reason'}; next: ${snapshot.runtimeActivation.nextAction ?? 'inspect the workspace runtime state'}) — the activation truth is UNKNOWN, so independence is never assumed; ${snapshot.targetPrinciple.ledgerRuleCount} ledger-attributed rule(s) remain the only count evidence`
|
|
231
|
+
: null;
|
|
232
|
+
const evidenceNote = degradedNote
|
|
233
|
+
?? (targetRuleCodeCount === 0
|
|
234
|
+
? 'no RuleCode is attributed to the target principle by either the runtime activation table or the ledger — independence is an evidenced zero'
|
|
235
|
+
: `${targetRows.length} runtime RuleCode activation(s) and ${snapshot.targetPrinciple.ledgerRuleCount} ledger-attributed rule(s) bound to the TARGET; independent control of prompt injection while holding these at baseline is UNPROVEN — INTERVENTION_NOT_IDENTIFIABLE unless separately evidenced`);
|
|
236
|
+
return { ruleCodes: resolved, principleRuleCodeIndependence: independence, targetRuleCodeCount, evidenceNote };
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Read a study's frozen production snapshot for DISPLAY (read-only; null when
|
|
240
|
+
* absent/unrecognized). The snapshot is frozen study data, not a trusted
|
|
241
|
+
* contract: the return type stays a plain untrusted record (rc-1) — no typed
|
|
242
|
+
* facade over parsed JSON.
|
|
243
|
+
*/
|
|
244
|
+
/** rc-1/rc-2: plain-object type guard over parsed JSON (no `as` narrowing). */
|
|
245
|
+
function isPlainObject(value) {
|
|
246
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
247
|
+
}
|
|
248
|
+
export function readProductionSnapshot(studyDir) {
|
|
249
|
+
const file = path.join(studyDir, 'production-snapshot.json');
|
|
250
|
+
if (!fs.existsSync(file))
|
|
251
|
+
return null;
|
|
252
|
+
try {
|
|
253
|
+
const parsed = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
254
|
+
if (!isPlainObject(parsed))
|
|
255
|
+
return null;
|
|
256
|
+
if (parsed.schema !== 'examiner-production-snapshot.v1')
|
|
257
|
+
return null;
|
|
258
|
+
return parsed;
|
|
259
|
+
}
|
|
260
|
+
catch {
|
|
261
|
+
return null;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* SPEC-PD-EXAMINER-001 v0.9 §8 — Examiner Console API
|
|
2
|
+
* SPEC-PD-EXAMINER-001 v0.9 §8 + v0.10.1 §4/§5/§6/§12 — Examiner Console API.
|
|
3
3
|
*
|
|
4
4
|
* Product entry for the Examiner research pipeline on top of the existing
|
|
5
5
|
* principle-learning-lab (single research engine — no parallel Runner/Ledger/
|
|
@@ -7,34 +7,43 @@
|
|
|
7
7
|
* (`console/lab`), so this surface is available in an installed runtime; a
|
|
8
8
|
* developer checkout is served from the repository's scripts/dev/ lab.
|
|
9
9
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
10
|
+
* Surface:
|
|
11
|
+
* v0.9 (offline-first, semantics unchanged):
|
|
12
|
+
* GET /api/v1/examiner/overview
|
|
13
|
+
* GET /api/v1/examiner/principles
|
|
14
|
+
* POST /api/v1/examiner/studies (v0.10.1: + suiteKey)
|
|
15
|
+
* GET /api/v1/examiner/studies/:studyId
|
|
16
|
+
* POST /api/v1/examiner/studies/:studyId/preflight
|
|
17
|
+
* POST /api/v1/examiner/studies/:studyId/run (offline-stub, synchronous)
|
|
18
|
+
* GET /api/v1/examiner/studies/:studyId/report
|
|
19
|
+
* POST /api/v1/examiner/studies/:studyId/report
|
|
20
|
+
* v0.10.1:
|
|
21
|
+
* GET /api/v1/examiner/principles/:id/evidence — production evidence aggregate
|
|
22
|
+
* GET /api/v1/examiner/suites — challenge-suite catalog
|
|
23
|
+
* GET /api/v1/examiner/hosts/openclaw/readiness — host readiness projection
|
|
24
|
+
* POST /api/v1/examiner/studies/:studyId/run — modes 'host-dry-run' /
|
|
25
|
+
* 'f2-real-host' follow the §12.3 async contract: 202 + runId, state
|
|
26
|
+
* derived from the study ledger, one live run per study, restart
|
|
27
|
+
* recovery, RESULT_UNKNOWN never auto-retried, f2 gated on an APPROVED
|
|
28
|
+
* Stage R authorization bound to the frozen study.
|
|
23
29
|
*
|
|
24
30
|
* Safety contract: this route layer contains NO filesystem access — every
|
|
25
31
|
* study path is resolved inside examiner-executor by exact membership in the
|
|
26
32
|
* server's own directory enumeration, and study identities are always
|
|
27
33
|
* server-minted crypto-random handles. A request only SELECTS: the target
|
|
28
34
|
* principle travels onward as an index into the server's own ledger list,
|
|
29
|
-
* the experiment mode as a boolean
|
|
30
|
-
* research pipeline,
|
|
35
|
+
* the experiment mode as a boolean, the suite as a fixed-catalog key. No
|
|
36
|
+
* request string crosses into the research pipeline, no request value
|
|
37
|
+
* constructs a path, and no request value selects an executable.
|
|
31
38
|
*
|
|
32
39
|
* Two roots (P1-1): NEW console studies are WORKSPACE DATA — written under
|
|
33
40
|
* `<workspace>/.pd/examiner/studies` — and require the server's own ownership
|
|
34
41
|
* marker; the committed `examiner-studies/` research assets that travel with
|
|
35
42
|
* the lab payload are visible but ALWAYS read-only here (POST /run and
|
|
36
43
|
* POST /report answer 403). The frozen `challenge-studies/` area is never a
|
|
37
|
-
* write target. Real-host execution is refused
|
|
44
|
+
* write target. Real-host execution is refused until an APPROVED Stage R
|
|
45
|
+
* authorization exists inside the study — and the runner re-verifies that
|
|
46
|
+
* authority per unit regardless.
|
|
38
47
|
*/
|
|
39
48
|
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
40
49
|
/**
|