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.
@@ -138,6 +138,23 @@ function probeOpenclawVersion(openclawBin, openclawPrefixArgs, versionProbe) {
138
138
  return version;
139
139
  }
140
140
 
141
+ /**
142
+ * SPEC-PD-EXAMINER-001 v0.10.1 §12.3 — read-only CLI reachability probe for
143
+ * the Console host dry-run. Same local `--version` process as transport
144
+ * creation, but NEVER throws and never creates anything: the result is a
145
+ * structured verdict the dry-run can show as a Gate 2 prerequisite. The
146
+ * binary defaults to the FIXED literal 'openclaw'; callers that pass an
147
+ * executable path take responsibility for it (the console never does).
148
+ */
149
+ export function probeOpenClawCli({ openclawBin = 'openclaw', openclawPrefixArgs = [], versionProbe = null } = {}) {
150
+ try {
151
+ const version = probeOpenclawVersion(openclawBin, openclawPrefixArgs, versionProbe);
152
+ return { ok: true, cliVersion: version, reason: null };
153
+ } catch (err) {
154
+ return { ok: false, cliVersion: null, reason: err && err.message ? err.message : String(err) };
155
+ }
156
+ }
157
+
141
158
  /** Same suffix discipline as verifyF2ProbeEvidence.sameModel — a provider-
142
159
  * prefixed model id ("zai/glm-5.3-flash") matches its bare id. */
143
160
  const sameModel = (a, b) => a === b || (typeof a === 'string' && typeof b === 'string' && (a.endsWith(`/${b}`) || b.endsWith(`/${a}`)));
@@ -410,6 +410,7 @@ export async function runChallengeStudy({
410
410
  owner = 'challenge-runner',
411
411
  deps = {},
412
412
  execution = null,
413
+ admissionCheck = null,
413
414
  }) {
414
415
  const declaredMode = execution && execution.mode !== undefined ? execution.mode : 'offline-stub';
415
416
  if (!RUNNER_EXECUTION_MODES.includes(declaredMode)) {
@@ -452,6 +453,14 @@ export async function runChallengeStudy({
452
453
  // unchanged. Same run-scoped discipline as the unknown streak.
453
454
  let consecutiveCapacityFailures = 0;
454
455
  try {
456
+ // P1-3 (offline↔async single-active): the caller-injected admission check
457
+ // runs FIRST under this study write lock — before RUN_STARTED, before any
458
+ // attempt reservation and before any actor is constructed. A refusal here
459
+ // throws, the lock is released by the catch below, and the ledger keeps no
460
+ // half-started run.
461
+ if (typeof admissionCheck === 'function') {
462
+ await admissionCheck({ studyDir });
463
+ }
455
464
  // P1-4 server-enforced readiness: identity/path ownership are the caller's
456
465
  // gate (the console resolves the directory from its own enumeration and
457
466
  // verifies the write permission marker); the FROZEN study is re-verified
@@ -455,6 +455,7 @@ export function buildExaminerStudyManifest({
455
455
  invalidationPolicy,
456
456
  armPayloadHashes = null,
457
457
  authorizationRef = null,
458
+ productionSnapshotHash = null,
458
459
  }) {
459
460
  if (!text(studyId)) throw new Error('buildExaminerStudyManifest: studyId required');
460
461
  if (!/^[A-Z0-9][A-Z0-9-]*$/.test(studyId)) throw new Error(`buildExaminerStudyManifest: studyId '${studyId}' must be UPPER-CASE with digits/dashes`);
@@ -511,6 +512,11 @@ export function buildExaminerStudyManifest({
511
512
  analysisPlanHash: hashOfCanonical(analysisPlan),
512
513
  invalidationPolicyHash: hashOfCanonical(invalidationPolicy),
513
514
  authorizationRef,
515
+ // v0.10.1 P1-1: the frozen production snapshot rides INSIDE the manifest
516
+ // (and therefore inside the manifestHash → binding → anchor chain).
517
+ // Conditional like armPayloadHashes so a v0.9-shaped manifest stays
518
+ // byte-identical with the historical field set.
519
+ ...(productionSnapshotHash ? { productionSnapshotHash } : {}),
514
520
  };
515
521
  return { ...core, manifestHash: hashOfCanonical(core) };
516
522
  }
@@ -730,6 +736,36 @@ export function computeExaminerStudyBindings(studyDir) {
730
736
  if (hashes.examinerManifestHash?.value !== manifest.manifestHash) push('hashes.examinerManifestHash != manifest.manifestHash');
731
737
  if (hashes.pairAssignmentHash?.value !== pair.assignmentHash) push('hashes.pairAssignmentHash != pair assignment hash');
732
738
 
739
+ // --- production snapshot (v0.10.1 §4.4/§6.4) ------------------------------
740
+ // The frozen production baseline is PART of the preregistration evidence:
741
+ // when the file exists it is hash-bound to the manifest and the binding; a
742
+ // missing file beside a manifest that declares a snapshot hash is drift;
743
+ // no file AND no manifest field is the v0.9 legacy shape (verified studies
744
+ // keep recomputing the exact historical binding). The snapshot content
745
+ // carries no self-hash, so the chain snapshot→manifestHash→binding→anchor
746
+ // has no cycle.
747
+ let productionSnapshotHash = null;
748
+ const snapshotPath = path.join(studyDir, 'production-snapshot.json');
749
+ if (fs.existsSync(snapshotPath)) {
750
+ let snapshotParsed;
751
+ try {
752
+ snapshotParsed = JSON.parse(fs.readFileSync(snapshotPath, 'utf8'));
753
+ } catch (e) {
754
+ push(`production-snapshot.json is not valid JSON: ${e.message}`);
755
+ snapshotParsed = null;
756
+ }
757
+ if (snapshotParsed !== null && typeof snapshotParsed === 'object' && !Array.isArray(snapshotParsed)) {
758
+ productionSnapshotHash = hashOfCanonical(snapshotParsed);
759
+ if (manifest.productionSnapshotHash !== productionSnapshotHash) {
760
+ push('production-snapshot.json hash != manifest.productionSnapshotHash — the frozen production baseline was modified after freezing');
761
+ }
762
+ } else if (snapshotParsed !== null) {
763
+ push('production-snapshot.json must be a JSON object');
764
+ }
765
+ } else if (manifest.productionSnapshotHash !== undefined) {
766
+ push('manifest.productionSnapshotHash declared but production-snapshot.json is missing — the frozen production baseline cannot be verified');
767
+ }
768
+
733
769
  const binding = {
734
770
  schema: PREREG_BINDING_SCHEMA,
735
771
  studyId: manifest.studyId ?? null,
@@ -749,6 +785,9 @@ export function computeExaminerStudyBindings(studyDir) {
749
785
  // definition after freezing (even with every file re-hashed) moves this.
750
786
  primaryEndpointHash: hashOfCanonical(manifest.primaryEndpoint ?? null),
751
787
  armPayloadHashes,
788
+ // P1-1: present ONLY when the study freezes a production snapshot — the
789
+ // v0.9 historical binding field set stays byte-identical otherwise.
790
+ ...(productionSnapshotHash ? { productionSnapshotHash } : {}),
752
791
  plannedPairs: manifest.plannedPairs ?? null,
753
792
  repetitionsPerCase: manifest.repetitionsPerCase ?? null,
754
793
  };
@@ -961,9 +1000,19 @@ export function writeExaminerStudyArtifacts(input) {
961
1000
  analysisPlan, invalidationPolicy, hostManifest, modelManifest,
962
1001
  scorerVersion, seed, repetitionsPerCase, authorizationRef = null,
963
1002
  goldProvenance = 'SYNTHETIC_OFFLINE',
1003
+ productionSnapshot = null,
964
1004
  } = input;
965
1005
  if (!studyDir || typeof studyDir !== 'string') throw new Error('writeExaminerStudyArtifacts: studyDir required');
966
1006
  if (fs.existsSync(studyDir)) throw new Error(`writeExaminerStudyArtifacts: '${studyDir}' already exists — a study identity is created once; overwriting is refused (create a NEW study id instead)`);
1007
+ // P1-1/rc-1: the production snapshot arrives as external runtime data. Freeze
1008
+ // it as the JSON it will be STORED as: canonical hashing keeps object keys
1009
+ // whose value is `undefined`, while JSON serialization drops them — hashing
1010
+ // the raw in-memory object would disagree with the file on disk and report
1011
+ // false drift. Binding verification hashes the written bytes; the manifest
1012
+ // must hash the same representation.
1013
+ const frozenProductionSnapshot = productionSnapshot === null
1014
+ ? null
1015
+ : JSON.parse(JSON.stringify(productionSnapshot));
967
1016
  const frozenArea = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', 'challenge-studies');
968
1017
  const resolved = path.resolve(studyDir);
969
1018
  const norm = (p) => (process.platform === 'win32' ? p.toLowerCase() : p);
@@ -1011,6 +1060,9 @@ export function writeExaminerStudyArtifacts(input) {
1011
1060
  invalidationPolicy,
1012
1061
  armPayloadHashes: { control: armPayloads.arms.control.payloadHash, treatment: armPayloads.arms.treatment.payloadHash },
1013
1062
  authorizationRef,
1063
+ // P1-1: the snapshot is gathered BEFORE the freeze and rides the full
1064
+ // manifest → binding → anchor chain (no post-freeze append can touch it).
1065
+ ...(frozenProductionSnapshot !== null ? { productionSnapshotHash: hashOfCanonical(frozenProductionSnapshot) } : {}),
1014
1066
  });
1015
1067
  const prereg = {
1016
1068
  studyId: input.studyId,
@@ -1046,6 +1098,9 @@ export function writeExaminerStudyArtifacts(input) {
1046
1098
  writeJson('scoring-contract.json', scoringContract);
1047
1099
  writeJson('principle-frozen.json', principleFrozen);
1048
1100
  writeJson('pain-mechanism-mapping.json', mapping);
1101
+ // P1-1: the production baseline freezes WITH the study (inside the binding
1102
+ // verification below) — not as a post-freeze append.
1103
+ if (frozenProductionSnapshot !== null) writeJson('production-snapshot.json', frozenProductionSnapshot);
1049
1104
  writeJson('input-manifest.json', { expectedSourceHashes: { principleContentHash: hashOfCanonical(principleFrozen.contentJson) } });
1050
1105
  writeJson('hashes.json', {
1051
1106
  preregistrationHash: { value: hashOfCanonical(prereg) },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-principles-disciple",
3
- "version": "1.146.8",
3
+ "version": "1.146.9",
4
4
  "description": "Interactive CLI installer for Principles Disciple OpenClaw plugin",
5
5
  "type": "module",
6
6
  "bin": {
@@ -98,5 +98,5 @@
98
98
  "pd": {
99
99
  "bundledPluginVersion": "2.2.13"
100
100
  },
101
- "gitHead": "9ef8d607d51cea885249077634df3b4126919a0d"
101
+ "gitHead": "8e0cdc607ee5d25e0b66a0655a6e0ce033a1fe99"
102
102
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-principles-disciple",
3
- "version": "1.146.8",
3
+ "version": "1.146.9",
4
4
  "description": "Interactive CLI installer for Principles Disciple OpenClaw plugin",
5
5
  "type": "module",
6
6
  "bin": {