@disclosureos/cli 1.0.0 → 2.0.0-beta.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.
package/dist/index.js CHANGED
@@ -2,8 +2,8 @@
2
2
 
3
3
  // src/index.ts
4
4
  import { readFileSync as readFileSync2 } from "fs";
5
- import { dirname, join } from "path";
6
- import { fileURLToPath } from "url";
5
+ import { dirname as dirname6, join as join10 } from "path";
6
+ import { fileURLToPath as fileURLToPath2 } from "url";
7
7
 
8
8
  // src/utils/args.ts
9
9
  var SHORT_FLAG_MAP = {
@@ -158,8 +158,8 @@ var CLASSIFICATION_TEMPLATE = [
158
158
  evidenceRefs: []
159
159
  }
160
160
  ];
161
- function getTemplate(name) {
162
- const data = name === "observation" ? buildObservation(false) : name === "observation-full" ? buildObservation(true) : name === "assessment" ? ASSESSMENT_TEMPLATE : CLASSIFICATION_TEMPLATE;
161
+ function getTemplate(name2) {
162
+ const data = name2 === "observation" ? buildObservation(false) : name2 === "observation-full" ? buildObservation(true) : name2 === "assessment" ? ASSESSMENT_TEMPLATE : CLASSIFICATION_TEMPLATE;
163
163
  return JSON.stringify(data, null, 2);
164
164
  }
165
165
 
@@ -177,8 +177,8 @@ function writeOutput(filePath, data) {
177
177
  function findJSONFiles(dirPath, recursive) {
178
178
  const resolved = resolve(dirPath);
179
179
  if (!existsSync(resolved)) return [];
180
- const stat = statSync(resolved);
181
- if (stat.isFile()) {
180
+ const stat2 = statSync(resolved);
181
+ if (stat2.isFile()) {
182
182
  return extname(resolved) === ".json" ? [resolved] : [];
183
183
  }
184
184
  const results = [];
@@ -259,8 +259,8 @@ function resolveTemplateName(args2) {
259
259
  console.log(error(`Unknown template: "${sub}"`));
260
260
  return null;
261
261
  }
262
- function getTypeScriptTemplate(name) {
263
- switch (name) {
262
+ function getTypeScriptTemplate(name2) {
263
+ switch (name2) {
264
264
  case "observation":
265
265
  return TS_OBSERVATION;
266
266
  case "observation-full":
@@ -395,7 +395,7 @@ Generate typed templates for DisclosureOS data structures.
395
395
  }
396
396
 
397
397
  // src/commands/validate.ts
398
- import { parseEvidenceRef } from "@disclosureos/records/shared";
398
+ import { parseEvidenceRef, isSensorRef } from "@disclosureos/records/shared";
399
399
  import { parseEnrichedObservation } from "@disclosureos/schema";
400
400
  function validate(args2) {
401
401
  if (args2.flags["help"]) {
@@ -413,10 +413,10 @@ function validate(args2) {
413
413
  const seen = /* @__PURE__ */ new Set();
414
414
  const files = [];
415
415
  const emptyTargets = [];
416
- for (const target of targets) {
417
- const found = findJSONFiles(target, recursive);
416
+ for (const target2 of targets) {
417
+ const found = findJSONFiles(target2, recursive);
418
418
  if (found.length === 0) {
419
- emptyTargets.push(target);
419
+ emptyTargets.push(target2);
420
420
  continue;
421
421
  }
422
422
  for (const f of found) {
@@ -503,7 +503,7 @@ function validateFileStructured(filePath, strict) {
503
503
  const obs = data;
504
504
  const structuralErrors = parseEnrichedObservation(obs).issues;
505
505
  const semanticErrors = validateSemantics(obs);
506
- const warnings = [...validateEvidenceRefs(obs), ...strict ? validateStrict(obs) : []];
506
+ const warnings = [...validateEvidenceRefs(obs), ...validateSensorRefs(obs), ...strict ? validateStrict(obs) : []];
507
507
  const allErrors = [...structuralErrors, ...semanticErrors];
508
508
  return {
509
509
  file: filePath,
@@ -527,7 +527,7 @@ function validateFile(filePath, strict) {
527
527
  const obs = data;
528
528
  const structuralErrors = parseEnrichedObservation(obs).issues;
529
529
  const semanticErrors = validateSemantics(obs);
530
- const warnings = [...validateEvidenceRefs(obs), ...strict ? validateStrict(obs) : []];
530
+ const warnings = [...validateEvidenceRefs(obs), ...validateSensorRefs(obs), ...strict ? validateStrict(obs) : []];
531
531
  const allErrors = [...structuralErrors, ...semanticErrors];
532
532
  if (allErrors.length > 0 || warnings.length > 0) {
533
533
  console.log(`
@@ -628,6 +628,25 @@ function validateEvidenceRefs(obs) {
628
628
  }
629
629
  return warnings;
630
630
  }
631
+ function validateSensorRefs(obs) {
632
+ const warnings = [];
633
+ const sensorEvidence = obs["sensorEvidence"];
634
+ if (!sensorEvidence || typeof sensorEvidence !== "object") return warnings;
635
+ const sensors = sensorEvidence["sensors"];
636
+ if (!Array.isArray(sensors)) return warnings;
637
+ sensors.forEach((sensor, i) => {
638
+ if (!sensor || typeof sensor !== "object") return;
639
+ const ref = sensor["sensorRef"];
640
+ if (ref === void 0) return;
641
+ if (typeof ref !== "string" || !isSensorRef(ref)) {
642
+ warnings.push({
643
+ path: `sensorEvidence.sensors[${i}].sensorRef`,
644
+ message: `malformed sensor ref ${JSON.stringify(ref)} (expected "<org-slug>:<sensor-id>", e.g. "eldaeon:dionysus-passive-radar")`
645
+ });
646
+ }
647
+ });
648
+ return warnings;
649
+ }
631
650
  function validateStrict(obs) {
632
651
  const warnings = [];
633
652
  const recommended = ["summary", "description", "objectCharacteristics", "sourceData", "witnesses"];
@@ -679,6 +698,7 @@ Validate observation JSON files against the full schema.
679
698
  console.log(` \u2022 OCS node IDs in origin classifications (if present)`);
680
699
  console.log(` \u2022 Unknown top-level keys rejected (third-party data belongs under "extensions")`);
681
700
  console.log(` \u2022 Evidence refs on claims resolve to in-record evidence (warns on dangling)`);
701
+ console.log(` \u2022 Sensor refs follow the "<org-slug>:<sensor-id>" manifest convention (warns on malformed)`);
682
702
  console.log(` \u2022 Semantic sanity (non-negative duration, speed, counts)
683
703
  `);
684
704
  console.log(`${dim("Examples:")}`);
@@ -690,306 +710,3675 @@ Validate observation JSON files against the full schema.
690
710
  `);
691
711
  }
692
712
 
693
- // src/commands/completeness.ts
694
- import { parseEnrichedObservation as parseEnrichedObservation2 } from "@disclosureos/schema";
695
- import { getCompleteness } from "@disclosureos/scoring";
696
- function completeness(args2) {
697
- if (args2.flags["help"]) {
713
+ // src/commands/manifest.ts
714
+ import { validateSensorManifest } from "@disclosureos/instruments";
715
+ function manifest(args2) {
716
+ if (args2.flags["help"] || args2.subcommand !== "validate") {
698
717
  printUsage3();
718
+ if (!args2.flags["help"] && args2.subcommand !== void 0 && args2.subcommand !== "validate") {
719
+ process.exit(1);
720
+ }
699
721
  return;
700
722
  }
701
- const targets = [args2.subcommand, ...args2.positional].filter((t) => !!t);
723
+ const targets = args2.positional.filter((t) => !!t);
702
724
  if (targets.length === 0) {
703
725
  printUsage3();
704
726
  return;
705
727
  }
706
728
  const recursive = !!args2.flags["recursive"];
707
729
  const jsonMode = !!args2.flags["json"];
708
- const seen = /* @__PURE__ */ new Set();
709
- const files = [];
710
- const emptyTargets = [];
711
- for (const target of targets) {
712
- const found = findJSONFiles(target, recursive);
713
- if (found.length === 0) {
714
- emptyTargets.push(target);
715
- continue;
716
- }
717
- for (const f of found) {
718
- if (!seen.has(f)) {
719
- seen.add(f);
720
- files.push(f);
721
- }
722
- }
730
+ const files = [...new Set(targets.flatMap((target2) => findJSONFiles(target2, recursive)))];
731
+ if (files.length === 0) {
732
+ console.log(error(`No JSON files found at: ${targets.join(", ")}`));
733
+ process.exit(1);
723
734
  }
735
+ const results = files.map(validateManifestFile);
724
736
  if (jsonMode) {
725
- runJsonMode2(files, emptyTargets);
737
+ const valid = results.every((result) => result.valid);
738
+ console.log(JSON.stringify({ files: results, valid }, null, 2));
739
+ if (!valid) process.exit(1);
726
740
  return;
727
741
  }
728
- for (const t of emptyTargets) {
729
- console.log(error(`No JSON files found at: ${t}`));
730
- }
731
- if (files.length === 0) {
732
- process.exit(1);
733
- }
734
- let hasErrors = false;
735
- const percentages = [];
736
- const reqPercentages = [];
737
- for (const file of files) {
738
- const result = scoreFile(file);
742
+ let failed = false;
743
+ for (const result of results) {
739
744
  if (!result.valid) {
740
- hasErrors = true;
745
+ failed = true;
746
+ console.log(`
747
+ ${error(`${result.file} INVALID`)}`);
748
+ for (const issue of result.errors) {
749
+ console.log(` ${error(`${issue.path}: ${issue.message}`)}`);
750
+ }
741
751
  continue;
742
752
  }
743
- percentages.push(result.percentage);
744
- reqPercentages.push(result.requiredPercentage);
753
+ const summary = result.summary;
754
+ console.log(`
755
+ ${success(`${result.file}`)}`);
756
+ console.log(` ${dim(formatManifestLine(summary))}`);
757
+ console.log(` ${dim("by modality:")} ${Object.entries(summary.byModality).map(([m, n]) => `${m} ${n}`).join(", ")}`);
758
+ if (summary.proposedSensorTypes.length > 0) {
759
+ console.log(` ${dim(`proposed sensor types (${summary.proposedSensorTypes.length}):`)} ${summary.proposedSensorTypes.join(", ")}`);
760
+ }
761
+ if (summary.proposedDetectionMethods.length > 0) {
762
+ console.log(` ${dim(`proposed detection methods (${summary.proposedDetectionMethods.length}):`)} ${summary.proposedDetectionMethods.join(", ")}`);
763
+ }
745
764
  }
746
765
  console.log("");
747
- if (hasErrors || emptyTargets.length > 0) {
748
- console.log(error("Some files could not be scored (see errors above)."));
766
+ if (failed) {
767
+ console.log(error(`Validation failed`));
749
768
  process.exit(1);
750
769
  }
751
- const mean = avg(percentages);
752
- const reqMean = avg(reqPercentages);
753
- console.log(
754
- success(
755
- `${files.length} file(s) scored \u2014 mean completeness ${mean}%, required fields ${reqMean}%`
756
- )
757
- );
770
+ console.log(success(`All ${files.length} manifest(s) valid`));
758
771
  }
759
- function avg(values) {
760
- return values.length > 0 ? Math.round(values.reduce((a, b) => a + b, 0) / values.length) : 0;
772
+ function formatManifestLine(summary) {
773
+ return `${summary.org} (${summary.orgSlug}) \u2014 ${summary.sensors} sensors, ${summary.futureUpgrades} future upgrades`;
761
774
  }
762
- function scoreFile(filePath) {
775
+ function validateManifestFile(file) {
763
776
  let data;
764
777
  try {
765
- data = readJSON(filePath);
778
+ data = readJSON(file);
766
779
  } catch {
767
- console.log(error(`Failed to parse: ${filePath}`));
768
- return emptyResult(filePath, "Failed to parse JSON");
769
- }
770
- if (!data || typeof data !== "object") {
771
- console.log(error(`${filePath}: not a valid JSON object`));
772
- return emptyResult(filePath, "Not a valid JSON object");
780
+ return { file, valid: false, errors: [{ path: "", message: "Failed to parse JSON" }] };
773
781
  }
774
- const parsed = parseEnrichedObservation2(data);
775
- if (!parsed.success) {
776
- console.log(error(`${filePath}: invalid observation (${parsed.issues.length} issue${parsed.issues.length !== 1 ? "s" : ""})`));
777
- for (const issue of parsed.issues.slice(0, 5)) {
778
- console.log(` ${dim(`${issue.path}: ${issue.message}`)}`);
779
- }
780
- return emptyResult(filePath, "Invalid observation");
782
+ const issues = validateSensorManifest(data);
783
+ if (issues.length > 0) {
784
+ return { file, valid: false, errors: issues };
781
785
  }
782
- const comp = getCompleteness(data);
783
- console.log(`
784
- ${dim(filePath)}`);
785
- console.log(` Completeness: ${comp.percentage}% (${comp.present}/${comp.total} fields)`);
786
- console.log(` Required: ${comp.requiredPercentage}% (${comp.requiredPresent}/${comp.requiredTotal})`);
787
- if (comp.missing.length > 0) {
788
- const shown = comp.missing.slice(0, 10);
789
- console.log(` Missing (top ${shown.length}):`);
790
- for (const path of shown) {
791
- console.log(` ${dim("\u2022")} ${path}`);
786
+ const parsed = data;
787
+ const byModality = {};
788
+ const proposedSensorTypes = [];
789
+ const proposedDetectionMethods = [];
790
+ for (const sensor of parsed.sensors) {
791
+ byModality[sensor.modality] = (byModality[sensor.modality] ?? 0) + 1;
792
+ if (sensor.recordsMapping?.proposedSensorType && sensor.recordsMapping.sensorType) {
793
+ proposedSensorTypes.push(sensor.recordsMapping.sensorType);
792
794
  }
793
- if (comp.missing.length > shown.length) {
794
- console.log(` ${dim(`\u2026 and ${comp.missing.length - shown.length} more`)}`);
795
+ if (sensor.recordsMapping?.proposedDetectionMethod && sensor.recordsMapping.detectionMethod) {
796
+ proposedDetectionMethods.push(sensor.recordsMapping.detectionMethod);
795
797
  }
796
798
  }
797
- return {
798
- file: filePath,
799
- valid: true,
800
- percentage: comp.percentage,
801
- requiredPercentage: comp.requiredPercentage,
802
- present: comp.present,
803
- total: comp.total,
804
- missing: comp.missing
805
- };
806
- }
807
- function emptyResult(file, errorMsg) {
808
799
  return {
809
800
  file,
810
- valid: false,
811
- percentage: 0,
812
- requiredPercentage: 0,
813
- present: 0,
814
- total: 0,
815
- missing: [],
816
- error: errorMsg
817
- };
818
- }
819
- function runJsonMode2(files, emptyTargets) {
820
- const fileResults = [];
821
- for (const file of files) {
822
- fileResults.push(scoreFileQuiet(file));
823
- }
824
- const validResults = fileResults.filter((r) => r.valid);
825
- const meanPercentage = avg(validResults.map((r) => r.percentage));
826
- const meanRequired = avg(validResults.map((r) => r.requiredPercentage));
827
- const allValid = fileResults.every((r) => r.valid) && emptyTargets.length === 0;
828
- const output = {
829
- files: fileResults,
830
- emptyTargets,
831
- totals: {
832
- files: files.length,
833
- meanPercentage,
834
- meanRequiredPercentage: meanRequired,
835
- emptyTargets: emptyTargets.length
836
- },
837
- valid: allValid
838
- };
839
- console.log(JSON.stringify(output, null, 2));
840
- if (!allValid) {
841
- process.exit(1);
842
- }
843
- }
844
- function scoreFileQuiet(filePath) {
845
- let data;
846
- try {
847
- data = readJSON(filePath);
848
- } catch {
849
- return emptyResult(filePath, "Failed to parse JSON");
850
- }
851
- if (!data || typeof data !== "object") {
852
- return emptyResult(filePath, "Not a valid JSON object");
853
- }
854
- const parsed = parseEnrichedObservation2(data);
855
- if (!parsed.success) {
856
- return emptyResult(filePath, `Invalid observation (${parsed.issues.length} issue${parsed.issues.length !== 1 ? "s" : ""})`);
857
- }
858
- const comp = getCompleteness(data);
859
- return {
860
- file: filePath,
861
801
  valid: true,
862
- percentage: comp.percentage,
863
- requiredPercentage: comp.requiredPercentage,
864
- present: comp.present,
865
- total: comp.total,
866
- missing: comp.missing
802
+ errors: [],
803
+ summary: {
804
+ org: parsed.org,
805
+ orgSlug: parsed.orgSlug,
806
+ sensors: parsed.sensors.length,
807
+ byModality,
808
+ proposedSensorTypes,
809
+ proposedDetectionMethods,
810
+ futureUpgrades: parsed.futureUpgrades?.length ?? 0
811
+ }
867
812
  };
868
813
  }
869
814
  function printUsage3() {
870
- console.log(heading(`${BRAND} completeness`));
815
+ console.log(heading(`${BRAND} manifest`));
871
816
  console.log(`
872
- Measure how fully observation JSON files populate the records schema.
817
+ Validate sensor-manifest JSON files (@disclosureos/instruments).
873
818
  `);
874
819
  console.log(`${dim("Usage:")}`);
875
- console.log(` disclosureos completeness <path...> [options]
876
- `);
877
- console.log(`${dim("Paths:")}`);
878
- console.log(` Accepts one or more files, directories, or shell globs.
820
+ console.log(` disclosureos manifest validate <path...> [options]
879
821
  `);
880
822
  console.log(`${dim("Options:")}`);
881
- console.log(` --recursive, -r Score all JSON files in directories recursively`);
823
+ console.log(` --recursive, -r Validate all JSON files in directories recursively`);
882
824
  console.log(` --json, -j Output results as structured JSON (for CI and scripts)`);
883
825
  console.log(` --help, -h Show this help message
884
826
  `);
885
- console.log(`${dim("Scoring:")}`);
886
- console.log(` Files must be structurally valid observations before completeness is measured.`);
887
- console.log(` Invalid JSON or invalid observations exit non-zero without a score.
888
- `);
889
- console.log(`${dim("Output:")}`);
890
- console.log(` \u2022 Completeness percentage (0\u2013100) \u2014 how many schema fields are populated`);
891
- console.log(` \u2022 Required field percentage \u2014 coverage of required fields only`);
892
- console.log(` \u2022 Top missing field paths \u2014 the next fields to target
827
+ console.log(`${dim("Validation checks:")}`);
828
+ console.log(` \u2022 Manifest structure (org, orgSlug, sensors, calibration, timing, measurements)`);
829
+ console.log(` \u2022 Sensor ids unique and kebab-case`);
830
+ console.log(` \u2022 recordsMapping values exist in the records enums or carry proposed* flags
893
831
  `);
894
832
  console.log(`${dim("Examples:")}`);
895
- console.log(` disclosureos completeness ./data/nimitz.json`);
896
- console.log(` disclosureos completeness ./data/ --recursive`);
897
- console.log(` disclosureos completeness ./out/*.json --json
833
+ console.log(` disclosureos manifest validate ./manifests/eldaeon-sensors.json`);
834
+ console.log(` disclosureos manifest validate ./manifests/ --recursive --json
898
835
  `);
899
836
  }
900
837
 
901
- // src/commands/registry.ts
902
- import { deriveFieldPaths } from "@disclosureos/scoring";
838
+ // src/commands/packet.ts
903
839
  import {
904
- ALL_OBSERVABLES,
905
- TECHNOLOGY_OBSERVABLES,
906
- BIOLOGICS_OBSERVABLES,
907
- ASSESSMENT_LEVELS
908
- } from "@disclosureos/observables";
840
+ constants,
841
+ openSync,
842
+ closeSync,
843
+ fstatSync,
844
+ readSync,
845
+ lstatSync
846
+ } from "fs";
847
+ import { resolve as resolve2, dirname, join } from "path";
848
+ import { fileURLToPath } from "url";
849
+ import { createHash } from "crypto";
909
850
  import {
910
- OCS_TAXONOMY,
911
- getNode,
912
- getChildren,
913
- getTestableNodes,
914
- ORIGIN_DOMAINS,
915
- ORIGIN_DOMAIN_LABELS
916
- } from "@disclosureos/origins";
917
- function registry(args2) {
918
- if (args2.flags["help"]) {
919
- printUsage4();
851
+ ReproductionPacketSchema,
852
+ evaluateReproductionPacket
853
+ } from "@disclosureos/schema/experimental/v2";
854
+ import { parseExperimentalClaimHistory } from "@disclosureos/records/experimental/v2";
855
+ var MANIFEST_LIMIT = 8 * 1024 * 1024;
856
+ var TOTAL_LIMIT = 256 * 1024 * 1024;
857
+ var FILE_LIMIT = 4096;
858
+ var hash = (bytes2) => createHash("sha256").update(bytes2).digest("hex");
859
+ function readLocal(path, limit) {
860
+ const fd = openSync(
861
+ path,
862
+ constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK
863
+ );
864
+ try {
865
+ const stat2 = fstatSync(fd);
866
+ if (!stat2.isFile()) throw Error("Expected a regular file.");
867
+ if (stat2.size > limit) throw Error("Packet exceeds the CLI memory limit.");
868
+ const chunks = [];
869
+ let size = 0;
870
+ for (; ; ) {
871
+ const chunk = Buffer.alloc(Math.min(65536, limit - size + 1));
872
+ const count = readSync(fd, chunk, 0, chunk.length, null);
873
+ if (!count) break;
874
+ size += count;
875
+ if (size > limit) throw Error("Packet exceeds the CLI memory limit.");
876
+ chunks.push(chunk.subarray(0, count));
877
+ }
878
+ return Buffer.concat(chunks, size);
879
+ } finally {
880
+ closeSync(fd);
881
+ }
882
+ }
883
+ var json = (bytes2) => JSON.parse(
884
+ new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(bytes2)
885
+ );
886
+ var object = (value) => {
887
+ if (!value || typeof value !== "object" || Array.isArray(value))
888
+ throw Error("Expected a JSON object.");
889
+ return value;
890
+ };
891
+ function usage() {
892
+ console.log(`DisclosureOS experimental packet workflow
893
+
894
+ disclosureos packet inspect <packet.json> [--json]
895
+ disclosureos packet validate <packet.json> [--json]
896
+ disclosureos packet reproduce <packet.json> [--json]
897
+
898
+ Files are read from files/<file ID> beside packet.json. Inspection reads only the
899
+ manifest; validation verifies files and research prerequisites. Reproduction runs
900
+ only the built-in synthetic-radial-velocity-mean v1 control, never bundled code.
901
+ Limits: 8 MiB manifest, 4096 files, 256 MiB combined file bytes. No network access.
902
+ Exit codes: 0 success, 1 failed or incomplete check, 2 usage or manifest-read error.
903
+ Use --json for the complete validator diagnostics and unchecked scientific limits.`);
904
+ }
905
+ function emit(output, jsonMode) {
906
+ if (jsonMode) {
907
+ console.log(JSON.stringify(output, null, 2));
920
908
  return;
921
909
  }
922
- const sub = args2.subcommand;
923
- switch (sub) {
924
- case "fields":
925
- registryFields(args2);
926
- break;
927
- case "observables":
928
- registryObservables(args2);
929
- break;
930
- case "origins":
931
- registryOrigins(args2);
932
- break;
933
- default:
934
- printUsage4();
910
+ console.log(
911
+ `DisclosureOS packet ${output.command}: ${output.success ? "passed" : "failed or incomplete"}`
912
+ );
913
+ if (output.packetId)
914
+ console.log(
915
+ `Packet: ${output.packetId}
916
+ Manifest SHA-256: ${output.manifestSha256}`
917
+ );
918
+ if (output.inspection)
919
+ console.log(
920
+ `${output.inspection.fileCount} declared files; ${output.inspection.declaredBytes} bytes.
921
+ File identity and research prerequisites: not checked.
922
+ Next: disclosureos packet validate <packet.json>`
923
+ );
924
+ if (output.validation) {
925
+ const v = output.validation;
926
+ console.log(
927
+ `Structural: ${v.checks.structural}; semantic: ${v.checks.semantic}; profile: ${v.checks.profile}; external: ${v.checks.external}`
928
+ );
929
+ for (const i of v.issues)
930
+ console.log(`${i.code} ${i.pointer}: ${i.message}`);
931
+ if (v.success && output.command === "validate")
932
+ console.log(
933
+ "Next: disclosureos packet reproduce <packet.json> (synthetic control only)."
934
+ );
935
+ }
936
+ if (output.reproduction) {
937
+ const r = output.reproduction;
938
+ console.log(
939
+ `Reproduction: ${r.status}${r.value === void 0 ? "" : `; ${r.value} ${r.unit}, tolerance ${r.absoluteTolerance}`}`
940
+ );
941
+ if (r.message) console.log(r.message);
935
942
  }
943
+ for (const i of output.issues)
944
+ console.log(`${i.code} ${i.pointer}: ${i.message}`);
945
+ console.log("Scientific eligibility: not checked.");
936
946
  }
937
- function registryFields(args2) {
938
- const group = args2.flags["category"];
939
- const paths = deriveFieldPaths();
940
- const groups = /* @__PURE__ */ new Map();
941
- for (const field of paths) {
942
- const top = field.path.split(".")[0] ?? field.path;
943
- const list = groups.get(top) ?? [];
944
- list.push(field);
945
- groups.set(top, list);
947
+ function reproduce(packet2, files) {
948
+ const method = packet2.method;
949
+ const result = {
950
+ status: "not_checked",
951
+ executor: "disclosureos-cli/synthetic-mean/0.1.0",
952
+ runtime: process.version,
953
+ methodId: method.id,
954
+ methodVersion: method.version,
955
+ unit: method.unit,
956
+ absoluteTolerance: method.absoluteTolerance
957
+ };
958
+ if (method.id !== "synthetic-radial-velocity-mean" || method.version !== "1") {
959
+ result.message = "Unsupported method. This CLI never executes packet-supplied implementations.";
960
+ return result;
946
961
  }
947
- console.log(heading("Observation Field Registry (schema-derived)"));
948
- if (group) {
949
- const fields = groups.get(group);
950
- if (!fields || fields.length === 0) {
951
- console.log(`
952
- No fields found for group "${group}"`);
953
- return;
954
- }
955
- console.log(`
956
- Group: ${group}
957
- `);
958
- const rows = fields.map((f) => [f.path, f.required ? "required" : "optional"]);
959
- console.log(table(rows));
960
- } else {
961
- for (const groupName of [...groups.keys()].sort()) {
962
- const fields = groups.get(groupName);
963
- console.log(`
964
- ${groupName} (${fields.length} fields)`);
965
- for (const f of fields) {
966
- console.log(bullet(`${f.path}${f.required ? " [required]" : ""}`));
967
- }
968
- }
962
+ try {
963
+ result.executorSha256 = hash(
964
+ readLocal(fileURLToPath(import.meta.url), TOTAL_LIMIT)
965
+ );
966
+ if (method.unit !== "m/s" || packet2.assets.context.find((a) => a.ref === "product:captured-raw")?.fileRef !== method.inputRef || packet2.assets.observation.find((a) => a.ref === "product:summary")?.fileRef !== method.expectedOutputRef)
967
+ throw Error("Method does not match the fixed synthetic control mapping.");
968
+ const input = object(json(files.get(method.inputRef))), expected = object(json(files.get(method.expectedOutputRef)));
969
+ const samples = input["samples"];
970
+ if (!Array.isArray(samples) || !samples.length || !samples.every(
971
+ (v) => typeof v === "number" && Number.isFinite(v)
972
+ ))
973
+ throw Error("Expected a nonempty array of finite samples.");
974
+ let sum = 0;
975
+ for (const value2 of samples) sum += value2;
976
+ const value = sum / samples.length, expectedValue = expected["velocity"];
977
+ if (!Number.isFinite(value) || typeof expectedValue !== "number" || !Number.isFinite(expectedValue))
978
+ throw Error("Nonfinite or malformed calculation result.");
979
+ result.value = value;
980
+ result.expected = expectedValue;
981
+ const history2 = parseExperimentalClaimHistory(
982
+ json(files.get(packet2.documents.history))
983
+ );
984
+ const measurement = history2.success ? history2.data.observation.measurements.find(
985
+ (m) => `measurement:${m.id}` === method.measurementRef
986
+ ) : void 0;
987
+ if (!measurement || measurement.value.unit !== method.unit || Math.abs(value - expectedValue) > method.absoluteTolerance || Math.abs(value - measurement.value.value) > method.absoluteTolerance)
988
+ throw Error(
989
+ "Computed result differs from the expected output or measurement beyond the declared tolerance."
990
+ );
991
+ result.status = "passed";
992
+ return result;
993
+ } catch (error2) {
994
+ result.status = "failed";
995
+ result.message = error2 instanceof Error ? error2.message : "Calculation failed.";
996
+ return result;
969
997
  }
970
- console.log(`
971
- ${dim(`Total: ${paths.length} schema-derived field paths`)}
972
- `);
973
998
  }
974
- function registryObservables(args2) {
975
- const biologicsOnly = !!args2.flags["biologics"];
976
- const techOnly = !!args2.flags["technology"];
977
- console.log(heading("Observable Registry"));
978
- if (!biologicsOnly) {
979
- console.log(`
980
- Technology Observables (AATIP-derived)
981
- `);
982
- const techObs = Object.values(TECHNOLOGY_OBSERVABLES);
983
- for (const obs of techObs) {
984
- console.log(` ${obs.code} ${obs.label}`);
985
- if (obs.description) {
986
- console.log(` ${dim(obs.description)}`);
987
- }
988
- }
999
+ async function packet(args2) {
1000
+ if (args2.flags["help"]) {
1001
+ usage();
1002
+ return;
989
1003
  }
990
- if (!techOnly) {
991
- console.log(`
992
- Biologics Observables (NHI Detection)
1004
+ const output = {
1005
+ command: args2.subcommand,
1006
+ success: false,
1007
+ issues: [],
1008
+ scientificEligibility: "not_checked"
1009
+ };
1010
+ const jsonMode = !!args2.flags["json"];
1011
+ if (!["inspect", "validate", "reproduce"].includes(args2.subcommand) || args2.positional.length !== 1 || Object.keys(args2.flags).some((f) => !["json", "help"].includes(f))) {
1012
+ output.issues.push({
1013
+ code: "CLI.USAGE",
1014
+ pointer: "",
1015
+ message: "Use packet inspect|validate|reproduce <packet.json> [--json]."
1016
+ });
1017
+ emit(output, jsonMode);
1018
+ if (!jsonMode) usage();
1019
+ process.exitCode = 2;
1020
+ return;
1021
+ }
1022
+ let path, bytes2, input;
1023
+ try {
1024
+ path = resolve2(args2.positional[0]);
1025
+ bytes2 = readLocal(path, MANIFEST_LIMIT);
1026
+ input = json(bytes2);
1027
+ output.manifestSha256 = hash(bytes2);
1028
+ } catch (error2) {
1029
+ output.issues.push({
1030
+ code: "CLI.MANIFEST",
1031
+ pointer: "",
1032
+ message: error2 instanceof Error ? error2.message : "Cannot read manifest."
1033
+ });
1034
+ emit(output, jsonMode);
1035
+ process.exitCode = 2;
1036
+ return;
1037
+ }
1038
+ const parsed = ReproductionPacketSchema.safeParse(input);
1039
+ if (!parsed.success) {
1040
+ output.validation = await evaluateReproductionPacket(input);
1041
+ emit(output, jsonMode);
1042
+ process.exitCode = 1;
1043
+ return;
1044
+ }
1045
+ const data = parsed.data;
1046
+ output.packetId = data.id;
1047
+ if (args2.subcommand === "inspect") {
1048
+ output.inspection = {
1049
+ fileCount: data.files.length,
1050
+ declaredBytes: data.files.reduce((n, f) => n + f.byteLength, 0),
1051
+ documents: data.documents,
1052
+ method: data.method,
1053
+ fileIdentity: "not_checked",
1054
+ researchPrerequisites: "not_checked"
1055
+ };
1056
+ output.success = true;
1057
+ emit(output, jsonMode);
1058
+ return;
1059
+ }
1060
+ const files = /* @__PURE__ */ new Map();
1061
+ if (data.files.length > FILE_LIMIT || data.files.reduce((n, f) => n + f.byteLength, 0) > TOTAL_LIMIT) {
1062
+ output.issues.push({
1063
+ code: "CLI.LIMIT",
1064
+ pointer: "/files",
1065
+ message: "Declared packet exceeds 4096 files or 256 MiB."
1066
+ });
1067
+ emit(output, jsonMode);
1068
+ process.exitCode = 1;
1069
+ return;
1070
+ }
1071
+ const directory5 = join(dirname(path), "files");
1072
+ let remaining = TOTAL_LIMIT;
1073
+ try {
1074
+ if (!lstatSync(directory5).isDirectory())
1075
+ throw Error("files must be a real directory, not a symlink.");
1076
+ } catch (error2) {
1077
+ output.issues.push({
1078
+ code: "CLI.FILES_DIRECTORY",
1079
+ pointer: "/files",
1080
+ message: error2 instanceof Error ? error2.message : "Cannot read files directory."
1081
+ });
1082
+ }
1083
+ if (!output.issues.length)
1084
+ for (const [i, file] of data.files.entries())
1085
+ try {
1086
+ if (files.has(file.id)) continue;
1087
+ const bytes3 = readLocal(join(directory5, file.id), remaining);
1088
+ files.set(file.id, bytes3);
1089
+ remaining -= bytes3.byteLength;
1090
+ } catch (error2) {
1091
+ output.issues.push({
1092
+ code: "CLI.FILE_READ",
1093
+ pointer: `/files/${i}`,
1094
+ message: error2 instanceof Error ? error2.message : "Cannot read file."
1095
+ });
1096
+ }
1097
+ output.validation = await evaluateReproductionPacket(input, { files });
1098
+ output.success = output.validation.success && !output.issues.length;
1099
+ if (args2.subcommand === "reproduce" && output.success) {
1100
+ output.reproduction = reproduce(data, files);
1101
+ output.success = output.reproduction.status === "passed";
1102
+ }
1103
+ emit(output, jsonMode);
1104
+ if (!output.success) process.exitCode = 1;
1105
+ }
1106
+
1107
+ // src/commands/dataset.ts
1108
+ import { lstatSync as lstatSync2 } from "fs";
1109
+ import { resolve as resolve3, dirname as dirname2, join as join2 } from "path";
1110
+ import { createHash as createHash2 } from "crypto";
1111
+ import {
1112
+ DatasetReleaseSchema,
1113
+ ReproductionPacketSchema as ReproductionPacketSchema2,
1114
+ evaluateDatasetRelease
1115
+ } from "@disclosureos/schema/experimental/v2";
1116
+ var LIMIT = 256 * 1024 * 1024;
1117
+ var MANIFEST_LIMIT2 = 8 * 1024 * 1024;
1118
+ var json2 = (bytes2) => JSON.parse(
1119
+ new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(bytes2)
1120
+ );
1121
+ function directory(path) {
1122
+ if (!lstatSync2(path).isDirectory())
1123
+ throw Error("Expected a real directory, not a symlink.");
1124
+ }
1125
+ async function dataset(args2) {
1126
+ if (args2.flags["help"]) {
1127
+ console.log(
1128
+ "DisclosureOS experimental dataset workflow\n\n disclosureos dataset inspect <dataset.json> [--json]\n disclosureos dataset validate <dataset.json> [--json]\n\nMember packets live at packets/<entry ID>/packet.json and files/<file ID>.\nInspection reads only the release manifest. Validation checks pinned packets and\nnominal session membership. No code execution or inferred scientific eligibility.\nLimits: 128 packets, 4096 total files, 256 MiB total packet/file bytes.\nExit: 0 success, 1 failed/incomplete, 2 usage or release-manifest read error."
1129
+ );
1130
+ return;
1131
+ }
1132
+ const output = {
1133
+ command: args2.subcommand,
1134
+ success: false,
1135
+ issues: [],
1136
+ scientificEligibility: "not_checked",
1137
+ authorization: "not_checked"
1138
+ };
1139
+ const emit3 = () => {
1140
+ if (args2.flags["json"]) {
1141
+ console.log(JSON.stringify(output, null, 2));
1142
+ return;
1143
+ }
1144
+ console.log(
1145
+ `DisclosureOS dataset ${args2.subcommand}: ${output.success ? "passed" : "failed or incomplete"}`
1146
+ );
1147
+ if (output.manifestSha256)
1148
+ console.log(`Manifest SHA-256: ${output.manifestSha256}`);
1149
+ if (output.inspection)
1150
+ console.log(JSON.stringify(output.inspection, null, 2));
1151
+ if (output.validation) {
1152
+ console.log(JSON.stringify(output.validation.checks));
1153
+ for (const i of output.validation.issues)
1154
+ console.log(`${i.code} ${i.pointer}: ${i.message}`);
1155
+ for (const packet2 of output.validation.packets)
1156
+ for (const i of packet2.validation?.issues ?? [])
1157
+ console.log(
1158
+ `${packet2.packetRef} ${i.code} ${i.pointer}: ${i.message}`
1159
+ );
1160
+ }
1161
+ for (const i of output.issues)
1162
+ console.log(`${i.code} ${i.pointer}: ${i.message}`);
1163
+ console.log("Scientific eligibility and authorization: not checked.");
1164
+ };
1165
+ if (!["inspect", "validate"].includes(args2.subcommand) || args2.positional.length !== 1 || Object.keys(args2.flags).some((f) => !["json", "help"].includes(f))) {
1166
+ output.issues.push({
1167
+ code: "CLI.USAGE",
1168
+ pointer: "",
1169
+ message: "Use dataset inspect|validate <dataset.json> [--json]."
1170
+ });
1171
+ emit3();
1172
+ process.exitCode = 2;
1173
+ return;
1174
+ }
1175
+ let input, path;
1176
+ try {
1177
+ path = resolve3(args2.positional[0]);
1178
+ const bytes2 = readLocal(path, MANIFEST_LIMIT2);
1179
+ output.manifestSha256 = createHash2("sha256").update(bytes2).digest("hex");
1180
+ input = json2(bytes2);
1181
+ } catch (error2) {
1182
+ output.issues.push({
1183
+ code: "CLI.MANIFEST",
1184
+ pointer: "",
1185
+ message: error2 instanceof Error ? error2.message : "Cannot read dataset manifest."
1186
+ });
1187
+ emit3();
1188
+ process.exitCode = 2;
1189
+ return;
1190
+ }
1191
+ const parsed = DatasetReleaseSchema.safeParse(input);
1192
+ if (!parsed.success) {
1193
+ output.validation = await evaluateDatasetRelease(input);
1194
+ emit3();
1195
+ process.exitCode = 1;
1196
+ return;
1197
+ }
1198
+ const release = parsed.data;
1199
+ if (args2.subcommand === "inspect") {
1200
+ output.inspection = {
1201
+ id: release.id,
1202
+ version: release.version,
1203
+ packetCount: release.packets.length,
1204
+ sessions: release.sessions,
1205
+ access: release.access,
1206
+ license: release.license,
1207
+ fileIdentity: "not_checked",
1208
+ membership: "not_checked"
1209
+ };
1210
+ output.success = true;
1211
+ emit3();
1212
+ return;
1213
+ }
1214
+ if (release.packets.length > 128) {
1215
+ output.issues.push({
1216
+ code: "CLI.LIMIT",
1217
+ pointer: "/packets",
1218
+ message: "At most 128 packets are supported."
1219
+ });
1220
+ emit3();
1221
+ process.exitCode = 1;
1222
+ return;
1223
+ }
1224
+ const packets = /* @__PURE__ */ new Map();
1225
+ let remaining = LIMIT, count = 0;
1226
+ const root = join2(dirname2(path), "packets");
1227
+ try {
1228
+ directory(root);
1229
+ } catch (error2) {
1230
+ output.issues.push({
1231
+ code: "CLI.PACKET_DIRECTORY",
1232
+ pointer: "/packets",
1233
+ message: error2 instanceof Error ? error2.message : "Cannot read packets directory."
1234
+ });
1235
+ }
1236
+ if (!output.issues.length)
1237
+ for (const [i, entry] of release.packets.entries()) {
1238
+ if (packets.has(entry.id)) continue;
1239
+ const base = join2(root, entry.id), issuesPath = `/packets/${i}`;
1240
+ try {
1241
+ directory(base);
1242
+ const manifest2 = readLocal(
1243
+ join2(base, "packet.json"),
1244
+ Math.min(remaining, MANIFEST_LIMIT2)
1245
+ );
1246
+ remaining -= manifest2.byteLength;
1247
+ const files = /* @__PURE__ */ new Map();
1248
+ packets.set(entry.id, { manifest: manifest2, files });
1249
+ const packet2 = ReproductionPacketSchema2.safeParse(json2(manifest2));
1250
+ if (!packet2.success) continue;
1251
+ if (count + packet2.data.files.length > 4096)
1252
+ throw Error("Dataset exceeds the 4096-file limit.");
1253
+ count += packet2.data.files.length;
1254
+ directory(join2(base, "files"));
1255
+ for (const file of packet2.data.files) {
1256
+ if (files.has(file.id)) continue;
1257
+ try {
1258
+ const bytes2 = readLocal(join2(base, "files", file.id), remaining);
1259
+ remaining -= bytes2.byteLength;
1260
+ files.set(file.id, bytes2);
1261
+ } catch (error2) {
1262
+ output.issues.push({
1263
+ code: "CLI.FILE_READ",
1264
+ pointer: issuesPath,
1265
+ message: error2 instanceof Error ? error2.message : "Cannot read member file."
1266
+ });
1267
+ }
1268
+ }
1269
+ } catch (error2) {
1270
+ output.issues.push({
1271
+ code: "CLI.PACKET_READ",
1272
+ pointer: issuesPath,
1273
+ message: error2 instanceof Error ? error2.message : "Cannot read member packet."
1274
+ });
1275
+ }
1276
+ }
1277
+ output.validation = await evaluateDatasetRelease(input, { packets });
1278
+ output.success = output.validation.success && !output.issues.length;
1279
+ emit3();
1280
+ if (!output.success) process.exitCode = 1;
1281
+ }
1282
+
1283
+ // src/commands/intake.ts
1284
+ import { mkdirSync, writeFileSync as writeFileSync2, rmSync, lstatSync as lstatSync3 } from "fs";
1285
+ import { resolve as resolve4, dirname as dirname3, join as join3, basename } from "path";
1286
+ import { randomUUID, createHash as createHash3 } from "crypto";
1287
+ import {
1288
+ SourceIntakeSchema,
1289
+ evaluateSourceIntake
1290
+ } from "@disclosureos/schema/experimental/v2";
1291
+ var LIMIT2 = 256 * 1024 * 1024;
1292
+ var hash2 = (bytes2) => createHash3("sha256").update(bytes2).digest("hex");
1293
+ var decode = (bytes2) => JSON.parse(
1294
+ new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(bytes2)
1295
+ );
1296
+ var unknown = () => ({
1297
+ state: "unknown",
1298
+ reason: "Not established at file intake."
1299
+ });
1300
+ async function intake(args2) {
1301
+ if (args2.flags["help"]) {
1302
+ console.log(
1303
+ "DisclosureOS experimental source intake\n\n disclosureos intake create <file...> --out <new-directory> [--json]\n disclosureos intake inspect <intake.json> [--json]\n disclosureos intake validate <intake.json> [--json]\n\nCreate copies exact bytes into files/<ID> and records receipt time and hashes.\nContext, format, role, access and licensing remain explicitly unknown. No decoding\nor assessment is inferred. Destination must not exist. Limits: 4096 files,\n256 MiB combined bytes, 8 MiB manifest. No network access.\nExit: 0 success, 1 failed/incomplete, 2 usage or file-operation error."
1304
+ );
1305
+ return;
1306
+ }
1307
+ const output = {
1308
+ command: args2.subcommand,
1309
+ success: false,
1310
+ issues: [],
1311
+ researchEligibility: "not_checked"
1312
+ };
1313
+ const emit3 = () => {
1314
+ if (args2.flags["json"]) {
1315
+ console.log(JSON.stringify(output, null, 2));
1316
+ return;
1317
+ }
1318
+ console.log(
1319
+ `DisclosureOS intake ${args2.subcommand}: ${output.success ? "passed" : "failed or incomplete"}`
1320
+ );
1321
+ if (output.path) console.log(`Receipt: ${output.path}`);
1322
+ if (output.manifestSha256)
1323
+ console.log(`Manifest SHA-256: ${output.manifestSha256}`);
1324
+ if (output.inspection) {
1325
+ console.log(
1326
+ `${output.inspection.artifacts.length} artifacts; file identity not checked.`
1327
+ );
1328
+ console.log(JSON.stringify(output.inspection, null, 2));
1329
+ }
1330
+ if (output.validation) {
1331
+ console.log(JSON.stringify(output.validation.checks));
1332
+ for (const i of output.validation.issues)
1333
+ console.log(`${i.code} ${i.pointer}: ${i.message}`);
1334
+ for (const gap of output.validation.gaps)
1335
+ console.log(
1336
+ `MISSING ${gap.pointer}: ${gap.reason} Next: ${gap.nextAction}`
1337
+ );
1338
+ }
1339
+ for (const i of output.issues)
1340
+ console.log(`${i.code} ${i.pointer}: ${i.message}`);
1341
+ console.log(
1342
+ "Research eligibility, artifact contents and authorization: not checked."
1343
+ );
1344
+ };
1345
+ const create = args2.subcommand === "create";
1346
+ if (!["create", "inspect", "validate"].includes(args2.subcommand) || Object.keys(args2.flags).some(
1347
+ (f) => !["help", "json", ...create ? ["out"] : []].includes(f)
1348
+ ) || (create ? args2.positional.length < 1 || typeof args2.flags["out"] !== "string" : args2.positional.length !== 1)) {
1349
+ output.issues.push({
1350
+ code: "CLI.USAGE",
1351
+ pointer: "",
1352
+ message: "Use intake create <file...> --out <new-directory>, or inspect|validate <intake.json>."
1353
+ });
1354
+ emit3();
1355
+ process.exitCode = 2;
1356
+ return;
1357
+ }
1358
+ const files = /* @__PURE__ */ new Map();
1359
+ let remaining = LIMIT2;
1360
+ if (create) {
1361
+ let destination, created = false;
1362
+ try {
1363
+ if (args2.positional.length > 4096)
1364
+ throw Error("At most 4096 source files are supported.");
1365
+ const artifacts = [];
1366
+ for (const [i, path2] of args2.positional.entries()) {
1367
+ const bytes3 = readLocal(resolve4(path2), remaining);
1368
+ remaining -= bytes3.length;
1369
+ const id = `file-${i + 1}`;
1370
+ files.set(id, bytes3);
1371
+ artifacts.push({
1372
+ id,
1373
+ originalName: basename(path2),
1374
+ role: "unknown",
1375
+ format: unknown(),
1376
+ sha256: hash2(bytes3),
1377
+ byteLength: bytes3.length,
1378
+ context: {
1379
+ instrument: unknown(),
1380
+ calibration: unknown(),
1381
+ clock: unknown(),
1382
+ acquisitionTime: unknown()
1383
+ }
1384
+ });
1385
+ }
1386
+ const receipt = {
1387
+ kind: "source_intake",
1388
+ schemaVersion: "0.1.0",
1389
+ id: `intake-${randomUUID()}`,
1390
+ receivedAt: (/* @__PURE__ */ new Date()).toISOString(),
1391
+ source: { publisher: unknown() },
1392
+ access: "unknown",
1393
+ license: unknown(),
1394
+ artifacts
1395
+ };
1396
+ const bytes2 = Buffer.from(JSON.stringify(receipt, null, 2) + "\n");
1397
+ if (bytes2.length > 8 * 1024 * 1024)
1398
+ throw Error("Generated manifest exceeds 8 MiB.");
1399
+ output.validation = await evaluateSourceIntake(receipt, { files });
1400
+ if (!output.validation.success)
1401
+ throw Error("Receipt did not pass file identity checks.");
1402
+ destination = resolve4(args2.flags["out"]);
1403
+ mkdirSync(destination, { mode: 448 });
1404
+ created = true;
1405
+ mkdirSync(join3(destination, "files"), { mode: 448 });
1406
+ for (const [id, b] of files)
1407
+ writeFileSync2(join3(destination, "files", id), b, {
1408
+ flag: "wx",
1409
+ mode: 384
1410
+ });
1411
+ writeFileSync2(join3(destination, "intake.json"), bytes2, {
1412
+ flag: "wx",
1413
+ mode: 384
1414
+ });
1415
+ output.path = join3(destination, "intake.json");
1416
+ output.manifestSha256 = hash2(bytes2);
1417
+ output.success = true;
1418
+ } catch (error2) {
1419
+ if (created && destination)
1420
+ rmSync(destination, { recursive: true, force: true });
1421
+ output.issues.push({
1422
+ code: "CLI.INTAKE_CREATE",
1423
+ pointer: "",
1424
+ message: error2 instanceof Error ? error2.message : "Intake creation failed."
1425
+ });
1426
+ process.exitCode = 2;
1427
+ }
1428
+ emit3();
1429
+ return;
1430
+ }
1431
+ let input, path;
1432
+ try {
1433
+ path = resolve4(args2.positional[0]);
1434
+ const bytes2 = readLocal(path, 8 * 1024 * 1024);
1435
+ output.manifestSha256 = hash2(bytes2);
1436
+ input = decode(bytes2);
1437
+ } catch (error2) {
1438
+ output.issues.push({
1439
+ code: "CLI.MANIFEST",
1440
+ pointer: "",
1441
+ message: error2 instanceof Error ? error2.message : "Cannot read intake manifest."
1442
+ });
1443
+ emit3();
1444
+ process.exitCode = 2;
1445
+ return;
1446
+ }
1447
+ const parsed = SourceIntakeSchema.safeParse(input);
1448
+ if (!parsed.success) {
1449
+ output.validation = await evaluateSourceIntake(input);
1450
+ emit3();
1451
+ process.exitCode = 1;
1452
+ return;
1453
+ }
1454
+ if (args2.subcommand === "inspect") {
1455
+ output.inspection = parsed.data;
1456
+ output.fileIdentity = "not_checked";
1457
+ output.success = true;
1458
+ emit3();
1459
+ return;
1460
+ }
1461
+ if (parsed.data.artifacts.length > 4096 || parsed.data.artifacts.reduce((n, a) => n + a.byteLength, 0) > LIMIT2) {
1462
+ output.issues.push({
1463
+ code: "CLI.LIMIT",
1464
+ pointer: "/artifacts",
1465
+ message: "Intake exceeds 4096 artifacts or 256 MiB."
1466
+ });
1467
+ emit3();
1468
+ process.exitCode = 1;
1469
+ return;
1470
+ }
1471
+ const directory5 = join3(dirname3(path), "files");
1472
+ try {
1473
+ if (!lstatSync3(directory5).isDirectory())
1474
+ throw Error("Expected a real files directory, not a symlink.");
1475
+ } catch (error2) {
1476
+ output.issues.push({
1477
+ code: "CLI.FILES_DIRECTORY",
1478
+ pointer: "/artifacts",
1479
+ message: error2 instanceof Error ? error2.message : "Cannot read files directory."
1480
+ });
1481
+ }
1482
+ if (!output.issues.length)
1483
+ for (const [i, a] of parsed.data.artifacts.entries())
1484
+ try {
1485
+ if (files.has(a.id)) continue;
1486
+ const b = readLocal(join3(directory5, a.id), remaining);
1487
+ remaining -= b.length;
1488
+ files.set(a.id, b);
1489
+ } catch (error2) {
1490
+ output.issues.push({
1491
+ code: "CLI.FILE_READ",
1492
+ pointer: `/artifacts/${i}`,
1493
+ message: error2 instanceof Error ? error2.message : "Cannot read artifact."
1494
+ });
1495
+ }
1496
+ output.validation = await evaluateSourceIntake(input, { files });
1497
+ output.success = output.validation.success && !output.issues.length;
1498
+ emit3();
1499
+ if (!output.success) process.exitCode = 1;
1500
+ }
1501
+
1502
+ // src/commands/profile-prepare.ts
1503
+ import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync3, rmSync as rmSync2, lstatSync as lstatSync4 } from "fs";
1504
+ import { resolve as resolve5, dirname as dirname4, join as join4 } from "path";
1505
+ import { createHash as createHash4 } from "crypto";
1506
+ import {
1507
+ ProfilePreparationSchema,
1508
+ evaluateProfileEvaluation
1509
+ } from "@disclosureos/schema/experimental/v2";
1510
+ var META = 8 * 1024 * 1024;
1511
+ var TOTAL = 256 * 1024 * 1024;
1512
+ var FILES = 4096;
1513
+ var pin = (bytes2) => ({
1514
+ sha256: createHash4("sha256").update(bytes2).digest("hex"),
1515
+ byteLength: bytes2.length
1516
+ });
1517
+ function directory2(path) {
1518
+ if (!lstatSync4(path).isDirectory())
1519
+ throw Error("Expected a real directory, not a symlink.");
1520
+ }
1521
+ var decode2 = (bytes2) => JSON.parse(
1522
+ new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(bytes2)
1523
+ );
1524
+ async function prepareProfile(args2) {
1525
+ const output = {
1526
+ command: "prepare",
1527
+ success: false,
1528
+ issues: [],
1529
+ scientificEligibility: "not_checked"
1530
+ };
1531
+ let destination, created = false;
1532
+ const emit3 = () => {
1533
+ if (args2.flags["json"]) console.log(JSON.stringify(output, null, 2));
1534
+ else {
1535
+ console.log(
1536
+ "DisclosureOS profile preparation: " + (output.success ? "bundle prepared" : "not prepared")
1537
+ );
1538
+ if (output.path) console.log("Bundle: " + JSON.stringify(output.path));
1539
+ if (output.profileStatus)
1540
+ console.log(
1541
+ "Profile outcome: " + output.profileStatus + "; receipt " + (output.receiptCreated ? "created (unsigned)" : "not created")
1542
+ );
1543
+ for (const i of output.issues)
1544
+ console.log(i.code + ": " + JSON.stringify(i.message));
1545
+ console.log(
1546
+ "Preparation success is not a passing profile. Run profile check on evaluation.json for requirements and next actions."
1547
+ );
1548
+ }
1549
+ };
1550
+ if (args2.positional.length !== 1 || typeof args2.flags["out"] !== "string" || Object.keys(args2.flags).some((f) => !["json", "out", "help"].includes(f))) {
1551
+ output.issues.push({
1552
+ code: "CLI.USAGE",
1553
+ message: "Use profile prepare <plan.json> --out <new-directory> [--json]."
1554
+ });
1555
+ emit3();
1556
+ process.exitCode = 2;
1557
+ return;
1558
+ }
1559
+ try {
1560
+ const planPath = resolve5(args2.positional[0]), base = dirname4(planPath);
1561
+ directory2(base);
1562
+ const planBytes = readLocal(planPath, META), parsed = ProfilePreparationSchema.safeParse(decode2(planBytes));
1563
+ output.planSha256 = pin(planBytes).sha256;
1564
+ if (!parsed.success) {
1565
+ output.issues.push(
1566
+ ...parsed.error.issues.map((i) => ({
1567
+ code: "PREPARE.PLAN",
1568
+ message: i.path.join("/") + ": " + i.message
1569
+ }))
1570
+ );
1571
+ emit3();
1572
+ process.exitCode = 1;
1573
+ return;
1574
+ }
1575
+ const plan = parsed.data, deps = [
1576
+ ...plan.dependencies.vocabularies,
1577
+ plan.dependencies.implementation,
1578
+ plan.dependencies.environment
1579
+ ];
1580
+ if (plan.assets.length + deps.length + 2 > FILES)
1581
+ throw Error("Preparation exceeds 4096 input files.");
1582
+ if (new Set(deps.map((d) => d.ref)).size !== deps.length || new Set(plan.dependencies.vocabularies.map((d) => d.id)).size !== plan.dependencies.vocabularies.length || new Set(plan.assets.map((a) => a.ref)).size !== plan.assets.length || new Set(plan.assets.map((a) => a.fileRef)).size !== plan.assets.length)
1583
+ throw Error("Duplicate dependency, vocabulary or asset references.");
1584
+ let remaining = TOTAL - planBytes.length;
1585
+ const read = (path, limit) => {
1586
+ const parts = path.split("/");
1587
+ let parent = base;
1588
+ for (const part of parts.slice(0, -1)) {
1589
+ parent = join4(parent, part);
1590
+ directory2(parent);
1591
+ }
1592
+ const bytes2 = readLocal(join4(base, ...parts), Math.min(limit, remaining));
1593
+ remaining -= bytes2.length;
1594
+ if (!bytes2.length)
1595
+ throw Error(
1596
+ "Empty inputs cannot receive positive-length evaluation pins: " + path
1597
+ );
1598
+ return bytes2;
1599
+ };
1600
+ const historyBytes = read(plan.history, META), selectionBytes = read(plan.selection, META);
1601
+ decode2(historyBytes);
1602
+ decode2(selectionBytes);
1603
+ const assetFiles = new Map(
1604
+ plan.assets.map((a) => [a.fileRef, read(a.path, remaining)])
1605
+ );
1606
+ const dependencyFiles = new Map(
1607
+ deps.map((d) => [d.ref, read(d.path, remaining)])
1608
+ );
1609
+ const dependency = (d) => ({
1610
+ ref: d.ref,
1611
+ id: d.id,
1612
+ version: d.version,
1613
+ ...pin(dependencyFiles.get(d.ref))
1614
+ });
1615
+ const manifest2 = {
1616
+ kind: "profile_evaluation",
1617
+ schemaVersion: "0.1.0",
1618
+ id: plan.id,
1619
+ workflow: plan.workflow,
1620
+ history: pin(historyBytes),
1621
+ selection: pin(selectionBytes),
1622
+ assets: plan.assets.map((a) => ({
1623
+ ref: a.ref,
1624
+ fileRef: a.fileRef,
1625
+ ...pin(assetFiles.get(a.fileRef))
1626
+ })),
1627
+ dependencies: {
1628
+ vocabularies: plan.dependencies.vocabularies.map(dependency),
1629
+ implementation: dependency(plan.dependencies.implementation),
1630
+ environment: dependency(plan.dependencies.environment)
1631
+ }
1632
+ };
1633
+ const evaluation = await evaluateProfileEvaluation(
1634
+ manifest2,
1635
+ { historyBytes, selectionBytes, assetFiles, dependencyFiles }
1636
+ );
1637
+ if (!evaluation.receipt)
1638
+ throw Error(
1639
+ "Pinned inputs did not produce a receipt: " + JSON.stringify(evaluation.issues)
1640
+ );
1641
+ const manifestBytes = Buffer.from(JSON.stringify(manifest2, null, 2) + "\n"), receiptBytes = Buffer.from(
1642
+ JSON.stringify(evaluation.receipt, null, 2) + "\n"
1643
+ );
1644
+ if (manifestBytes.length > META || receiptBytes.length > META)
1645
+ throw Error("Generated metadata exceeds 8 MiB.");
1646
+ const inputBytes = historyBytes.length + selectionBytes.length + [...assetFiles.values(), ...dependencyFiles.values()].reduce(
1647
+ (n, b) => n + b.length,
1648
+ 0
1649
+ );
1650
+ if (inputBytes + manifestBytes.length + receiptBytes.length > TOTAL)
1651
+ throw Error("Generated bundle exceeds 256 MiB.");
1652
+ destination = resolve5(args2.flags["out"]);
1653
+ directory2(dirname4(destination));
1654
+ mkdirSync2(destination, { mode: 448 });
1655
+ created = true;
1656
+ for (const name2 of ["assets", "dependencies"])
1657
+ mkdirSync2(join4(destination, name2), { mode: 448 });
1658
+ const write = (path, bytes2) => writeFileSync3(join4(destination, path), bytes2, {
1659
+ flag: "wx",
1660
+ mode: 384
1661
+ });
1662
+ write("history.json", historyBytes);
1663
+ write("selection.json", selectionBytes);
1664
+ write("evaluation.json", manifestBytes);
1665
+ write("receipt.json", receiptBytes);
1666
+ for (const [ref, bytes2] of assetFiles) write("assets/" + ref, bytes2);
1667
+ for (const [ref, bytes2] of dependencyFiles)
1668
+ write("dependencies/" + ref, bytes2);
1669
+ output.success = true;
1670
+ output.path = destination;
1671
+ output.profileStatus = evaluation.checks.profile;
1672
+ output.profileSuccess = evaluation.success;
1673
+ output.receiptCreated = true;
1674
+ } catch (error2) {
1675
+ if (created && destination)
1676
+ rmSync2(destination, { recursive: true, force: true });
1677
+ output.issues.push({
1678
+ code: "PREPARE.INPUT_OR_WRITE",
1679
+ message: error2 instanceof Error ? error2.message : String(error2)
1680
+ });
1681
+ process.exitCode = 2;
1682
+ }
1683
+ emit3();
1684
+ }
1685
+
1686
+ // src/commands/profile.ts
1687
+ import { lstatSync as lstatSync5 } from "fs";
1688
+ import { resolve as resolve6, dirname as dirname5, join as join5 } from "path";
1689
+ import { createHash as createHash5 } from "crypto";
1690
+ import {
1691
+ ProfileEvaluationSchema,
1692
+ evaluateProfileEvaluation as evaluateProfileEvaluation2,
1693
+ RELEASED_DOCUMENT_PROFILE,
1694
+ HISTORICAL_TESTIMONY_PROFILE,
1695
+ PHYSICAL_SAMPLE_PROFILE,
1696
+ INSTRUMENT_RESEARCH_PREREQUISITES_PROFILE
1697
+ } from "@disclosureos/schema/experimental/v2";
1698
+ var META2 = 8 * 1024 * 1024;
1699
+ var TOTAL2 = 256 * 1024 * 1024;
1700
+ var FILES2 = 4096;
1701
+ var catalog = [
1702
+ {
1703
+ id: "instrument-research",
1704
+ title: "Instrument research",
1705
+ profile: INSTRUMENT_RESEARCH_PREREQUISITES_PROFILE,
1706
+ workflow: null,
1707
+ command: "disclosureos packet validate <packet.json>",
1708
+ useWhen: "Instrument observations with acquisition, calibration, timing and measurement records.",
1709
+ limits: "Checks documented research prerequisites; does not establish scientific validity."
1710
+ },
1711
+ {
1712
+ id: "released-documents",
1713
+ title: "Released documents",
1714
+ profile: RELEASED_DOCUMENT_PROFILE,
1715
+ workflow: "released-documents:0.1.0",
1716
+ command: "disclosureos profile check <evaluation.json>",
1717
+ useWhen: "Explicitly selected public document sources and their direct extractions.",
1718
+ limits: "Does not authenticate releases or establish the truth of document contents."
1719
+ },
1720
+ {
1721
+ id: "historical-testimony",
1722
+ title: "Historical testimony",
1723
+ profile: HISTORICAL_TESTIMONY_PROFILE,
1724
+ workflow: "historical-testimony:0.1.0",
1725
+ command: "disclosureos profile check <evaluation.json>",
1726
+ useWhen: "Selected testimony accounts, including restricted accounts and documented pseudonyms.",
1727
+ limits: "Does not assess witness credibility, firsthand knowledge or independent corroboration."
1728
+ },
1729
+ {
1730
+ id: "physical-samples",
1731
+ title: "Physical samples",
1732
+ profile: PHYSICAL_SAMPLE_PROFILE,
1733
+ workflow: "physical-samples:0.1.0",
1734
+ command: "disclosureos profile check <evaluation.json>",
1735
+ useWhen: "Declared collected specimens with collection and custody records.",
1736
+ limits: "Does not cover derived samples or mixtures, or certify physical identity, custody truth, composition or origin."
1737
+ }
1738
+ ];
1739
+ var safe = (value) => value.replace(
1740
+ /[\u0000-\u001f\u007f-\u009f\u202a-\u202e\u2066-\u2069]/g,
1741
+ (c) => "\\u" + c.charCodeAt(0).toString(16).padStart(4, "0")
1742
+ );
1743
+ var line = (value) => console.log(safe(value));
1744
+ var label3 = (value) => {
1745
+ const words = value.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/_/g, " ").toLowerCase();
1746
+ return words.charAt(0).toUpperCase() + words.slice(1);
1747
+ };
1748
+ var status = (value) => ({
1749
+ passed: "passed",
1750
+ failed: "failed",
1751
+ not_checked: "not checked",
1752
+ satisfied: "satisfied",
1753
+ missing: "missing",
1754
+ not_applicable: "not applicable"
1755
+ })[value] ?? value;
1756
+ function emit2(output, jsonMode) {
1757
+ if (jsonMode) {
1758
+ console.log(JSON.stringify(output, null, 2));
1759
+ return;
1760
+ }
1761
+ line("DisclosureOS experimental profile checks");
1762
+ if (output.catalog) {
1763
+ for (const p of output.catalog) {
1764
+ line("");
1765
+ line(p.title);
1766
+ line(" Use for: " + p.useWhen);
1767
+ line(" Run: " + p.command);
1768
+ if (p.workflow) line(" Manifest workflow: " + p.workflow);
1769
+ line(" Limit: " + p.limits);
1770
+ }
1771
+ line("");
1772
+ line(
1773
+ "Choose the profile from the source records; no automatic selection or aggregate score is applied."
1774
+ );
1775
+ return;
1776
+ }
1777
+ if (output.inspection) {
1778
+ const i = output.inspection;
1779
+ line("Selected: " + i.title);
1780
+ line("Workflow: " + i.workflow);
1781
+ line(
1782
+ `Declared files: ${i.assetCount} source files, ${i.dependencyCount} dependencies, history and selection`
1783
+ );
1784
+ line(
1785
+ "Manifest inspected. Input integrity and profile outcome: not checked. No receipt created."
1786
+ );
1787
+ }
1788
+ const evaluation = output.evaluation;
1789
+ if (evaluation) {
1790
+ if (!output.inspection) {
1791
+ line(
1792
+ "Selected: " + (catalog.find((p2) => p2.workflow === output.selectedWorkflow)?.title ?? "Manifest not accepted")
1793
+ );
1794
+ if (output.selectedWorkflow) line("Workflow: " + output.selectedWorkflow);
1795
+ }
1796
+ line("Input integrity: " + status(evaluation.checks.external));
1797
+ line("Profile outcome: " + status(evaluation.checks.profile));
1798
+ line(
1799
+ "Receipt: " + (evaluation.receipt ? "created (unsigned)" : "not created")
1800
+ );
1801
+ const p = evaluation.profile;
1802
+ if (p) {
1803
+ const rows = "documents" in p ? p.documents : "accounts" in p ? p.accounts : p.samples;
1804
+ for (const row of rows) {
1805
+ line("");
1806
+ line(
1807
+ ("sourceRef" in row ? row.sourceRef : row.sampleId) + ": " + status(row.status)
1808
+ );
1809
+ for (const r of row.requirements) {
1810
+ line(
1811
+ ` ${r.importance === "required" ? "Required" : "Recommended"} | ${r.label}: ${status(r.status)}`
1812
+ );
1813
+ line(" " + r.reason);
1814
+ for (const issue of r.issues) line(" Next: " + issue.nextAction);
1815
+ if (r.blockedBy.length)
1816
+ line(" Blocked by: " + r.blockedBy.map(label3).join(", "));
1817
+ }
1818
+ }
1819
+ if (!rows.length)
1820
+ for (const issue of p.issues) {
1821
+ line(issue.code + " " + issue.pointer + ": " + issue.message);
1822
+ line(" Next: " + issue.nextAction);
1823
+ }
1824
+ line("");
1825
+ line("Profile limits:");
1826
+ for (const [key, value] of Object.entries(p))
1827
+ if (value === "not_checked") line(" " + label3(key) + ": not checked");
1828
+ }
1829
+ if (!p)
1830
+ for (const issue of evaluation.issues)
1831
+ line(issue.code + " " + issue.pointer + ": " + issue.message);
1832
+ line(
1833
+ "A receipt preserves a result; it does not make a failed or unchecked profile pass."
1834
+ );
1835
+ line("Implementation and environment execution: not attested.");
1836
+ }
1837
+ for (const issue of output.issues) {
1838
+ line(issue.code + " " + issue.pointer + ": " + issue.message);
1839
+ line(" Next: " + issue.nextAction);
1840
+ }
1841
+ line(
1842
+ "Scientific eligibility: not checked. Use --json for the full result and receipt."
1843
+ );
1844
+ }
1845
+ function directory3(path) {
1846
+ const s = lstatSync5(path);
1847
+ if (!s.isDirectory() || s.isSymbolicLink())
1848
+ throw Error("Expected a real directory.");
1849
+ }
1850
+ async function profile(args2) {
1851
+ if (args2.flags["help"]) {
1852
+ console.log(`DisclosureOS experimental profile checks
1853
+
1854
+ disclosureos profile prepare <plan.json> --out <new-directory> [--json]
1855
+ disclosureos profile list [--json]
1856
+ disclosureos profile inspect <evaluation.json> [--json]
1857
+ disclosureos profile check <evaluation.json> [--json]
1858
+
1859
+ The manifest explicitly selects released-documents:0.1.0, historical-testimony:0.1.0
1860
+ or physical-samples:0.1.0. Instrument research uses packet validate.
1861
+ Inspect reads only the manifest. Check reads history.json, selection.json,
1862
+ assets/<fileRef> and dependencies/<ref> beside it. No network access or supplied-code execution.
1863
+ Limits: 8 MiB metadata, 4096 input files, 256 MiB total bytes.
1864
+ Prepare writes a new private bundle from a profile_preparation plan; preparation
1865
+ success does not mean the profile passed. Other commands are read-only.
1866
+ Exit: 0 prepared/listed/inspected or profile passed with receipt, 1 failed/unchecked checks,
1867
+ 2 usage or unreadable/malformed manifest. Full JSON may contain restricted metadata;
1868
+ apply access policy before sharing. No automatic profile selection or aggregate score.`);
1869
+ return;
1870
+ }
1871
+ if (args2.subcommand === "prepare") {
1872
+ await prepareProfile(args2);
1873
+ return;
1874
+ }
1875
+ const output = {
1876
+ command: args2.subcommand,
1877
+ success: false,
1878
+ issues: [],
1879
+ scientificEligibility: "not_checked"
1880
+ };
1881
+ const finish = (code) => {
1882
+ emit2(output, !!args2.flags["json"]);
1883
+ if (code) process.exitCode = code;
1884
+ };
1885
+ if (!["list", "inspect", "check"].includes(args2.subcommand) || args2.positional.length !== (args2.subcommand === "list" ? 0 : 1) || Object.keys(args2.flags).some((f) => !["json", "help"].includes(f))) {
1886
+ output.issues.push({
1887
+ code: "CLI.USAGE",
1888
+ pointer: "",
1889
+ message: "Use profile list or profile inspect|check <evaluation.json> [--json].",
1890
+ nextAction: "Run disclosureos profile --help for explicit workflow and input layout."
1891
+ });
1892
+ finish(2);
1893
+ return;
1894
+ }
1895
+ if (args2.subcommand === "list") {
1896
+ output.catalog = catalog;
1897
+ output.success = true;
1898
+ finish(0);
1899
+ return;
1900
+ }
1901
+ let input, manifestBytes, path;
1902
+ try {
1903
+ path = resolve6(args2.positional[0]);
1904
+ manifestBytes = readLocal(path, META2);
1905
+ output.manifestSha256 = createHash5("sha256").update(manifestBytes).digest("hex");
1906
+ input = JSON.parse(
1907
+ new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(
1908
+ manifestBytes
1909
+ )
1910
+ );
1911
+ } catch {
1912
+ output.issues.push({
1913
+ code: "CLI.MANIFEST",
1914
+ pointer: "",
1915
+ message: "Cannot read the manifest as regular-file UTF-8 JSON within 8 MiB.",
1916
+ nextAction: "Supply a local evaluation manifest without a BOM or symlink; keep the file within the metadata limit."
1917
+ });
1918
+ finish(2);
1919
+ return;
1920
+ }
1921
+ const parsed = ProfileEvaluationSchema.safeParse(input), preflight = await evaluateProfileEvaluation2(input);
1922
+ if (!parsed.success || preflight.checks.semantic === "failed") {
1923
+ output.evaluation = preflight;
1924
+ finish(1);
1925
+ return;
1926
+ }
1927
+ output.selectedWorkflow = parsed.data.workflow;
1928
+ const manifest2 = parsed.data, dependencies = [
1929
+ ...manifest2.dependencies.vocabularies,
1930
+ manifest2.dependencies.implementation,
1931
+ manifest2.dependencies.environment
1932
+ ];
1933
+ const selected = catalog.find((p) => p.workflow === manifest2.workflow);
1934
+ if (args2.subcommand === "inspect") {
1935
+ output.inspection = {
1936
+ id: manifest2.id,
1937
+ workflow: manifest2.workflow,
1938
+ title: selected.title,
1939
+ profile: manifest2.workflow === "released-documents:0.1.0" ? RELEASED_DOCUMENT_PROFILE : manifest2.workflow === "historical-testimony:0.1.0" ? HISTORICAL_TESTIMONY_PROFILE : PHYSICAL_SAMPLE_PROFILE,
1940
+ assetCount: manifest2.assets.length,
1941
+ dependencyCount: dependencies.length,
1942
+ declaredInputBytes: manifest2.history.byteLength + manifest2.selection.byteLength + manifest2.assets.reduce((n, a) => n + a.byteLength, 0) + dependencies.reduce((n, d) => n + d.byteLength, 0),
1943
+ inputIntegrity: "not_checked",
1944
+ profileOutcome: "not_checked",
1945
+ receipt: "not_created"
1946
+ };
1947
+ output.success = true;
1948
+ finish(0);
1949
+ return;
1950
+ }
1951
+ if (manifest2.assets.length + dependencies.length + 2 > FILES2 || manifestBytes.length + manifest2.history.byteLength + manifest2.selection.byteLength + manifest2.assets.reduce((n, a) => n + a.byteLength, 0) + dependencies.reduce((n, d) => n + d.byteLength, 0) > TOTAL2) {
1952
+ output.issues.push({
1953
+ code: "CLI.LIMIT",
1954
+ pointer: "",
1955
+ message: "Declared inputs exceed the 4096-file or 256 MiB budget.",
1956
+ nextAction: "Use a bounded evaluation input set; do not omit necessary records merely to make a profile pass."
1957
+ });
1958
+ finish(1);
1959
+ return;
1960
+ }
1961
+ const root = dirname5(path);
1962
+ let remaining = TOTAL2 - manifestBytes.length;
1963
+ try {
1964
+ directory3(root);
1965
+ } catch {
1966
+ output.issues.push({
1967
+ code: "CLI.DIRECTORY",
1968
+ pointer: "",
1969
+ message: "The bundle root must be a real directory.",
1970
+ nextAction: "Use a local directory without a symlink root."
1971
+ });
1972
+ finish(1);
1973
+ return;
1974
+ }
1975
+ const read = (relative, limit) => {
1976
+ try {
1977
+ const bytes2 = readLocal(join5(root, relative), Math.min(remaining, limit));
1978
+ remaining -= bytes2.length;
1979
+ return bytes2;
1980
+ } catch {
1981
+ output.issues.push({
1982
+ code: "CLI.INPUT_FILE",
1983
+ pointer: relative,
1984
+ message: "Cannot read this input as a regular file within its byte budget.",
1985
+ nextAction: "Provide the original pinned local file, respecting access restrictions; do not substitute another file."
1986
+ });
1987
+ return void 0;
1988
+ }
1989
+ };
1990
+ const historyBytes = read("history.json", META2), selectionBytes = read("selection.json", META2);
1991
+ const assetFiles = /* @__PURE__ */ new Map(), dependencyFiles = /* @__PURE__ */ new Map();
1992
+ for (const [name2, entries, map] of [
1993
+ ["assets", manifest2.assets.map((a) => a.fileRef), assetFiles],
1994
+ ["dependencies", dependencies.map((d) => d.ref), dependencyFiles]
1995
+ ]) {
1996
+ try {
1997
+ directory3(join5(root, name2));
1998
+ } catch {
1999
+ output.issues.push({
2000
+ code: "CLI.DIRECTORY",
2001
+ pointer: name2,
2002
+ message: "Expected a real input directory.",
2003
+ nextAction: "Keep the assets and dependencies directories beside the manifest, including an empty assets directory when no files are declared."
2004
+ });
2005
+ continue;
2006
+ }
2007
+ for (const ref of entries) {
2008
+ const bytes2 = read(name2 + "/" + ref, remaining);
2009
+ if (bytes2) map.set(ref, bytes2);
2010
+ }
2011
+ }
2012
+ output.evaluation = await evaluateProfileEvaluation2(input, {
2013
+ ...historyBytes ? { historyBytes } : {},
2014
+ ...selectionBytes ? { selectionBytes } : {},
2015
+ assetFiles,
2016
+ dependencyFiles
2017
+ });
2018
+ output.success = output.evaluation.success && output.issues.length === 0;
2019
+ finish(output.success ? 0 : 1);
2020
+ }
2021
+
2022
+ // src/commands/migration-compatibility.ts
2023
+ import { createHash as createHash8 } from "crypto";
2024
+ import {
2025
+ MigrationCompatibilitySchema
2026
+ } from "@disclosureos/schema/experimental/v2";
2027
+ import {
2028
+ parseAcquisitionContext
2029
+ } from "@disclosureos/instruments/experimental/v2";
2030
+
2031
+ // src/commands/migration-review.ts
2032
+ import { createHash as createHash7 } from "crypto";
2033
+ import {
2034
+ MigrationReviewSchema
2035
+ } from "@disclosureos/schema/experimental/v2";
2036
+ import { parseExperimentalClaimHistory as parseExperimentalClaimHistory3 } from "@disclosureos/records/experimental/v2";
2037
+
2038
+ // src/commands/migration-plan.ts
2039
+ import { createHash as createHash6 } from "crypto";
2040
+ import { parseEnrichedObservation as parseEnrichedObservation2 } from "@disclosureos/schema";
2041
+ import {
2042
+ parseExperimentalClaimHistory as parseExperimentalClaimHistory2
2043
+ } from "@disclosureos/records/experimental/v2";
2044
+ var POLICY = "legacy-migration-dry-run:0.1.0";
2045
+ var sha256 = (bytes2) => createHash6("sha256").update(bytes2).digest("hex");
2046
+ var object2 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
2047
+ var escape = (key) => key.replace(/~/g, "~0").replace(/\//g, "~1");
2048
+ function fields(value) {
2049
+ const pending = [{ value, pointer: "", depth: 0 }];
2050
+ const leaves = [];
2051
+ let visited = 0;
2052
+ while (pending.length) {
2053
+ const item = pending.pop();
2054
+ if (++visited > 1e5 || item.depth > 64)
2055
+ throw new Error("Record exceeds 100000 nodes or depth 64");
2056
+ const entries = item.value !== null && typeof item.value === "object" ? Object.entries(item.value) : [];
2057
+ if (!entries.length) leaves.push(item.pointer);
2058
+ for (let i = entries.length - 1; i >= 0; i--) {
2059
+ const [key, value2] = entries[i];
2060
+ pending.push({
2061
+ value: value2,
2062
+ pointer: `${item.pointer}/${escape(key)}`,
2063
+ depth: item.depth + 1
2064
+ });
2065
+ }
2066
+ }
2067
+ return leaves;
2068
+ }
2069
+ function mapping(pointer, candidate) {
2070
+ if (candidate && ["/id", "/createdAt", "/updatedAt", "/summary"].includes(pointer)) {
2071
+ return {
2072
+ pointer,
2073
+ disposition: "mapped",
2074
+ target: `/observation${pointer}`,
2075
+ reason: pointer === "/id" ? "Deterministic namespace and legacy ID hash; original ID retained in legacy data." : "Copied without inference."
2076
+ };
2077
+ }
2078
+ return {
2079
+ pointer,
2080
+ disposition: "unresolved",
2081
+ reason: !candidate ? "Quarantined record retained; no candidate mapping applied." : pointer === "/status" ? "Candidate is a review draft; original publication status retained only in legacy data." : pointer.startsWith("/temporal/") ? "Time scale, precision, provenance and possible sentinel use require explicit review." : pointer.startsWith("/location/") ? "Reference frame, provenance and possible sentinel use require explicit review; zero is retained." : "Retained only in versioned legacy data; requires explicit mapping. Legacy evaluations are not v2 claims or comparable scores."
2082
+ };
2083
+ }
2084
+ function buildMigrationReport(bytes2, namespace) {
2085
+ const input = JSON.parse(
2086
+ new TextDecoder("utf-8", { fatal: true }).decode(bytes2)
2087
+ );
2088
+ const rows = Array.isArray(input) ? input : [input];
2089
+ if (!rows.length || rows.length > 1e4)
2090
+ throw new Error("Input must contain 1 to 10000 records");
2091
+ const digest = sha256(bytes2);
2092
+ const counts = /* @__PURE__ */ new Map();
2093
+ for (const row of rows) {
2094
+ if (object2(row) && typeof row.id === "string")
2095
+ counts.set(row.id, (counts.get(row.id) ?? 0) + 1);
2096
+ }
2097
+ const records = rows.map((legacy, index) => {
2098
+ const sourcePointer = Array.isArray(input) ? `/${index}` : "";
2099
+ const reasons = [];
2100
+ let pointers = [];
2101
+ try {
2102
+ pointers = fields(legacy);
2103
+ } catch (error2) {
2104
+ reasons.push(error2.message);
2105
+ }
2106
+ if (object2(legacy) && typeof legacy.id === "string" && counts.get(legacy.id) > 1) {
2107
+ reasons.push(
2108
+ "Duplicate legacy ID in this input; all occurrences quarantined."
2109
+ );
2110
+ }
2111
+ const validation = reasons.length ? void 0 : parseEnrichedObservation2(legacy);
2112
+ if (validation && !validation.success) {
2113
+ reasons.push(
2114
+ ...validation.issues.map((issue) => `${issue.path}: ${issue.message}`)
2115
+ );
2116
+ }
2117
+ let candidate;
2118
+ if (!reasons.length && object2(legacy)) {
2119
+ const id = `legacy-${sha256(JSON.stringify([namespace, legacy.id]))}`;
2120
+ const proposal = {
2121
+ kind: "claim_history",
2122
+ schemaVersion: "0.1.0",
2123
+ id: `${id}-history`,
2124
+ observation: {
2125
+ kind: "observation",
2126
+ schemaVersion: "0.1.0",
2127
+ id,
2128
+ status: "draft",
2129
+ createdAt: legacy.createdAt,
2130
+ updatedAt: legacy.updatedAt,
2131
+ ...legacy.summary !== void 0 ? { summary: legacy.summary } : {},
2132
+ eventTime: { state: "unknown", reason: "unavailable" },
2133
+ position: { state: "unknown", reason: "unavailable" },
2134
+ sources: [
2135
+ {
2136
+ id: "legacy-input",
2137
+ kind: "other",
2138
+ access: "unknown",
2139
+ digest: { algorithm: "sha256", value: digest }
2140
+ }
2141
+ ],
2142
+ products: [],
2143
+ methods: [],
2144
+ frames: [],
2145
+ assertions: [],
2146
+ measurements: [],
2147
+ processing: []
2148
+ },
2149
+ claims: []
2150
+ };
2151
+ const parsed = parseExperimentalClaimHistory2(proposal);
2152
+ if (parsed.success) candidate = parsed.data;
2153
+ else
2154
+ reasons.push(
2155
+ ...parsed.issues.map((issue) => `${issue.pointer}: ${issue.message}`)
2156
+ );
2157
+ }
2158
+ return {
2159
+ sourcePointer,
2160
+ sourceId: object2(legacy) && typeof legacy.id === "string" ? legacy.id : null,
2161
+ decision: candidate ? "candidate_requires_review" : "quarantined",
2162
+ legacy: {
2163
+ format: "disclosureos-v1-input",
2164
+ validation: validation?.success ? "valid" : validation ? "invalid" : "not_checked",
2165
+ // Do not re-serialize an over-depth tree; exact bytes and row pointer retain it.
2166
+ ...pointers.length ? { data: legacy } : { retainedIn: "source.bytes" }
2167
+ },
2168
+ ...candidate ? { candidate } : {},
2169
+ reasons,
2170
+ mappingComplete: pointers.length > 0,
2171
+ mapping: pointers.map(
2172
+ (pointer) => mapping(pointer, candidate !== void 0)
2173
+ )
2174
+ };
2175
+ });
2176
+ const candidates = records.filter(
2177
+ (row) => row.decision === "candidate_requires_review"
2178
+ ).length;
2179
+ return {
2180
+ kind: "legacy_migration_dry_run",
2181
+ schemaVersion: "0.1.0",
2182
+ policy: POLICY,
2183
+ namespace,
2184
+ source: {
2185
+ sha256: digest,
2186
+ byteLength: bytes2.byteLength,
2187
+ encoding: "base64",
2188
+ bytes: Buffer.from(bytes2).toString("base64")
2189
+ },
2190
+ counts: {
2191
+ input: rows.length,
2192
+ candidates,
2193
+ quarantined: rows.length - candidates,
2194
+ migrated: 0
2195
+ },
2196
+ records
2197
+ };
2198
+ }
2199
+
2200
+ // src/commands/migration-review.ts
2201
+ var hash3 = (bytes2) => createHash7("sha256").update(bytes2).digest("hex");
2202
+ function decimalKey(literal) {
2203
+ const [mantissa = "", exponent = "0"] = literal.toLowerCase().split("e");
2204
+ if (exponent.replace(/^[+-]/, "").length > 6)
2205
+ throw new Error("JSON numeric exponent exceeds review budget");
2206
+ const negative = mantissa.startsWith("-");
2207
+ const unsigned = negative ? mantissa.slice(1) : mantissa;
2208
+ const [whole = "", fraction = ""] = unsigned.split(".");
2209
+ let digits = (whole + fraction).replace(/^0+/, "");
2210
+ if (!digits) return "0";
2211
+ const withoutTrailing = digits.replace(/0+$/, "");
2212
+ const scale = Number(exponent) - fraction.length + digits.length - withoutTrailing.length;
2213
+ digits = withoutTrailing;
2214
+ return `${negative ? "-" : ""}${digits}e${scale}`;
2215
+ }
2216
+ function reviewedJson(bytes2) {
2217
+ const text = new TextDecoder("utf-8", { fatal: true }).decode(bytes2);
2218
+ const value = JSON.parse(text);
2219
+ const stack = [];
2220
+ for (let i = 0; i < text.length; ) {
2221
+ const char = text[i];
2222
+ if (char === "{" || char === "[") {
2223
+ stack.push(char === "{" ? /* @__PURE__ */ new Set() : null);
2224
+ if (stack.length > 64) throw new Error("Reviewed JSON exceeds depth 64");
2225
+ i++;
2226
+ continue;
2227
+ }
2228
+ if (char === "}" || char === "]") {
2229
+ stack.pop();
2230
+ i++;
2231
+ continue;
2232
+ }
2233
+ if (char === '"') {
2234
+ const start = i++;
2235
+ while (i < text.length) {
2236
+ if (text[i] === "\\") {
2237
+ i += 2;
2238
+ continue;
2239
+ }
2240
+ if (text[i++] === '"') break;
2241
+ }
2242
+ let after = i;
2243
+ while (/\s/.test(text[after] ?? "") && after < text.length) after++;
2244
+ if (text[after] === ":") {
2245
+ const key = JSON.parse(text.slice(start, i));
2246
+ const keys = stack[stack.length - 1];
2247
+ if (keys?.has(key))
2248
+ throw new Error(
2249
+ "Duplicate JSON object key; reviewed mapping requires unambiguous input"
2250
+ );
2251
+ keys?.add(key);
2252
+ }
2253
+ continue;
2254
+ }
2255
+ if (char === "-" || char >= "0" && char <= "9") {
2256
+ const start = i++;
2257
+ while (i < text.length && !/[\s,\]}]/.test(text[i])) i++;
2258
+ const literal = text.slice(start, i);
2259
+ const number = Number(literal);
2260
+ if (!Number.isFinite(number) || Number.isInteger(number) && !Number.isSafeInteger(number) || Object.is(number, -0) || decimalKey(literal) !== decimalKey(String(number))) {
2261
+ throw new Error(
2262
+ "Unsafe JSON numeric representation; reviewed mapping requires explicit source normalization"
2263
+ );
2264
+ }
2265
+ continue;
2266
+ }
2267
+ i++;
2268
+ }
2269
+ return value;
2270
+ }
2271
+ function applyDecisions(row, decisions, plan, digest) {
2272
+ if (!decisions.length) return { ...row, review: { status: "not_requested" } };
2273
+ if (!row.candidate)
2274
+ return {
2275
+ ...row,
2276
+ review: { status: "rejected", reason: "Base row is quarantined." }
2277
+ };
2278
+ const candidate = structuredClone(row.candidate);
2279
+ const issues = [];
2280
+ const mapped = /* @__PURE__ */ new Map();
2281
+ const observation = { ...candidate.observation };
2282
+ for (const decision of decisions) {
2283
+ const prefix = decision.field === "eventTime" ? "/temporal/" : "/location/";
2284
+ for (const pointer of decision.sourcePointers) {
2285
+ if (!pointer.startsWith(prefix) || !row.mapping.some((field) => field.pointer === pointer)) {
2286
+ issues.push(`Unknown or out-of-domain source pointer: ${pointer}`);
2287
+ }
2288
+ mapped.set(pointer, {
2289
+ target: `/observation/${decision.field}`,
2290
+ reason: decision.rationale
2291
+ });
2292
+ }
2293
+ observation[decision.field] = decision.value;
2294
+ if (decision.field === "position") observation.frames = decision.frames;
2295
+ }
2296
+ observation.extensions = {
2297
+ "disclosureos.migration": {
2298
+ policy: "legacy-migration-review:0.1.0",
2299
+ reviewSha256: digest,
2300
+ sourceId: row.sourceId,
2301
+ sourcePointer: row.sourcePointer,
2302
+ reviewedBy: plan.reviewedBy,
2303
+ reviewedAt: plan.reviewedAt,
2304
+ decisions,
2305
+ reviewerIdentity: "not_authenticated",
2306
+ scientificValidity: "not_checked"
2307
+ }
2308
+ };
2309
+ const validated = parseExperimentalClaimHistory3({
2310
+ ...candidate,
2311
+ observation
2312
+ });
2313
+ if (!validated.success)
2314
+ issues.push(
2315
+ ...validated.issues.map((issue) => `${issue.pointer}: ${issue.message}`)
2316
+ );
2317
+ if (issues.length || !validated.success) {
2318
+ const { candidate: omitted, ...rest } = row;
2319
+ void omitted;
2320
+ return {
2321
+ ...rest,
2322
+ decision: "quarantined",
2323
+ reasons: [...row.reasons, ...issues],
2324
+ mapping: row.mapping.map((field) => ({
2325
+ pointer: field.pointer,
2326
+ disposition: "unresolved",
2327
+ reason: "Reviewed mapping rejected; no candidate emitted."
2328
+ })),
2329
+ review: { status: "rejected" }
2330
+ };
2331
+ }
2332
+ return {
2333
+ ...row,
2334
+ candidate: validated.data,
2335
+ mapping: row.mapping.map(
2336
+ (field) => mapped.has(field.pointer) ? {
2337
+ pointer: field.pointer,
2338
+ disposition: "mapped",
2339
+ ...mapped.get(field.pointer)
2340
+ } : field
2341
+ ),
2342
+ review: {
2343
+ status: "applied",
2344
+ reviewerIdentity: "not_authenticated",
2345
+ scientificValidity: "not_checked"
2346
+ }
2347
+ };
2348
+ }
2349
+ function reviewMigration(args2) {
2350
+ if (args2.flags["help"]) {
2351
+ console.log(
2352
+ "disclosureos migrate review <legacy.json> <review.json> [--json]\nApply explicit time/position decisions to review drafts; no imports or file writes."
2353
+ );
2354
+ return;
2355
+ }
2356
+ try {
2357
+ if (args2.positional.length !== 2 || Object.keys(args2.flags).some((key) => key !== "json")) {
2358
+ throw new Error(
2359
+ "Usage: disclosureos migrate review <legacy.json> <review.json> [--json]"
2360
+ );
2361
+ }
2362
+ const reviewBytes = readLocal(args2.positional[1], 2 * 1024 * 1024);
2363
+ const source = readLocal(args2.positional[0], 8 * 1024 * 1024);
2364
+ const report = buildReviewedMigration(source, reviewBytes);
2365
+ if (args2.flags["json"]) console.log(JSON.stringify(report, null, 2));
2366
+ else {
2367
+ console.log(
2368
+ `Experimental migration review: ${report.counts.input} inputs, ${report.counts.candidates} candidates, ${report.counts.quarantined} quarantined, 0 migrated.`
2369
+ );
2370
+ for (const row of report.records)
2371
+ console.log(
2372
+ JSON.stringify({
2373
+ sourceId: row.sourceId,
2374
+ decision: row.decision,
2375
+ review: row.review,
2376
+ unresolvedFields: row.mapping.filter(
2377
+ (field) => field.disposition === "unresolved"
2378
+ ).length,
2379
+ reasons: row.reasons
2380
+ })
2381
+ );
2382
+ console.log(
2383
+ "Review identity and scientific validity remain unchecked. Use --json for pinned inputs, decisions and candidate mappings."
2384
+ );
2385
+ }
2386
+ if (report.counts.quarantined) process.exitCode = 1;
2387
+ } catch (error2) {
2388
+ const message = error2 instanceof Error ? error2.message : String(error2);
2389
+ if (args2.flags["json"])
2390
+ console.log(
2391
+ JSON.stringify({
2392
+ success: false,
2393
+ stage: "review_input",
2394
+ error: message
2395
+ })
2396
+ );
2397
+ else console.error(message);
2398
+ process.exitCode = 2;
2399
+ }
2400
+ }
2401
+ function buildReviewedMigration(source, reviewBytes) {
2402
+ const parsed = MigrationReviewSchema.safeParse(reviewedJson(reviewBytes));
2403
+ if (!parsed.success)
2404
+ throw new Error(`Invalid review plan: ${parsed.error.message}`);
2405
+ const plan = parsed.data;
2406
+ if (hash3(source) !== plan.source.sha256 || source.byteLength !== plan.source.byteLength)
2407
+ throw new Error(
2408
+ "Source byte pin mismatch; regenerate and review decisions for the exact input"
2409
+ );
2410
+ reviewedJson(source);
2411
+ const base = buildMigrationReport(source, plan.namespace);
2412
+ const seen = /* @__PURE__ */ new Set();
2413
+ const sourceIds = new Set(base.records.map((row) => row.sourceId));
2414
+ const byId = /* @__PURE__ */ new Map();
2415
+ for (const decision of plan.decisions) {
2416
+ const key = JSON.stringify([decision.sourceId, decision.field]);
2417
+ if (seen.has(key))
2418
+ throw new Error("Duplicate decision for source ID and field");
2419
+ seen.add(key);
2420
+ const group = byId.get(decision.sourceId) ?? [];
2421
+ group.push(decision);
2422
+ byId.set(decision.sourceId, group);
2423
+ if (!sourceIds.has(decision.sourceId))
2424
+ throw new Error("Review decision names an unknown source ID");
2425
+ if (new Set(decision.sourcePointers).size !== decision.sourcePointers.length)
2426
+ throw new Error("Duplicate source pointer in review decision");
2427
+ }
2428
+ const digest = hash3(reviewBytes);
2429
+ const records = base.records.map(
2430
+ (row) => applyDecisions(row, byId.get(row.sourceId ?? "") ?? [], plan, digest)
2431
+ );
2432
+ const candidates = records.filter(
2433
+ (row) => row.decision === "candidate_requires_review"
2434
+ ).length;
2435
+ return {
2436
+ ...base,
2437
+ kind: "legacy_migration_review_result",
2438
+ policy: "legacy-migration-review:0.1.0",
2439
+ review: {
2440
+ sha256: digest,
2441
+ byteLength: reviewBytes.byteLength,
2442
+ encoding: "base64",
2443
+ bytes: Buffer.from(reviewBytes).toString("base64"),
2444
+ reviewerIdentity: "not_authenticated"
2445
+ },
2446
+ records,
2447
+ counts: {
2448
+ input: records.length,
2449
+ candidates,
2450
+ quarantined: records.length - candidates,
2451
+ migrated: 0
2452
+ }
2453
+ };
2454
+ }
2455
+
2456
+ // src/commands/migration-compatibility.ts
2457
+ var hash4 = (b) => createHash8("sha256").update(b).digest("hex");
2458
+ function unpack(blob) {
2459
+ const bytes2 = Buffer.from(blob.bytes, "base64");
2460
+ if (bytes2.toString("base64") !== blob.bytes || bytes2.length !== blob.byteLength || hash4(bytes2) !== blob.sha256)
2461
+ throw new Error(
2462
+ "Embedded artifact byte pin mismatch or noncanonical base64"
2463
+ );
2464
+ return bytes2;
2465
+ }
2466
+ var fieldPolicy = (pointer) => {
2467
+ const root = pointer.split("/")[1];
2468
+ if (["temporal", "location"].includes(root ?? ""))
2469
+ return {
2470
+ disposition: "unresolved",
2471
+ reason: "Explicit time/frame review is available through migrate review; no sentinel or datum inference."
2472
+ };
2473
+ if (root === "sensorEvidence")
2474
+ return {
2475
+ disposition: "unresolved",
2476
+ reason: "Sensor revision, acquisition, units and measurement provenance require explicit review; bare sensor labels and calibrated booleans are insufficient."
2477
+ };
2478
+ if ([
2479
+ "observableAssessments",
2480
+ "origin",
2481
+ "investigation",
2482
+ "testimony",
2483
+ "physicalEvidence",
2484
+ "documents",
2485
+ "provenance",
2486
+ "media",
2487
+ "featuredMedia",
2488
+ "internalNotes",
2489
+ "extensions",
2490
+ "status",
2491
+ "schemaVersion",
2492
+ "identifiers",
2493
+ "dataSourceId",
2494
+ "description",
2495
+ "objectCharacteristics",
2496
+ "eventType",
2497
+ "sourceData",
2498
+ "movement",
2499
+ "witnesses",
2500
+ "responseImpact",
2501
+ "environment",
2502
+ "relations",
2503
+ "aviation"
2504
+ ].includes(root ?? ""))
2505
+ return {
2506
+ disposition: "retained_without_conversion",
2507
+ reason: "Retained in the original legacy envelope; no automatic v2 assertion, claim, measurement, publication or evaluation conversion is supported."
2508
+ };
2509
+ return {
2510
+ disposition: "unresolved",
2511
+ reason: "No supported conversion for this field; original bytes remain authoritative."
2512
+ };
2513
+ };
2514
+ function compatibilityReport(source, namespace, reviewBytes) {
2515
+ reviewedJson(source);
2516
+ const base = buildMigrationReport(source, namespace);
2517
+ let plan;
2518
+ let context;
2519
+ const decisions = /* @__PURE__ */ new Map();
2520
+ const rowsById = /* @__PURE__ */ new Map();
2521
+ for (const row of base.records) {
2522
+ const group = rowsById.get(row.sourceId) ?? [];
2523
+ group.push(row);
2524
+ rowsById.set(row.sourceId, group);
2525
+ }
2526
+ if (reviewBytes) {
2527
+ plan = MigrationCompatibilitySchema.parse(reviewedJson(reviewBytes));
2528
+ if (plan.namespace !== namespace || plan.source.sha256 !== base.source.sha256 || plan.source.byteLength !== source.length)
2529
+ throw new Error("Sensor review namespace or source pin mismatch");
2530
+ context = parseAcquisitionContext(reviewedJson(unpack(plan.context)));
2531
+ if (!context.success)
2532
+ throw new Error(
2533
+ "Invalid acquisition context: " + context.issues.map((i) => i.message).join("; ")
2534
+ );
2535
+ for (const d of plan.decisions) {
2536
+ const key = JSON.stringify([d.sourceId, d.sensorPointer]);
2537
+ if (decisions.has(key))
2538
+ throw new Error("Duplicate sensor revision decision");
2539
+ const rows = rowsById.get(d.sourceId) ?? [];
2540
+ if (rows.length !== 1 || !rows[0].candidate)
2541
+ throw new Error("Sensor review requires a unique, valid legacy row");
2542
+ if (!rows[0].mapping.some((f) => f.pointer === d.sensorPointer))
2543
+ throw new Error("Unknown legacy sensor pointer");
2544
+ decisions.set(key, d);
2545
+ }
2546
+ }
2547
+ const acquisitions = new Map(
2548
+ context?.success ? context.data.acquisitions.map(
2549
+ (a) => [`acquisition:${a.id}`, a]
2550
+ ) : []
2551
+ );
2552
+ const manifests = new Map(
2553
+ context?.success ? context.data.manifests.map((m) => [`manifest:${m.id}`, m]) : []
2554
+ );
2555
+ const records = base.records.map((row) => {
2556
+ const sensors2 = row.mapping.filter(
2557
+ (f) => /^\/sensorEvidence\/sensors\/(?:0|[1-9][0-9]*)\/sensorRef$/.test(
2558
+ f.pointer
2559
+ )
2560
+ ).map((f) => {
2561
+ const d = decisions.get(JSON.stringify([row.sourceId, f.pointer]));
2562
+ const unresolved = {
2563
+ sourcePointer: f.pointer,
2564
+ status: "unresolved"
2565
+ };
2566
+ if (!d)
2567
+ return {
2568
+ ...unresolved,
2569
+ reason: "No explicit revision review supplied."
2570
+ };
2571
+ try {
2572
+ if (!("data" in row.legacy))
2573
+ throw new Error("Legacy row data unavailable");
2574
+ const data = row.legacy.data;
2575
+ const sensor = data.sensorEvidence.sensors[Number(f.pointer.split("/")[3])];
2576
+ if (sensor?.sensorRef !== d.legacyRef)
2577
+ throw new Error("Declared legacy reference differs from source");
2578
+ const acquisition = acquisitions.get(d.acquisitionRef);
2579
+ if (!acquisition || acquisition.manifest.state !== "pinned")
2580
+ throw new Error(
2581
+ "Acquisition must explicitly pin a manifest; no automatic revision selection"
2582
+ );
2583
+ const manifestRef = acquisition.manifest.ref;
2584
+ const manifest2 = manifests.get(manifestRef);
2585
+ if (!manifest2) throw new Error("Pinned manifest is unavailable");
2586
+ const manifestBytes = unpack(d.manifest);
2587
+ unpack(d.provenance);
2588
+ if (hash4(manifestBytes) !== manifest2.artifact.digest.value || manifestBytes.length !== manifest2.artifact.byteLength)
2589
+ throw new Error("Manifest bytes differ from acquisition revision");
2590
+ return {
2591
+ sourcePointer: f.pointer,
2592
+ status: "reviewed_revision_pin",
2593
+ legacyRef: d.legacyRef,
2594
+ acquisitionRef: d.acquisitionRef,
2595
+ instrumentRef: acquisition.instrumentRef,
2596
+ manifestRef,
2597
+ manifestVersion: manifest2.version,
2598
+ manifest: {
2599
+ sha256: d.manifest.sha256,
2600
+ byteLength: d.manifest.byteLength
2601
+ },
2602
+ provenance: {
2603
+ sha256: d.provenance.sha256,
2604
+ byteLength: d.provenance.byteLength,
2605
+ locator: d.provenanceLocator
2606
+ },
2607
+ rationale: d.rationale,
2608
+ associationSupport: "reviewer_declared_not_verified",
2609
+ calibration: "not_checked",
2610
+ measurementConversion: "not_performed"
2611
+ };
2612
+ } catch (error2) {
2613
+ return {
2614
+ ...unresolved,
2615
+ reason: error2 instanceof Error ? error2.message : String(error2)
2616
+ };
2617
+ }
2618
+ });
2619
+ const fields2 = row.mapping.map((f) => ({
2620
+ pointer: f.pointer,
2621
+ ...f.disposition === "mapped" ? { disposition: "mapped", target: f.target, reason: f.reason } : row.candidate ? fieldPolicy(f.pointer) : {
2622
+ disposition: "unresolved",
2623
+ reason: "Source row quarantined; no conversion supported."
2624
+ }
2625
+ }));
2626
+ return {
2627
+ sourcePointer: row.sourcePointer,
2628
+ sourceId: row.sourceId,
2629
+ rowStatus: row.decision,
2630
+ reasons: row.reasons,
2631
+ mappingComplete: row.mappingComplete,
2632
+ fields: fields2,
2633
+ sensors: sensors2
2634
+ };
2635
+ });
2636
+ const allFields = records.flatMap((r) => r.fields), sensors = records.flatMap((r) => r.sensors);
2637
+ return {
2638
+ kind: "legacy_migration_compatibility_report",
2639
+ schemaVersion: "0.1.0",
2640
+ policy: "legacy-migration-compatibility:0.1.0",
2641
+ namespace,
2642
+ source: base.source,
2643
+ ...plan && reviewBytes ? {
2644
+ review: {
2645
+ sha256: hash4(reviewBytes),
2646
+ byteLength: reviewBytes.length,
2647
+ encoding: "base64",
2648
+ bytes: Buffer.from(reviewBytes).toString("base64"),
2649
+ reviewedBy: plan.reviewedBy,
2650
+ reviewedAt: plan.reviewedAt,
2651
+ reviewerIdentity: "not_authenticated"
2652
+ }
2653
+ } : {},
2654
+ records,
2655
+ counts: {
2656
+ input: records.length,
2657
+ candidates: base.counts.candidates,
2658
+ quarantined: base.counts.quarantined,
2659
+ fields: allFields.length,
2660
+ mapped: allFields.filter((f) => f.disposition === "mapped").length,
2661
+ retainedWithoutConversion: allFields.filter(
2662
+ (f) => f.disposition === "retained_without_conversion"
2663
+ ).length,
2664
+ unresolved: allFields.filter((f) => f.disposition === "unresolved").length,
2665
+ sensorReferences: sensors.length,
2666
+ reviewedRevisionPins: sensors.filter(
2667
+ (s) => s.status === "reviewed_revision_pin"
2668
+ ).length,
2669
+ unresolvedSensorReferences: sensors.filter(
2670
+ (s) => s.status === "unresolved"
2671
+ ).length,
2672
+ indexImported: 0
2673
+ },
2674
+ scientificValidity: "not_checked",
2675
+ applicationChanges: "none",
2676
+ scope: "compatibility_inventory_and_declared_revision_review"
2677
+ };
2678
+ }
2679
+ function migrationCompatibility(args2) {
2680
+ const usage2 = "migrate compatibility <legacy.json> [sensor-review.json] --id <namespace> [--json]";
2681
+ if (args2.flags["help"]) {
2682
+ console.log(usage2);
2683
+ return;
2684
+ }
2685
+ try {
2686
+ const namespace = args2.flags["id"];
2687
+ if (![1, 2].includes(args2.positional.length) || typeof namespace !== "string" || !/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(namespace) || Object.keys(args2.flags).some((k) => !["id", "json"].includes(k)))
2688
+ throw new Error(usage2);
2689
+ const report = compatibilityReport(
2690
+ readLocal(args2.positional[0], 8 * 1024 * 1024),
2691
+ namespace,
2692
+ args2.positional[1] ? readLocal(args2.positional[1], 2 * 1024 * 1024) : void 0
2693
+ );
2694
+ if (args2.flags["json"]) console.log(JSON.stringify(report, null, 2));
2695
+ else
2696
+ console.log(
2697
+ `Compatibility: ${report.counts.input} rows; ${report.counts.mapped} mapped fields, ${report.counts.retainedWithoutConversion} retained without conversion, ${report.counts.unresolved} unresolved.
2698
+ Sensor references: ${report.counts.reviewedRevisionPins} reviewed pins, ${report.counts.unresolvedSensorReferences} unresolved. No application changes.`
2699
+ );
2700
+ if (report.counts.quarantined || report.counts.unresolved || report.counts.unresolvedSensorReferences)
2701
+ process.exitCode = 1;
2702
+ } catch (error2) {
2703
+ const message = error2 instanceof Error ? error2.message : String(error2);
2704
+ if (args2.flags["json"])
2705
+ console.log(
2706
+ JSON.stringify({
2707
+ success: false,
2708
+ stage: "migration_compatibility",
2709
+ error: message
2710
+ })
2711
+ );
2712
+ else console.error(message);
2713
+ process.exitCode = 2;
2714
+ }
2715
+ }
2716
+
2717
+ // src/commands/migration-legacy-scores.ts
2718
+ import { createHash as createHash9 } from "crypto";
2719
+ import { MigrationLegacyScoresSchema } from "@disclosureos/schema/experimental/v2";
2720
+ import {
2721
+ CompletenessResultSchema,
2722
+ ScoreResultSchema,
2723
+ SCORING_SCHEMA_VERSION
2724
+ } from "@disclosureos/scoring";
2725
+ var hash5 = (bytes2) => createHash9("sha256").update(bytes2).digest("hex");
2726
+ var archive = (bytes2) => ({
2727
+ sha256: hash5(bytes2),
2728
+ byteLength: bytes2.byteLength,
2729
+ encoding: "base64",
2730
+ bytes: Buffer.from(bytes2).toString("base64")
2731
+ });
2732
+ function preserveLegacyScores(source, planBytes) {
2733
+ const plan = MigrationLegacyScoresSchema.parse(reviewedJson(planBytes));
2734
+ const sourceDigest = hash5(source), reviewDigest = hash5(planBytes);
2735
+ if (sourceDigest !== plan.source.sha256 || source.byteLength !== plan.source.byteLength)
2736
+ throw new Error("Legacy score source byte pin mismatch");
2737
+ const input = reviewedJson(source);
2738
+ const rows = Array.isArray(input) ? input : [input];
2739
+ if (!rows.length || rows.length > 1e4)
2740
+ throw new Error("Legacy score source requires 1 to 10000 rows");
2741
+ const pointers = rows.map((_, i) => Array.isArray(input) ? `/${i}` : "");
2742
+ const decisions = /* @__PURE__ */ new Map();
2743
+ const knownPointers = new Set(pointers);
2744
+ for (const decision of plan.decisions) {
2745
+ if (!knownPointers.has(decision.sourcePointer))
2746
+ throw new Error("Decision names an unknown whole-row source pointer");
2747
+ if (decisions.has(decision.sourcePointer))
2748
+ throw new Error("Duplicate decision for legacy score row");
2749
+ decisions.set(decision.sourcePointer, decision);
2750
+ }
2751
+ const records = rows.map((data, index) => {
2752
+ const sourcePointer = pointers[index];
2753
+ const decision = decisions.get(sourcePointer);
2754
+ const legacy = { format: "standalone_legacy_score_input", data };
2755
+ if (!decision)
2756
+ return {
2757
+ sourcePointer,
2758
+ disposition: "pending_review",
2759
+ legacy,
2760
+ reasons: [
2761
+ "No explicit output contract, methodology version or observation association supplied."
2762
+ ]
2763
+ };
2764
+ const reasons = [];
2765
+ if (decision.outputContractVersion !== "2.0.0" || SCORING_SCHEMA_VERSION !== "2.0.0")
2766
+ reasons.push(
2767
+ "Unsupported historical scoring output contract; retained without conversion."
2768
+ );
2769
+ const validation = reasons.length ? void 0 : decision.resultKind === "compellingness" ? ScoreResultSchema.safeParse(data) : CompletenessResultSchema.safeParse(data);
2770
+ if (validation && !validation.success)
2771
+ reasons.push(
2772
+ ...validation.error.issues.map(
2773
+ (i) => `${i.path.join(".")}: ${i.message}`
2774
+ )
2775
+ );
2776
+ if (decision.resultKind === "compellingness" && data && typeof data === "object" && !Array.isArray(data) && data.scoringVersion !== decision.methodologyVersion)
2777
+ reasons.push(
2778
+ "Declared methodology version does not match the stored scoringVersion"
2779
+ );
2780
+ if (reasons.length)
2781
+ return {
2782
+ sourcePointer,
2783
+ disposition: "quarantined",
2784
+ legacy,
2785
+ decision,
2786
+ reasons
2787
+ };
2788
+ const observationIdentity = `legacy-${hash5(
2789
+ JSON.stringify([plan.namespace, decision.sourceId])
2790
+ )}`;
2791
+ return {
2792
+ sourcePointer,
2793
+ disposition: "preserved_historical",
2794
+ legacy,
2795
+ decision,
2796
+ reasons,
2797
+ historicalOutput: {
2798
+ kind: "legacy_score_history",
2799
+ schemaVersion: "0.1.0",
2800
+ id: `legacy-score-${hash5(
2801
+ JSON.stringify([
2802
+ plan.namespace,
2803
+ sourceDigest,
2804
+ sourcePointer,
2805
+ reviewDigest
2806
+ ])
2807
+ )}`,
2808
+ association: {
2809
+ sourceId: decision.sourceId,
2810
+ observationIdentity,
2811
+ verification: "declared_not_verified"
2812
+ },
2813
+ resultKind: decision.resultKind,
2814
+ outputContract: {
2815
+ package: "@disclosureos/scoring",
2816
+ version: decision.outputContractVersion
2817
+ },
2818
+ methodologyVersion: decision.methodologyVersion,
2819
+ // Keep original values, including additional fields. Never substitute stripping parser output.
2820
+ value: data,
2821
+ validation: "historical_output_shape_only",
2822
+ calculation: "not_recomputed",
2823
+ scientificValidity: "not_checked",
2824
+ comparableToV2: false,
2825
+ rankingEligibility: "excluded"
2826
+ }
2827
+ };
2828
+ });
2829
+ return {
2830
+ kind: "legacy_score_preservation_report",
2831
+ schemaVersion: "0.1.0",
2832
+ policy: "legacy-score-preservation:0.1.0",
2833
+ namespace: plan.namespace,
2834
+ source: archive(source),
2835
+ review: archive(planBytes),
2836
+ reviewedBy: plan.reviewedBy,
2837
+ reviewedAt: plan.reviewedAt,
2838
+ reviewerIdentity: "not_authenticated",
2839
+ records,
2840
+ counts: {
2841
+ input: rows.length,
2842
+ preservedHistorical: records.filter(
2843
+ (r) => r.disposition === "preserved_historical"
2844
+ ).length,
2845
+ pendingReview: records.filter((r) => r.disposition === "pending_review").length,
2846
+ quarantined: records.filter((r) => r.disposition === "quarantined").length,
2847
+ v2Evaluations: 0,
2848
+ indexImported: 0
2849
+ },
2850
+ rankingPolicy: "exclude_legacy_scores_from_v2_rankings"
2851
+ };
2852
+ }
2853
+ function legacyScores(args2) {
2854
+ const usage2 = "migrate legacy-scores <scores.json> <preservation-plan.json> [--json]";
2855
+ if (args2.flags["help"]) {
2856
+ console.log(usage2);
2857
+ return;
2858
+ }
2859
+ try {
2860
+ if (args2.positional.length !== 2 || Object.keys(args2.flags).some((k) => k !== "json"))
2861
+ throw new Error(usage2);
2862
+ const report = preserveLegacyScores(
2863
+ readLocal(args2.positional[0], 8 * 1024 * 1024),
2864
+ readLocal(args2.positional[1], 2 * 1024 * 1024)
2865
+ );
2866
+ if (args2.flags["json"]) console.log(JSON.stringify(report, null, 2));
2867
+ else
2868
+ console.log(
2869
+ `Legacy scores: ${report.counts.preservedHistorical} historical outputs preserved, ${report.counts.pendingReview} pending review, ${report.counts.quarantined} quarantined, 0 v2 evaluations.
2870
+ Historical values remain excluded from v2 rankings. Use --json for private retained data and declared associations.`
2871
+ );
2872
+ if (report.counts.quarantined || report.counts.pendingReview)
2873
+ process.exitCode = 1;
2874
+ } catch (error2) {
2875
+ const message = error2 instanceof Error ? error2.message : String(error2);
2876
+ if (args2.flags["json"])
2877
+ console.log(
2878
+ JSON.stringify({
2879
+ success: false,
2880
+ stage: "legacy_score_input",
2881
+ error: message
2882
+ })
2883
+ );
2884
+ else console.error(message);
2885
+ process.exitCode = 2;
2886
+ }
2887
+ }
2888
+
2889
+ // src/commands/migration-read-path.ts
2890
+ import { createHash as createHash14, randomUUID as randomUUID3 } from "crypto";
2891
+ import {
2892
+ mkdirSync as mkdirSync6,
2893
+ lstatSync as lstatSync9,
2894
+ readdirSync as readdirSync5,
2895
+ openSync as openSync3,
2896
+ writeFileSync as writeFileSync7,
2897
+ fsyncSync as fsyncSync2,
2898
+ closeSync as closeSync3,
2899
+ linkSync as linkSync2,
2900
+ unlinkSync as unlinkSync2
2901
+ } from "fs";
2902
+ import { resolve as resolve11, join as join9, isAbsolute } from "path";
2903
+
2904
+ // src/commands/migration-apply.ts
2905
+ import { createHash as createHash13, randomUUID as randomUUID2 } from "crypto";
2906
+ import {
2907
+ mkdirSync as mkdirSync5,
2908
+ lstatSync as lstatSync8,
2909
+ readdirSync as readdirSync4,
2910
+ openSync as openSync2,
2911
+ writeFileSync as writeFileSync6,
2912
+ fsyncSync,
2913
+ closeSync as closeSync2,
2914
+ linkSync,
2915
+ unlinkSync
2916
+ } from "fs";
2917
+ import { resolve as resolve10, join as join8 } from "path";
2918
+
2919
+ // src/commands/migration-ledger.ts
2920
+ import { createHash as createHash11 } from "crypto";
2921
+ import {
2922
+ mkdirSync as mkdirSync4,
2923
+ writeFileSync as writeFileSync5,
2924
+ rmSync as rmSync4,
2925
+ lstatSync as lstatSync7,
2926
+ readdirSync as readdirSync3
2927
+ } from "fs";
2928
+ import { resolve as resolve8, join as join7 } from "path";
2929
+
2930
+ // src/commands/migration-export.ts
2931
+ import { createHash as createHash10 } from "crypto";
2932
+ import {
2933
+ mkdirSync as mkdirSync3,
2934
+ writeFileSync as writeFileSync4,
2935
+ rmSync as rmSync3,
2936
+ lstatSync as lstatSync6,
2937
+ readdirSync as readdirSync2
2938
+ } from "fs";
2939
+ import { resolve as resolve7, join as join6 } from "path";
2940
+ var POLICY2 = "legacy-migration-export:0.1.0";
2941
+ var TOTAL_LIMIT2 = 256 * 1024 * 1024;
2942
+ var json3 = (value) => Buffer.from(JSON.stringify(value, null, 2) + "\n");
2943
+ var pin2 = (bytes2) => ({
2944
+ sha256: createHash10("sha256").update(bytes2).digest("hex"),
2945
+ byteLength: bytes2.byteLength
2946
+ });
2947
+ function assembleMigrationBundle(source, review) {
2948
+ const report = buildReviewedMigration(source, review);
2949
+ const files = /* @__PURE__ */ new Map([
2950
+ ["source.json", source],
2951
+ ["review.json", review]
2952
+ ]);
2953
+ let total = source.byteLength + review.byteLength;
2954
+ const add = (path, bytes2) => {
2955
+ total += bytes2.byteLength;
2956
+ if (total > TOTAL_LIMIT2)
2957
+ throw new Error("Migration export exceeds 256 MiB budget");
2958
+ files.set(path, bytes2);
2959
+ };
2960
+ add("report.json", json3(report));
2961
+ const rows = report.records.map((row) => {
2962
+ if ("candidate" in row && row.candidate && row.review.status === "applied") {
2963
+ const path = `candidates/${row.candidate.observation.id}.json`;
2964
+ add(path, json3(row.candidate));
2965
+ return {
2966
+ sourcePointer: row.sourcePointer,
2967
+ sourceId: row.sourceId,
2968
+ disposition: "exported",
2969
+ path
2970
+ };
2971
+ }
2972
+ return {
2973
+ sourcePointer: row.sourcePointer,
2974
+ sourceId: row.sourceId,
2975
+ disposition: row.decision === "quarantined" ? "quarantined" : "pending_review"
2976
+ };
2977
+ });
2978
+ const count = (disposition) => rows.filter((row) => row.disposition === disposition).length;
2979
+ const manifest2 = {
2980
+ kind: "legacy_migration_export",
2981
+ schemaVersion: "0.1.0",
2982
+ policy: POLICY2,
2983
+ namespace: report.namespace,
2984
+ counts: {
2985
+ input: rows.length,
2986
+ exported: count("exported"),
2987
+ pendingReview: count("pending_review"),
2988
+ quarantined: count("quarantined"),
2989
+ migrated: 0
2990
+ },
2991
+ rows,
2992
+ files: [...files].map(([path, bytes2]) => ({ path, ...pin2(bytes2) })),
2993
+ reviewerIdentity: "not_authenticated",
2994
+ scientificValidity: "not_checked"
2995
+ };
2996
+ add("manifest.json", json3(manifest2));
2997
+ return { files, manifest: manifest2 };
2998
+ }
2999
+ function privateDirectory(path) {
3000
+ const stat2 = lstatSync6(path);
3001
+ if (!stat2.isDirectory() || (stat2.mode & 63) !== 0)
3002
+ throw new Error("Expected a private real directory (0700)");
3003
+ }
3004
+ function compare(destination, bundle) {
3005
+ privateDirectory(destination);
3006
+ privateDirectory(join6(destination, "candidates"));
3007
+ const rootNames = [
3008
+ "candidates",
3009
+ "manifest.json",
3010
+ "report.json",
3011
+ "review.json",
3012
+ "source.json"
3013
+ ];
3014
+ const candidateNames = [...bundle.files.keys()].filter((path) => path.startsWith("candidates/")).map((path) => path.slice(11));
3015
+ if (JSON.stringify(readdirSync2(destination).sort()) !== JSON.stringify(rootNames.sort()) || JSON.stringify(readdirSync2(join6(destination, "candidates")).sort()) !== JSON.stringify(candidateNames.sort())) {
3016
+ throw new Error("Bundle conflict: unexpected or missing files");
3017
+ }
3018
+ for (const [path, expected] of bundle.files) {
3019
+ const file = join6(destination, path);
3020
+ const stat2 = lstatSync6(file);
3021
+ if (!stat2.isFile() || (stat2.mode & 63) !== 0)
3022
+ throw new Error(`Bundle conflict: nonprivate or nonregular file ${path}`);
3023
+ const actual = readLocal(file, expected.byteLength);
3024
+ if (!Buffer.from(actual).equals(Buffer.from(expected)))
3025
+ throw new Error(`Bundle conflict: content differs at ${path}`);
3026
+ }
3027
+ }
3028
+ function exportMigration(args2) {
3029
+ if (args2.flags["help"]) {
3030
+ console.log(
3031
+ "disclosureos migrate export <legacy.json> <review.json> --out <bundle> [--json]\ndisclosureos migrate verify <bundle> [--json]\nPrivate review artifacts only; no database imports."
3032
+ );
3033
+ return;
3034
+ }
3035
+ let created;
3036
+ try {
3037
+ const verify = args2.subcommand === "verify";
3038
+ const output = args2.flags["out"];
3039
+ if (verify ? args2.positional.length !== 1 || Object.keys(args2.flags).some((key) => key !== "json") : args2.positional.length !== 2 || typeof output !== "string" || Object.keys(args2.flags).some((key) => !["out", "json"].includes(key))) {
3040
+ throw new Error(
3041
+ "Usage: migrate export <legacy.json> <review.json> --out <bundle> [--json], or migrate verify <bundle> [--json]"
3042
+ );
3043
+ }
3044
+ const destination = resolve7(
3045
+ verify ? args2.positional[0] : output
3046
+ );
3047
+ if (verify) privateDirectory(destination);
3048
+ const source = readLocal(
3049
+ verify ? join6(destination, "source.json") : args2.positional[0],
3050
+ 8 * 1024 * 1024
3051
+ );
3052
+ const review = readLocal(
3053
+ verify ? join6(destination, "review.json") : args2.positional[1],
3054
+ 2 * 1024 * 1024
3055
+ );
3056
+ const bundle = assembleMigrationBundle(source, review);
3057
+ let action = "verified";
3058
+ if (!verify) {
3059
+ try {
3060
+ mkdirSync3(destination, { mode: 448 });
3061
+ created = destination;
3062
+ } catch (error2) {
3063
+ if (error2.code !== "EEXIST") throw error2;
3064
+ }
3065
+ if (created) {
3066
+ mkdirSync3(join6(destination, "candidates"), { mode: 448 });
3067
+ for (const [path, bytes2] of bundle.files)
3068
+ writeFileSync4(join6(destination, path), bytes2, {
3069
+ mode: 384,
3070
+ flag: "wx"
3071
+ });
3072
+ action = "created";
3073
+ } else action = "reused";
3074
+ }
3075
+ compare(destination, bundle);
3076
+ created = void 0;
3077
+ const result = {
3078
+ success: true,
3079
+ action,
3080
+ directory: destination,
3081
+ policy: POLICY2,
3082
+ counts: bundle.manifest.counts,
3083
+ manifest: pin2(bundle.files.get("manifest.json")),
3084
+ verification: "current_code_replay",
3085
+ migrationStatus: "not_imported",
3086
+ reviewerIdentity: "not_authenticated",
3087
+ scientificValidity: "not_checked"
3088
+ };
3089
+ if (args2.flags["json"]) console.log(JSON.stringify(result, null, 2));
3090
+ else
3091
+ console.log(
3092
+ `Migration bundle ${action}: ${result.counts.exported} exported, ${result.counts.pendingReview} pending review, ${result.counts.quarantined} quarantined, 0 migrated.
3093
+ ${destination}`
3094
+ );
3095
+ if (result.counts.quarantined || result.counts.pendingReview)
3096
+ process.exitCode = 1;
3097
+ } catch (error2) {
3098
+ if (created) rmSync3(created, { recursive: true, force: true });
3099
+ const message = error2 instanceof Error ? error2.message : String(error2);
3100
+ if (args2.flags["json"])
3101
+ console.log(
3102
+ JSON.stringify({
3103
+ success: false,
3104
+ stage: "migration_export",
3105
+ error: message
3106
+ })
3107
+ );
3108
+ else console.error(message);
3109
+ process.exitCode = 2;
3110
+ }
3111
+ }
3112
+ function verifiedMigrationBundle(destination) {
3113
+ privateDirectory(destination);
3114
+ const bundle = assembleMigrationBundle(
3115
+ readLocal(join6(destination, "source.json"), 8 * 1024 * 1024),
3116
+ readLocal(join6(destination, "review.json"), 2 * 1024 * 1024)
3117
+ );
3118
+ compare(destination, bundle);
3119
+ return bundle;
3120
+ }
3121
+
3122
+ // src/commands/migration-ledger.ts
3123
+ var POLICY3 = "legacy-migration-ledger:0.1.0";
3124
+ var pin3 = (bytes2) => ({
3125
+ sha256: createHash11("sha256").update(bytes2).digest("hex"),
3126
+ byteLength: bytes2.byteLength
3127
+ });
3128
+ var json4 = (value) => Buffer.from(JSON.stringify(value, null, 2) + "\n");
3129
+ var privateDir = (path) => {
3130
+ const s = lstatSync7(path);
3131
+ if (!s.isDirectory() || s.mode & 63)
3132
+ throw new Error("Expected a private real ledger directory");
3133
+ };
3134
+ function planLedger(bundles) {
3135
+ const unique = new Map(
3136
+ bundles.map((bundle) => [
3137
+ pin3(bundle.files.get("manifest.json")).sha256,
3138
+ bundle
3139
+ ])
3140
+ );
3141
+ const files = /* @__PURE__ */ new Map();
3142
+ const dirs = /* @__PURE__ */ new Set(["", "inputs"]);
3143
+ let total = 0;
3144
+ const add = (path, bytes2) => {
3145
+ total += bytes2.byteLength;
3146
+ if (total > 256 * 1024 * 1024 || files.size >= 2e4)
3147
+ throw new Error("Ledger exceeds 256 MiB or 20000-file budget");
3148
+ files.set(path, bytes2);
3149
+ };
3150
+ const identities = /* @__PURE__ */ new Map();
3151
+ const receipts = [...unique].sort(([a], [b]) => a.localeCompare(b)).map(([bundleId, bundle]) => {
3152
+ const root = `inputs/${bundleId}`;
3153
+ dirs.add(root);
3154
+ dirs.add(`${root}/candidates`);
3155
+ for (const [path, bytes2] of bundle.files) add(`${root}/${path}`, bytes2);
3156
+ const rows2 = bundle.manifest.rows.map((row) => {
3157
+ if (row.disposition !== "exported" || !row.path)
3158
+ return { ...row, status: row.disposition };
3159
+ const path = `${root}/${row.path}`;
3160
+ const revision = pin3(bundle.files.get(row.path)).sha256;
3161
+ const identity = row.path.slice("candidates/".length, -".json".length);
3162
+ const revisions = identities.get(identity) ?? /* @__PURE__ */ new Map();
3163
+ const occurrences = revisions.get(revision) ?? [];
3164
+ occurrences.push({ bundleId, sourcePointer: row.sourcePointer, path });
3165
+ revisions.set(revision, occurrences);
3166
+ identities.set(identity, revisions);
3167
+ return { ...row, identity, revision, status: "staged" };
3168
+ });
3169
+ return {
3170
+ bundleId,
3171
+ namespace: bundle.manifest.namespace,
3172
+ manifest: pin3(bundle.files.get("manifest.json")),
3173
+ rows: rows2
3174
+ };
3175
+ });
3176
+ const inventory = [...identities].sort(([a], [b]) => a.localeCompare(b)).map(([identity, revisions]) => ({
3177
+ identity,
3178
+ status: revisions.size === 1 ? "ready_for_import_review" : "conflict",
3179
+ revisions: [...revisions].sort(([a], [b]) => a.localeCompare(b)).map(([sha2562, occurrences]) => ({ sha256: sha2562, occurrences }))
3180
+ }));
3181
+ const conflicts = new Set(
3182
+ inventory.filter((item) => item.status === "conflict").map((item) => item.identity)
3183
+ );
3184
+ const finalized = receipts.map((receipt) => ({
3185
+ ...receipt,
3186
+ rows: receipt.rows.map((row) => ({
3187
+ ...row,
3188
+ status: "identity" in row && conflicts.has(row.identity) ? "conflict" : row.status
3189
+ }))
3190
+ }));
3191
+ const rows = finalized.flatMap((receipt) => receipt.rows);
3192
+ const count = (status2) => rows.filter((row) => row.status === status2).length;
3193
+ const ledger = {
3194
+ kind: "legacy_migration_ledger",
3195
+ schemaVersion: "0.1.0",
3196
+ policy: POLICY3,
3197
+ counts: {
3198
+ bundles: unique.size,
3199
+ input: rows.length,
3200
+ stagedRows: count("staged"),
3201
+ conflictRows: count("conflict"),
3202
+ pendingReview: count("pending_review"),
3203
+ quarantined: count("quarantined"),
3204
+ identities: inventory.length,
3205
+ conflictingIdentities: conflicts.size,
3206
+ migrated: 0
3207
+ },
3208
+ receipts: finalized,
3209
+ identities: inventory,
3210
+ files: [...files].map(([path, bytes2]) => ({ path, ...pin3(bytes2) })),
3211
+ verification: "current_code_replay",
3212
+ reviewerIdentity: "not_authenticated",
3213
+ applicationStatus: "not_imported"
3214
+ };
3215
+ add("ledger.json", json4(ledger));
3216
+ return { files, dirs, ledger };
3217
+ }
3218
+ function compareLedger(root, plan) {
3219
+ for (const dir of plan.dirs) {
3220
+ const path = join7(root, dir);
3221
+ privateDir(path);
3222
+ const expected = /* @__PURE__ */ new Set();
3223
+ const prefix = dir ? `${dir}/` : "";
3224
+ for (const name2 of [...plan.dirs, ...plan.files.keys()])
3225
+ if (name2.startsWith(prefix) && name2 !== dir)
3226
+ expected.add(name2.slice(prefix.length).split("/")[0]);
3227
+ if (JSON.stringify(readdirSync3(path).sort()) !== JSON.stringify([...expected].sort()))
3228
+ throw new Error("Ledger conflict: missing or unexpected entries");
3229
+ }
3230
+ for (const [path, bytes2] of plan.files) {
3231
+ const file = join7(root, path), stat2 = lstatSync7(file);
3232
+ if (!stat2.isFile() || stat2.mode & 63)
3233
+ throw new Error("Ledger conflict: nonprivate or nonregular artifact");
3234
+ if (!Buffer.from(readLocal(file, bytes2.byteLength)).equals(Buffer.from(bytes2)))
3235
+ throw new Error(`Ledger conflict: changed artifact ${path}`);
3236
+ }
3237
+ }
3238
+ function migrationLedger(args2) {
3239
+ if (args2.flags["help"]) {
3240
+ console.log(
3241
+ "migrate ledger <bundle>... --out <ledger> [--json]\nmigrate ledger-verify <ledger> [--json]\nImmutable local staging receipts; no application imports."
3242
+ );
3243
+ return;
3244
+ }
3245
+ let created;
3246
+ try {
3247
+ const verify = args2.subcommand === "ledger-verify", output = args2.flags["out"];
3248
+ if (verify ? args2.positional.length !== 1 || Object.keys(args2.flags).some((key) => key !== "json") : !args2.positional.length || args2.positional.length > 16 || typeof output !== "string" || Object.keys(args2.flags).some((key) => !["out", "json"].includes(key)))
3249
+ throw new Error(
3250
+ "Usage: migrate ledger <1-16 bundles> --out <ledger>, or migrate ledger-verify <ledger>"
3251
+ );
3252
+ const root = resolve8(verify ? args2.positional[0] : output);
3253
+ let paths;
3254
+ if (verify) {
3255
+ privateDir(root);
3256
+ privateDir(join7(root, "inputs"));
3257
+ const ids = readdirSync3(join7(root, "inputs")).sort();
3258
+ if (!ids.length || ids.length > 16 || ids.some((id) => !/^[a-f0-9]{64}$/.test(id)))
3259
+ throw new Error("Invalid ledger input inventory");
3260
+ paths = ids.map((id) => join7(root, "inputs", id));
3261
+ } else paths = args2.positional.map((path) => resolve8(path));
3262
+ let bytes2 = 0, entries = 0;
3263
+ const bundles = paths.map((path) => {
3264
+ const bundle = verifiedMigrationBundle(path);
3265
+ for (const data of bundle.files.values()) bytes2 += data.byteLength;
3266
+ entries += bundle.files.size;
3267
+ if (bytes2 > 256 * 1024 * 1024 || entries >= 2e4)
3268
+ throw new Error("Ledger input budget exceeded");
3269
+ return bundle;
3270
+ });
3271
+ const plan = planLedger(bundles);
3272
+ let action = "verified";
3273
+ if (!verify) {
3274
+ try {
3275
+ mkdirSync4(root, { mode: 448 });
3276
+ created = root;
3277
+ } catch (error2) {
3278
+ if (error2.code !== "EEXIST") throw error2;
3279
+ }
3280
+ if (created) {
3281
+ for (const dir of plan.dirs)
3282
+ if (dir) mkdirSync4(join7(root, dir), { mode: 448 });
3283
+ for (const [path, data] of plan.files)
3284
+ writeFileSync5(join7(root, path), data, { mode: 384, flag: "wx" });
3285
+ action = "created";
3286
+ } else action = "reused";
3287
+ }
3288
+ compareLedger(root, plan);
3289
+ created = void 0;
3290
+ const result = {
3291
+ success: true,
3292
+ action,
3293
+ directory: root,
3294
+ ledger: pin3(plan.files.get("ledger.json")),
3295
+ counts: plan.ledger.counts,
3296
+ applicationStatus: "not_imported",
3297
+ verification: "current_code_replay"
3298
+ };
3299
+ if (args2.flags["json"]) console.log(JSON.stringify(result, null, 2));
3300
+ else
3301
+ console.log(
3302
+ `Migration ledger ${action}: ${result.counts.stagedRows} staged, ${result.counts.conflictRows} conflicting, ${result.counts.pendingReview} pending, ${result.counts.quarantined} quarantined, 0 migrated.
3303
+ ${root}`
3304
+ );
3305
+ if (result.counts.conflictRows || result.counts.pendingReview || result.counts.quarantined)
3306
+ process.exitCode = 1;
3307
+ } catch (error2) {
3308
+ if (created) rmSync4(created, { recursive: true, force: true });
3309
+ const message = error2 instanceof Error ? error2.message : String(error2);
3310
+ if (args2.flags["json"])
3311
+ console.log(
3312
+ JSON.stringify({
3313
+ success: false,
3314
+ stage: "migration_ledger",
3315
+ error: message
3316
+ })
3317
+ );
3318
+ else console.error(message);
3319
+ process.exitCode = 2;
3320
+ }
3321
+ }
3322
+ function verifiedMigrationLedger(root) {
3323
+ privateDir(root);
3324
+ privateDir(join7(root, "inputs"));
3325
+ const ids = readdirSync3(join7(root, "inputs")).sort();
3326
+ if (!ids.length || ids.length > 16 || ids.some((id) => !/^[a-f0-9]{64}$/.test(id)))
3327
+ throw new Error("Invalid ledger input inventory");
3328
+ let bytes2 = 0, entries = 0;
3329
+ const bundles = ids.map((id) => {
3330
+ const bundle = verifiedMigrationBundle(join7(root, "inputs", id));
3331
+ for (const data of bundle.files.values()) bytes2 += data.byteLength;
3332
+ entries += bundle.files.size;
3333
+ if (bytes2 > 256 * 1024 * 1024 || entries >= 2e4)
3334
+ throw new Error("Ledger input budget exceeded");
3335
+ return bundle;
3336
+ });
3337
+ const plan = planLedger(bundles);
3338
+ compareLedger(root, plan);
3339
+ return plan;
3340
+ }
3341
+
3342
+ // src/commands/migration-resolution.ts
3343
+ import { createHash as createHash12 } from "crypto";
3344
+ import { resolve as resolve9 } from "path";
3345
+ import {
3346
+ MigrationResolutionSchema
3347
+ } from "@disclosureos/schema/experimental/v2";
3348
+ var pin4 = (bytes2) => ({
3349
+ sha256: createHash12("sha256").update(bytes2).digest("hex"),
3350
+ byteLength: bytes2.byteLength
3351
+ });
3352
+ function resolveMigration(args2) {
3353
+ if (args2.flags["help"]) {
3354
+ console.log(
3355
+ "migrate resolve <ledger> <decisions.json> [--json]\nExplicit pinned selection/defer decisions only; no application imports."
3356
+ );
3357
+ return;
3358
+ }
3359
+ try {
3360
+ if (args2.positional.length !== 2 || Object.keys(args2.flags).some((key) => key !== "json"))
3361
+ throw new Error(
3362
+ "Usage: migrate resolve <ledger> <decisions.json> [--json]"
3363
+ );
3364
+ const bytes2 = readLocal(args2.positional[1], 2 * 1024 * 1024);
3365
+ const snapshot = verifiedMigrationLedger(resolve9(args2.positional[0]));
3366
+ const report = buildMigrationResolution(snapshot, bytes2);
3367
+ if (args2.flags["json"]) console.log(JSON.stringify(report, null, 2));
3368
+ else {
3369
+ console.log(
3370
+ `Migration resolution: ${report.selected.length} selected candidates, ${report.counts.deferredRows} deferred rows, ${report.counts.unresolvedRows} unresolved rows, ${report.counts.pendingReview} pending, ${report.counts.quarantined} quarantined, 0 migrated.`
3371
+ );
3372
+ for (const identity of report.identities)
3373
+ console.log(
3374
+ JSON.stringify({
3375
+ identity: identity.identity,
3376
+ status: identity.status,
3377
+ decision: identity.decision
3378
+ })
3379
+ );
3380
+ console.log(
3381
+ "Selections are application planning choices, not scientific findings. Use --json for pinned decisions and complete row accounting."
3382
+ );
3383
+ }
3384
+ if (report.counts.deferredRows || report.counts.unresolvedRows || report.counts.pendingReview || report.counts.quarantined)
3385
+ process.exitCode = 1;
3386
+ } catch (error2) {
3387
+ const message = error2 instanceof Error ? error2.message : String(error2);
3388
+ if (args2.flags["json"])
3389
+ console.log(
3390
+ JSON.stringify({
3391
+ success: false,
3392
+ stage: "migration_resolution",
3393
+ error: message
3394
+ })
3395
+ );
3396
+ else console.error(message);
3397
+ process.exitCode = 2;
3398
+ }
3399
+ }
3400
+ function buildMigrationResolution(snapshot, bytes2) {
3401
+ const parsed = MigrationResolutionSchema.safeParse(reviewedJson(bytes2));
3402
+ if (!parsed.success)
3403
+ throw new Error(`Invalid resolution plan: ${parsed.error.message}`);
3404
+ const plan = parsed.data;
3405
+ const ledgerPin = pin4(snapshot.files.get("ledger.json"));
3406
+ if (plan.ledger.sha256 !== ledgerPin.sha256 || plan.ledger.byteLength !== ledgerPin.byteLength)
3407
+ throw new Error(
3408
+ "Ledger byte pin mismatch; decisions require the exact verified snapshot"
3409
+ );
3410
+ const inventory = new Map(
3411
+ snapshot.ledger.identities.map((identity) => [identity.identity, identity])
3412
+ );
3413
+ const decisions = /* @__PURE__ */ new Map();
3414
+ for (const decision of plan.decisions) {
3415
+ if (decisions.has(decision.identity))
3416
+ throw new Error("Duplicate resolution decision for identity");
3417
+ const identity = inventory.get(decision.identity);
3418
+ if (!identity) throw new Error("Resolution names an unknown identity");
3419
+ if (decision.action === "select" && !identity.revisions.some(
3420
+ (revision) => revision.sha256 === decision.revision
3421
+ ))
3422
+ throw new Error(
3423
+ "Selected revision does not belong to the named identity"
3424
+ );
3425
+ decisions.set(decision.identity, decision);
3426
+ }
3427
+ const identities = snapshot.ledger.identities.map((identity) => {
3428
+ const decision = decisions.get(identity.identity);
3429
+ return {
3430
+ ...identity,
3431
+ ledgerStatus: identity.status,
3432
+ status: decision?.action === "select" ? "selected" : decision ? "deferred" : "unresolved",
3433
+ ...decision ? { decision } : {}
3434
+ };
3435
+ });
3436
+ const selected = identities.flatMap((identity) => {
3437
+ const decision = decisions.get(identity.identity);
3438
+ if (decision?.action !== "select") return [];
3439
+ const revision = identity.revisions.find(
3440
+ (revision2) => revision2.sha256 === decision.revision
3441
+ );
3442
+ const occurrence = revision.occurrences[0];
3443
+ return [
3444
+ {
3445
+ identity: identity.identity,
3446
+ revision: revision.sha256,
3447
+ path: occurrence.path,
3448
+ candidate: pin4(snapshot.files.get(occurrence.path)),
3449
+ occurrences: revision.occurrences
3450
+ }
3451
+ ];
3452
+ });
3453
+ const receipts = snapshot.ledger.receipts.map((receipt) => ({
3454
+ ...receipt,
3455
+ rows: receipt.rows.map((row) => {
3456
+ if (!("identity" in row)) return { ...row, ledgerStatus: row.status };
3457
+ const decision = decisions.get(row.identity);
3458
+ const status2 = decision?.action === "select" ? decision.revision === row.revision ? "selected" : "not_selected" : decision ? "deferred" : "unresolved";
3459
+ return { ...row, ledgerStatus: row.status, status: status2 };
3460
+ })
3461
+ }));
3462
+ const rows = receipts.flatMap((receipt) => receipt.rows), count = (status2) => rows.filter((row) => row.status === status2).length;
3463
+ return {
3464
+ kind: "legacy_migration_resolution_result",
3465
+ schemaVersion: "0.1.0",
3466
+ policy: "legacy-migration-resolution:0.1.0",
3467
+ ledger: ledgerPin,
3468
+ plan: {
3469
+ ...pin4(bytes2),
3470
+ encoding: "base64",
3471
+ bytes: Buffer.from(bytes2).toString("base64")
3472
+ },
3473
+ decidedBy: plan.decidedBy,
3474
+ decidedAt: plan.decidedAt,
3475
+ identities,
3476
+ selected,
3477
+ receipts,
3478
+ counts: {
3479
+ input: rows.length,
3480
+ selectedRows: count("selected"),
3481
+ notSelectedRows: count("not_selected"),
3482
+ deferredRows: count("deferred"),
3483
+ unresolvedRows: count("unresolved"),
3484
+ pendingReview: count("pending_review"),
3485
+ quarantined: count("quarantined"),
3486
+ selectedCandidates: selected.length,
3487
+ migrated: 0
3488
+ },
3489
+ decisionIdentity: "not_authenticated",
3490
+ scientificValidity: "not_checked",
3491
+ applicationStatus: "not_imported",
3492
+ verification: "current_code_replay"
3493
+ };
3494
+ }
3495
+
3496
+ // src/commands/migration-apply.ts
3497
+ var POLICY4 = "legacy-migration-application:0.1.0";
3498
+ var LIMIT3 = 256 * 1024 * 1024;
3499
+ var pin5 = (bytes2) => ({
3500
+ sha256: createHash13("sha256").update(bytes2).digest("hex"),
3501
+ byteLength: bytes2.byteLength
3502
+ });
3503
+ var json5 = (value) => Buffer.from(JSON.stringify(value, null, 2) + "\n");
3504
+ var stat = (path) => {
3505
+ try {
3506
+ return lstatSync8(path);
3507
+ } catch (error2) {
3508
+ if (error2.code === "ENOENT") return void 0;
3509
+ throw error2;
3510
+ }
3511
+ };
3512
+ function directory4(path) {
3513
+ const s = lstatSync8(path);
3514
+ if (!s.isDirectory() || s.mode & 63)
3515
+ throw new Error("Expected a private real application directory");
3516
+ }
3517
+ function exact(path, bytes2) {
3518
+ const s = lstatSync8(path);
3519
+ if (!s.isFile() || s.mode & 63)
3520
+ throw new Error("Application artifact must be a private regular file");
3521
+ if (!Buffer.from(readLocal(path, bytes2.byteLength)).equals(Buffer.from(bytes2)))
3522
+ throw new Error(`Application conflict: changed artifact ${path}`);
3523
+ }
3524
+ function prepare(ledger, decisions) {
3525
+ const snapshot = verifiedMigrationLedger(ledger), resolution = buildMigrationResolution(snapshot, decisions), c = resolution.counts;
3526
+ if (c.deferredRows || c.unresolvedRows || c.pendingReview || c.quarantined || !resolution.selected.length)
3527
+ throw new Error(
3528
+ "Local application requires explicit selections for all identities, no pending/quarantined rows and at least one candidate"
3529
+ );
3530
+ const files = /* @__PURE__ */ new Map(), dirs = /* @__PURE__ */ new Set(["", ".pending", "ledger", "candidates"]);
3531
+ let total = 0;
3532
+ const add = (path, bytes2) => {
3533
+ total += bytes2.byteLength;
3534
+ if (total > LIMIT3 || files.size >= 3e4)
3535
+ throw new Error("Application exceeds 256 MiB or 30000-file budget");
3536
+ files.set(path, bytes2);
3537
+ };
3538
+ for (const dir of snapshot.dirs) if (dir) dirs.add(`ledger/${dir}`);
3539
+ for (const [path, bytes2] of snapshot.files) add(`ledger/${path}`, bytes2);
3540
+ add("resolution.json", decisions);
3541
+ add("resolution-report.json", json5(resolution));
3542
+ const candidates = resolution.selected.map((item) => {
3543
+ const path = `candidates/${item.identity}.json`;
3544
+ const bytes2 = snapshot.files.get(item.path);
3545
+ add(path, bytes2);
3546
+ return { ...item, path };
3547
+ });
3548
+ const receipt = {
3549
+ kind: "legacy_migration_application_receipt",
3550
+ schemaVersion: "0.1.0",
3551
+ policy: POLICY4,
3552
+ status: "complete",
3553
+ target: "private_local_candidate_store",
3554
+ ledger: resolution.ledger,
3555
+ resolution: pin5(decisions),
3556
+ counts: {
3557
+ inputRows: c.input,
3558
+ selectedRows: c.selectedRows,
3559
+ retainedHistoricalRows: c.notSelectedRows,
3560
+ appliedCandidates: candidates.length,
3561
+ indexImported: 0
3562
+ },
3563
+ candidates,
3564
+ files: [...files].map(([path, bytes2]) => ({ path, ...pin5(bytes2) })),
3565
+ decisionIdentity: "not_authenticated",
3566
+ scientificValidity: "not_checked",
3567
+ readPath: "not_activated"
3568
+ };
3569
+ add("receipt.json", json5(receipt));
3570
+ const intent = json5({
3571
+ kind: "legacy_migration_application_intent",
3572
+ schemaVersion: "0.1.0",
3573
+ policy: POLICY4,
3574
+ ledger: resolution.ledger,
3575
+ resolution: pin5(decisions),
3576
+ receipt: pin5(files.get("receipt.json"))
3577
+ });
3578
+ add("intent.json", intent);
3579
+ return { files, dirs, receipt };
3580
+ }
3581
+ function inspect(root, plan, complete) {
3582
+ directory4(root);
3583
+ const children = /* @__PURE__ */ new Map();
3584
+ for (const dir of plan.dirs) children.set(dir, /* @__PURE__ */ new Set());
3585
+ for (const path of [...plan.dirs, ...plan.files.keys()]) {
3586
+ if (!path) continue;
3587
+ const slash = path.lastIndexOf("/");
3588
+ children.get(slash < 0 ? "" : path.slice(0, slash)).add(path.slice(slash + 1));
3589
+ }
3590
+ for (const dir of plan.dirs) {
3591
+ const path = join8(root, dir);
3592
+ if (!stat(path)) {
3593
+ if (complete)
3594
+ throw new Error("Completed application is missing a directory");
3595
+ continue;
3596
+ }
3597
+ directory4(path);
3598
+ if (dir !== ".pending" && readdirSync4(path).some((name2) => !children.get(dir).has(name2)))
3599
+ throw new Error("Application conflict: unexpected entries");
3600
+ }
3601
+ for (const [path, bytes2] of plan.files) {
3602
+ if (stat(join8(root, path))) exact(join8(root, path), bytes2);
3603
+ else if (complete)
3604
+ throw new Error(`Completed application is missing ${path}`);
3605
+ }
3606
+ const pending = join8(root, ".pending");
3607
+ if (stat(pending)) {
3608
+ const names = readdirSync4(pending);
3609
+ if (names.length > 64)
3610
+ throw new Error("Application staging-file budget exceeded");
3611
+ const expected = new Map(
3612
+ [...plan.files.values()].map((bytes2) => [pin5(bytes2).sha256, bytes2])
3613
+ );
3614
+ let total = 0;
3615
+ for (const name2 of names) {
3616
+ if (!/^[a-f0-9]{64}-[a-f0-9-]{36}$/.test(name2))
3617
+ throw new Error("Unexpected application staging file");
3618
+ const bytes2 = expected.get(name2.slice(0, 64));
3619
+ if (!bytes2) throw new Error("Unknown staged content");
3620
+ const path = join8(pending, name2), s = stat(path);
3621
+ if (!s) continue;
3622
+ if (!s.isFile() || s.mode & 63)
3623
+ throw new Error("Invalid staging file");
3624
+ total += s.size;
3625
+ if (total > LIMIT3)
3626
+ throw new Error("Application staging-byte budget exceeded");
3627
+ let partial;
3628
+ try {
3629
+ partial = readLocal(path, bytes2.byteLength);
3630
+ } catch (error2) {
3631
+ if (error2.code === "ENOENT") continue;
3632
+ throw error2;
3633
+ }
3634
+ if (!Buffer.from(bytes2).subarray(0, partial.byteLength).equals(Buffer.from(partial)))
3635
+ throw new Error("Conflicting staged bytes");
3636
+ }
3637
+ }
3638
+ }
3639
+ function publish(root, path, bytes2) {
3640
+ const target2 = join8(root, path);
3641
+ if (stat(target2)) {
3642
+ exact(target2, bytes2);
3643
+ return;
3644
+ }
3645
+ const temporary = join8(
3646
+ root,
3647
+ ".pending",
3648
+ `${pin5(bytes2).sha256}-${randomUUID2()}`
3649
+ );
3650
+ let fd;
3651
+ try {
3652
+ fd = openSync2(temporary, "wx", 384);
3653
+ writeFileSync6(fd, bytes2);
3654
+ fsyncSync(fd);
3655
+ closeSync2(fd);
3656
+ fd = void 0;
3657
+ try {
3658
+ linkSync(temporary, target2);
3659
+ } catch (error2) {
3660
+ if (error2.code !== "EEXIST") throw error2;
3661
+ exact(target2, bytes2);
3662
+ }
3663
+ } finally {
3664
+ if (fd !== void 0) closeSync2(fd);
3665
+ if (stat(temporary)) unlinkSync(temporary);
3666
+ }
3667
+ }
3668
+ function verifiedMigrationApplication(root) {
3669
+ directory4(root);
3670
+ const plan = prepare(
3671
+ join8(root, "ledger"),
3672
+ readLocal(join8(root, "resolution.json"), 2 * 1024 * 1024)
3673
+ );
3674
+ inspect(root, plan, true);
3675
+ return plan;
3676
+ }
3677
+ function applyMigration(args2) {
3678
+ if (args2.flags["help"]) {
3679
+ console.log(
3680
+ "migrate apply <ledger> <decisions.json> --out <store> [--json]\nmigrate apply-verify <store> [--json]\nPrivate local application only; Index read paths remain unchanged."
3681
+ );
3682
+ return;
3683
+ }
3684
+ try {
3685
+ const verify = args2.subcommand === "apply-verify", out = args2.flags["out"];
3686
+ if (verify ? args2.positional.length !== 1 || Object.keys(args2.flags).some((key) => key !== "json") : args2.positional.length !== 2 || typeof out !== "string" || Object.keys(args2.flags).some((key) => !["out", "json"].includes(key)))
3687
+ throw new Error(
3688
+ "Usage: migrate apply <ledger> <decisions.json> --out <store>, or migrate apply-verify <store>"
3689
+ );
3690
+ const root = resolve10(verify ? args2.positional[0] : out);
3691
+ if (verify) directory4(root);
3692
+ const plan = prepare(
3693
+ verify ? join8(root, "ledger") : resolve10(args2.positional[0]),
3694
+ readLocal(
3695
+ verify ? join8(root, "resolution.json") : args2.positional[1],
3696
+ 2 * 1024 * 1024
3697
+ )
3698
+ );
3699
+ let created = false;
3700
+ if (!verify) {
3701
+ try {
3702
+ mkdirSync5(root, { mode: 448 });
3703
+ created = true;
3704
+ } catch (error2) {
3705
+ if (error2.code !== "EEXIST") throw error2;
3706
+ }
3707
+ }
3708
+ if (created) {
3709
+ mkdirSync5(join8(root, ".pending"), { mode: 448 });
3710
+ publish(root, "intent.json", plan.files.get("intent.json"));
3711
+ } else exact(join8(root, "intent.json"), plan.files.get("intent.json"));
3712
+ const completed = !!stat(join8(root, "receipt.json"));
3713
+ inspect(root, plan, verify || completed);
3714
+ if (!verify && !completed) {
3715
+ for (const dir of plan.dirs)
3716
+ if (dir) {
3717
+ try {
3718
+ mkdirSync5(join8(root, dir), { mode: 448 });
3719
+ } catch (error2) {
3720
+ if (error2.code !== "EEXIST") throw error2;
3721
+ directory4(join8(root, dir));
3722
+ }
3723
+ }
3724
+ for (const [path, bytes2] of plan.files)
3725
+ if (path !== "receipt.json") publish(root, path, bytes2);
3726
+ inspect(root, plan, false);
3727
+ publish(root, "receipt.json", plan.files.get("receipt.json"));
3728
+ }
3729
+ inspect(root, plan, true);
3730
+ const result = {
3731
+ success: true,
3732
+ action: verify ? "verified" : completed ? "reused" : created ? "applied" : "resumed",
3733
+ directory: root,
3734
+ receipt: pin5(plan.files.get("receipt.json")),
3735
+ counts: plan.receipt.counts,
3736
+ target: plan.receipt.target,
3737
+ readPath: "not_activated",
3738
+ verification: "current_code_replay"
3739
+ };
3740
+ if (args2.flags["json"]) console.log(JSON.stringify(result, null, 2));
3741
+ else
3742
+ console.log(
3743
+ `Local migration ${result.action}: ${result.counts.appliedCandidates} candidates, ${result.counts.retainedHistoricalRows} historical rows retained, 0 Index imports.
3744
+ ${root}`
3745
+ );
3746
+ } catch (error2) {
3747
+ const message = error2 instanceof Error ? error2.message : String(error2);
3748
+ if (args2.flags["json"])
3749
+ console.log(
3750
+ JSON.stringify({
3751
+ success: false,
3752
+ stage: "migration_application",
3753
+ error: message
3754
+ })
3755
+ );
3756
+ else console.error(message);
3757
+ process.exitCode = 2;
3758
+ }
3759
+ }
3760
+
3761
+ // src/commands/migration-read-path.ts
3762
+ var HASH = /^[a-f0-9]{64}$/;
3763
+ var LIMIT4 = 16384;
3764
+ var MAX_EVENTS = 1024;
3765
+ var bytes = (value) => Buffer.from(JSON.stringify(value, null, 2) + "\n");
3766
+ var pin6 = (value) => ({
3767
+ sha256: createHash14("sha256").update(value).digest("hex"),
3768
+ byteLength: value.byteLength
3769
+ });
3770
+ var name = (sequence) => `${String(sequence).padStart(6, "0")}.json`;
3771
+ function privateDirectory2(path) {
3772
+ const stat2 = lstatSync9(path);
3773
+ if (!stat2.isDirectory() || stat2.mode & 63)
3774
+ throw new Error("Selector requires private real directories");
3775
+ }
3776
+ function privateFile(path) {
3777
+ const stat2 = lstatSync9(path);
3778
+ if (!stat2.isFile() || stat2.mode & 63)
3779
+ throw new Error("Selector requires private regular files");
3780
+ return readLocal(path, LIMIT4);
3781
+ }
3782
+ function object3(value) {
3783
+ if (!value || typeof value !== "object" || Array.isArray(value))
3784
+ throw new Error("Invalid read transition object");
3785
+ return value;
3786
+ }
3787
+ function target(value) {
3788
+ const v = object3(value);
3789
+ if (v.kind === "legacy") return { kind: "legacy" };
3790
+ const p = object3(v.receipt);
3791
+ if (v.kind !== "local_store" || typeof v.directory !== "string" || v.directory.length > 4096 || !isAbsolute(v.directory) || resolve11(v.directory) !== v.directory || typeof p.sha256 !== "string" || !HASH.test(p.sha256) || !Number.isSafeInteger(p.byteLength) || p.byteLength < 1 || p.byteLength > 256 * 1024 * 1024)
3792
+ throw new Error("Invalid pinned local store target");
3793
+ return {
3794
+ kind: "local_store",
3795
+ directory: v.directory,
3796
+ receipt: { sha256: p.sha256, byteLength: p.byteLength }
3797
+ };
3798
+ }
3799
+ function history(root) {
3800
+ privateDirectory2(root);
3801
+ privateDirectory2(join9(root, ".pending"));
3802
+ const names = readdirSync5(root).filter((v) => v !== ".pending").sort();
3803
+ if (!names.length || names.length > MAX_EVENTS || names.some((v, i) => v !== name(i)))
3804
+ throw new Error(
3805
+ "Selector history is missing, noncontiguous or exceeds its budget"
3806
+ );
3807
+ const pending = readdirSync5(join9(root, ".pending"));
3808
+ if (pending.length > 64) throw new Error("Selector staging budget exceeded");
3809
+ for (const n of pending) {
3810
+ if (!/^[a-f0-9-]{36}$/.test(n))
3811
+ throw new Error("Unexpected selector staging entry");
3812
+ try {
3813
+ privateFile(join9(root, ".pending", n));
3814
+ } catch (error2) {
3815
+ if (error2.code !== "ENOENT") throw error2;
3816
+ }
3817
+ }
3818
+ const entries = [];
3819
+ for (const [i, n] of names.entries()) {
3820
+ const raw = privateFile(join9(root, n));
3821
+ const v = object3(reviewedJson(raw));
3822
+ const t = target(v.target);
3823
+ const previous = entries.at(-1)?.id ?? null;
3824
+ let action;
3825
+ let rollbackTo = null;
3826
+ if (i === 0 && v.action === "initialize" && t.kind === "legacy")
3827
+ action = "initialize";
3828
+ else if (i > 0 && v.action === "activate" && t.kind === "local_store")
3829
+ action = "activate";
3830
+ else if (i > 0 && v.action === "rollback" && typeof v.rollbackTo === "string") {
3831
+ const earlier = entries.find((e) => e.id === v.rollbackTo);
3832
+ if (!earlier || !bytes(earlier.event.target).equals(bytes(t)))
3833
+ throw new Error("Rollback must restore an earlier recorded target");
3834
+ action = "rollback";
3835
+ rollbackTo = v.rollbackTo;
3836
+ } else throw new Error("Invalid read transition action");
3837
+ const event = {
3838
+ kind: "legacy_migration_read_transition",
3839
+ schemaVersion: "0.1.0",
3840
+ sequence: i,
3841
+ previous,
3842
+ action,
3843
+ target: t,
3844
+ rollbackTo
3845
+ };
3846
+ if (!bytes(event).equals(Buffer.from(raw)))
3847
+ throw new Error("Changed or noncanonical selector history");
3848
+ entries.push({ id: pin6(raw).sha256, event });
3849
+ }
3850
+ return entries;
3851
+ }
3852
+ function verifiedTarget(t) {
3853
+ if (t.kind === "legacy") return [];
3854
+ const plan = verifiedMigrationApplication(t.directory);
3855
+ if (!bytes(pin6(plan.files.get("receipt.json"))).equals(bytes(t.receipt)))
3856
+ throw new Error("Selected store receipt does not match its activation pin");
3857
+ return plan.receipt.candidates.map((c) => ({
3858
+ identity: c.identity,
3859
+ revision: c.revision,
3860
+ candidate: JSON.parse(
3861
+ Buffer.from(plan.files.get(c.path)).toString("utf8")
3862
+ )
3863
+ }));
3864
+ }
3865
+ function publish2(root, event) {
3866
+ const raw = bytes(event);
3867
+ if (raw.length > LIMIT4)
3868
+ throw new Error("Read transition exceeds byte budget");
3869
+ const temporary = join9(root, ".pending", randomUUID3());
3870
+ let fd;
3871
+ try {
3872
+ fd = openSync3(temporary, "wx", 384);
3873
+ writeFileSync7(fd, raw);
3874
+ fsyncSync2(fd);
3875
+ closeSync3(fd);
3876
+ fd = void 0;
3877
+ linkSync2(temporary, join9(root, name(event.sequence)));
3878
+ } finally {
3879
+ if (fd !== void 0) closeSync3(fd);
3880
+ try {
3881
+ unlinkSync2(temporary);
3882
+ } catch (error2) {
3883
+ if (error2.code !== "ENOENT") throw error2;
3884
+ }
3885
+ }
3886
+ }
3887
+ function migrationReadPath(args2) {
3888
+ const usage2 = "migrate read-init --out <selector> [--json]\nmigrate read-path <selector> [--json]\nmigrate read-activate <selector> <completed-store> --id <expected-head> [--json]\nmigrate read-rollback <selector> <earlier-head> --id <expected-head> [--json]";
3889
+ if (args2.flags["help"]) {
3890
+ console.log(usage2);
3891
+ return;
3892
+ }
3893
+ let currentHead;
3894
+ try {
3895
+ const init = args2.subcommand === "read-init", read = args2.subcommand === "read-path";
3896
+ const allowed = init ? ["out", "json"] : read ? ["json"] : ["id", "json"];
3897
+ if (Object.keys(args2.flags).some((k) => !allowed.includes(k)) || args2.positional.length !== (init ? 0 : read ? 1 : 2) || init && typeof args2.flags["out"] !== "string" || !init && !read && (typeof args2.flags["id"] !== "string" || !HASH.test(args2.flags["id"])))
3898
+ throw new Error(usage2);
3899
+ const root = resolve11(
3900
+ init ? args2.flags["out"] : args2.positional[0]
3901
+ );
3902
+ if (init) {
3903
+ mkdirSync6(root, { mode: 448 });
3904
+ mkdirSync6(join9(root, ".pending"), { mode: 448 });
3905
+ publish2(root, {
3906
+ kind: "legacy_migration_read_transition",
3907
+ schemaVersion: "0.1.0",
3908
+ sequence: 0,
3909
+ previous: null,
3910
+ action: "initialize",
3911
+ target: { kind: "legacy" },
3912
+ rollbackTo: null
3913
+ });
3914
+ }
3915
+ let entries = history(root);
3916
+ currentHead = entries.at(-1).id;
3917
+ if (!init && !read) {
3918
+ const head2 = entries.at(-1);
3919
+ if (args2.flags["id"] !== head2.id)
3920
+ throw new Error(
3921
+ "Stale expected read head; inspect the current selector before retrying"
3922
+ );
3923
+ if (entries.length >= MAX_EVENTS)
3924
+ throw new Error("Selector transition budget exceeded");
3925
+ let t;
3926
+ let rollbackTo = null;
3927
+ if (args2.subcommand === "read-activate") {
3928
+ const directory5 = resolve11(args2.positional[1]);
3929
+ const plan = verifiedMigrationApplication(directory5);
3930
+ t = {
3931
+ kind: "local_store",
3932
+ directory: directory5,
3933
+ receipt: pin6(plan.files.get("receipt.json"))
3934
+ };
3935
+ } else {
3936
+ const earlier = entries.slice(0, -1).find((e) => e.id === args2.positional[1]);
3937
+ if (!earlier)
3938
+ throw new Error(
3939
+ "Rollback target must be an earlier head in this selector"
3940
+ );
3941
+ t = earlier.event.target;
3942
+ rollbackTo = earlier.id;
3943
+ verifiedTarget(t);
3944
+ }
3945
+ publish2(root, {
3946
+ kind: "legacy_migration_read_transition",
3947
+ schemaVersion: "0.1.0",
3948
+ sequence: entries.length,
3949
+ previous: head2.id,
3950
+ action: rollbackTo ? "rollback" : "activate",
3951
+ target: t,
3952
+ rollbackTo
3953
+ });
3954
+ entries = history(root);
3955
+ }
3956
+ const head = entries.at(-1);
3957
+ currentHead = head.id;
3958
+ const candidates = verifiedTarget(head.event.target);
3959
+ const result = {
3960
+ success: true,
3961
+ scope: "private_local_read_selector",
3962
+ head: head.id,
3963
+ target: head.event.target,
3964
+ history: entries,
3965
+ candidates,
3966
+ indexImported: 0,
3967
+ scientificValidity: "not_checked",
3968
+ decisionIdentity: "not_authenticated",
3969
+ verification: "current_code_replay"
3970
+ };
3971
+ if (args2.flags["json"]) console.log(JSON.stringify(result, null, 2));
3972
+ else
3973
+ console.log(
3974
+ `Local read path: ${head.event.target.kind}; ${candidates.length} verified candidates.
3975
+ Head: ${head.id}
3976
+ Index read path unchanged.`
3977
+ );
3978
+ } catch (error2) {
3979
+ const message = error2 instanceof Error ? error2.message : String(error2);
3980
+ if (args2.flags["json"])
3981
+ console.log(
3982
+ JSON.stringify({
3983
+ success: false,
3984
+ stage: "migration_read_path",
3985
+ head: currentHead,
3986
+ error: message
3987
+ })
3988
+ );
3989
+ else console.error(message);
3990
+ process.exitCode = 2;
3991
+ }
3992
+ }
3993
+
3994
+ // src/commands/migrate.ts
3995
+ function migrate(args2) {
3996
+ if (args2.subcommand === "compatibility") {
3997
+ migrationCompatibility(args2);
3998
+ return;
3999
+ }
4000
+ if (args2.subcommand === "legacy-scores") {
4001
+ legacyScores(args2);
4002
+ return;
4003
+ }
4004
+ if (["read-init", "read-path", "read-activate", "read-rollback"].includes(
4005
+ args2.subcommand
4006
+ )) {
4007
+ migrationReadPath(args2);
4008
+ return;
4009
+ }
4010
+ if (args2.subcommand === "apply" || args2.subcommand === "apply-verify") {
4011
+ applyMigration(args2);
4012
+ return;
4013
+ }
4014
+ if (args2.subcommand === "resolve") {
4015
+ resolveMigration(args2);
4016
+ return;
4017
+ }
4018
+ if (args2.subcommand === "ledger" || args2.subcommand === "ledger-verify") {
4019
+ migrationLedger(args2);
4020
+ return;
4021
+ }
4022
+ if (args2.subcommand === "export" || args2.subcommand === "verify") {
4023
+ exportMigration(args2);
4024
+ return;
4025
+ }
4026
+ if (args2.subcommand === "review") {
4027
+ reviewMigration(args2);
4028
+ return;
4029
+ }
4030
+ if (args2.flags["help"]) {
4031
+ console.log(
4032
+ "disclosureos migrate dry-run <legacy.json> --id <source-namespace> [--json]\nFor explicit mappings: disclosureos migrate review <legacy.json> <review.json> [--json]\nExport: disclosureos migrate export <legacy.json> <review.json> --out <bundle> [--json]\nVerify: disclosureos migrate verify <bundle> [--json]\nLedger: disclosureos migrate ledger <bundle>... --out <ledger> [--json]\nVerify ledger: disclosureos migrate ledger-verify <ledger> [--json]\nResolve: disclosureos migrate resolve <ledger> <decisions.json> [--json]\nApply locally: disclosureos migrate apply <ledger> <decisions.json> --out <store> [--json]\nVerify application: disclosureos migrate apply-verify <store> [--json]\nRead selector: disclosureos migrate read-path <selector> [--json]\nSee migrate read-init --help for activation and rollback.\nPreserve historical scores: disclosureos migrate legacy-scores <scores.json> <plan.json> [--json]\nField inventory and sensor review: disclosureos migrate compatibility <legacy.json> [sensor-review.json] --id <namespace> [--json]\nExperimental local artifacts only; no Index database writes."
4033
+ );
4034
+ return;
4035
+ }
4036
+ try {
4037
+ const namespace = args2.flags["id"];
4038
+ if (args2.subcommand !== "dry-run" || args2.positional.length !== 1 || typeof namespace !== "string" || !/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(namespace) || Object.keys(args2.flags).some((key) => !["id", "json"].includes(key))) {
4039
+ throw new Error(
4040
+ "Usage: disclosureos migrate dry-run <legacy.json> --id <source-namespace> [--json]"
4041
+ );
4042
+ }
4043
+ const bytes2 = readLocal(args2.positional[0], 8 * 1024 * 1024);
4044
+ const report = buildMigrationReport(bytes2, namespace);
4045
+ if (args2.flags["json"]) console.log(JSON.stringify(report, null, 2));
4046
+ else {
4047
+ console.log(
4048
+ `Experimental migration dry run: ${report.counts.input} inputs, ${report.counts.candidates} review candidates, ${report.counts.quarantined} quarantined, 0 migrated.`
4049
+ );
4050
+ for (const row of report.records) {
4051
+ console.log(
4052
+ JSON.stringify({
4053
+ sourcePointer: row.sourcePointer,
4054
+ sourceId: row.sourceId,
4055
+ decision: row.decision,
4056
+ unresolvedFields: row.mapping.filter(
4057
+ (field) => field.disposition === "unresolved"
4058
+ ).length,
4059
+ reasons: row.reasons
4060
+ })
4061
+ );
4062
+ }
4063
+ console.log(
4064
+ "Use --json for exact source bytes, retained legacy data, candidates and field mappings. Every candidate requires explicit mapping review."
4065
+ );
4066
+ }
4067
+ if (report.counts.quarantined > 0) process.exitCode = 1;
4068
+ } catch (error2) {
4069
+ const message = error2 instanceof Error ? error2.message : String(error2);
4070
+ if (args2.flags["json"])
4071
+ console.log(
4072
+ JSON.stringify({ success: false, stage: "input", error: message })
4073
+ );
4074
+ else console.error(message);
4075
+ process.exitCode = 2;
4076
+ }
4077
+ }
4078
+
4079
+ // src/commands/completeness.ts
4080
+ import { parseEnrichedObservation as parseEnrichedObservation3 } from "@disclosureos/schema";
4081
+ import { getPopulationCoverage } from "@disclosureos/scoring";
4082
+ function completeness(args2) {
4083
+ const population = args2.command === "population-coverage";
4084
+ if (args2.flags["help"]) {
4085
+ printUsage4(population);
4086
+ return;
4087
+ }
4088
+ const targets = [args2.subcommand, ...args2.positional].filter((t) => !!t);
4089
+ if (targets.length === 0) {
4090
+ printUsage4(population);
4091
+ return;
4092
+ }
4093
+ const recursive = !!args2.flags["recursive"];
4094
+ const jsonMode = !!args2.flags["json"];
4095
+ const seen = /* @__PURE__ */ new Set();
4096
+ const files = [];
4097
+ const emptyTargets = [];
4098
+ for (const target2 of targets) {
4099
+ const found = findJSONFiles(target2, recursive);
4100
+ if (found.length === 0) {
4101
+ emptyTargets.push(target2);
4102
+ continue;
4103
+ }
4104
+ for (const f of found) {
4105
+ if (!seen.has(f)) {
4106
+ seen.add(f);
4107
+ files.push(f);
4108
+ }
4109
+ }
4110
+ }
4111
+ if (jsonMode) {
4112
+ runJsonMode2(files, emptyTargets);
4113
+ return;
4114
+ }
4115
+ for (const t of emptyTargets) {
4116
+ console.log(error(`No JSON files found at: ${t}`));
4117
+ }
4118
+ if (files.length === 0) {
4119
+ process.exit(1);
4120
+ }
4121
+ let hasErrors = false;
4122
+ const percentages = [];
4123
+ const reqPercentages = [];
4124
+ for (const file of files) {
4125
+ const result = scoreFile(file, population);
4126
+ if (!result.valid) {
4127
+ hasErrors = true;
4128
+ continue;
4129
+ }
4130
+ percentages.push(result.percentage);
4131
+ reqPercentages.push(result.requiredPercentage);
4132
+ }
4133
+ console.log("");
4134
+ if (hasErrors || emptyTargets.length > 0) {
4135
+ console.log(error("Some files could not be scored (see errors above)."));
4136
+ process.exit(1);
4137
+ }
4138
+ const mean = avg(percentages);
4139
+ const reqMean = avg(reqPercentages);
4140
+ console.log(
4141
+ success(
4142
+ `${files.length} file(s) scored \u2014 mean ${population ? "population coverage" : "completeness"} ${mean}%, required fields ${reqMean}%`
4143
+ )
4144
+ );
4145
+ }
4146
+ function avg(values) {
4147
+ return values.length > 0 ? Math.round(values.reduce((a, b) => a + b, 0) / values.length) : 0;
4148
+ }
4149
+ function scoreFile(filePath, population = false) {
4150
+ let data;
4151
+ try {
4152
+ data = readJSON(filePath);
4153
+ } catch {
4154
+ console.log(error(`Failed to parse: ${filePath}`));
4155
+ return emptyResult(filePath, "Failed to parse JSON");
4156
+ }
4157
+ if (!data || typeof data !== "object") {
4158
+ console.log(error(`${filePath}: not a valid JSON object`));
4159
+ return emptyResult(filePath, "Not a valid JSON object");
4160
+ }
4161
+ const parsed = parseEnrichedObservation3(data);
4162
+ if (!parsed.success) {
4163
+ console.log(error(`${filePath}: invalid observation (${parsed.issues.length} issue${parsed.issues.length !== 1 ? "s" : ""})`));
4164
+ for (const issue of parsed.issues.slice(0, 5)) {
4165
+ console.log(` ${dim(`${issue.path}: ${issue.message}`)}`);
4166
+ }
4167
+ return emptyResult(filePath, "Invalid observation");
4168
+ }
4169
+ const comp = getPopulationCoverage(data);
4170
+ console.log(`
4171
+ ${dim(filePath)}`);
4172
+ console.log(` ${population ? "Population coverage" : "Completeness"}: ${comp.percentage}% (${comp.present}/${comp.total} fields)`);
4173
+ console.log(` Required: ${comp.requiredPercentage}% (${comp.requiredPresent}/${comp.requiredTotal})`);
4174
+ if (comp.missing.length > 0) {
4175
+ const shown = comp.missing.slice(0, 10);
4176
+ console.log(` Missing (top ${shown.length}):`);
4177
+ for (const path of shown) {
4178
+ console.log(` ${dim("\u2022")} ${path}`);
4179
+ }
4180
+ if (comp.missing.length > shown.length) {
4181
+ console.log(` ${dim(`\u2026 and ${comp.missing.length - shown.length} more`)}`);
4182
+ }
4183
+ }
4184
+ return {
4185
+ file: filePath,
4186
+ valid: true,
4187
+ percentage: comp.percentage,
4188
+ requiredPercentage: comp.requiredPercentage,
4189
+ present: comp.present,
4190
+ total: comp.total,
4191
+ missing: comp.missing
4192
+ };
4193
+ }
4194
+ function emptyResult(file, errorMsg) {
4195
+ return {
4196
+ file,
4197
+ valid: false,
4198
+ percentage: 0,
4199
+ requiredPercentage: 0,
4200
+ present: 0,
4201
+ total: 0,
4202
+ missing: [],
4203
+ error: errorMsg
4204
+ };
4205
+ }
4206
+ function runJsonMode2(files, emptyTargets) {
4207
+ const fileResults = [];
4208
+ for (const file of files) {
4209
+ fileResults.push(scoreFileQuiet(file));
4210
+ }
4211
+ const validResults = fileResults.filter((r) => r.valid);
4212
+ const meanPercentage = avg(validResults.map((r) => r.percentage));
4213
+ const meanRequired = avg(validResults.map((r) => r.requiredPercentage));
4214
+ const allValid = fileResults.every((r) => r.valid) && emptyTargets.length === 0;
4215
+ const output = {
4216
+ files: fileResults,
4217
+ emptyTargets,
4218
+ totals: {
4219
+ files: files.length,
4220
+ meanPercentage,
4221
+ meanRequiredPercentage: meanRequired,
4222
+ emptyTargets: emptyTargets.length
4223
+ },
4224
+ valid: allValid
4225
+ };
4226
+ console.log(JSON.stringify(output, null, 2));
4227
+ if (!allValid) {
4228
+ process.exit(1);
4229
+ }
4230
+ }
4231
+ function scoreFileQuiet(filePath) {
4232
+ let data;
4233
+ try {
4234
+ data = readJSON(filePath);
4235
+ } catch {
4236
+ return emptyResult(filePath, "Failed to parse JSON");
4237
+ }
4238
+ if (!data || typeof data !== "object") {
4239
+ return emptyResult(filePath, "Not a valid JSON object");
4240
+ }
4241
+ const parsed = parseEnrichedObservation3(data);
4242
+ if (!parsed.success) {
4243
+ return emptyResult(filePath, `Invalid observation (${parsed.issues.length} issue${parsed.issues.length !== 1 ? "s" : ""})`);
4244
+ }
4245
+ const comp = getPopulationCoverage(data);
4246
+ return {
4247
+ file: filePath,
4248
+ valid: true,
4249
+ percentage: comp.percentage,
4250
+ requiredPercentage: comp.requiredPercentage,
4251
+ present: comp.present,
4252
+ total: comp.total,
4253
+ missing: comp.missing
4254
+ };
4255
+ }
4256
+ function printUsage4(population = false) {
4257
+ const command = population ? "population-coverage" : "completeness";
4258
+ console.log(heading(`${BRAND} ${command}`));
4259
+ console.log(`
4260
+ Measure how fully observation JSON files populate the records schema.
4261
+ `);
4262
+ console.log(`${dim("Usage:")}`);
4263
+ console.log(` disclosureos ${command} <path...> [options]
4264
+ `);
4265
+ console.log(`${dim("Paths:")}`);
4266
+ console.log(` Accepts one or more files, directories, or shell globs.
4267
+ `);
4268
+ console.log(`${dim("Options:")}`);
4269
+ console.log(` --recursive, -r Score all JSON files in directories recursively`);
4270
+ console.log(` --json, -j Output results as structured JSON (for CI and scripts)`);
4271
+ console.log(` --help, -h Show this help message
4272
+ `);
4273
+ if (population) console.log(" Counts field presence in the legacy schema, not analytical readiness or scientific quality.\n");
4274
+ console.log(`${dim("Scoring:")}`);
4275
+ console.log(` Files must be structurally valid observations before ${population ? "population coverage" : "completeness"} is measured.`);
4276
+ console.log(` Invalid JSON or invalid observations exit non-zero without a score.
4277
+ `);
4278
+ console.log(`${dim("Output:")}`);
4279
+ console.log(` \u2022 ${population ? "Population coverage" : "Completeness"} percentage (0\u2013100) \u2014 how many schema fields are populated`);
4280
+ console.log(` \u2022 Required field percentage \u2014 coverage of required fields only`);
4281
+ console.log(` \u2022 Top missing field paths \u2014 ${population ? "absent paths, without applicability or priority judgments" : "the next fields to target"}
4282
+ `);
4283
+ console.log(`${dim("Examples:")}`);
4284
+ console.log(` disclosureos ${command} ./data/nimitz.json`);
4285
+ console.log(` disclosureos ${command} ./data/ --recursive`);
4286
+ console.log(` disclosureos ${command} ./out/*.json --json
4287
+ `);
4288
+ }
4289
+
4290
+ // src/commands/registry.ts
4291
+ import { deriveFieldPaths } from "@disclosureos/scoring";
4292
+ import {
4293
+ ALL_OBSERVABLES,
4294
+ TECHNOLOGY_OBSERVABLES,
4295
+ BIOLOGICS_OBSERVABLES,
4296
+ ASSESSMENT_LEVELS
4297
+ } from "@disclosureos/observables";
4298
+ import {
4299
+ OCS_TAXONOMY,
4300
+ getNode,
4301
+ getChildren,
4302
+ getTestableNodes,
4303
+ ORIGIN_DOMAINS,
4304
+ ORIGIN_DOMAIN_LABELS
4305
+ } from "@disclosureos/origins";
4306
+ function registry(args2) {
4307
+ if (args2.flags["help"]) {
4308
+ printUsage5();
4309
+ return;
4310
+ }
4311
+ const sub = args2.subcommand;
4312
+ switch (sub) {
4313
+ case "fields":
4314
+ registryFields(args2);
4315
+ break;
4316
+ case "observables":
4317
+ registryObservables(args2);
4318
+ break;
4319
+ case "origins":
4320
+ registryOrigins(args2);
4321
+ break;
4322
+ default:
4323
+ printUsage5();
4324
+ }
4325
+ }
4326
+ function registryFields(args2) {
4327
+ const group = args2.flags["category"];
4328
+ const paths = deriveFieldPaths();
4329
+ const groups = /* @__PURE__ */ new Map();
4330
+ for (const field of paths) {
4331
+ const top = field.path.split(".")[0] ?? field.path;
4332
+ const list = groups.get(top) ?? [];
4333
+ list.push(field);
4334
+ groups.set(top, list);
4335
+ }
4336
+ console.log(heading("Observation Field Registry (schema-derived)"));
4337
+ if (group) {
4338
+ const fields2 = groups.get(group);
4339
+ if (!fields2 || fields2.length === 0) {
4340
+ console.log(`
4341
+ No fields found for group "${group}"`);
4342
+ return;
4343
+ }
4344
+ console.log(`
4345
+ Group: ${group}
4346
+ `);
4347
+ const rows = fields2.map((f) => [f.path, f.required ? "required" : "optional"]);
4348
+ console.log(table(rows));
4349
+ } else {
4350
+ for (const groupName of [...groups.keys()].sort()) {
4351
+ const fields2 = groups.get(groupName);
4352
+ console.log(`
4353
+ ${groupName} (${fields2.length} fields)`);
4354
+ for (const f of fields2) {
4355
+ console.log(bullet(`${f.path}${f.required ? " [required]" : ""}`));
4356
+ }
4357
+ }
4358
+ }
4359
+ console.log(`
4360
+ ${dim(`Total: ${paths.length} schema-derived field paths`)}
4361
+ `);
4362
+ }
4363
+ function registryObservables(args2) {
4364
+ const biologicsOnly = !!args2.flags["biologics"];
4365
+ const techOnly = !!args2.flags["technology"];
4366
+ console.log(heading("Observable Registry"));
4367
+ if (!biologicsOnly) {
4368
+ console.log(`
4369
+ Technology Observables (AATIP-derived)
4370
+ `);
4371
+ const techObs = Object.values(TECHNOLOGY_OBSERVABLES);
4372
+ for (const obs of techObs) {
4373
+ console.log(` ${obs.code} ${obs.label}`);
4374
+ if (obs.description) {
4375
+ console.log(` ${dim(obs.description)}`);
4376
+ }
4377
+ }
4378
+ }
4379
+ if (!techOnly) {
4380
+ console.log(`
4381
+ Biologics Observables (NHI Detection)
993
4382
  `);
994
4383
  const bioObs = Object.values(BIOLOGICS_OBSERVABLES);
995
4384
  for (const obs of bioObs) {
@@ -1070,7 +4459,7 @@ ${label("ID", node.id)}`);
1070
4459
  }
1071
4460
  console.log("");
1072
4461
  }
1073
- function printUsage4() {
4462
+ function printUsage5() {
1074
4463
  console.log(heading(`${BRAND} registry`));
1075
4464
  console.log(`
1076
4465
  Introspect DisclosureOS field, observable, and origin registries.
@@ -1110,7 +4499,7 @@ import {
1110
4499
  var FIELD_PATH_COUNT = deriveFieldPaths2().length;
1111
4500
  function info(args2) {
1112
4501
  if (args2.flags["help"]) {
1113
- printUsage5();
4502
+ printUsage6();
1114
4503
  return;
1115
4504
  }
1116
4505
  const sub = args2.subcommand;
@@ -1257,7 +4646,7 @@ ${label("ID", node.id)}`);
1257
4646
  }
1258
4647
  console.log("");
1259
4648
  }
1260
- function printUsage5() {
4649
+ function printUsage6() {
1261
4650
  console.log(heading(`${BRAND} info`));
1262
4651
  console.log(`
1263
4652
  Quick reference for DisclosureOS types and definitions.
@@ -1291,6 +4680,25 @@ if (args.flags["version"]) {
1291
4680
  case "validate":
1292
4681
  validate(args);
1293
4682
  break;
4683
+ case "migrate":
4684
+ migrate(args);
4685
+ break;
4686
+ case "profile":
4687
+ await profile(args);
4688
+ break;
4689
+ case "intake":
4690
+ await intake(args);
4691
+ break;
4692
+ case "dataset":
4693
+ await dataset(args);
4694
+ break;
4695
+ case "packet":
4696
+ await packet(args);
4697
+ break;
4698
+ case "manifest":
4699
+ manifest(args);
4700
+ break;
4701
+ case "population-coverage":
1294
4702
  case "completeness":
1295
4703
  completeness(args);
1296
4704
  break;
@@ -1322,7 +4730,14 @@ function printHelp() {
1322
4730
  console.log(`${dim("Commands:")}`);
1323
4731
  console.log(` scaffold Generate typed data structure templates`);
1324
4732
  console.log(` validate Validate observation JSON files`);
1325
- console.log(` completeness Measure how fully observations populate the schema`);
4733
+ console.log(` manifest Validate sensor-manifest JSON files`);
4734
+ console.log(` packet Inspect, validate, or reproduce an experimental v2 packet`);
4735
+ console.log(` dataset Inspect or validate an experimental dataset release`);
4736
+ console.log(` intake Receive and verify unassessed source files locally`);
4737
+ console.log(` migrate Plan experimental v1-to-v2 migration without importing`);
4738
+ console.log(` profile List, inspect, or run explicit experimental profile checks`);
4739
+ console.log(` population-coverage Count populated legacy observation fields`);
4740
+ console.log(` completeness Legacy name for population coverage`);
1326
4741
  console.log(` registry Introspect field, observable, and origin registries`);
1327
4742
  console.log(` info Quick reference for types and definitions`);
1328
4743
  console.log(` help Show this help message`);
@@ -1335,7 +4750,7 @@ function printHelp() {
1335
4750
  console.log(`${dim("Examples:")}`);
1336
4751
  console.log(` disclosureos scaffold observation --full`);
1337
4752
  console.log(` disclosureos validate ./data/ --recursive`);
1338
- console.log(` disclosureos completeness ./out/ --recursive`);
4753
+ console.log(` disclosureos population-coverage ./out/ --recursive`);
1339
4754
  console.log(` disclosureos registry origins --id 1.1.3`);
1340
4755
  console.log(` disclosureos info observable TO-3
1341
4756
  `);
@@ -1345,8 +4760,8 @@ function printVersion() {
1345
4760
  }
1346
4761
  function readCliVersion() {
1347
4762
  try {
1348
- const packagePath = join(
1349
- dirname(fileURLToPath(import.meta.url)),
4763
+ const packagePath = join10(
4764
+ dirname6(fileURLToPath2(import.meta.url)),
1350
4765
  "..",
1351
4766
  "package.json"
1352
4767
  );