@tiangong-ai/cli 0.0.39 → 0.0.41

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.
@@ -15,7 +15,7 @@ interface ScientificReviewStageInput {
15
15
  path: string;
16
16
  sha256: string;
17
17
  bytes: number;
18
- purpose: "inherited-gap" | "model-implementation" | "model-environment-lock" | "stage-output";
18
+ purpose: "construct-canary" | "inherited-gap" | "model-implementation" | "model-environment-lock" | "stage-output";
19
19
  ownerId: string;
20
20
  sourceLocator: string;
21
21
  hashBasis: "raw-file-bytes";
@@ -85,6 +85,7 @@ export declare function prepareScientificReview(input: {
85
85
  assessmentPath: string;
86
86
  reviewerAgent: AgentKind;
87
87
  reviewerSessionId: string;
88
+ canaryArtifactPaths?: string[];
88
89
  }): Promise<ScientificReviewPacket>;
89
90
  export declare function submitScientificReview(input: {
90
91
  root: string;
@@ -1,6 +1,6 @@
1
1
  import { Ajv2020 } from "ajv/dist/2020.js";
2
2
  import { lstat, readFile } from "node:fs/promises";
3
- import { isAbsolute, join } from "node:path";
3
+ import { isAbsolute, join, relative, resolve, sep } from "node:path";
4
4
  import { CliError } from "../../errors.js";
5
5
  import { appendJournalEvent, verifyJournal } from "./journal.js";
6
6
  import { loadProject, nextScientificGate, saveProject } from "./projects.js";
@@ -268,7 +268,7 @@ export async function prepareScientificReview(input) {
268
268
  exitCode: 3,
269
269
  });
270
270
  }
271
- const stageInputs = await stageInputRecords(input.root, project, input.role, design);
271
+ const stageInputs = await stageInputRecords(input.root, project, input.role, design, assessment, input.canaryArtifactPaths ?? []);
272
272
  const assessmentSha256 = exactJsonSha256(assessment);
273
273
  const assessmentLocator = `projects/${project.id}/scientific/assessments/${input.role}/${assessmentSha256}.json`;
274
274
  await writeImmutableJson(join(paths.control, assessmentLocator), assessment);
@@ -284,7 +284,7 @@ export async function prepareScientificReview(input) {
284
284
  };
285
285
  await assertBoundPolicyObject(input.root, project, packetPolicy);
286
286
  const designEvaluation = evaluateScientificDesign(design);
287
- const issues = evaluateAssessment(input.role, assessment, design, project.evidenceRequirements, project.publicationPolicy);
287
+ const issues = evaluateAssessment(input.role, assessment, design, project.evidenceRequirements, project.publicationPolicy, await assessmentEvidenceContext(input.root, project, input.role, stageInputs));
288
288
  const futureGateObligations = scientificFutureGateObligations(design, input.role);
289
289
  const packetCore = {
290
290
  schemaVersion: 1,
@@ -440,7 +440,7 @@ export async function assertScientificGateForStage(root, project, stage) {
440
440
  const packet = await loadBoundPacket(root, project, role, gate.packetSha256);
441
441
  const review = await loadBoundReview(root, project, role, gate.reviewSha256);
442
442
  const assessment = await loadBoundAssessment(root, project, role, packet);
443
- const currentIssues = evaluateAssessment(role, assessment, design, project.evidenceRequirements, project.publicationPolicy).map((issue) => issue.code);
443
+ const currentIssues = evaluateAssessment(role, assessment, design, project.evidenceRequirements, project.publicationPolicy, await assessmentEvidenceContext(root, project, role, packet.stageInputs)).map((issue) => issue.code);
444
444
  if (packet.assessment.sha256 !== gate.assessmentSha256 ||
445
445
  packet.reviewer.sessionSha256 !== gate.reviewerSessionSha256 ||
446
446
  review.packetSha256 !== packet.packetSha256 ||
@@ -473,7 +473,7 @@ export async function inspectScientificReviewStatus(root, projectId) {
473
473
  gates: structuredClone(project.scientificDesign.gates),
474
474
  };
475
475
  }
476
- function evaluateAssessment(role, assessment, design, requirements, policy) {
476
+ function evaluateAssessment(role, assessment, design, requirements, policy, evidenceContext = null) {
477
477
  const issues = new Map();
478
478
  const add = (code, message, objectIds = []) => {
479
479
  if (!issues.has(code))
@@ -529,38 +529,102 @@ function evaluateAssessment(role, assessment, design, requirements, policy) {
529
529
  canary.artifactSha256s.length < 1) {
530
530
  add("CANARY_NOT_REAL", "Construct canary must use real records without inspecting outcomes.");
531
531
  }
532
+ const declaredCanarySha256s = new Set(canary.artifactSha256s);
533
+ if (!evidenceContext ||
534
+ declaredCanarySha256s.size !== canary.artifactSha256s.length ||
535
+ declaredCanarySha256s.size !== evidenceContext.canaryArtifactSha256s.size ||
536
+ [...declaredCanarySha256s].some((sha256) => !evidenceContext.canaryArtifactSha256s.has(sha256))) {
537
+ add("CANARY_ARTIFACT_UNBOUND", "Every construct-canary digest must bind one exact promoted artifact and no undeclared artifact is permitted.");
538
+ }
532
539
  const centralEdges = design.edges
533
540
  .filter((edge) => edge.role === "central")
534
541
  .map((edge) => edge.id);
542
+ const knownEdgeIds = new Set(design.edges.map((edge) => edge.id));
543
+ const unknownCanaryEdgeIds = [...canary.constructedEdgeIds, ...canary.failedEdgeIds].filter((edgeId) => !knownEdgeIds.has(edgeId));
544
+ if (unknownCanaryEdgeIds.length) {
545
+ add("CANARY_EDGE_ID_UNKNOWN", "Construct-canary edge IDs must exist in the frozen scientific design.", [...new Set(unknownCanaryEdgeIds)]);
546
+ }
535
547
  const unconstructed = centralEdges.filter((edgeId) => !canary.constructedEdgeIds.includes(edgeId) || canary.failedEdgeIds.includes(edgeId));
536
548
  if (unconstructed.length) {
537
549
  add("CENTRAL_EDGE_UNCONSTRUCTED", "Every central claim edge must survive a real-record construct canary.", unconstructed);
538
550
  }
551
+ const expectedRoleIds = new Set(design.evidenceRoles.filter((item) => item.required).map((item) => item.id));
552
+ const providedRoleIds = value.evidenceRoleCoverage.map((coverage) => coverage.roleId);
553
+ if (new Set(providedRoleIds).size !== providedRoleIds.length ||
554
+ providedRoleIds.some((roleId) => !expectedRoleIds.has(roleId))) {
555
+ add("EVIDENCE_ROLE_COVERAGE_INVALID", "Evidence-role coverage must contain only distinct required roles from the frozen design.");
556
+ }
557
+ const allIndependentSourceIds = new Set();
558
+ const allFullTextSourceIds = new Set();
559
+ const allDatedSourceIds = new Set();
539
560
  for (const required of design.evidenceRoles.filter((item) => item.required)) {
540
561
  const coverage = value.evidenceRoleCoverage.find((item) => item.roleId === required.id);
562
+ const independentIds = new Set(coverage?.independentSourceIds ?? []);
563
+ const fullTextIds = new Set(coverage?.fullTextSourceIds ?? []);
564
+ const datedIds = new Set(coverage?.datedSourceIds ?? []);
565
+ const peerReviewedIds = new Set(coverage?.peerReviewedSourceIds ?? []);
566
+ const referencedIds = new Set([
567
+ ...independentIds,
568
+ ...fullTextIds,
569
+ ...datedIds,
570
+ ...peerReviewedIds,
571
+ ]);
572
+ const unknownIds = [...referencedIds].filter((sourceId) => !evidenceContext?.sources.has(sourceId));
573
+ if (unknownIds.length) {
574
+ add("EVIDENCE_SOURCE_ID_UNKNOWN", "Evidence-role coverage may reference only source IDs from the frozen post-acquisition snapshot.", unknownIds);
575
+ }
576
+ const knownIndependent = [...independentIds].filter((sourceId) => evidenceContext?.sources.has(sourceId));
577
+ const validFullText = [...fullTextIds].filter((sourceId) => evidenceContext?.sources.get(sourceId)?.fullTextAvailable === true);
578
+ const validDated = [...datedIds].filter((sourceId) => Boolean(evidenceContext?.sources.get(sourceId)?.publicationDate));
579
+ const validPeerReviewed = [...peerReviewedIds].filter((sourceId) => {
580
+ const source = evidenceContext?.sources.get(sourceId);
581
+ return source?.fullTextAvailable === true;
582
+ });
583
+ for (const sourceId of knownIndependent)
584
+ allIndependentSourceIds.add(sourceId);
585
+ for (const sourceId of validFullText)
586
+ allFullTextSourceIds.add(sourceId);
587
+ for (const sourceId of validDated)
588
+ allDatedSourceIds.add(sourceId);
589
+ if ([...fullTextIds].some((sourceId) => !validFullText.includes(sourceId))) {
590
+ add("EVIDENCE_SOURCE_FULLTEXT_INVALID", "A claimed full-text source is not producer-readable in the frozen evidence snapshot.", [required.id]);
591
+ }
592
+ if ([...datedIds].some((sourceId) => !validDated.includes(sourceId))) {
593
+ add("EVIDENCE_SOURCE_DATE_INVALID", "A claimed dated source has no publication date in the frozen evidence snapshot.", [required.id]);
594
+ }
595
+ if ([...peerReviewedIds].some((sourceId) => !validPeerReviewed.includes(sourceId))) {
596
+ add("EVIDENCE_SOURCE_PEER_REVIEW_INVALID", "A claimed peer-reviewed source must be full text in the frozen evidence snapshot; the independent reviewer verifies its publication status.", [required.id]);
597
+ }
541
598
  if (!coverage ||
542
- new Set(coverage.fullTextSourceIds).size < required.minimumFullText ||
543
- new Set(coverage.independentSourceIds).size < required.minimumIndependentSources) {
599
+ new Set(validFullText).size < required.minimumFullText ||
600
+ new Set(knownIndependent).size < required.minimumIndependentSources) {
544
601
  add("EVIDENCE_ROLE_FULLTEXT_INSUFFICIENT", "A required evidence role lacks its frozen full-text or independence minimum.", [required.id]);
545
602
  }
546
- if (!coverage || new Set(coverage.datedSourceIds).size < required.minimumDatedSources) {
603
+ if (!coverage || new Set(validDated).size < required.minimumDatedSources) {
547
604
  add("EVIDENCE_ROLE_DATED_INSUFFICIENT", "A required evidence role lacks its frozen dated-source minimum.", [required.id]);
548
605
  }
549
606
  if (required.peerReviewedRequired &&
550
- (!coverage || new Set(coverage.peerReviewedSourceIds).size < required.minimumFullText)) {
607
+ (!coverage || new Set(validPeerReviewed).size < required.minimumFullText)) {
551
608
  add("EVIDENCE_ROLE_PEER_REVIEWED_INSUFFICIENT", "A peer-reviewed evidence role lacks its required frozen peer-reviewed full texts.", [required.id]);
552
609
  }
553
- const dimensions = new Set(coverage?.dimensionIds ?? []);
610
+ const dimensions = new Set(knownIndependent.flatMap((sourceId) => evidenceContext?.sources.get(sourceId)?.coverageDimensions ?? []));
611
+ if (coverage?.dimensionIds.some((dimension) => !dimensions.has(dimension))) {
612
+ add("EVIDENCE_ROLE_DIMENSION_CLAIM_INVALID", "Declared role dimensions must be supported by the role's frozen snapshot sources.", [required.id]);
613
+ }
554
614
  if (required.coverageDimensionIds.some((dimension) => !dimensions.has(dimension))) {
555
615
  add("EVIDENCE_ROLE_DIMENSION_UNCOVERED", "A required evidence role did not demonstrate every declared research dimension.", [required.id]);
556
616
  }
557
- const sourceTypes = new Set(coverage?.sourceTypes ?? []);
617
+ const sourceTypes = new Set(knownIndependent.flatMap((sourceId) => {
618
+ const sourceType = evidenceContext?.sources.get(sourceId)?.sourceType;
619
+ return sourceType ? [sourceType] : [];
620
+ }));
621
+ if (coverage?.sourceTypes.some((sourceType) => !sourceTypes.has(sourceType))) {
622
+ add("EVIDENCE_ROLE_SOURCE_TYPE_CLAIM_INVALID", "Declared role source types must be present among the role's frozen snapshot sources.", [required.id]);
623
+ }
558
624
  if (required.sourceTypeRequirements.some((sourceType) => !sourceTypes.has(sourceType))) {
559
625
  add("EVIDENCE_ROLE_SOURCE_TYPE_UNCOVERED", "A required evidence role did not demonstrate every declared source type.", [required.id]);
560
626
  }
561
627
  if (coverage) {
562
- const independentIds = new Set(coverage.independentSourceIds);
563
- const fullTextIds = new Set(coverage.fullTextSourceIds);
564
628
  if (coverage.fullTextSourceIds.some((sourceId) => !independentIds.has(sourceId)) ||
565
629
  coverage.datedSourceIds.some((sourceId) => !independentIds.has(sourceId)) ||
566
630
  coverage.peerReviewedSourceIds.some((sourceId) => !independentIds.has(sourceId) || !fullTextIds.has(sourceId))) {
@@ -568,9 +632,6 @@ function evaluateAssessment(role, assessment, design, requirements, policy) {
568
632
  }
569
633
  }
570
634
  }
571
- const allIndependentSourceIds = new Set(value.evidenceRoleCoverage.flatMap((coverage) => coverage.independentSourceIds));
572
- const allFullTextSourceIds = new Set(value.evidenceRoleCoverage.flatMap((coverage) => coverage.fullTextSourceIds));
573
- const allDatedSourceIds = new Set(value.evidenceRoleCoverage.flatMap((coverage) => coverage.datedSourceIds));
574
635
  if (allIndependentSourceIds.size < requirements.minSources) {
575
636
  add("EVIDENCE_UNIQUE_SOURCE_COVERAGE_INSUFFICIENT", "Unique evidence sources across roles do not meet the project minimum.");
576
637
  }
@@ -581,7 +642,7 @@ function evaluateAssessment(role, assessment, design, requirements, policy) {
581
642
  add("EVIDENCE_UNIQUE_DATED_COVERAGE_INSUFFICIENT", "Unique dated sources across roles do not meet the project minimum.");
582
643
  }
583
644
  if (!value.closestWorkDispositionComplete) {
584
- add("CLOSEST_WORK_DISPOSITION_INCOMPLETE", "Closest prior work must be obtained, compared, and dispositioned before acquisition.");
645
+ add("CLOSEST_WORK_DISPOSITION_INCOMPLETE", "Closest prior work must be obtained, compared, and dispositioned before analysis.");
585
646
  }
586
647
  if (!value.centralEvidenceFitsContext) {
587
648
  add("CENTRAL_CONTEXT_OVERFLOW", "Central evidence does not fit the planned context and extraction route.");
@@ -651,7 +712,7 @@ function evaluateAssessment(role, assessment, design, requirements, policy) {
651
712
  }
652
713
  return [...issues.values()];
653
714
  }
654
- async function stageInputRecords(root, project, role, design) {
715
+ async function stageInputRecords(root, project, role, design, assessment, canaryArtifactPaths) {
655
716
  const paths = workspacePaths(root);
656
717
  const projectRoot = join(paths.projects, project.id);
657
718
  if (role === "research-design") {
@@ -728,7 +789,7 @@ async function stageInputRecords(root, project, role, design) {
728
789
  }
729
790
  return records;
730
791
  }
731
- const requiredPackage = role === "evidence-construct" ? "discover" : "acquire";
792
+ const requiredPackage = "acquire";
732
793
  const packageState = project.packages.find((item) => item.id === requiredPackage);
733
794
  if (packageState?.status !== "complete") {
734
795
  throw new CliError(`Scientific ${role} review requires completed ${requiredPackage}.`, {
@@ -737,10 +798,8 @@ async function stageInputRecords(root, project, role, design) {
737
798
  details: { role, requiredPackage, status: packageState?.status ?? null },
738
799
  });
739
800
  }
740
- const relativeOutputs = role === "evidence-construct"
741
- ? ["outputs/evidence.json"]
742
- : ["outputs/acquisition.json", "outputs/evidence-snapshot.json"];
743
- return Promise.all(relativeOutputs.map(async (relativePath) => {
801
+ const relativeOutputs = ["outputs/acquisition.json", "outputs/evidence-snapshot.json"];
802
+ const stageOutputs = await Promise.all(relativeOutputs.map(async (relativePath) => {
744
803
  const sourceLocator = `projects/${project.id}/${relativePath}`;
745
804
  return {
746
805
  ...(await fileRecord(join(projectRoot, relativePath), sourceLocator)),
@@ -750,6 +809,130 @@ async function stageInputRecords(root, project, role, design) {
750
809
  hashBasis: "raw-file-bytes",
751
810
  };
752
811
  }));
812
+ if (role !== "evidence-construct") {
813
+ if (canaryArtifactPaths.length)
814
+ throw canaryArtifactError("Only evidence-construct accepts canary artifacts.");
815
+ return stageOutputs;
816
+ }
817
+ const expectedSha256s = new Set(assessment.constructCanary.artifactSha256s);
818
+ const seenSha256s = new Set();
819
+ const canaryRecords = [];
820
+ const controlRoot = resolve(paths.control);
821
+ for (const selected of canaryArtifactPaths) {
822
+ if (!isAbsolute(selected) || resolve(selected) !== selected) {
823
+ throw canaryArtifactError("Canary artifact paths must be absolute and canonical.");
824
+ }
825
+ const sourcePath = resolve(selected);
826
+ const relation = relative(controlRoot, sourcePath);
827
+ if (sourcePath === controlRoot || (!relation.startsWith(`..${sep}`) && relation !== "..")) {
828
+ throw canaryArtifactError("Canary artifacts must originate outside .tiangong-research.");
829
+ }
830
+ const info = await lstat(sourcePath).catch(() => undefined);
831
+ if (!info?.isFile() ||
832
+ info.isSymbolicLink() ||
833
+ info.size < 2 ||
834
+ info.size > MAX_SCIENTIFIC_REVIEW_BYTES) {
835
+ throw canaryArtifactError("Canary artifacts must be bounded regular non-symlink files.");
836
+ }
837
+ const sourceBytes = await readFile(sourcePath, "utf8");
838
+ let value;
839
+ try {
840
+ value = JSON.parse(sourceBytes);
841
+ }
842
+ catch {
843
+ throw canaryArtifactError("Canary artifacts must contain reviewable JSON.");
844
+ }
845
+ if (containsSensitiveCanaryField(value)) {
846
+ throw canaryArtifactError("Canary artifacts cannot contain credential-like fields.");
847
+ }
848
+ const sha256 = await sha256File(sourcePath);
849
+ if (!expectedSha256s.has(sha256)) {
850
+ throw canaryArtifactError("A supplied canary artifact is not declared by the assessment.");
851
+ }
852
+ if (seenSha256s.has(sha256)) {
853
+ throw canaryArtifactError("Canary artifacts must have distinct content hashes.");
854
+ }
855
+ seenSha256s.add(sha256);
856
+ const locator = `projects/${project.id}/scientific/canary-artifacts/evidence-construct/${sha256}.json`;
857
+ const promoted = resolveContained(paths.control, locator);
858
+ if (await pathExists(promoted)) {
859
+ const promotedInfo = await lstat(promoted).catch(() => undefined);
860
+ if (!promotedInfo?.isFile() ||
861
+ promotedInfo.isSymbolicLink() ||
862
+ promotedInfo.size !== info.size ||
863
+ (await sha256File(promoted)) !== sha256) {
864
+ throw scientificGateError("A promoted construct-canary artifact failed its immutable binding.", role);
865
+ }
866
+ }
867
+ else {
868
+ await writeTextAtomic(promoted, sourceBytes);
869
+ }
870
+ canaryRecords.push({
871
+ path: locator,
872
+ sha256,
873
+ bytes: info.size,
874
+ purpose: "construct-canary",
875
+ ownerId: "evidence-construct",
876
+ sourceLocator: `native-canary:${sha256}`,
877
+ hashBasis: "raw-file-bytes",
878
+ });
879
+ }
880
+ return [...stageOutputs, ...canaryRecords];
881
+ }
882
+ async function assessmentEvidenceContext(root, project, role, stageInputs) {
883
+ if (role !== "evidence-construct")
884
+ return null;
885
+ const snapshotRecord = stageInputs.find((record) => record.purpose === "stage-output" &&
886
+ record.sourceLocator === `projects/${project.id}/outputs/evidence-snapshot.json`);
887
+ if (!snapshotRecord) {
888
+ throw scientificGateError("Evidence-construct review requires the frozen post-acquisition evidence snapshot.", role);
889
+ }
890
+ const snapshot = await readExactJson(resolveContained(workspacePaths(root).control, snapshotRecord.path), "Evidence snapshot", "RESEARCH_SCIENTIFIC_GATE_INVALID");
891
+ if (!isObject(snapshot) ||
892
+ snapshot.schemaVersion !== 1 ||
893
+ snapshot.kind !== "tiangong-evidence-snapshot" ||
894
+ snapshot.projectId !== project.id ||
895
+ !Array.isArray(snapshot.sources)) {
896
+ throw scientificGateError("Evidence snapshot is malformed for scientific review.", role);
897
+ }
898
+ const snapshotSha256 = typeof snapshot.snapshotSha256 === "string" ? snapshot.snapshotSha256 : "";
899
+ const snapshotId = typeof snapshot.snapshotId === "string" ? snapshot.snapshotId : "";
900
+ const { snapshotSha256: _recordedSnapshotSha256, ...snapshotCore } = snapshot;
901
+ const immutableSnapshot = resolveContained(workspacePaths(root).projects, `${project.id}/evidence/snapshots/${snapshotSha256}.json`);
902
+ const snapshotBytes = normalizedJson(snapshot);
903
+ if (!new RegExp(SHA256_PATTERN).test(snapshotSha256) ||
904
+ !snapshotId ||
905
+ sha256Text(canonicalJson(snapshotCore)) !== snapshotSha256 ||
906
+ project.evidenceState.currentSnapshotId !== snapshotId ||
907
+ project.evidenceState.currentSnapshotSha256 !== snapshotSha256 ||
908
+ !(await pathExists(immutableSnapshot)) ||
909
+ (await readFile(immutableSnapshot, "utf8")) !== snapshotBytes) {
910
+ throw scientificGateError("Evidence-construct review requires the current immutable evidence snapshot binding.", role);
911
+ }
912
+ const sources = new Map();
913
+ for (const item of snapshot.sources) {
914
+ if (!isObject(item) || typeof item.id !== "string" || !item.id) {
915
+ throw scientificGateError("Evidence snapshot contains a malformed source.", role);
916
+ }
917
+ if (sources.has(item.id)) {
918
+ throw scientificGateError("Evidence snapshot contains duplicate source IDs.", role);
919
+ }
920
+ sources.set(item.id, {
921
+ id: item.id,
922
+ sourceType: typeof item.sourceType === "string" ? item.sourceType : null,
923
+ publicationDate: typeof item.publicationDate === "string" ? item.publicationDate : null,
924
+ fullTextAvailable: item.fullTextAvailable === true,
925
+ coverageDimensions: Array.isArray(item.coverageDimensions)
926
+ ? item.coverageDimensions.filter((value) => typeof value === "string")
927
+ : [],
928
+ });
929
+ }
930
+ return {
931
+ sources,
932
+ canaryArtifactSha256s: new Set(stageInputs
933
+ .filter((record) => record.purpose === "construct-canary")
934
+ .map((record) => record.sha256)),
935
+ };
753
936
  }
754
937
  async function loadBoundScientificDesign(root, project) {
755
938
  const binding = project.scientificDesign;
@@ -962,6 +1145,7 @@ function isScientificReviewPacket(value, projectId, role, finalPublicationReview
962
1145
  Number.isSafeInteger(record.bytes) &&
963
1146
  record.bytes >= 0 &&
964
1147
  [
1148
+ "construct-canary",
965
1149
  "inherited-gap",
966
1150
  "model-implementation",
967
1151
  "model-environment-lock",
@@ -1027,9 +1211,30 @@ function requiredGateRoles(stage) {
1027
1211
  if (stage === "discover")
1028
1212
  return ordered.slice(0, 1);
1029
1213
  if (stage === "acquire")
1030
- return ordered.slice(0, 2);
1214
+ return ordered.slice(0, 1);
1031
1215
  return ordered;
1032
1216
  }
1217
+ function containsSensitiveCanaryField(value) {
1218
+ if (Array.isArray(value))
1219
+ return value.some(containsSensitiveCanaryField);
1220
+ if (typeof value === "string") {
1221
+ return (/\b(?:authorization|cookie|set-cookie)\b\s*[:=]/iu.test(value) ||
1222
+ /\bbearer\s+[A-Za-z0-9._~+/=-]+/iu.test(value) ||
1223
+ /[?&](?:access[_-]?token|api[_-]?key|apikey|auth|authorization|cookie|credential|key|password|secret|session(?:[_-]?id)?|sig|signature|token)=/iu.test(value));
1224
+ }
1225
+ if (!isObject(value))
1226
+ return false;
1227
+ return Object.entries(value).some(([key, item]) => (/^(?:access[_-]?token|api[_-]?key|apikey|auth|authorization|cookie|credential|key|password|secret|session(?:[_-]?id)?|sig|signature|token)$/iu.test(key) &&
1228
+ item !== null &&
1229
+ item !== "") ||
1230
+ containsSensitiveCanaryField(item));
1231
+ }
1232
+ function canaryArtifactError(message) {
1233
+ return new CliError(message, {
1234
+ code: "RESEARCH_SCIENTIFIC_CANARY_ARTIFACT_INVALID",
1235
+ exitCode: 3,
1236
+ });
1237
+ }
1033
1238
  function reviewInstructions(role) {
1034
1239
  return [
1035
1240
  `Review only the exact immutable ${role} packet and its referenced objects.`,