@tiangong-ai/cli 0.0.32 → 0.0.33

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/AGENTS.md +2 -2
  2. package/README.md +51 -17
  3. package/dist/research/orchestration.js +388 -31
  4. package/dist/research/orchestration.js.map +1 -1
  5. package/dist/research/workspace/acquisition.d.ts +71 -0
  6. package/dist/research/workspace/acquisition.js +593 -0
  7. package/dist/research/workspace/acquisition.js.map +1 -0
  8. package/dist/research/workspace/artifacts.d.ts +43 -0
  9. package/dist/research/workspace/artifacts.js +544 -0
  10. package/dist/research/workspace/artifacts.js.map +1 -0
  11. package/dist/research/workspace/broker.js +265 -56
  12. package/dist/research/workspace/broker.js.map +1 -1
  13. package/dist/research/workspace/discovery-planning.d.ts +25 -0
  14. package/dist/research/workspace/discovery-planning.js +106 -0
  15. package/dist/research/workspace/discovery-planning.js.map +1 -0
  16. package/dist/research/workspace/discovery-status.d.ts +46 -0
  17. package/dist/research/workspace/discovery-status.js +183 -0
  18. package/dist/research/workspace/discovery-status.js.map +1 -0
  19. package/dist/research/workspace/discovery.d.ts +25 -0
  20. package/dist/research/workspace/discovery.js +268 -0
  21. package/dist/research/workspace/discovery.js.map +1 -0
  22. package/dist/research/workspace/downloads.d.ts +76 -0
  23. package/dist/research/workspace/downloads.js +274 -0
  24. package/dist/research/workspace/downloads.js.map +1 -0
  25. package/dist/research/workspace/evidence-ledger.d.ts +52 -0
  26. package/dist/research/workspace/evidence-ledger.js +487 -0
  27. package/dist/research/workspace/evidence-ledger.js.map +1 -0
  28. package/dist/research/workspace/evidence.d.ts +1 -0
  29. package/dist/research/workspace/evidence.js +2 -0
  30. package/dist/research/workspace/evidence.js.map +1 -1
  31. package/dist/research/workspace/input-plan.js +4 -0
  32. package/dist/research/workspace/input-plan.js.map +1 -1
  33. package/dist/research/workspace/native-activity.d.ts +65 -0
  34. package/dist/research/workspace/native-activity.js +153 -0
  35. package/dist/research/workspace/native-activity.js.map +1 -0
  36. package/dist/research/workspace/preflight.d.ts +5 -0
  37. package/dist/research/workspace/preflight.js +37 -17
  38. package/dist/research/workspace/preflight.js.map +1 -1
  39. package/dist/research/workspace/projects.d.ts +3 -1
  40. package/dist/research/workspace/projects.js +346 -5
  41. package/dist/research/workspace/projects.js.map +1 -1
  42. package/dist/research/workspace/runtime.d.ts +106 -4
  43. package/dist/research/workspace/runtime.js +947 -80
  44. package/dist/research/workspace/runtime.js.map +1 -1
  45. package/dist/research/workspace/sanitization.js +28 -6
  46. package/dist/research/workspace/sanitization.js.map +1 -1
  47. package/dist/research/workspace/schemas.d.ts +3 -0
  48. package/dist/research/workspace/schemas.js +206 -33
  49. package/dist/research/workspace/schemas.js.map +1 -1
  50. package/dist/research/workspace/setup-wizard.js +10 -4
  51. package/dist/research/workspace/setup-wizard.js.map +1 -1
  52. package/dist/research/workspace/setup.d.ts +4 -1
  53. package/dist/research/workspace/setup.js +61 -11
  54. package/dist/research/workspace/setup.js.map +1 -1
  55. package/dist/research/workspace/types.d.ts +31 -3
  56. package/dist/research/workspace/workspace.js +45 -6
  57. package/dist/research/workspace/workspace.js.map +1 -1
  58. package/package.json +1 -1
@@ -0,0 +1,268 @@
1
+ import { basename, join } from "node:path";
2
+ import { appendEvidenceLedgerEvent, evidenceLedgerPath, listEvidenceCandidates, } from "./evidence-ledger.js";
3
+ import { loadProjectEvidenceReceipts } from "./evidence.js";
4
+ import { readJournal } from "./journal.js";
5
+ import { parseDiscoveryAssessmentBatch, StructuredOutputError } from "./schemas.js";
6
+ import { canonicalJson, isObject, readJsonFile, sha256Text, workspacePaths } from "./storage.js";
7
+ export async function materializeDiscoveryEvidence(root, project, value) {
8
+ const closeout = parseCloseoutValue(value);
9
+ const assessments = await latestDiscoveryAssessments(root, project.id);
10
+ const admissions = assessments.filter(isAdmission);
11
+ const rejections = assessments.filter(isRejection);
12
+ const candidates = new Map((await listEvidenceCandidates(root, project.id)).map((candidate) => [candidate.id, candidate]));
13
+ const receipts = new Map((await loadProjectEvidenceReceipts(root, project.id)).map((receipt) => [
14
+ receipt.attemptId,
15
+ receipt,
16
+ ]));
17
+ const inputs = new Map(project.inputs.map((input) => [input.id, input]));
18
+ const admittedCandidateIds = new Set(admissions.map((item) => item.candidateId));
19
+ const rejectedCandidateIds = new Set(rejections.map((item) => item.candidateId));
20
+ for (const candidateId of [...admittedCandidateIds, ...rejectedCandidateIds]) {
21
+ if (!candidates.has(candidateId)) {
22
+ throw discoveryError(`Discovery decision refers to unknown candidate ${candidateId}.`);
23
+ }
24
+ }
25
+ for (const candidateId of admittedCandidateIds) {
26
+ if (rejectedCandidateIds.has(candidateId)) {
27
+ throw discoveryError(`Candidate ${candidateId} cannot be both admitted and rejected.`);
28
+ }
29
+ }
30
+ const requiredDimensions = new Set(project.evidenceRequirements.dimensions);
31
+ const judgmentIds = new Set(closeout.dimensionJudgments.map((item) => item.id));
32
+ if (judgmentIds.size !== requiredDimensions.size ||
33
+ [...requiredDimensions].some((dimension) => !judgmentIds.has(dimension))) {
34
+ throw discoveryError("Dimension judgments must cover every reviewed evidence dimension exactly once.");
35
+ }
36
+ const sources = admissions.map((admission) => {
37
+ if (admission.coverageDimensions.some((dimension) => !requiredDimensions.has(dimension))) {
38
+ throw discoveryError(`Admission ${admission.sourceId} declares an unreviewed evidence dimension.`);
39
+ }
40
+ const candidate = candidates.get(admission.candidateId);
41
+ const admissibleOrigin = candidate.occurrences.find((origin) => origin.kind === "input" || origin.kind === "broker");
42
+ if (!admissibleOrigin) {
43
+ throw new StructuredOutputError("Native Web or Browser discovery must first be formalized through an immutable broker receipt for the same canonical URL or DOI.", {
44
+ validation: ["native candidates require immutable broker provenance"],
45
+ candidateId: candidate.id,
46
+ });
47
+ }
48
+ const provenance = admissibleOrigin.kind === "broker"
49
+ ? brokerProvenance(admissibleOrigin.receiptId, admissibleOrigin.locator, receipts)
50
+ : inputProvenance(admissibleOrigin.inputId, admissibleOrigin.locator, inputs);
51
+ const input = provenance.kind === "input" ? inputs.get(provenance.id) : undefined;
52
+ return {
53
+ id: admission.sourceId,
54
+ title: candidate.title,
55
+ locator: provenance.locator,
56
+ relevance: admission.relevance,
57
+ provenance: { kind: provenance.kind, id: provenance.id },
58
+ sourceType: admission.sourceType,
59
+ retrievedAt: admissibleOrigin.retrievedAt,
60
+ fullTextAvailable: provenance.kind === "input" ? input?.fullText !== false : false,
61
+ url: candidate.url,
62
+ doi: candidate.doi,
63
+ publicationDate: candidate.publicationDate ?? input?.publicationDate ?? null,
64
+ excerpt: candidate.excerpt,
65
+ jsonPointer: admissibleOrigin.jsonPointer,
66
+ quality: admission.quality,
67
+ applicability: admission.applicability,
68
+ coverageDimensions: admission.coverageDimensions,
69
+ };
70
+ });
71
+ const sourceLimitations = admissions.flatMap((admission) => admission.limitations.map((limitation) => `${admission.sourceId}: ${limitation}`));
72
+ return {
73
+ schemaVersion: 1,
74
+ sources,
75
+ limitations: [...new Set([...closeout.limitations, ...sourceLimitations])],
76
+ coverage: {
77
+ dimensions: closeout.dimensionJudgments.map((item) => ({
78
+ id: item.id,
79
+ status: item.status,
80
+ sourceIds: [],
81
+ })),
82
+ sourceTypes: [],
83
+ fullTextSources: 0,
84
+ datedSources: 0,
85
+ publicationDateRange: { earliest: null, latest: null },
86
+ decision: "pass",
87
+ gaps: closeout.gaps,
88
+ },
89
+ };
90
+ }
91
+ export async function commitDiscoveryDecisions(root, projectId, value) {
92
+ parseCloseoutValue(value);
93
+ const assessments = await latestDiscoveryAssessments(root, projectId);
94
+ const events = await readJournal(evidenceLedgerPath(root, projectId));
95
+ const committed = new Set(events
96
+ .filter((event) => event.type === "candidate.admitted" || event.type === "candidate.rejected")
97
+ .map((event) => `${event.type}:${String(event.payload.judgmentSha256)}`));
98
+ for (const admission of assessments.filter(isAdmission)) {
99
+ const judgmentSha256 = sha256Text(canonicalJson(admission));
100
+ const key = `candidate.admitted:${judgmentSha256}`;
101
+ if (committed.has(key))
102
+ continue;
103
+ await appendEvidenceLedgerEvent(root, projectId, "candidate.admitted", {
104
+ candidateId: admission.candidateId,
105
+ sourceId: admission.sourceId,
106
+ sourceType: admission.sourceType,
107
+ judgmentSha256,
108
+ coverageDimensions: admission.coverageDimensions,
109
+ });
110
+ }
111
+ for (const rejection of assessments.filter(isRejection)) {
112
+ const judgmentSha256 = sha256Text(canonicalJson(rejection));
113
+ const key = `candidate.rejected:${judgmentSha256}`;
114
+ if (committed.has(key))
115
+ continue;
116
+ await appendEvidenceLedgerEvent(root, projectId, "candidate.rejected", {
117
+ candidateId: rejection.candidateId,
118
+ reasonCode: rejection.reasonCode,
119
+ rationale: rejection.rationale,
120
+ judgmentSha256,
121
+ });
122
+ }
123
+ }
124
+ export async function recordDiscoveryAssessmentBatch(input) {
125
+ const parsed = parseDiscoveryAssessmentBatch(input.value);
126
+ const assessments = parsed.assessments;
127
+ const project = await readJsonFile(join(workspacePaths(input.root).projects, input.projectId, "project.json"), `Research project ${input.projectId}`);
128
+ const packages = Array.isArray(project.packages) ? project.packages : [];
129
+ const discover = packages.find((workPackage) => isObject(workPackage) && workPackage.stage === "discover");
130
+ if (!isObject(discover) || discover.status !== "running") {
131
+ throw discoveryError("Discovery assessments may be recorded only during the active discover stage.");
132
+ }
133
+ const requiredDimensions = new Set(isObject(project.evidenceRequirements) && Array.isArray(project.evidenceRequirements.dimensions)
134
+ ? project.evidenceRequirements.dimensions.filter((dimension) => typeof dimension === "string")
135
+ : []);
136
+ const candidates = new Map((await listEvidenceCandidates(input.root, input.projectId)).map((candidate) => [
137
+ candidate.id,
138
+ candidate,
139
+ ]));
140
+ for (const assessment of assessments) {
141
+ const candidate = candidates.get(assessment.candidateId);
142
+ if (!candidate) {
143
+ throw discoveryError(`Discovery assessment refers to unknown candidate ${assessment.candidateId}.`);
144
+ }
145
+ if (assessment.decision === "admit") {
146
+ if (assessment.coverageDimensions.some((dimension) => !requiredDimensions.has(dimension))) {
147
+ throw discoveryError(`Admission ${assessment.sourceId} declares an unreviewed evidence dimension.`);
148
+ }
149
+ if (!candidate.occurrences.some((origin) => origin.kind === "input" || origin.kind === "broker")) {
150
+ throw discoveryError(`Native candidate ${candidate.id} must be formalized by a broker receipt before admission.`);
151
+ }
152
+ }
153
+ }
154
+ const existing = await latestDiscoveryAssessments(input.root, input.projectId);
155
+ const replacementIds = new Set(assessments.map((assessment) => assessment.candidateId));
156
+ const sourceOwners = new Map(existing
157
+ .filter(isAdmission)
158
+ .filter((assessment) => !replacementIds.has(assessment.candidateId))
159
+ .map((assessment) => [assessment.sourceId, assessment.candidateId]));
160
+ for (const assessment of assessments.filter(isAdmission)) {
161
+ const owner = sourceOwners.get(assessment.sourceId);
162
+ if (owner && owner !== assessment.candidateId) {
163
+ throw discoveryError(`Source ID ${assessment.sourceId} is already assigned to ${owner}.`);
164
+ }
165
+ sourceOwners.set(assessment.sourceId, assessment.candidateId);
166
+ }
167
+ const priorHashes = new Set((await readJournal(evidenceLedgerPath(input.root, input.projectId)))
168
+ .filter((event) => event.type === "candidate.assessed")
169
+ .map((event) => String(event.payload.assessmentSha256)));
170
+ let recorded = 0;
171
+ let unchanged = 0;
172
+ for (const assessment of assessments) {
173
+ const assessmentSha256 = sha256Text(canonicalJson(assessment));
174
+ if (priorHashes.has(assessmentSha256)) {
175
+ unchanged += 1;
176
+ continue;
177
+ }
178
+ await appendEvidenceLedgerEvent(input.root, input.projectId, "candidate.assessed", {
179
+ candidateId: assessment.candidateId,
180
+ decision: assessment.decision,
181
+ assessment,
182
+ assessmentSha256,
183
+ });
184
+ priorHashes.add(assessmentSha256);
185
+ recorded += 1;
186
+ }
187
+ const latest = await latestDiscoveryAssessments(input.root, input.projectId);
188
+ return {
189
+ recorded,
190
+ unchanged,
191
+ assessedCandidates: latest.length,
192
+ admittedCandidates: latest.filter(isAdmission).length,
193
+ rejectedCandidates: latest.filter(isRejection).length,
194
+ };
195
+ }
196
+ function brokerProvenance(receiptId, locator, receipts) {
197
+ const receipt = receiptId ? receipts.get(receiptId) : undefined;
198
+ if (!receipt || !locator || receipt.locator !== locator) {
199
+ throw discoveryError("Broker candidate provenance does not match an immutable receipt.");
200
+ }
201
+ return { kind: "broker", id: receipt.attemptId, locator: receipt.locator };
202
+ }
203
+ function inputProvenance(inputId, locator, inputs) {
204
+ const input = inputId ? inputs.get(inputId) : undefined;
205
+ const expectedLocator = input
206
+ ? join("inputs", input.id, basename(input.path)).replaceAll("\\", "/")
207
+ : null;
208
+ if (!input || !locator || locator !== expectedLocator) {
209
+ throw discoveryError("Input candidate provenance does not match a registered immutable input.");
210
+ }
211
+ return { kind: "input", id: input.id, locator: expectedLocator };
212
+ }
213
+ async function latestDiscoveryAssessments(root, projectId) {
214
+ const events = await readJournal(evidenceLedgerPath(root, projectId));
215
+ const latest = new Map();
216
+ for (const event of events) {
217
+ if (event.type !== "candidate.assessed" || !isObject(event.payload.assessment))
218
+ continue;
219
+ const assessment = event.payload.assessment;
220
+ if (!isAdmission(assessment) && !isRejection(assessment)) {
221
+ throw discoveryError("Persisted discovery assessment is malformed.");
222
+ }
223
+ latest.set(assessment.candidateId, assessment);
224
+ }
225
+ return [...latest.values()];
226
+ }
227
+ function parseCloseoutValue(value) {
228
+ if (value.schemaVersion !== 2 ||
229
+ !Array.isArray(value.limitations) ||
230
+ !Array.isArray(value.dimensionJudgments) ||
231
+ !Array.isArray(value.gaps) ||
232
+ value.dimensionJudgments.some((item) => !isDimensionJudgment(item))) {
233
+ throw discoveryError("Discovery closeout value is malformed.");
234
+ }
235
+ return value;
236
+ }
237
+ function isAdmission(value) {
238
+ return (isObject(value) &&
239
+ value.decision === "admit" &&
240
+ typeof value.candidateId === "string" &&
241
+ typeof value.sourceId === "string" &&
242
+ typeof value.sourceType === "string" &&
243
+ typeof value.relevance === "string" &&
244
+ isObject(value.quality) &&
245
+ typeof value.quality.level === "string" &&
246
+ typeof value.quality.rationale === "string" &&
247
+ typeof value.applicability === "string" &&
248
+ Array.isArray(value.coverageDimensions) &&
249
+ value.coverageDimensions.every((item) => typeof item === "string") &&
250
+ Array.isArray(value.limitations) &&
251
+ value.limitations.every((item) => typeof item === "string"));
252
+ }
253
+ function isRejection(value) {
254
+ return (isObject(value) &&
255
+ value.decision === "reject" &&
256
+ typeof value.candidateId === "string" &&
257
+ typeof value.reasonCode === "string" &&
258
+ typeof value.rationale === "string");
259
+ }
260
+ function isDimensionJudgment(value) {
261
+ return (isObject(value) &&
262
+ typeof value.id === "string" &&
263
+ ["covered", "partial", "missing"].includes(String(value.status)));
264
+ }
265
+ function discoveryError(message) {
266
+ return new StructuredOutputError(message, { validation: [message] });
267
+ }
268
+ //# sourceMappingURL=discovery.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"discovery.js","sourceRoot":"","sources":["../../../src/research/workspace/discovery.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,76 @@
1
+ export declare const downloadBindingRecordSchema: {
2
+ readonly $id: "https://schemas.tiangong.ai/research/download-binding-input-v1.json";
3
+ readonly type: "object";
4
+ readonly additionalProperties: false;
5
+ readonly required: readonly ["schemaVersion", "backend", "status", "downloadUrl"];
6
+ readonly properties: {
7
+ readonly schemaVersion: {
8
+ readonly type: "integer";
9
+ readonly const: 1;
10
+ };
11
+ readonly backend: {
12
+ readonly type: "string";
13
+ readonly enum: readonly ["native-browser", "chrome", "cloakbrowser", "skill-adapter", "direct-http"];
14
+ };
15
+ readonly status: {
16
+ readonly type: "string";
17
+ readonly enum: readonly ["completed", "failed", "cancelled"];
18
+ };
19
+ readonly path: {
20
+ readonly type: "string";
21
+ };
22
+ readonly downloadUrl: {
23
+ readonly type: "string";
24
+ readonly format: "uri";
25
+ };
26
+ readonly suggestedFilename: {
27
+ readonly type: "string";
28
+ readonly maxLength: 500;
29
+ };
30
+ readonly downloadIdentifier: {
31
+ readonly type: "string";
32
+ readonly maxLength: 1000;
33
+ };
34
+ readonly failureCode: {
35
+ readonly type: "string";
36
+ readonly pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$";
37
+ };
38
+ };
39
+ };
40
+ export interface EvidenceDownloadBinding {
41
+ schemaVersion: 1;
42
+ bindingId: string;
43
+ projectId: string;
44
+ candidateId: string;
45
+ backend: "native-browser" | "chrome" | "cloakbrowser" | "skill-adapter" | "direct-http";
46
+ status: "completed";
47
+ fileSha256: string;
48
+ fileBytes: number;
49
+ filePathSha256: string;
50
+ downloadUrl: string;
51
+ suggestedFilename: string | null;
52
+ downloadIdentifierSha256: string | null;
53
+ boundAt: string;
54
+ bindingSha256: string;
55
+ }
56
+ export declare function bindEvidenceDownload(input: {
57
+ root: string;
58
+ projectId: string;
59
+ candidateId: string;
60
+ value: Record<string, unknown>;
61
+ }): Promise<{
62
+ status: "completed";
63
+ binding: EvidenceDownloadBinding;
64
+ } | {
65
+ status: "failed" | "cancelled";
66
+ binding: null;
67
+ nextAction: string;
68
+ }>;
69
+ export declare function loadAndVerifyDownloadBinding(input: {
70
+ root: string;
71
+ projectId: string;
72
+ candidateId: string;
73
+ bindingId: string;
74
+ path: string;
75
+ }): Promise<EvidenceDownloadBinding>;
76
+ export declare function parseEvidenceDownloadBinding(value: unknown): EvidenceDownloadBinding;
@@ -0,0 +1,274 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { chmod, lstat, readFile } from "node:fs/promises";
3
+ import { basename, join, relative, resolve, sep } from "node:path";
4
+ import { CliError } from "../../errors.js";
5
+ import { appendEvidenceLedgerEvent, evidenceLedgerPath, listEvidenceCandidates, } from "./evidence-ledger.js";
6
+ import { readJournal } from "./journal.js";
7
+ import { sanitizeResearchText, sanitizeResearchValue } from "./sanitization.js";
8
+ import { canonicalJson, isObject, pathExists, readJsonFile, resolveContained, sha256File, sha256Text, workspacePaths, writeJsonAtomic, } from "./storage.js";
9
+ import { loadWorkspaceConfig } from "./workspace.js";
10
+ const IDENTIFIER = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/;
11
+ const SENSITIVE_QUERY_KEY = /^(?:access[_-]?token|api[_-]?key|apikey|auth|authorization|awsaccesskeyid|code|cookie|credential|key|password|secret|session(?:[_-]?id)?|sig|signature|token|x[_-]amz[_-](?:credential|security[_-]?token|signature)|x[_-]goog[_-](?:credential|signature))$/i;
12
+ const TRACKING_QUERY_KEY = /^(?:utm_[a-z0-9_]+|fbclid|gclid|mc_cid|mc_eid|ref|source)$/i;
13
+ export const downloadBindingRecordSchema = {
14
+ $id: "https://schemas.tiangong.ai/research/download-binding-input-v1.json",
15
+ type: "object",
16
+ additionalProperties: false,
17
+ required: ["schemaVersion", "backend", "status", "downloadUrl"],
18
+ properties: {
19
+ schemaVersion: { type: "integer", const: 1 },
20
+ backend: {
21
+ type: "string",
22
+ enum: ["native-browser", "chrome", "cloakbrowser", "skill-adapter", "direct-http"],
23
+ },
24
+ status: { type: "string", enum: ["completed", "failed", "cancelled"] },
25
+ path: { type: "string" },
26
+ downloadUrl: { type: "string", format: "uri" },
27
+ suggestedFilename: { type: "string", maxLength: 500 },
28
+ downloadIdentifier: { type: "string", maxLength: 1_000 },
29
+ failureCode: { type: "string", pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$" },
30
+ },
31
+ };
32
+ export async function bindEvidenceDownload(input) {
33
+ const value = sanitizeResearchValue(input.value);
34
+ if (!isObject(value))
35
+ throw downloadError("Download binding input must be a JSON object.");
36
+ const allowed = new Set([
37
+ "schemaVersion",
38
+ "backend",
39
+ "status",
40
+ "path",
41
+ "downloadUrl",
42
+ "suggestedFilename",
43
+ "downloadIdentifier",
44
+ "failureCode",
45
+ ]);
46
+ const unknown = Object.keys(value).filter((key) => !allowed.has(key));
47
+ const backends = [
48
+ "native-browser",
49
+ "chrome",
50
+ "cloakbrowser",
51
+ "skill-adapter",
52
+ "direct-http",
53
+ ];
54
+ const statuses = ["completed", "failed", "cancelled"];
55
+ if (unknown.length ||
56
+ value.schemaVersion !== 1 ||
57
+ !backends.includes(value.backend) ||
58
+ !statuses.includes(value.status) ||
59
+ typeof value.downloadUrl !== "string" ||
60
+ (value.suggestedFilename !== undefined &&
61
+ (typeof value.suggestedFilename !== "string" || value.suggestedFilename.length > 500)) ||
62
+ (value.downloadIdentifier !== undefined &&
63
+ (typeof value.downloadIdentifier !== "string" || value.downloadIdentifier.length > 1_000)) ||
64
+ (value.failureCode !== undefined &&
65
+ (typeof value.failureCode !== "string" || !IDENTIFIER.test(value.failureCode)))) {
66
+ throw downloadError("Download binding input failed validation.");
67
+ }
68
+ const backend = value.backend;
69
+ const status = value.status;
70
+ const downloadUrl = safeDownloadUrl(value.downloadUrl);
71
+ await requireActiveAcquisition(input.root, input.projectId, input.candidateId);
72
+ if (status !== "completed") {
73
+ if (value.path !== undefined) {
74
+ throw downloadError("Failed or cancelled downloads must not claim a completed file path.");
75
+ }
76
+ const failureCode = typeof value.failureCode === "string" ? value.failureCode : `download-${status}`;
77
+ await appendEvidenceLedgerEvent(input.root, input.projectId, "download.failed", {
78
+ eventId: `download-event-${randomUUID()}`,
79
+ candidateId: input.candidateId,
80
+ backend,
81
+ status,
82
+ downloadUrl,
83
+ failureCode,
84
+ recordedAt: new Date().toISOString(),
85
+ });
86
+ return {
87
+ status,
88
+ binding: null,
89
+ nextAction: "Stop acquisition for this attempt, report the failure, and do not register an artifact or submit a successful acquisition decision.",
90
+ };
91
+ }
92
+ if (typeof value.path !== "string") {
93
+ throw downloadError("A completed download requires one explicit absolute file path.");
94
+ }
95
+ const sourcePath = requireExactExternalPath(input.root, value.path);
96
+ const [info, config] = await Promise.all([
97
+ lstat(sourcePath).catch(() => undefined),
98
+ loadWorkspaceConfig(input.root),
99
+ ]);
100
+ if (!info?.isFile() || info.isSymbolicLink()) {
101
+ throw downloadError("Downloaded file must be one existing regular non-symlink file.");
102
+ }
103
+ if (info.size < 1 || info.size > config.budget.maxBytesPerPackage) {
104
+ throw downloadError(`Downloaded file size must be 1-${config.budget.maxBytesPerPackage} bytes.`);
105
+ }
106
+ const fileSha256 = await sha256File(sourcePath);
107
+ const core = {
108
+ schemaVersion: 1,
109
+ bindingId: `download-${sha256Text(canonicalJson({ candidateId: input.candidateId, backend, fileSha256 })).slice(0, 24)}`,
110
+ projectId: input.projectId,
111
+ candidateId: input.candidateId,
112
+ backend,
113
+ status: "completed",
114
+ fileSha256,
115
+ fileBytes: info.size,
116
+ filePathSha256: sha256Text(sourcePath),
117
+ downloadUrl,
118
+ suggestedFilename: typeof value.suggestedFilename === "string"
119
+ ? safeFilename(value.suggestedFilename)
120
+ : safeFilename(basename(sourcePath)),
121
+ downloadIdentifierSha256: typeof value.downloadIdentifier === "string"
122
+ ? sha256Text(sanitizeResearchText(value.downloadIdentifier))
123
+ : null,
124
+ boundAt: new Date().toISOString(),
125
+ };
126
+ const binding = {
127
+ ...core,
128
+ bindingSha256: sha256Text(canonicalJson(core)),
129
+ };
130
+ const recordPath = downloadBindingPath(input.root, input.projectId, binding.bindingId);
131
+ if (await pathExists(recordPath)) {
132
+ const existing = parseEvidenceDownloadBinding(JSON.parse(await readFile(recordPath, "utf8")));
133
+ if (existing.fileSha256 !== binding.fileSha256 ||
134
+ existing.filePathSha256 !== binding.filePathSha256 ||
135
+ existing.candidateId !== binding.candidateId) {
136
+ throw downloadError("Download binding identity collides with different file provenance.");
137
+ }
138
+ await ensureBoundEvent(input.root, input.projectId, existing);
139
+ return { status: "completed", binding: existing };
140
+ }
141
+ await writeJsonAtomic(recordPath, binding, 0o444);
142
+ await chmod(recordPath, 0o444).catch(() => undefined);
143
+ await ensureBoundEvent(input.root, input.projectId, binding);
144
+ return { status: "completed", binding };
145
+ }
146
+ async function ensureBoundEvent(root, projectId, binding) {
147
+ const events = await readJournal(evidenceLedgerPath(root, projectId));
148
+ if (events.some((event) => event.type === "download.bound" &&
149
+ event.payload.bindingId === binding.bindingId &&
150
+ event.payload.bindingSha256 === binding.bindingSha256)) {
151
+ return;
152
+ }
153
+ await appendEvidenceLedgerEvent(root, projectId, "download.bound", {
154
+ bindingId: binding.bindingId,
155
+ bindingSha256: binding.bindingSha256,
156
+ candidateId: binding.candidateId,
157
+ backend: binding.backend,
158
+ fileSha256: binding.fileSha256,
159
+ fileBytes: binding.fileBytes,
160
+ downloadUrl: binding.downloadUrl,
161
+ suggestedFilename: binding.suggestedFilename,
162
+ downloadIdentifierSha256: binding.downloadIdentifierSha256,
163
+ boundAt: binding.boundAt,
164
+ });
165
+ }
166
+ export async function loadAndVerifyDownloadBinding(input) {
167
+ if (!/^download-[0-9a-f]{24}$/.test(input.bindingId)) {
168
+ throw downloadError("Download binding ID is invalid.");
169
+ }
170
+ const sourcePath = requireExactExternalPath(input.root, input.path);
171
+ const binding = parseEvidenceDownloadBinding(JSON.parse(await readFile(downloadBindingPath(input.root, input.projectId, input.bindingId), "utf8")));
172
+ const info = await lstat(sourcePath).catch(() => undefined);
173
+ if (binding.projectId !== input.projectId ||
174
+ binding.candidateId !== input.candidateId ||
175
+ binding.filePathSha256 !== sha256Text(sourcePath) ||
176
+ !info?.isFile() ||
177
+ info.isSymbolicLink() ||
178
+ info.size !== binding.fileBytes ||
179
+ (await sha256File(sourcePath)) !== binding.fileSha256) {
180
+ throw downloadError("Downloaded file no longer matches its exact event binding.");
181
+ }
182
+ return binding;
183
+ }
184
+ export function parseEvidenceDownloadBinding(value) {
185
+ if (!isObject(value) ||
186
+ value.schemaVersion !== 1 ||
187
+ typeof value.bindingId !== "string" ||
188
+ !/^download-[0-9a-f]{24}$/.test(value.bindingId) ||
189
+ typeof value.projectId !== "string" ||
190
+ typeof value.candidateId !== "string" ||
191
+ !["native-browser", "chrome", "cloakbrowser", "skill-adapter", "direct-http"].includes(String(value.backend)) ||
192
+ value.status !== "completed" ||
193
+ typeof value.fileSha256 !== "string" ||
194
+ !/^[0-9a-f]{64}$/.test(value.fileSha256) ||
195
+ !Number.isInteger(value.fileBytes) ||
196
+ typeof value.filePathSha256 !== "string" ||
197
+ !/^[0-9a-f]{64}$/.test(value.filePathSha256) ||
198
+ typeof value.downloadUrl !== "string" ||
199
+ (value.suggestedFilename !== null && typeof value.suggestedFilename !== "string") ||
200
+ (value.downloadIdentifierSha256 !== null &&
201
+ (typeof value.downloadIdentifierSha256 !== "string" ||
202
+ !/^[0-9a-f]{64}$/.test(value.downloadIdentifierSha256))) ||
203
+ typeof value.boundAt !== "string" ||
204
+ typeof value.bindingSha256 !== "string" ||
205
+ !/^[0-9a-f]{64}$/.test(value.bindingSha256)) {
206
+ throw downloadError("Download binding record is malformed.");
207
+ }
208
+ const { bindingSha256, ...core } = value;
209
+ if (sha256Text(canonicalJson(core)) !== bindingSha256) {
210
+ throw downloadError("Download binding record failed its hash binding.");
211
+ }
212
+ return value;
213
+ }
214
+ async function requireActiveAcquisition(root, projectId, candidateId) {
215
+ const [project, candidates] = await Promise.all([
216
+ readJsonFile(join(workspacePaths(root).projects, projectId, "project.json"), `Research project ${projectId}`),
217
+ listEvidenceCandidates(root, projectId),
218
+ ]);
219
+ const packages = Array.isArray(project.packages) ? project.packages : [];
220
+ const acquire = packages.find((workPackage) => isObject(workPackage) &&
221
+ workPackage.stage === "acquire" &&
222
+ workPackage.status === "running" &&
223
+ workPackage.executor === "producer");
224
+ if (!acquire) {
225
+ throw downloadError("Download binding is allowed only during active native acquisition.");
226
+ }
227
+ if (!candidates.some((candidate) => candidate.id === candidateId)) {
228
+ throw downloadError(`Download binding refers to unknown candidate ${candidateId}.`);
229
+ }
230
+ }
231
+ function requireExactExternalPath(root, path) {
232
+ const selected = resolve(path);
233
+ if (selected !== path)
234
+ throw downloadError("Downloaded file path must be absolute and normalized.");
235
+ const controlRoot = resolve(workspacePaths(root).control);
236
+ if (!relative(controlRoot, selected).startsWith(`..${sep}`) && selected !== controlRoot) {
237
+ throw downloadError("Downloaded file must be an explicit staging file outside control state.");
238
+ }
239
+ return selected;
240
+ }
241
+ function safeDownloadUrl(value) {
242
+ let url;
243
+ try {
244
+ url = new URL(value);
245
+ }
246
+ catch {
247
+ throw downloadError("Download URL is invalid.");
248
+ }
249
+ if (url.protocol !== "https:")
250
+ throw downloadError("Download URL must use HTTPS.");
251
+ url.username = "";
252
+ url.password = "";
253
+ url.hash = "";
254
+ for (const key of [...url.searchParams.keys()]) {
255
+ if (SENSITIVE_QUERY_KEY.test(key) || TRACKING_QUERY_KEY.test(key)) {
256
+ url.searchParams.delete(key);
257
+ }
258
+ }
259
+ url.searchParams.sort();
260
+ return url.toString();
261
+ }
262
+ function safeFilename(value) {
263
+ const filename = sanitizeResearchText(basename(value))
264
+ .replace(/[\r\n\0]/g, "")
265
+ .trim();
266
+ return filename ? filename.slice(0, 500) : null;
267
+ }
268
+ function downloadBindingPath(root, projectId, bindingId) {
269
+ return resolveContained(workspacePaths(root).projects, `${projectId}/evidence/downloads/${bindingId}.json`);
270
+ }
271
+ function downloadError(message) {
272
+ return new CliError(message, { code: "RESEARCH_DOWNLOAD_BINDING_INVALID", exitCode: 3 });
273
+ }
274
+ //# sourceMappingURL=downloads.js.map