create-principles-disciple 1.145.43 → 1.146.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/_release/product-identity.json +2 -2
  2. package/console/dist/server/routes/examiner-executor.d.ts +8 -2
  3. package/console/dist/server/routes/examiner-executor.js +10 -4
  4. package/console/dist/server/routes/examiner.d.ts +23 -17
  5. package/console/dist/server/routes/examiner.js +30 -27
  6. package/console/dist/ui/App.js +1 -1
  7. package/console/dist/ui/components/layout/app-sidebar.d.ts +3 -4
  8. package/console/dist/ui/components/layout/app-sidebar.js +5 -7
  9. package/console/dist/ui/i18n/en.json +1 -1
  10. package/console/dist/ui/i18n/zh-CN.json +1 -1
  11. package/console/dist/ui/utils/runtime-mode.d.ts +2 -1
  12. package/console/dist/ui/utils/runtime-mode.js +2 -1
  13. package/console/dist/web/assets/app.js +23 -23
  14. package/console/lab/lib/challenge-actor.mjs +394 -0
  15. package/console/lab/lib/challenge-arms.mjs +102 -0
  16. package/console/lab/lib/challenge-behavior-report.mjs +2484 -0
  17. package/console/lab/lib/challenge-compile.mjs +211 -0
  18. package/console/lab/lib/challenge-freeze.mjs +215 -0
  19. package/console/lab/lib/challenge-isolation-probe.mjs +550 -0
  20. package/console/lab/lib/challenge-ledger.mjs +206 -0
  21. package/console/lab/lib/challenge-openclaw-host.mjs +630 -0
  22. package/console/lab/lib/challenge-runner.mjs +868 -0
  23. package/console/lab/lib/challenge-stage-r-gate.mjs +458 -0
  24. package/console/lab/lib/challenge-statistics.mjs +591 -0
  25. package/console/lab/lib/claim-evidence-env.mjs +652 -0
  26. package/console/lab/lib/claim-evidence-scorer.mjs +784 -0
  27. package/console/lab/lib/examiner-injection-evidence.mjs +367 -0
  28. package/console/lab/lib/examiner-study.mjs +1203 -0
  29. package/console/lab/lib/examiner-v07-cases.mjs +187 -0
  30. package/console/lab/lib/examiner-v2-cases.mjs +61 -0
  31. package/console/lab/lib/fixture.mjs +382 -0
  32. package/console/lab/lib/g2.mjs +146 -0
  33. package/console/lab/lib/host-actor.mjs +987 -0
  34. package/console/lab/lib/sidecar.mjs +273 -0
  35. package/package.json +1 -1
  36. package/release-manager/package.json +1 -1
@@ -0,0 +1,273 @@
1
+ // PRI-969 — research sidecar data contract + validator.
2
+ //
3
+ // The sidecar is a RESEARCH VIEW ONLY (SPEC §6): it references facts, it
4
+ // never becomes a second authority for Principle lifecycle, approvals,
5
+ // activation or identity. No Ledger, Approval or Activation records are
6
+ // fabricated here; simulated arms carry pipelineReachability=NOT_REACHED.
7
+ //
8
+ // Contracts:
9
+ // principle-study.v1 — study metadata, claims registry, arms (with frozen
10
+ // candidates), assignment, memory manifests
11
+ // adjudication.v0.1 — one row per (arm, opportunity) via evaluator
12
+ //
13
+ // validateSidecar() enforces the shape invariants AND the SPEC's hard
14
+ // negative tests:
15
+ // NT-01 gold leak into actor input (scanGoldLeak basis)
16
+ // NT-02 foreign leakage-group pairing (assembleActorInput basis)
17
+ // NT-03 same-episode history→future is NOT a leak (must stay legal)
18
+ // NT-04 no-PD-activation arm still scored (NOT_REACHED + graded)
19
+ // NT-05 NOT_REACHED must not equal behavior failure (reason-code guard)
20
+ // NT-06 no principleId fabrication by title (id registry guard)
21
+ // NT-07 no post-cutoff evidence in early labels (occurredAt guard)
22
+ // NT-08 antiPatterns ≠ non-applicability (NA reason allowlist)
23
+
24
+ import { canonicalJson, sha256 } from './fixture.mjs';
25
+
26
+ export const STUDY_SCHEMA = 'principle-study.v1';
27
+ export const ADJUDICATION_SCHEMA = 'adjudication.v0.1';
28
+
29
+ export const CLAIM_STATUS_VALUES = [
30
+ 'METHOD_VALIDATED_ONLY',
31
+ 'NOT_ESTIMABLE',
32
+ 'PARTIALLY_OBSERVABLE',
33
+ 'NOT_SUPPORTED',
34
+ ];
35
+ export const PIPELINE_REACHABILITY_VALUES = ['NOT_REACHED', 'REACHED', 'UNKNOWN'];
36
+ export const ACTOR_RUN_STATUS_VALUES = ['COMPLETED', 'FAILED', 'NO_RESPONSE', 'INTERRUPTED', 'UNKNOWN'];
37
+ export const OBJECTIVE_TASK_STATUS_VALUES = ['PASS', 'FAIL', 'UNKNOWN'];
38
+ export const PRINCIPLE_ALIGNMENT_VALUES = ['ALIGNED', 'ACCEPTABLE', 'MISALIGNED', 'INCONCLUSIVE', 'NOT_APPLICABLE'];
39
+ export const DATA_QUALITY_FLAGS = ['VALID', 'INVALID_PROTOCOL', 'UNCERTAIN'];
40
+ const NA_REASON_ALLOWLIST = ['no_principle_in_force', 'opportunity_out_of_scope'];
41
+
42
+ /**
43
+ * PRI-971 G2 freeze interface (also used by G1 simulated arms): freeze a
44
+ * candidate's exact bytes + hash + delivery representation. The same function
45
+ * will freeze real I0/I1 outputs in G2 — real runs differ only in where the
46
+ * policyText bytes come from, never in the freeze mechanics.
47
+ */
48
+ export function freezeCandidate({ policyText, sourceStage, deliveredRepresentation }) {
49
+ if (typeof policyText !== 'string' || policyText.length === 0) {
50
+ throw new Error('freezeCandidate: policyText must be a non-empty string');
51
+ }
52
+ return {
53
+ policyText,
54
+ contentHash: sha256(policyText),
55
+ byteLength: Buffer.byteLength(policyText, 'utf8'),
56
+ sourceStage,
57
+ deliveredRepresentation,
58
+ // G1 has no real Principle identity; NT-06 forbids inventing one.
59
+ principleId: null,
60
+ principleIdStatus: 'UNKNOWN',
61
+ };
62
+ }
63
+
64
+ /** Build the principle-study sidecar for a completed pilot run. */
65
+ export function buildStudySidecar({ dataset, studyId, arms, claims, assignment, runtime }) {
66
+ return {
67
+ schemaVersion: STUDY_SCHEMA,
68
+ studyId,
69
+ specRef: dataset.manifest.specRef,
70
+ dataset: {
71
+ datasetId: dataset.manifest.datasetId,
72
+ datasetVersion: dataset.manifest.datasetVersion,
73
+ fixtureHash: dataset.fixtureHash,
74
+ },
75
+ claims,
76
+ arms,
77
+ assignment,
78
+ runtime,
79
+ authorityNote:
80
+ 'Research view only. Runtime facts (if any) remain owned by the production stores; this sidecar fabricates no governance records.',
81
+ };
82
+ }
83
+
84
+ function isPlainObject(v) {
85
+ return typeof v === 'object' && v !== null && !Array.isArray(v);
86
+ }
87
+
88
+ /**
89
+ * Validate a study + adjudications against the loaded dataset and the recorded
90
+ * actor inputs. Returns problems[]; empty = valid. rc-1/rc-3/rc-4 discipline:
91
+ * every field is checked loudly, element types included.
92
+ */
93
+ export function validateSidecar({ dataset, study, adjudications, actorInputs, knownStrategyIds }) {
94
+ const problems = [];
95
+ const p = (msg) => problems.push(msg);
96
+
97
+ if (!isPlainObject(study)) {
98
+ return ['study must be a JSON object'];
99
+ }
100
+ if (study.schemaVersion !== STUDY_SCHEMA) p(`study.schemaVersion must be ${STUDY_SCHEMA}`);
101
+ if (typeof study.studyId !== 'string' || study.studyId === '') p('study.studyId must be a non-empty string');
102
+ if (!isPlainObject(study.dataset)) p('study.dataset must be an object');
103
+ else {
104
+ if (study.dataset.fixtureHash !== dataset.fixtureHash) {
105
+ p(`study.dataset.fixtureHash ${study.dataset.fixtureHash} does not match the loaded fixture ${dataset.fixtureHash}`);
106
+ }
107
+ if (study.dataset.datasetId !== dataset.manifest.datasetId) p('study.dataset.datasetId mismatch');
108
+ }
109
+
110
+ // Claims registry.
111
+ if (!Array.isArray(study.claims) || study.claims.length === 0) p('study.claims must be a non-empty array');
112
+ else {
113
+ for (const c of study.claims) {
114
+ if (!isPlainObject(c)) p('study.claims entries must be objects');
115
+ else {
116
+ if (typeof c.claimId !== 'string' || c.claimId === '') p('claim.claimId must be a non-empty string');
117
+ if (typeof c.contrastId !== 'string' || c.contrastId === '') p(`claim ${c.claimId} must carry contrastId`);
118
+ if (!Array.isArray(c.competingExplanations) || c.competingExplanations.length === 0) {
119
+ p(`claim ${c.claimId} must list at least one competing explanation`);
120
+ }
121
+ if (typeof c.analysisUnit !== 'string') p(`claim ${c.claimId} must carry analysisUnit`);
122
+ if (!CLAIM_STATUS_VALUES.includes(c.status)) {
123
+ p(`claim ${c.claimId} status must be one of ${CLAIM_STATUS_VALUES.join('/')}`);
124
+ }
125
+ }
126
+ }
127
+ }
128
+
129
+ // Arms.
130
+ if (!Array.isArray(study.arms) || study.arms.length === 0) p('study.arms must be a non-empty array');
131
+ const armIds = new Set();
132
+ for (const arm of study.arms ?? []) {
133
+ if (!isPlainObject(arm)) {
134
+ p('study.arms entries must be objects');
135
+ continue;
136
+ }
137
+ if (typeof arm.armId !== 'string' || arm.armId === '') p('arm.armId must be a non-empty string');
138
+ if (armIds.has(arm.armId)) p(`duplicate armId ${arm.armId}`);
139
+ armIds.add(arm.armId);
140
+ if (!dataset.histories[arm.historyId]) p(`arm ${arm.armId} references unknown history ${arm.historyId}`);
141
+ if (!knownStrategyIds.has(arm.strategyId)) p(`arm ${arm.armId} references unknown strategy ${arm.strategyId}`);
142
+ if (!isPlainObject(arm.initialMemoryManifest)) {
143
+ p(`arm ${arm.armId} must carry initialMemoryManifest`);
144
+ }
145
+ const fc = arm.frozenCandidate;
146
+ if (!isPlainObject(fc)) {
147
+ p(`arm ${arm.armId} must carry frozenCandidate`);
148
+ } else {
149
+ if (fc.contentHash !== sha256(fc.policyText ?? '')) {
150
+ p(`arm ${arm.armId} frozenCandidate.contentHash does not match sha256(policyText)`);
151
+ }
152
+ // NT-06: no fabricated Principle identity (title-guessed ids included).
153
+ if (fc.principleId !== null && fc.principleId !== undefined) {
154
+ p(`arm ${arm.armId} fabricates principleId '${fc.principleId}' — G1 has no real Principle identity (NT-06)`);
155
+ }
156
+ // 'model-summary' and 'raw-history' are the C-A contrast conditions
157
+ // (prereg real-candidate-vs-summary-vs-raw-vs-none): same freeze
158
+ // mechanics, source stage names what produced the delivered bytes.
159
+ const SOURCE_STAGES = ['simulated-learn', 'i0-reflection', 'i1-pipeline', 'model-summary', 'raw-history'];
160
+ if (!SOURCE_STAGES.includes(fc.sourceStage)) {
161
+ p(`arm ${arm.armId} frozenCandidate.sourceStage must be ${SOURCE_STAGES.join('|')}`);
162
+ }
163
+ }
164
+ }
165
+
166
+ // Adjudications: element-wise shape + enum + coverage.
167
+ if (!Array.isArray(adjudications) || adjudications.length === 0) p('adjudications must be a non-empty array');
168
+ const seen = new Map();
169
+ for (const row of adjudications ?? []) {
170
+ if (!isPlainObject(row)) {
171
+ p('adjudication rows must be objects');
172
+ continue;
173
+ }
174
+ const key = `${row.armId}|${row.opportunityId}`;
175
+ if (seen.has(key)) p(`duplicate adjudication for ${key}`);
176
+ seen.set(key, row);
177
+ if (row.schemaVersion !== ADJUDICATION_SCHEMA) p(`adjudication ${key} schemaVersion must be ${ADJUDICATION_SCHEMA}`);
178
+ if (row.studyId !== study.studyId) p(`adjudication ${key} studyId mismatch`);
179
+ if (!PIPELINE_REACHABILITY_VALUES.includes(row.pipelineReachability)) p(`adjudication ${key} bad pipelineReachability`);
180
+ if (!ACTOR_RUN_STATUS_VALUES.includes(row.actorRunStatus)) p(`adjudication ${key} bad actorRunStatus`);
181
+ if (!OBJECTIVE_TASK_STATUS_VALUES.includes(row.objectiveTaskStatus)) p(`adjudication ${key} bad objectiveTaskStatus`);
182
+ if (!PRINCIPLE_ALIGNMENT_VALUES.includes(row.principleAlignment)) p(`adjudication ${key} bad principleAlignment`);
183
+ if (!DATA_QUALITY_FLAGS.includes(row.dataQualityFlag)) p(`adjudication ${key} bad dataQualityFlag`);
184
+ if (row.preTaskEligibility !== 'eligible') p(`adjudication ${key} must record preTaskEligibility`);
185
+ // NT-04: a NOT_REACHED row must still be graded.
186
+ if (row.pipelineReachability === 'NOT_REACHED') {
187
+ if (row.objectiveTaskStatus === 'UNKNOWN' && row.actorRunStatus === 'COMPLETED') {
188
+ p(`adjudication ${key}: pipeline NOT_REACHED but the completed actor run was not graded (NT-04)`);
189
+ }
190
+ }
191
+ // NT-05: pipeline reachability must never be the REASON for a task FAIL.
192
+ if (
193
+ row.objectiveTaskStatus === 'FAIL'
194
+ && typeof row.judge?.decisionRationale === 'string'
195
+ && /pipeline[ _-]?not[ _-]?reached/i.test(row.judge.decisionRationale)
196
+ ) {
197
+ p(`adjudication ${key}: objectiveTaskStatus FAIL is justified by pipeline NOT_REACHED — reachability must not masquerade as behavior failure (NT-05)`);
198
+ }
199
+ // NT-08: NOT_APPLICABLE needs an allowlisted reason; anti-pattern matches
200
+ // never establish non-applicability.
201
+ if (row.principleAlignment === 'NOT_APPLICABLE') {
202
+ const reason = row.alignmentReasonCode ?? row.judge?.decisionRationale ?? '';
203
+ if (/anti[ _-]?pattern/i.test(reason)) {
204
+ p(`adjudication ${key}: NOT_APPLICABLE justified by an antiPattern match (NT-08)`);
205
+ }
206
+ if (!NA_REASON_ALLOWLIST.includes(row.alignmentReasonCode)) {
207
+ p(`adjudication ${key}: NOT_APPLICABLE requires alignmentReasonCode in ${NA_REASON_ALLOWLIST.join('|')}`);
208
+ }
209
+ }
210
+ if (!Array.isArray(row.realOutcomes?.checksRun)) p(`adjudication ${key} realOutcomes.checksRun must be an array`);
211
+ if (!isPlainObject(row.ownerCost)) p(`adjudication ${key} ownerCost must be an object`);
212
+ if (row.judge?.judgeType !== 'mechanical') p(`adjudication ${key} G1 judge must be mechanical`);
213
+ }
214
+
215
+ // Coverage: every (arm × opportunity) exactly once.
216
+ for (const arm of study.arms ?? []) {
217
+ for (const probeId of Object.keys(dataset.probes)) {
218
+ const probe = dataset.probes[probeId];
219
+ const oppIds = (probe.opportunities ?? [{ opportunityId: probeId }]).map((o) => o.opportunityId);
220
+ for (const oppId of oppIds) {
221
+ const key = `${arm.armId}|${oppId}`;
222
+ if (!seen.has(key)) p(`missing adjudication for ${key}`);
223
+ }
224
+ }
225
+ }
226
+ for (const key of seen.keys()) {
227
+ const [armId] = key.split('|');
228
+ if (!armIds.has(armId)) p(`adjudication references unknown arm ${armId}`);
229
+ }
230
+
231
+ // NT-01 / NT-03 basis: recorded actor inputs must be canary-clean, and the
232
+ // recorded history→future pairing must be same-leakage-group.
233
+ if (actorInputs) {
234
+ for (const input of actorInputs) {
235
+ if (!isPlainObject(input)) {
236
+ p('actorInputs entries must be objects');
237
+ continue;
238
+ }
239
+ if (input.historySynthetic !== true) p(`actorInput ${input.historyId}/${input.opportunityId} must record synthetic history`);
240
+ const g = dataset.gold.gold[input.probeId];
241
+ const record = g.phases ? { canaryStrings: g.phases[input.opportunityId]?.canaryStrings ?? [] } : g;
242
+ for (const canary of record.canaryStrings ?? []) {
243
+ if (input.visibleText?.includes(canary)) {
244
+ p(`canary '${canary}' leaked into actor input ${input.historyId}/${input.opportunityId} (NT-01)`);
245
+ }
246
+ }
247
+ if (input.leakageGroupId !== dataset.manifest.leakageGroupId) {
248
+ p(`actorInput ${input.historyId}/${input.opportunityId} leakage group mismatch`);
249
+ }
250
+ }
251
+ }
252
+
253
+ // NT-07: episode initial-decision evidence must respect the cutoff.
254
+ const cutoff = Date.parse(dataset.episode.informationCutoff);
255
+ for (const ref of dataset.episode.evidenceRefs) {
256
+ const occurred = Date.parse(ref.occurredAt);
257
+ if (!Number.isNaN(occurred) && occurred > cutoff) {
258
+ p(`evidenceRef ${ref.sourceExactRef} post-cutoff evidence used before the decision (NT-07)`);
259
+ }
260
+ }
261
+
262
+ return problems;
263
+ }
264
+
265
+ /** Serialize adjudications as JSONL (deterministic field order via JSON.parse identity). */
266
+ export function adjudicationsToJsonl(adjudications) {
267
+ return adjudications.map((r) => JSON.stringify(r)).join('\n') + '\n';
268
+ }
269
+
270
+ /** Canonical study hash — the replay-identity of the whole run. */
271
+ export function studyHash(study, adjudications) {
272
+ return sha256(canonicalJson(study) + '\n' + adjudicationsToJsonl(adjudications));
273
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-principles-disciple",
3
- "version": "1.145.43",
3
+ "version": "1.146.0",
4
4
  "description": "Interactive CLI installer for Principles Disciple OpenClaw plugin",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-principles-disciple",
3
- "version": "1.145.43",
3
+ "version": "1.146.0",
4
4
  "description": "Interactive CLI installer for Principles Disciple OpenClaw plugin",
5
5
  "type": "module",
6
6
  "bin": {