@tiangong-ai/cli 0.0.59 → 0.0.61

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 (104) hide show
  1. package/AGENTS.md +2 -2
  2. package/README.md +287 -41
  3. package/dist/research/orchestration.js +209 -62
  4. package/dist/research/orchestration.js.map +1 -1
  5. package/dist/research/workspace/acquisition-forecast.d.ts +3 -0
  6. package/dist/research/workspace/acquisition-forecast.js +28 -3
  7. package/dist/research/workspace/acquisition-forecast.js.map +1 -1
  8. package/dist/research/workspace/acquisition-revision.d.ts +13 -0
  9. package/dist/research/workspace/acquisition-revision.js +142 -0
  10. package/dist/research/workspace/acquisition-revision.js.map +1 -0
  11. package/dist/research/workspace/acquisition-routes.d.ts +1 -1
  12. package/dist/research/workspace/acquisition-routes.js +2 -11
  13. package/dist/research/workspace/acquisition-routes.js.map +1 -1
  14. package/dist/research/workspace/acquisition.d.ts +3 -1
  15. package/dist/research/workspace/acquisition.js +67 -35
  16. package/dist/research/workspace/acquisition.js.map +1 -1
  17. package/dist/research/workspace/analysis-run.d.ts +2 -0
  18. package/dist/research/workspace/analysis-run.js +28 -0
  19. package/dist/research/workspace/analysis-run.js.map +1 -0
  20. package/dist/research/workspace/artifact-read-audit.d.ts +16 -0
  21. package/dist/research/workspace/artifact-read-audit.js +176 -0
  22. package/dist/research/workspace/artifact-read-audit.js.map +1 -0
  23. package/dist/research/workspace/artifact-trace.d.ts +12 -0
  24. package/dist/research/workspace/artifact-trace.js +109 -0
  25. package/dist/research/workspace/artifact-trace.js.map +1 -0
  26. package/dist/research/workspace/artifact-view-mcp.d.ts +75 -0
  27. package/dist/research/workspace/artifact-view-mcp.js +214 -0
  28. package/dist/research/workspace/artifact-view-mcp.js.map +1 -0
  29. package/dist/research/workspace/artifact-views.d.ts +100 -0
  30. package/dist/research/workspace/artifact-views.js +395 -0
  31. package/dist/research/workspace/artifact-views.js.map +1 -0
  32. package/dist/research/workspace/artifacts.d.ts +3 -0
  33. package/dist/research/workspace/artifacts.js +13 -0
  34. package/dist/research/workspace/artifacts.js.map +1 -1
  35. package/dist/research/workspace/audit-bundle.d.ts +9 -0
  36. package/dist/research/workspace/audit-bundle.js +124 -8
  37. package/dist/research/workspace/audit-bundle.js.map +1 -1
  38. package/dist/research/workspace/content-evidence.d.ts +3 -0
  39. package/dist/research/workspace/content-evidence.js +149 -22
  40. package/dist/research/workspace/content-evidence.js.map +1 -1
  41. package/dist/research/workspace/executor.d.ts +5 -1
  42. package/dist/research/workspace/executor.js +106 -22
  43. package/dist/research/workspace/executor.js.map +1 -1
  44. package/dist/research/workspace/native-run.d.ts +77 -0
  45. package/dist/research/workspace/native-run.js +637 -0
  46. package/dist/research/workspace/native-run.js.map +1 -0
  47. package/dist/research/workspace/preflight.d.ts +8 -2
  48. package/dist/research/workspace/preflight.js +17 -14
  49. package/dist/research/workspace/preflight.js.map +1 -1
  50. package/dist/research/workspace/project-authority.d.ts +21 -0
  51. package/dist/research/workspace/project-authority.js +186 -0
  52. package/dist/research/workspace/project-authority.js.map +1 -0
  53. package/dist/research/workspace/project-mutations.d.ts +26 -0
  54. package/dist/research/workspace/project-mutations.js +332 -0
  55. package/dist/research/workspace/project-mutations.js.map +1 -0
  56. package/dist/research/workspace/projects.d.ts +2 -1
  57. package/dist/research/workspace/projects.js +133 -91
  58. package/dist/research/workspace/projects.js.map +1 -1
  59. package/dist/research/workspace/publication-workflow.d.ts +3 -0
  60. package/dist/research/workspace/publication-workflow.js +15 -3
  61. package/dist/research/workspace/publication-workflow.js.map +1 -1
  62. package/dist/research/workspace/request-provenance.d.ts +67 -0
  63. package/dist/research/workspace/request-provenance.js +115 -0
  64. package/dist/research/workspace/request-provenance.js.map +1 -0
  65. package/dist/research/workspace/review-executor.js +22 -11
  66. package/dist/research/workspace/review-executor.js.map +1 -1
  67. package/dist/research/workspace/runtime.d.ts +57 -0
  68. package/dist/research/workspace/runtime.js +292 -140
  69. package/dist/research/workspace/runtime.js.map +1 -1
  70. package/dist/research/workspace/sanitization.js +1 -1
  71. package/dist/research/workspace/sanitization.js.map +1 -1
  72. package/dist/research/workspace/schema-compatibility.d.ts +5 -0
  73. package/dist/research/workspace/schema-compatibility.js +55 -0
  74. package/dist/research/workspace/schema-compatibility.js.map +1 -0
  75. package/dist/research/workspace/schemas.d.ts +6 -0
  76. package/dist/research/workspace/schemas.js +13 -2
  77. package/dist/research/workspace/schemas.js.map +1 -1
  78. package/dist/research/workspace/scientific-design.js +8 -17
  79. package/dist/research/workspace/scientific-design.js.map +1 -1
  80. package/dist/research/workspace/scientific-fulfillment-audit.d.ts +3 -0
  81. package/dist/research/workspace/scientific-fulfillment-audit.js +137 -0
  82. package/dist/research/workspace/scientific-fulfillment-audit.js.map +1 -0
  83. package/dist/research/workspace/scientific-fulfillment.d.ts +121 -0
  84. package/dist/research/workspace/scientific-fulfillment.js +539 -0
  85. package/dist/research/workspace/scientific-fulfillment.js.map +1 -0
  86. package/dist/research/workspace/scientific-review-execution.js +84 -61
  87. package/dist/research/workspace/scientific-review-execution.js.map +1 -1
  88. package/dist/research/workspace/scientific-review.d.ts +8 -2
  89. package/dist/research/workspace/scientific-review.js +205 -53
  90. package/dist/research/workspace/scientific-review.js.map +1 -1
  91. package/dist/research/workspace/setup-catalog.js +2 -2
  92. package/dist/research/workspace/task-acceptance.d.ts +134 -0
  93. package/dist/research/workspace/task-acceptance.js +544 -0
  94. package/dist/research/workspace/task-acceptance.js.map +1 -0
  95. package/dist/research/workspace/task-audit.d.ts +12 -0
  96. package/dist/research/workspace/task-audit.js +231 -0
  97. package/dist/research/workspace/task-audit.js.map +1 -0
  98. package/dist/research/workspace/task-contract.d.ts +142 -0
  99. package/dist/research/workspace/task-contract.js +506 -0
  100. package/dist/research/workspace/task-contract.js.map +1 -0
  101. package/dist/research/workspace/types.d.ts +6 -3
  102. package/dist/research/workspace/workspace.js +11 -8
  103. package/dist/research/workspace/workspace.js.map +1 -1
  104. package/package.json +1 -1
@@ -0,0 +1,121 @@
1
+ import { type ScientificDesignContract } from "./scientific-design.js";
2
+ import type { JournalEvent, ProjectState, ScientificReviewRole } from "./types.js";
3
+ type DeferredGate = "evidence-construct" | "pilot-methods";
4
+ type ObjectInput = {
5
+ modelId: string;
6
+ objectLocator: string;
7
+ sha256: string;
8
+ recordSha256: string;
9
+ };
10
+ type ImplementationInput = ObjectInput & {
11
+ entrypoint: string;
12
+ };
13
+ interface ParameterInput {
14
+ parameterId: string;
15
+ states: Array<{
16
+ stateId: string;
17
+ value: string;
18
+ evidenceAtomIds: string[];
19
+ }>;
20
+ }
21
+ interface FulfillmentInput {
22
+ schemaVersion: 1;
23
+ designSha256: string;
24
+ parentFulfillmentSha256: string | null;
25
+ reason: string;
26
+ modelImplementations: ImplementationInput[];
27
+ environmentLocks: ObjectInput[];
28
+ parameterStates: ParameterInput[];
29
+ }
30
+ export interface ScientificFulfillmentRecord extends Omit<FulfillmentInput, "parameterStates" | "modelImplementations" | "environmentLocks"> {
31
+ kind: "tiangong-scientific-fulfillment";
32
+ projectId: string;
33
+ requestSha256: string;
34
+ modelImplementations: Array<ImplementationInput & {
35
+ dueGate: DeferredGate;
36
+ }>;
37
+ environmentLocks: Array<ObjectInput & {
38
+ dueGate: DeferredGate;
39
+ }>;
40
+ parameterStates: Array<{
41
+ parameterId: string;
42
+ dueGate: DeferredGate;
43
+ states: Array<{
44
+ stateId: string;
45
+ value: string;
46
+ atoms: Array<{
47
+ id: string;
48
+ sha256: string;
49
+ }>;
50
+ }>;
51
+ }>;
52
+ recordSha256: string;
53
+ }
54
+ export interface ScientificFulfillmentView {
55
+ base: ScientificDesignContract;
56
+ contract: ScientificDesignContract;
57
+ headSha256: string | null;
58
+ effectiveSha256: string;
59
+ records: ScientificFulfillmentRecord[];
60
+ deferredObjectRuleIds: string[];
61
+ }
62
+ export declare function scientificFulfillmentSchema(): Record<string, unknown>;
63
+ export declare function scientificFulfillmentLocator(projectId: string, hash: string): string;
64
+ export declare function validateScientificFulfillmentRecord(value: unknown, projectId: string, hash: string): ScientificFulfillmentRecord;
65
+ /** The base bytes never change. The per-gate projection includes only slots due by that gate. */
66
+ export declare function loadScientificFulfillmentView(root: string, project: ProjectState, throughGate?: ScientificReviewRole, knownEvents?: JournalEvent[]): Promise<ScientificFulfillmentView>;
67
+ export declare function applyScientificFulfillmentRecord(design: ScientificDesignContract, record: ScientificFulfillmentRecord, throughGate?: ScientificReviewRole): void;
68
+ export declare function recordScientificFulfillment(root: string, projectId: string, value: unknown): Promise<ScientificFulfillmentRecord>;
69
+ export declare function inspectScientificFulfillment(root: string, projectId: string): Promise<{
70
+ projectId: string;
71
+ designSha256: string;
72
+ fulfillmentSha256: string | null;
73
+ effectiveDesignSha256: string;
74
+ records: {
75
+ recordSha256: string;
76
+ parentFulfillmentSha256: string | null;
77
+ }[];
78
+ pendingModels: {
79
+ id: string;
80
+ label: string;
81
+ family: string;
82
+ version: string;
83
+ equationSet: string;
84
+ coefficientSet: string;
85
+ implementationArtifactSha256: string | null;
86
+ implementationArtifactLocator: string | null;
87
+ implementationEntrypoint: string | null;
88
+ implementationStatus: "executable-frozen" | "pending-source-acquisition";
89
+ implementationFreezeBeforeGate: "research-design" | "evidence-construct" | "pilot-methods";
90
+ environmentLockSha256: string | null;
91
+ environmentLockLocator: string | null;
92
+ environmentLockStatus: "exact-frozen" | "pending-runtime-lock";
93
+ environmentLockFreezeBeforeGate: "research-design" | "evidence-construct" | "pilot-methods";
94
+ artifactHashBasis: "raw-file-bytes";
95
+ baselineRole: "candidate" | "reference" | "strongest-available";
96
+ baselineSelectionJustification: string;
97
+ sourceEvidenceRoleIds: string[];
98
+ }[];
99
+ pendingParameters: {
100
+ id: string;
101
+ label: string;
102
+ distributionOrRange: string;
103
+ states: Array<{
104
+ id: string;
105
+ label: string;
106
+ value: string;
107
+ unit: string;
108
+ }>;
109
+ stateValueType: "numeric" | "categorical";
110
+ stateValueStatus: "frozen" | "pending-source-acquisition";
111
+ freezeBeforeGate: "research-design" | "evidence-construct" | "pilot-methods";
112
+ factorIds: string[];
113
+ applicationPoint: string;
114
+ compositionRule: string;
115
+ preservesFactorLevelIdentity: boolean;
116
+ sourceEvidenceRoleIds: string[];
117
+ quantityIds: string[];
118
+ }[];
119
+ policyRulesAwaitingIndependentJudgement: string[];
120
+ }>;
121
+ export {};
@@ -0,0 +1,539 @@
1
+ import { Ajv2020 } from "ajv/dist/2020.js";
2
+ import { lstat, mkdir, readFile } from "node:fs/promises";
3
+ import { join } from "node:path";
4
+ import { CliError } from "../../errors.js";
5
+ import { appendJournalEvent, readVerifiedJournal } from "./journal.js";
6
+ import { assertProjectAuthority, projectAuthorityIndex } from "./project-authority.js";
7
+ import { beginProjectMutation, prepareProjectMutation, projectMutationBinding, settleProjectMutation, } from "./project-mutations.js";
8
+ import { loadProject } from "./projects.js";
9
+ import { configuredResearchSecrets, sanitizeResearchValue } from "./sanitization.js";
10
+ import { evaluateScientificDesign, readAndVerifyScientificDesign, } from "./scientific-design.js";
11
+ import { resolveScientificObjectBinding } from "./scientific-objects.js";
12
+ import { canonicalJson, isObject, pathExists, resolveContained, sha256Text, workspacePaths, writeJsonAtomic, } from "./storage.js";
13
+ import { withWorkspaceLock } from "./workspace.js";
14
+ const HASH = /^[a-f0-9]{64}$/;
15
+ const ID = "^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$";
16
+ const roles = ["research-design", "evidence-construct", "pilot-methods"];
17
+ const rank = (role) => roles.indexOf(role);
18
+ const objectProperties = {
19
+ modelId: { type: "string", pattern: ID },
20
+ objectLocator: { type: "string", pattern: "^lineage/objects/[a-f0-9]{64}/blob$" },
21
+ sha256: { type: "string", pattern: HASH.source },
22
+ recordSha256: { type: "string", pattern: HASH.source },
23
+ };
24
+ const inputSchema = {
25
+ type: "object",
26
+ additionalProperties: false,
27
+ required: [
28
+ "schemaVersion",
29
+ "designSha256",
30
+ "parentFulfillmentSha256",
31
+ "reason",
32
+ "modelImplementations",
33
+ "environmentLocks",
34
+ "parameterStates",
35
+ ],
36
+ properties: {
37
+ schemaVersion: { const: 1 },
38
+ designSha256: { type: "string", pattern: HASH.source },
39
+ parentFulfillmentSha256: { type: ["string", "null"], pattern: HASH.source },
40
+ reason: { type: "string", minLength: 8, maxLength: 4000 },
41
+ modelImplementations: {
42
+ type: "array",
43
+ items: {
44
+ type: "object",
45
+ additionalProperties: false,
46
+ required: [...Object.keys(objectProperties), "entrypoint"],
47
+ properties: {
48
+ ...objectProperties,
49
+ entrypoint: { type: "string", minLength: 1, maxLength: 512 },
50
+ },
51
+ },
52
+ },
53
+ environmentLocks: {
54
+ type: "array",
55
+ items: {
56
+ type: "object",
57
+ additionalProperties: false,
58
+ required: Object.keys(objectProperties),
59
+ properties: objectProperties,
60
+ },
61
+ },
62
+ parameterStates: {
63
+ type: "array",
64
+ items: {
65
+ type: "object",
66
+ additionalProperties: false,
67
+ required: ["parameterId", "states"],
68
+ properties: {
69
+ parameterId: { type: "string", pattern: ID },
70
+ states: {
71
+ type: "array",
72
+ minItems: 1,
73
+ items: {
74
+ type: "object",
75
+ additionalProperties: false,
76
+ required: ["stateId", "value", "evidenceAtomIds"],
77
+ properties: {
78
+ stateId: { type: "string", pattern: ID },
79
+ value: { type: "string", minLength: 1, maxLength: 2000 },
80
+ evidenceAtomIds: {
81
+ type: "array",
82
+ minItems: 1,
83
+ uniqueItems: true,
84
+ items: { type: "string", pattern: ID },
85
+ },
86
+ },
87
+ },
88
+ },
89
+ },
90
+ },
91
+ },
92
+ },
93
+ };
94
+ const validate = new Ajv2020({ strict: false, allErrors: true }).compile(inputSchema);
95
+ export function scientificFulfillmentSchema() {
96
+ return structuredClone(inputSchema);
97
+ }
98
+ function failure(message, code = "RESEARCH_SCIENTIFIC_FULFILLMENT_INVALID", exitCode = 2) {
99
+ return new CliError(message, { code, exitCode });
100
+ }
101
+ function conflict() {
102
+ return failure("Fulfillment no longer matches its exact declared pending slots or parent. Inspect status; material design changes require a reviewed successor.", "RESEARCH_SCIENTIFIC_FULFILLMENT_CONFLICT", 3);
103
+ }
104
+ function parseInput(value) {
105
+ if (!validate(value) ||
106
+ !isObject(value) ||
107
+ String(value.reason).trim().length < 8 ||
108
+ canonicalJson(sanitizeResearchValue(value, configuredResearchSecrets(process.env))) !==
109
+ canonicalJson(value)) {
110
+ throw failure("Fulfillment must match the closed, secret-free input schema; it cannot change scientific assumptions or policy.");
111
+ }
112
+ const input = value;
113
+ if (!input.modelImplementations.length &&
114
+ !input.environmentLocks.length &&
115
+ !input.parameterStates.length)
116
+ throw failure("Fulfillment must name at least one predeclared pending slot.");
117
+ for (const items of [
118
+ input.modelImplementations.map((item) => item.modelId),
119
+ input.environmentLocks.map((item) => item.modelId),
120
+ input.parameterStates.map((item) => item.parameterId),
121
+ ]) {
122
+ if (new Set(items).size !== items.length)
123
+ throw failure("A pending slot can be supplied only once per fulfillment.");
124
+ }
125
+ return input;
126
+ }
127
+ function inputFromRecord(record) {
128
+ return {
129
+ schemaVersion: 1,
130
+ designSha256: record.designSha256,
131
+ parentFulfillmentSha256: record.parentFulfillmentSha256,
132
+ reason: record.reason,
133
+ modelImplementations: record.modelImplementations.map(({ dueGate: _gate, ...item }) => item),
134
+ environmentLocks: record.environmentLocks.map(({ dueGate: _gate, ...item }) => item),
135
+ parameterStates: record.parameterStates.map((item) => ({
136
+ parameterId: item.parameterId,
137
+ states: item.states.map((state) => ({
138
+ stateId: state.stateId,
139
+ value: state.value,
140
+ evidenceAtomIds: state.atoms.map((atom) => atom.id),
141
+ })),
142
+ })),
143
+ };
144
+ }
145
+ export function scientificFulfillmentLocator(projectId, hash) {
146
+ if (!/^[a-z0-9][a-z0-9-]{2,63}$/.test(projectId) || !HASH.test(hash))
147
+ throw conflict();
148
+ return `projects/${projectId}/scientific/fulfillments/${hash}.json`;
149
+ }
150
+ async function regularParents(root, locator, create = false) {
151
+ let directory = workspacePaths(root).control;
152
+ for (const part of locator.split("/").slice(0, -1)) {
153
+ directory = join(directory, part);
154
+ if (create && !(await pathExists(directory)))
155
+ await mkdir(directory, { mode: 0o700 });
156
+ const info = await lstat(directory).catch(() => null);
157
+ if (!info?.isDirectory() || info.isSymbolicLink())
158
+ throw conflict();
159
+ }
160
+ }
161
+ async function readRecord(root, projectId, hash) {
162
+ const locator = scientificFulfillmentLocator(projectId, hash);
163
+ await regularParents(root, locator);
164
+ const path = resolveContained(workspacePaths(root).control, locator);
165
+ const info = await lstat(path).catch(() => null);
166
+ if (!info?.isFile() || info.isSymbolicLink() || info.size > 16 * 1024 * 1024)
167
+ throw conflict();
168
+ let value;
169
+ try {
170
+ value = JSON.parse(await readFile(path, "utf8"));
171
+ }
172
+ catch {
173
+ throw conflict();
174
+ }
175
+ return validateScientificFulfillmentRecord(value, projectId, hash);
176
+ }
177
+ export function validateScientificFulfillmentRecord(value, projectId, hash) {
178
+ if (!isObject(value) ||
179
+ value.kind !== "tiangong-scientific-fulfillment" ||
180
+ value.projectId !== projectId ||
181
+ value.recordSha256 !== hash)
182
+ throw conflict();
183
+ if (Object.keys(value).sort().join(",") !==
184
+ "designSha256,environmentLocks,kind,modelImplementations,parameterStates,parentFulfillmentSha256,projectId,reason,recordSha256,requestSha256,schemaVersion")
185
+ throw conflict();
186
+ const { recordSha256: _hash, ...core } = value;
187
+ if (sha256Text(canonicalJson(core)) !== hash)
188
+ throw conflict();
189
+ try {
190
+ const input = parseInput(inputFromRecord(value));
191
+ if (sha256Text(canonicalJson(input)) !== value.requestSha256)
192
+ throw conflict();
193
+ }
194
+ catch {
195
+ throw conflict();
196
+ }
197
+ return value;
198
+ }
199
+ /** The base bytes never change. The per-gate projection includes only slots due by that gate. */
200
+ export async function loadScientificFulfillmentView(root, project, throughGate, knownEvents) {
201
+ const binding = project.scientificDesign;
202
+ if (!binding)
203
+ throw conflict();
204
+ const basePath = resolveContained(workspacePaths(root).control, binding.objectLocator);
205
+ await regularParents(root, binding.objectLocator);
206
+ const verified = await readAndVerifyScientificDesign(basePath, project.id);
207
+ if (verified.sha256 !== binding.designSha256 ||
208
+ sha256Text(await readFile(basePath, "utf8")) !== binding.designSha256) {
209
+ throw new CliError("Frozen evidence acquisition plan no longer matches its design binding.", {
210
+ code: "RESEARCH_EVIDENCE_ACCESS_PLAN_INVALID",
211
+ exitCode: 3,
212
+ });
213
+ }
214
+ const head = binding.fulfillmentSha256 ?? null;
215
+ const records = [];
216
+ if (head ||
217
+ (await pathExists(join(workspacePaths(root).projects, project.id, "scientific/fulfillments")))) {
218
+ const events = (knownEvents ?? (await readVerifiedJournal(workspacePaths(root).journal))).filter((event) => event.scope === project.id && event.type === "scientific.fulfillment.recorded");
219
+ if ((events.at(-1)?.payload.recordSha256 ?? null) !== head)
220
+ throw conflict();
221
+ let cursor = head;
222
+ const seen = new Set();
223
+ while (cursor) {
224
+ if (seen.has(cursor))
225
+ throw conflict();
226
+ seen.add(cursor);
227
+ const record = await readRecord(root, project.id, cursor);
228
+ const event = events.find((item) => item.payload.recordSha256 === cursor);
229
+ if (!event ||
230
+ record.designSha256 !== binding.designSha256 ||
231
+ event.payload.parentFulfillmentSha256 !== record.parentFulfillmentSha256 ||
232
+ event.payload.requestSha256 !== record.requestSha256)
233
+ throw conflict();
234
+ records.push(record);
235
+ cursor = record.parentFulfillmentSha256;
236
+ }
237
+ if (records.length !== events.length)
238
+ throw conflict();
239
+ records.reverse();
240
+ }
241
+ // Reuse verified atom identities within this load only. A later operation
242
+ // must reload the store so changed evidence cannot hide behind a cache.
243
+ const atomBindings = records.some((record) => record.parameterStates.length)
244
+ ? await loadAtomBindings(root, project.id)
245
+ : undefined;
246
+ const complete = structuredClone(verified.contract);
247
+ const contract = throughGate ? structuredClone(verified.contract) : complete;
248
+ for (const record of records) {
249
+ await assertObjectRecords(root, record, atomBindings);
250
+ // Even an early-gate projection must reject invalid future-due slots.
251
+ applyScientificFulfillmentRecord(complete, record);
252
+ if (throughGate)
253
+ applyScientificFulfillmentRecord(contract, record, throughGate);
254
+ }
255
+ return {
256
+ base: verified.contract,
257
+ contract,
258
+ headSha256: head,
259
+ effectiveSha256: sha256Text(canonicalJson(contract)),
260
+ records,
261
+ deferredObjectRuleIds: resolvedDeferredObjectRules(verified.contract, contract, records, throughGate),
262
+ };
263
+ }
264
+ async function loadAtomBindings(root, projectId) {
265
+ const { loadEvidenceAtomRecords } = await import("./content-evidence.js");
266
+ return new Map((await loadEvidenceAtomRecords(root, projectId)).map((atom) => [atom.atomId, atom.atomSha256]));
267
+ }
268
+ async function assertObjectRecords(root, record, knownAtomBindings) {
269
+ for (const [kind, items] of [
270
+ ["model-implementation", record.modelImplementations],
271
+ ["environment-lock", record.environmentLocks],
272
+ ]) {
273
+ for (const item of items) {
274
+ const object = await resolveScientificObjectBinding({
275
+ root,
276
+ objectKind: kind,
277
+ objectLocator: item.objectLocator,
278
+ expectedSha256: item.sha256,
279
+ });
280
+ if (object.record?.recordSha256 !== item.recordSha256)
281
+ throw conflict();
282
+ }
283
+ }
284
+ if (record.parameterStates.length) {
285
+ const atoms = knownAtomBindings ?? (await loadAtomBindings(root, record.projectId));
286
+ for (const parameter of record.parameterStates)
287
+ for (const state of parameter.states)
288
+ for (const atom of state.atoms) {
289
+ if (atoms.get(atom.id) !== atom.sha256)
290
+ throw conflict();
291
+ }
292
+ }
293
+ }
294
+ export function applyScientificFulfillmentRecord(design, record, throughGate) {
295
+ const include = (gate) => !throughGate || rank(gate) <= rank(throughGate);
296
+ for (const item of record.modelImplementations) {
297
+ if (!include(item.dueGate))
298
+ continue;
299
+ const model = design.identity.modelStructures.find((candidate) => candidate.id === item.modelId);
300
+ if (!model ||
301
+ model.implementationStatus !== "pending-source-acquisition" ||
302
+ model.implementationFreezeBeforeGate !== item.dueGate)
303
+ throw conflict();
304
+ model.implementationStatus = "executable-frozen";
305
+ model.implementationArtifactSha256 = item.sha256;
306
+ model.implementationArtifactLocator = item.objectLocator;
307
+ model.implementationEntrypoint = item.entrypoint;
308
+ }
309
+ for (const item of record.environmentLocks) {
310
+ if (!include(item.dueGate))
311
+ continue;
312
+ const model = design.identity.modelStructures.find((candidate) => candidate.id === item.modelId);
313
+ if (!model ||
314
+ model.environmentLockStatus !== "pending-runtime-lock" ||
315
+ model.environmentLockFreezeBeforeGate !== item.dueGate)
316
+ throw conflict();
317
+ model.environmentLockStatus = "exact-frozen";
318
+ model.environmentLockSha256 = item.sha256;
319
+ model.environmentLockLocator = item.objectLocator;
320
+ }
321
+ for (const item of record.parameterStates) {
322
+ if (!include(item.dueGate))
323
+ continue;
324
+ const parameter = design.uncertaintyParameters.find((candidate) => candidate.id === item.parameterId);
325
+ if (!parameter ||
326
+ parameter.stateValueStatus !== "pending-source-acquisition" ||
327
+ parameter.freezeBeforeGate !== item.dueGate ||
328
+ item.states.length !== parameter.states.length ||
329
+ new Set(item.states.map((state) => state.stateId)).size !== parameter.states.length)
330
+ throw conflict();
331
+ for (const state of parameter.states) {
332
+ const supplied = item.states.find((candidate) => candidate.stateId === state.id);
333
+ if (!supplied ||
334
+ !supplied.atoms.length ||
335
+ (parameter.stateValueType === "numeric" &&
336
+ (!supplied.value.trim() || !Number.isFinite(Number(supplied.value)))))
337
+ throw conflict();
338
+ state.value = supplied.value;
339
+ }
340
+ parameter.stateValueStatus = "frozen";
341
+ }
342
+ }
343
+ function resolvedDeferredObjectRules(base, effective, records, throughGate) {
344
+ if (!records.length)
345
+ return [];
346
+ return base.policyRuleDispositions
347
+ .filter((rule) => {
348
+ if (rule.status !== "planned" ||
349
+ rule.dueGate === "publication-freeze" ||
350
+ rule.dueGate === "research-design" ||
351
+ (throughGate && rank(rule.dueGate) > rank(throughGate)))
352
+ return false;
353
+ const modelSlots = base.identity.modelStructures
354
+ .filter((model) => rule.modelStructureIds.includes(model.id))
355
+ .flatMap((model) => [
356
+ ...(model.implementationStatus === "pending-source-acquisition" &&
357
+ model.implementationFreezeBeforeGate === rule.dueGate
358
+ ? [{ id: model.id, kind: "implementation" }]
359
+ : []),
360
+ ...(model.environmentLockStatus === "pending-runtime-lock" &&
361
+ model.environmentLockFreezeBeforeGate === rule.dueGate
362
+ ? [{ id: model.id, kind: "environment" }]
363
+ : []),
364
+ ]);
365
+ const parameters = base.uncertaintyParameters.filter((parameter) => rule.uncertaintyParameterIds.includes(parameter.id) &&
366
+ parameter.stateValueStatus === "pending-source-acquisition" &&
367
+ parameter.freezeBeforeGate === rule.dueGate);
368
+ if (!modelSlots.length && !parameters.length)
369
+ return false;
370
+ // Only the deferred object-filing blocker is discharged; the policy text and independent judgement are unchanged.
371
+ return (modelSlots.every((slot) => {
372
+ const model = effective.identity.modelStructures.find((item) => item.id === slot.id);
373
+ return slot.kind === "implementation"
374
+ ? model.implementationStatus === "executable-frozen"
375
+ : model.environmentLockStatus === "exact-frozen";
376
+ }) &&
377
+ parameters.every((parameter) => effective.uncertaintyParameters.find((item) => item.id === parameter.id)
378
+ ?.stateValueStatus === "frozen"));
379
+ })
380
+ .map((rule) => rule.ruleId);
381
+ }
382
+ export async function recordScientificFulfillment(root, projectId, value) {
383
+ const input = parseInput(value);
384
+ return withWorkspaceLock(root, "research.scientific.fulfillment", async () => {
385
+ const paths = workspacePaths(root);
386
+ const project = await loadProject(root, projectId);
387
+ const events = await readVerifiedJournal(paths.journal);
388
+ assertProjectAuthority(project, projectAuthorityIndex(events));
389
+ const view = await loadScientificFulfillmentView(root, project, undefined, events);
390
+ const requestSha256 = sha256Text(canonicalJson(input));
391
+ const replay = view.records.find((record) => record.requestSha256 === requestSha256);
392
+ if (replay)
393
+ return replay;
394
+ if (input.designSha256 !== project.scientificDesign.designSha256 ||
395
+ input.parentFulfillmentSha256 !== view.headSha256)
396
+ throw conflict();
397
+ const analyze = project.packages.find((item) => item.stage === "analyze");
398
+ if (project.status === "complete" ||
399
+ project.handoff.state !== "agent-actionable" ||
400
+ !analyze ||
401
+ analyze.attempts > 0 ||
402
+ analyze.startedAt ||
403
+ !["ready", "pending"].includes(analyze.status) ||
404
+ (await pathExists(join(paths.projects, project.id, "native/active.json"))) ||
405
+ (await pathExists(join(paths.projects, project.id, "outputs/inference-snapshot.json")))) {
406
+ throw failure("Fulfill planned scientific objects only at an idle pre-analysis boundary. Resolve the active session or use a reviewed successor after analysis.", "RESEARCH_SCIENTIFIC_FULFILLMENT_UNAVAILABLE", 3);
407
+ }
408
+ const dueFor = (modelId, kind) => {
409
+ const model = view.contract.identity.modelStructures.find((item) => item.id === modelId);
410
+ if (!model ||
411
+ (kind === "implementation"
412
+ ? model.implementationStatus !== "pending-source-acquisition"
413
+ : model.environmentLockStatus !== "pending-runtime-lock"))
414
+ throw conflict();
415
+ const due = kind === "implementation"
416
+ ? model.implementationFreezeBeforeGate
417
+ : model.environmentLockFreezeBeforeGate;
418
+ if (due === "research-design")
419
+ throw conflict();
420
+ return due;
421
+ };
422
+ const modelImplementations = input.modelImplementations.map((item) => ({
423
+ ...item,
424
+ dueGate: dueFor(item.modelId, "implementation"),
425
+ }));
426
+ const environmentLocks = input.environmentLocks.map((item) => ({
427
+ ...item,
428
+ dueGate: dueFor(item.modelId, "environment"),
429
+ }));
430
+ const parameterStates = [];
431
+ if (input.parameterStates.length) {
432
+ const { loadCurrentEvidenceContentSnapshot } = await import("./content-evidence.js");
433
+ const content = await loadCurrentEvidenceContentSnapshot(root, projectId);
434
+ const atoms = new Map(content.atoms.map((atom) => [atom.atomId, atom]));
435
+ for (const item of input.parameterStates) {
436
+ const parameter = view.contract.uncertaintyParameters.find((candidate) => candidate.id === item.parameterId);
437
+ if (!parameter ||
438
+ parameter.stateValueStatus !== "pending-source-acquisition" ||
439
+ parameter.freezeBeforeGate === "research-design")
440
+ throw conflict();
441
+ parameterStates.push({
442
+ parameterId: item.parameterId,
443
+ dueGate: parameter.freezeBeforeGate,
444
+ states: item.states.map((state) => ({
445
+ stateId: state.stateId,
446
+ value: state.value,
447
+ atoms: state.evidenceAtomIds.map((id) => {
448
+ const atom = atoms.get(id);
449
+ if (!atom ||
450
+ !atom.evidenceRoleIds.some((role) => parameter.sourceEvidenceRoleIds.includes(role)))
451
+ throw conflict();
452
+ return { id, sha256: atom.atomSha256 };
453
+ }),
454
+ })),
455
+ });
456
+ }
457
+ }
458
+ const core = {
459
+ ...input,
460
+ modelImplementations,
461
+ environmentLocks,
462
+ parameterStates,
463
+ kind: "tiangong-scientific-fulfillment",
464
+ projectId,
465
+ requestSha256,
466
+ };
467
+ const record = {
468
+ ...core,
469
+ recordSha256: sha256Text(canonicalJson(core)),
470
+ };
471
+ await assertObjectRecords(root, record);
472
+ const effective = structuredClone(view.contract);
473
+ applyScientificFulfillmentRecord(effective, record);
474
+ const previousIssues = new Set(evaluateScientificDesign(view.contract).issueCodes);
475
+ if (evaluateScientificDesign(effective).issueCodes.some((code) => !previousIssues.has(code)))
476
+ throw conflict();
477
+ const dueGate = [...modelImplementations, ...environmentLocks, ...parameterStates]
478
+ .map((item) => item.dueGate)
479
+ .sort((a, b) => rank(a) - rank(b))[0];
480
+ const locator = scientificFulfillmentLocator(projectId, record.recordSha256);
481
+ await regularParents(root, locator, true);
482
+ if (await pathExists(resolveContained(paths.control, locator))) {
483
+ if (canonicalJson(await readRecord(root, projectId, record.recordSha256)) !==
484
+ canonicalJson(record))
485
+ throw conflict();
486
+ }
487
+ else
488
+ await writeJsonAtomic(resolveContained(paths.control, locator), record, 0o444);
489
+ let mutation = await beginProjectMutation(root, "scientific-fulfillment", project, requestSha256);
490
+ try {
491
+ project.scientificDesign.fulfillmentSha256 = record.recordSha256;
492
+ const invalidatedRoles = roles.filter((role) => rank(role) >= rank(dueGate));
493
+ for (const role of invalidatedRoles)
494
+ project.scientificDesign.gates[role] = {
495
+ status: "pending",
496
+ packetSha256: null,
497
+ assessmentSha256: null,
498
+ reviewSha256: null,
499
+ reviewerSessionSha256: null,
500
+ };
501
+ project.updatedAt = new Date().toISOString();
502
+ mutation = await prepareProjectMutation(root, mutation, project);
503
+ await appendJournalEvent(paths.journal, "scientific.fulfillment.recorded", projectId, {
504
+ projectId,
505
+ designSha256: input.designSha256,
506
+ recordSha256: record.recordSha256,
507
+ parentFulfillmentSha256: input.parentFulfillmentSha256,
508
+ requestSha256,
509
+ invalidatedRoles,
510
+ mutation: projectMutationBinding(mutation),
511
+ });
512
+ await settleProjectMutation(root, mutation);
513
+ }
514
+ catch (error) {
515
+ await settleProjectMutation(root, mutation);
516
+ throw error;
517
+ }
518
+ return record;
519
+ });
520
+ }
521
+ export async function inspectScientificFulfillment(root, projectId) {
522
+ const project = await loadProject(root, projectId);
523
+ const view = await loadScientificFulfillmentView(root, project);
524
+ return {
525
+ projectId,
526
+ designSha256: project.scientificDesign.designSha256,
527
+ fulfillmentSha256: view.headSha256,
528
+ effectiveDesignSha256: view.effectiveSha256,
529
+ records: view.records.map((record) => ({
530
+ recordSha256: record.recordSha256,
531
+ parentFulfillmentSha256: record.parentFulfillmentSha256,
532
+ })),
533
+ pendingModels: view.contract.identity.modelStructures.filter((model) => model.implementationStatus === "pending-source-acquisition" ||
534
+ model.environmentLockStatus === "pending-runtime-lock"),
535
+ pendingParameters: view.contract.uncertaintyParameters.filter((parameter) => parameter.stateValueStatus === "pending-source-acquisition"),
536
+ policyRulesAwaitingIndependentJudgement: view.deferredObjectRuleIds,
537
+ };
538
+ }
539
+ //# sourceMappingURL=scientific-fulfillment.js.map