@deksden-com/dd-flow-cli 0.9.0-beta.136 → 0.9.0-beta.137
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/CHANGELOG.md +8 -0
- package/dist/build-info.json +3 -3
- package/dist/harness-runtime/DD-ZCODE.md +13 -0
- package/dist/harness-runtime/bin/dd-opencode.mjs +3 -3
- package/dist/harness-runtime/lib/dd-opencode-daemon.mjs +14 -1
- package/dist/harness-runtime/lib/dd-zcode-daemon.mjs +36 -1
- package/dist/harness-runtime/lib/dd-zcode.mjs +7 -3
- package/dist/harness-runtime/lib/opencode-plugin.mjs +3 -1
- package/dist/services/controller-child-observer.js +99 -0
- package/dist/services/controller-fanout.js +9 -2
- package/dist/services/run-controller.js +83 -8
- package/dist/services/stage-lifecycle.js +2 -1
- package/dist/services/stage-report-renderer.js +13 -0
- package/dist/services/vnext-fanout.js +66 -1
- package/dist/services/vnext-protocolize.js +60 -2
- package/package.json +1 -1
- package/dist/cli/command-metadata.js +0 -144
- package/dist/cli/command-option-descriptions.js +0 -186
- package/dist/cli/completion-cli.js +0 -70
- package/dist/cli/completion.js +0 -57
- package/dist/cli/error-output.js +0 -52
- package/dist/cli/executor-onboarding.js +0 -71
- package/dist/cli/harnesses.js +0 -84
- package/dist/cli/models.js +0 -39
- package/dist/cli/operator-profiles.js +0 -61
- package/dist/cli/output-options.js +0 -35
- package/dist/cli/response-file.js +0 -69
- package/dist/cli/setup.js +0 -147
- package/dist/services/cli-upgrade-allowlist.js +0 -27
- package/dist/services/discovery/agy-discovery.js +0 -2
- package/dist/services/discovery/codex-discovery.js +0 -2
- package/dist/services/discovery/droid-discovery.js +0 -2
- package/dist/services/discovery/grok-discovery.js +0 -2
- package/dist/services/discovery/opencode-discovery.js +0 -2
- package/dist/services/discovery/static.js +0 -31
- package/dist/services/discovery/zcode-discovery.js +0 -2
- package/dist/services/executor-onboarding.js +0 -56
- package/dist/services/harness-discovery.js +0 -29
- package/dist/services/harness-doctor.js +0 -75
- package/dist/services/harness-probe-effects.js +0 -51
- package/dist/services/harness-version-probe.js +0 -100
- package/dist/services/hybrid-contracts.js +0 -455
- package/dist/services/installation-lock.js +0 -92
- package/dist/services/model-catalog.js +0 -109
- package/dist/services/model-discovery.js +0 -46
- package/dist/services/operator-profiles.js +0 -236
- package/dist/services/release-archive-producer.js +0 -52
- package/dist/services/release-archive.js +0 -143
- package/dist/services/release-cache.js +0 -142
- package/dist/services/release-compatibility.js +0 -18
- package/dist/services/release-download.js +0 -67
- package/dist/services/release-envelope.js +0 -104
- package/dist/services/release-manifest.js +0 -45
- package/dist/services/release-payload.js +0 -114
- package/dist/services/release-producer.js +0 -63
- package/dist/services/release-provenance.js +0 -160
- package/dist/services/release-registry.js +0 -66
- package/dist/services/release-source.js +0 -194
- package/dist/services/setup.js +0 -377
|
@@ -1,455 +0,0 @@
|
|
|
1
|
-
import { createHash } from "node:crypto";
|
|
2
|
-
import fs from "node:fs";
|
|
3
|
-
import { Ajv2020 } from "ajv/dist/2020.js";
|
|
4
|
-
import { Ajv } from "ajv/dist/ajv.js";
|
|
5
|
-
/** Static E1 contracts only. No ingress, dispatch or runtime defaults are changed. */
|
|
6
|
-
export const hybridContractNames = [
|
|
7
|
-
"release-manifest", "harness-catalog", "model-catalog", "discovery-result",
|
|
8
|
-
"profile-settings", "flow-descriptor", "execution-settings",
|
|
9
|
-
"memory-bootstrap-receipt", "execution-bundle"
|
|
10
|
-
];
|
|
11
|
-
const hybridContractRegistry = new Set(hybridContractNames);
|
|
12
|
-
const record = (input) => input;
|
|
13
|
-
const list = (input) => input;
|
|
14
|
-
const string = (input) => input;
|
|
15
|
-
/** Deterministic JSON content identity, not a signature or secret-derived hash. */
|
|
16
|
-
export function hybridDigest(input) {
|
|
17
|
-
const canonical = (value) => {
|
|
18
|
-
if (Array.isArray(value))
|
|
19
|
-
return value.map(canonical);
|
|
20
|
-
if (value !== null && typeof value === "object") {
|
|
21
|
-
return Object.fromEntries(Object.entries(value).sort(([a], [b]) => a.localeCompare(b, "en"))
|
|
22
|
-
.map(([key, child]) => [key, canonical(child)]));
|
|
23
|
-
}
|
|
24
|
-
return value;
|
|
25
|
-
};
|
|
26
|
-
return createHash("sha256").update(JSON.stringify(canonical(input))).digest("hex");
|
|
27
|
-
}
|
|
28
|
-
/** E1 standalone validation. Runtime ingress must explicitly integrate it in E2/E3. */
|
|
29
|
-
export function validateHybridContract(name, input, context = {}) {
|
|
30
|
-
if (name === "flow-descriptor") {
|
|
31
|
-
if (!context.descriptorContext)
|
|
32
|
-
return [{ gate: "context", code: "missing_descriptor_context", path: "/" }];
|
|
33
|
-
return validateFlowDescriptor(input, context.descriptorContext);
|
|
34
|
-
}
|
|
35
|
-
const shape = validateHybridShape(name, input);
|
|
36
|
-
if (shape.length)
|
|
37
|
-
return shape;
|
|
38
|
-
const data = record(input), issues = [];
|
|
39
|
-
const fail = (code, path = "/") => issues.push({ gate: "context", code, path });
|
|
40
|
-
const seen = new Set();
|
|
41
|
-
const unique = (key, path) => {
|
|
42
|
-
if (seen.has(key))
|
|
43
|
-
fail("duplicate_identity", path);
|
|
44
|
-
seen.add(key);
|
|
45
|
-
};
|
|
46
|
-
const receipt = (reference, kind, required) => {
|
|
47
|
-
if (reference === null) {
|
|
48
|
-
if (required)
|
|
49
|
-
fail("missing_receipt", `/receipts/${kind}`);
|
|
50
|
-
return;
|
|
51
|
-
}
|
|
52
|
-
const ref = record(reference), fact = context.receipts?.get(string(ref.sha256));
|
|
53
|
-
if (!fact || fact.kind !== kind) {
|
|
54
|
-
fail("unverified_receipt", `/receipts/${kind}`);
|
|
55
|
-
return;
|
|
56
|
-
}
|
|
57
|
-
const expected = context.bootstrapIdentity;
|
|
58
|
-
if (expected && (fact.requestId !== expected.requestId || fact.projectRoot !== expected.projectRoot || fact.workspace !== expected.workspace)) {
|
|
59
|
-
fail("foreign_receipt", `/receipts/${kind}`);
|
|
60
|
-
}
|
|
61
|
-
};
|
|
62
|
-
if (name === "release-manifest") {
|
|
63
|
-
const canon = record(data.canon), engine = record(data.engine);
|
|
64
|
-
if (data.version !== canon.version)
|
|
65
|
-
fail("release_canon_version_mismatch");
|
|
66
|
-
const artifacts = [record(canon.archive), record(engine.archive)];
|
|
67
|
-
if (artifacts[0].url === artifacts[1].url || artifacts[0].sha256 === artifacts[1].sha256)
|
|
68
|
-
fail("conflicting_artifact_identity");
|
|
69
|
-
for (const value of list(data.qualified_routes)) {
|
|
70
|
-
const route = record(value);
|
|
71
|
-
unique([route.platform, route.harness, route.flow, route.delegation].join("/"), "/qualified_routes");
|
|
72
|
-
receipt(route.evidence, "route-qualification", true);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
else if (name === "harness-catalog") {
|
|
76
|
-
const families = new Set(["codex", "zcode", "agy", "opencode", "grok", "droid"]);
|
|
77
|
-
for (const value of list(data.entries)) {
|
|
78
|
-
const entry = record(value), id = string(entry.id);
|
|
79
|
-
unique(id, "/entries");
|
|
80
|
-
families.delete(id);
|
|
81
|
-
if (entry.probe_module !== `${id}-discovery`)
|
|
82
|
-
fail("foreign_probe_module", "/entries");
|
|
83
|
-
}
|
|
84
|
-
if (families.size)
|
|
85
|
-
fail("missing_harness_family", "/entries");
|
|
86
|
-
}
|
|
87
|
-
else if (name === "model-catalog") {
|
|
88
|
-
for (const value of list(data.entries)) {
|
|
89
|
-
const entry = record(value), qualification = record(entry.qualification);
|
|
90
|
-
unique([entry.harness, entry.provider, entry.model].join("/"), "/entries");
|
|
91
|
-
if (qualification.scope === "seed-only") {
|
|
92
|
-
if (qualification.evidence !== null)
|
|
93
|
-
fail("seed_has_qualification_receipt", "/entries");
|
|
94
|
-
}
|
|
95
|
-
else
|
|
96
|
-
receipt(qualification.evidence, `model-${qualification.scope}-qualification`, true);
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
else if (name === "discovery-result") {
|
|
100
|
-
if (data.probe_scope === "live")
|
|
101
|
-
receipt(data.live_authorization_receipt, "live-probe-authorization", true);
|
|
102
|
-
else if (data.live_authorization_receipt !== null)
|
|
103
|
-
fail("unexpected_live_authorization");
|
|
104
|
-
for (const value of list(data.entries)) {
|
|
105
|
-
const entry = record(value);
|
|
106
|
-
unique(string(entry.harness), "/entries");
|
|
107
|
-
const selected = list(entry.candidates).filter(candidate => record(candidate).selected === true);
|
|
108
|
-
if (selected.length > 1)
|
|
109
|
-
fail("ambiguous_selected_executable", "/entries");
|
|
110
|
-
if (entry.installed === "yes" && selected.length !== 1)
|
|
111
|
-
fail("missing_selected_executable", "/entries");
|
|
112
|
-
if (entry.installed === "no" && (selected.length || entry.compatible === "yes" || entry.protocol === "verified" || entry.auth === "verified" || entry.qualified === "yes"))
|
|
113
|
-
fail("contradictory_discovery_facts", "/entries");
|
|
114
|
-
if (entry.auth === "verified" || entry.protocol === "verified" || entry.qualified === "yes") {
|
|
115
|
-
if (data.probe_scope === "static" || list(entry.evidence).length === 0)
|
|
116
|
-
fail("unsupported_verified_fact", "/entries");
|
|
117
|
-
for (const evidence of list(entry.evidence))
|
|
118
|
-
receipt(evidence, "discovery-observation", true);
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
else if (name === "profile-settings") {
|
|
123
|
-
const profiles = new Map();
|
|
124
|
-
for (const value of list(data.profiles)) {
|
|
125
|
-
const entry = record(value), profile = record(entry.profile), id = string(entry.id);
|
|
126
|
-
unique(id, "/profiles");
|
|
127
|
-
profiles.set(id, profile);
|
|
128
|
-
if (profile.id !== id || hybridDigest(profile) !== entry.revision)
|
|
129
|
-
fail("profile_revision_mismatch", "/profiles");
|
|
130
|
-
}
|
|
131
|
-
const aliases = record(data.aliases);
|
|
132
|
-
for (const [alias, target] of Object.entries(aliases)) {
|
|
133
|
-
if (!profiles.has(string(target)))
|
|
134
|
-
fail("dangling_or_chained_alias", `/aliases/${alias}`);
|
|
135
|
-
if (profiles.has(alias))
|
|
136
|
-
fail("alias_id_collision", `/aliases/${alias}`);
|
|
137
|
-
}
|
|
138
|
-
if (data.default_profile !== null && !profiles.has(string(data.default_profile)) && !Object.hasOwn(aliases, string(data.default_profile)))
|
|
139
|
-
fail("dangling_default", "/default_profile");
|
|
140
|
-
const content = Object.fromEntries(Object.entries(data).filter(([key]) => key !== "revision"));
|
|
141
|
-
if (hybridDigest(content) !== data.revision)
|
|
142
|
-
fail("settings_revision_mismatch", "/revision");
|
|
143
|
-
}
|
|
144
|
-
else if (name === "execution-settings") {
|
|
145
|
-
if (!context.descriptor || !context.descriptorSha256 || !context.profileSettings) {
|
|
146
|
-
fail("missing_execution_context");
|
|
147
|
-
return issues;
|
|
148
|
-
}
|
|
149
|
-
const descriptor = record(context.descriptor), settings = record(context.profileSettings);
|
|
150
|
-
if (validateHybridShape("flow-descriptor", descriptor).length || validateHybridContract("profile-settings", settings).length) {
|
|
151
|
-
fail("invalid_execution_context");
|
|
152
|
-
return issues;
|
|
153
|
-
}
|
|
154
|
-
if (data.flow_id !== descriptor.id || data.descriptor_sha256 !== context.descriptorSha256)
|
|
155
|
-
fail("foreign_descriptor");
|
|
156
|
-
const available = new Map(list(settings.profiles).map(value => { const entry = record(value); return [string(entry.id), entry]; }));
|
|
157
|
-
const frozen = new Set();
|
|
158
|
-
for (const value of list(data.profiles)) {
|
|
159
|
-
const profile = record(value), id = string(profile.id);
|
|
160
|
-
unique(id, "/profiles");
|
|
161
|
-
frozen.add(id);
|
|
162
|
-
if (!available.has(id) || available.get(id).revision !== profile.revision)
|
|
163
|
-
fail("foreign_profile_revision", "/profiles");
|
|
164
|
-
}
|
|
165
|
-
const stageIds = new Set(list(descriptor.stages).map(value => string(record(value).id)));
|
|
166
|
-
for (const stage of Object.keys(record(data.stage_profiles)))
|
|
167
|
-
if (!stageIds.has(stage))
|
|
168
|
-
fail("foreign_stage", `/stage_profiles/${stage}`);
|
|
169
|
-
const routes = [data.root_profile, ...Object.values(record(data.stage_profiles)), ...list(data.work_profiles)].map(string);
|
|
170
|
-
for (const id of routes)
|
|
171
|
-
if (!frozen.has(id))
|
|
172
|
-
fail("unapproved_profile", "/routing");
|
|
173
|
-
if (data.execution === "attached")
|
|
174
|
-
receipt(data.attached_session_receipt, "attached-session-binding", true);
|
|
175
|
-
else if (data.attached_session_receipt !== null)
|
|
176
|
-
fail("unexpected_attached_binding");
|
|
177
|
-
const delegation = record(data.delegation);
|
|
178
|
-
const root = available.get(string(data.root_profile));
|
|
179
|
-
const sameProfile = (id) => {
|
|
180
|
-
const entry = available.get(id);
|
|
181
|
-
if (!entry || !root)
|
|
182
|
-
return false;
|
|
183
|
-
const meaningful = (value) => Object.fromEntries(Object.entries(record(value)).filter(([key]) => key !== "id"));
|
|
184
|
-
return hybridDigest(meaningful(entry.profile)) === hybridDigest(meaningful(root.profile));
|
|
185
|
-
};
|
|
186
|
-
if (data.stage_session_mode === "same_session") {
|
|
187
|
-
for (const route of Object.values(record(data.stage_profiles)).map(string)) {
|
|
188
|
-
if (!sameProfile(route))
|
|
189
|
-
fail("same_session_profile_override", "/stage_profiles");
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
if (delegation.mode === "native") {
|
|
193
|
-
receipt(delegation.native_capacity_receipt, "native-capacity", true);
|
|
194
|
-
for (const id of routes)
|
|
195
|
-
if (!sameProfile(id))
|
|
196
|
-
fail("native_profile_override", "/routing");
|
|
197
|
-
if (delegation.native_capacity_receipt !== null) {
|
|
198
|
-
const fact = context.receipts?.get(string(record(delegation.native_capacity_receipt).sha256));
|
|
199
|
-
if (!fact || !root || fact.harness !== record(root.profile).harness || fact.profileRevision !== root.revision
|
|
200
|
-
|| !Number.isInteger(fact.capacity) || fact.capacity < Number(delegation.max_parallel))
|
|
201
|
-
fail("native_capacity_scope_mismatch", "/delegation");
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
else if (delegation.native_capacity_receipt !== null)
|
|
205
|
-
fail("unexpected_native_capacity");
|
|
206
|
-
}
|
|
207
|
-
else if (name === "memory-bootstrap-receipt") {
|
|
208
|
-
const expected = context.bootstrapIdentity;
|
|
209
|
-
if (!expected) {
|
|
210
|
-
fail("missing_bootstrap_context");
|
|
211
|
-
return issues;
|
|
212
|
-
}
|
|
213
|
-
const workspace = record(data.workspace), execution = record(data.effective_execution), storage = record(data.storage);
|
|
214
|
-
if (data.request_id !== expected.requestId || data.project_root !== expected.projectRoot || workspace.path !== expected.workspace || data.baseline_sha256 !== expected.baselineSha256)
|
|
215
|
-
fail("foreign_bootstrap_identity");
|
|
216
|
-
if (execution.engine_sha256 !== expected.engineSha256 || execution.canon_sha256 !== expected.canonSha256)
|
|
217
|
-
fail("foreign_execution_identity");
|
|
218
|
-
receipt(storage.compatibility_receipt, "storage-compatibility", true);
|
|
219
|
-
const noOp = storage.route === "verified-no-op";
|
|
220
|
-
if (noOp) {
|
|
221
|
-
if (storage.outcome !== "no-op")
|
|
222
|
-
fail("invalid_no_op_outcome");
|
|
223
|
-
for (const field of ["drain_receipt", "backup_receipt", "plan_receipt", "apply_receipt", "verification_receipt"])
|
|
224
|
-
if (storage[field] !== null)
|
|
225
|
-
fail("no_op_has_migration_effect");
|
|
226
|
-
}
|
|
227
|
-
else if (storage.outcome === "applied") {
|
|
228
|
-
for (const [field, kind] of [["drain_receipt", "writer-drain"], ["backup_receipt", "storage-backup"], ["plan_receipt", "storage-plan"], ["apply_receipt", "storage-apply"], ["verification_receipt", "storage-verification"]])
|
|
229
|
-
receipt(storage[field], kind, true);
|
|
230
|
-
}
|
|
231
|
-
if (data.readiness === "ready" && !(noOp || storage.outcome === "applied"))
|
|
232
|
-
fail("storage_not_ready");
|
|
233
|
-
if (data.flow_id === "mb-upgrade" && data.project_current === null)
|
|
234
|
-
fail("missing_upgrade_baseline");
|
|
235
|
-
for (const value of list(data.effects)) {
|
|
236
|
-
const effect = record(value);
|
|
237
|
-
receipt(effect.receipt, string(effect.kind), true);
|
|
238
|
-
if (effect.kind === "storage-apply" && (noOp || storage.outcome !== "applied"))
|
|
239
|
-
fail("undeclared_storage_effect");
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
else if (name === "execution-bundle") {
|
|
243
|
-
if (!context.blobs || !context.engineChecksum || !context.engineBindingSha256 || !context.engineManifestSha256 || !context.bundleProvenance || !context.descriptorSha256
|
|
244
|
-
|| typeof context.bundleProjectRoot !== "string" || context.bundleProjectRoot.length === 0) {
|
|
245
|
-
fail("missing_bundle_context");
|
|
246
|
-
return issues;
|
|
247
|
-
}
|
|
248
|
-
if (record(data.descriptor).sha256 !== context.descriptorSha256)
|
|
249
|
-
fail("foreign_bundle_descriptor", "/descriptor");
|
|
250
|
-
if (hybridDigest(data.canon) !== hybridDigest(context.bundleProvenance.canon))
|
|
251
|
-
fail("foreign_bundle_canon", "/canon");
|
|
252
|
-
if (hybridDigest(data.project_pack) !== hybridDigest(context.bundleProvenance.project_pack))
|
|
253
|
-
fail("foreign_bundle_project_pack", "/project_pack");
|
|
254
|
-
const memoryFlow = data.flow_id === "mb-init" || data.flow_id === "mb-upgrade";
|
|
255
|
-
if (memoryFlow && context.bootstrapIdentity?.projectRoot !== context.bundleProjectRoot)
|
|
256
|
-
fail("foreign_bootstrap_identity", "/bootstrap_receipts");
|
|
257
|
-
if (memoryFlow && list(data.bootstrap_receipts).length === 0)
|
|
258
|
-
fail("missing_memory_bootstrap", "/bootstrap_receipts");
|
|
259
|
-
if (!memoryFlow && list(data.bootstrap_receipts).length > 0)
|
|
260
|
-
fail("foreign_memory_bootstrap", "/bootstrap_receipts");
|
|
261
|
-
const inventory = new Map();
|
|
262
|
-
for (const value of list(data.inventory)) {
|
|
263
|
-
const ref = record(value), path = string(ref.path);
|
|
264
|
-
unique(path, "/inventory");
|
|
265
|
-
inventory.set(path, string(ref.sha256));
|
|
266
|
-
const bytes = context.blobs.get(path);
|
|
267
|
-
if (!bytes)
|
|
268
|
-
fail("missing_bundle_blob", `/inventory/${path}`);
|
|
269
|
-
else if (createHash("sha256").update(bytes).digest("hex") !== ref.sha256)
|
|
270
|
-
fail("bundle_checksum_mismatch", `/inventory/${path}`);
|
|
271
|
-
}
|
|
272
|
-
for (const reference of [data.descriptor, data.execution_settings, data.profile_settings, data.engine_binding, data.engine_manifest, ...list(data.bootstrap_receipts)]) {
|
|
273
|
-
const ref = record(reference);
|
|
274
|
-
if (inventory.get(string(ref.path)) !== ref.sha256)
|
|
275
|
-
fail("unlisted_bundle_reference");
|
|
276
|
-
}
|
|
277
|
-
if (record(data.engine_binding).sha256 !== context.engineBindingSha256)
|
|
278
|
-
fail("foreign_engine_binding");
|
|
279
|
-
if (record(data.engine_manifest).sha256 !== context.engineManifestSha256)
|
|
280
|
-
fail("foreign_engine_manifest");
|
|
281
|
-
const loaded = (reference) => {
|
|
282
|
-
const bytes = context.blobs.get(string(record(reference).path));
|
|
283
|
-
try {
|
|
284
|
-
return bytes ? JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes)) : null;
|
|
285
|
-
}
|
|
286
|
-
catch {
|
|
287
|
-
fail("invalid_bundle_json");
|
|
288
|
-
return null;
|
|
289
|
-
}
|
|
290
|
-
};
|
|
291
|
-
const descriptor = loaded(data.descriptor), settings = loaded(data.execution_settings), profiles = loaded(data.profile_settings);
|
|
292
|
-
const binding = loaded(data.engine_binding);
|
|
293
|
-
const bindingSchema = JSON.parse(fs.readFileSync(new URL("../schemas/run-engine-binding.schema.json", import.meta.url), "utf8"));
|
|
294
|
-
const bindingValidator = new Ajv({ strict: false, validateFormats: false }).compile(bindingSchema);
|
|
295
|
-
const bindingValid = bindingValidator(binding);
|
|
296
|
-
if (!bindingValid)
|
|
297
|
-
fail("invalid_engine_binding");
|
|
298
|
-
else if (record(binding).run_id !== data.run_id || record(binding).project_root !== context.bundleProjectRoot
|
|
299
|
-
|| record(record(binding).engine).integrity_checksum !== context.engineChecksum)
|
|
300
|
-
fail("foreign_engine_binding");
|
|
301
|
-
const manifest = loaded(data.engine_manifest);
|
|
302
|
-
const manifestSchema = JSON.parse(fs.readFileSync(new URL("../schemas/engine-manifest.schema.json", import.meta.url), "utf8"));
|
|
303
|
-
const manifestValidator = new Ajv({ strict: false, validateFormats: false }).compile(manifestSchema);
|
|
304
|
-
if (!manifestValidator(manifest))
|
|
305
|
-
fail("invalid_engine_manifest");
|
|
306
|
-
else if (bindingValid) {
|
|
307
|
-
const selected = record(manifest), engine = record(record(binding).engine), integrity = record(selected.integrity);
|
|
308
|
-
if (["package_name", "package_version", "engine_version", "snapshot_root"].some(key => selected[key] !== engine[key])
|
|
309
|
-
|| integrity.mode !== "full_content" || integrity.checksum !== engine.integrity_checksum)
|
|
310
|
-
fail("foreign_engine_manifest");
|
|
311
|
-
}
|
|
312
|
-
if (!descriptor || !settings || !profiles)
|
|
313
|
-
fail("missing_bundle_contract");
|
|
314
|
-
else {
|
|
315
|
-
if (record(descriptor).id !== data.flow_id || record(settings).flow_id !== data.flow_id)
|
|
316
|
-
fail("foreign_bundle_flow");
|
|
317
|
-
const retained = new Map([...context.blobs].filter(([path]) => inventory.has(path)));
|
|
318
|
-
const descriptorContext = context.descriptorContext
|
|
319
|
-
? { ...context.descriptorContext, blobs: retained } : undefined;
|
|
320
|
-
const bundleContext = descriptorContext ? { ...context, descriptorContext } : context;
|
|
321
|
-
issues.push(...validateHybridContract("flow-descriptor", descriptor, bundleContext));
|
|
322
|
-
issues.push(...validateHybridContract("profile-settings", profiles, context));
|
|
323
|
-
issues.push(...validateHybridContract("execution-settings", settings, { ...context, descriptor,
|
|
324
|
-
descriptorSha256: string(record(data.descriptor).sha256), profileSettings: profiles }));
|
|
325
|
-
for (const reference of list(data.bootstrap_receipts)) {
|
|
326
|
-
const bootstrap = loaded(reference);
|
|
327
|
-
if (!bootstrap)
|
|
328
|
-
fail("missing_bundle_bootstrap");
|
|
329
|
-
else {
|
|
330
|
-
if (record(bootstrap).flow_id !== data.flow_id || record(bootstrap).readiness !== "ready")
|
|
331
|
-
fail("bundle_bootstrap_not_admitted");
|
|
332
|
-
issues.push(...validateHybridContract("memory-bootstrap-receipt", bootstrap, context));
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
else
|
|
338
|
-
fail("unknown_hybrid_contract");
|
|
339
|
-
return issues;
|
|
340
|
-
}
|
|
341
|
-
export function validateHybridShape(name, input) {
|
|
342
|
-
if (!hybridContractRegistry.has(name)) {
|
|
343
|
-
return [{ gate: "shape", code: "unknown_contract_name", path: "/" }];
|
|
344
|
-
}
|
|
345
|
-
const schemaUrl = new URL(`../schemas/${name}.schema.json`, import.meta.url);
|
|
346
|
-
const schema = JSON.parse(fs.readFileSync(schemaUrl, "utf8"));
|
|
347
|
-
const ajv = new Ajv2020({ allErrors: true, strict: true, validateFormats: false });
|
|
348
|
-
const validate = ajv.compile(schema);
|
|
349
|
-
if (validate(input))
|
|
350
|
-
return [];
|
|
351
|
-
return (validate.errors ?? []).map(error => ({
|
|
352
|
-
gate: "shape", code: error.keyword, path: error.instancePath || "/"
|
|
353
|
-
}));
|
|
354
|
-
}
|
|
355
|
-
/** Context must come from the trusted materializer, never from the descriptor. */
|
|
356
|
-
export function validateFlowDescriptor(input, context) {
|
|
357
|
-
const shape = validateHybridShape("flow-descriptor", input);
|
|
358
|
-
if (shape.length)
|
|
359
|
-
return shape;
|
|
360
|
-
const descriptor = input;
|
|
361
|
-
const issues = [];
|
|
362
|
-
const fail = (code, path) => issues.push({ gate: "context", code, path });
|
|
363
|
-
const ids = new Set(), directories = new Set();
|
|
364
|
-
const outgoing = new Map(), incoming = new Map();
|
|
365
|
-
for (const [index, stage] of descriptor.stages.entries()) {
|
|
366
|
-
if (ids.has(stage.id))
|
|
367
|
-
fail("duplicate_stage", `/stages/${index}/id`);
|
|
368
|
-
if (directories.has(stage.directory))
|
|
369
|
-
fail("duplicate_directory", `/stages/${index}/directory`);
|
|
370
|
-
ids.add(stage.id);
|
|
371
|
-
directories.add(stage.directory);
|
|
372
|
-
outgoing.set(stage.id, []);
|
|
373
|
-
incoming.set(stage.id, []);
|
|
374
|
-
if (!context.entryHandlers.has(stage.entry_handler))
|
|
375
|
-
fail("unknown_entry_handler", `/stages/${index}/entry_handler`);
|
|
376
|
-
if (!context.finishHandlers.has(stage.finish_handler))
|
|
377
|
-
fail("unknown_finish_handler", `/stages/${index}/finish_handler`);
|
|
378
|
-
for (const field of ["instruction", "result_schema"]) {
|
|
379
|
-
const reference = stage[field], bytes = context.blobs.get(reference.path);
|
|
380
|
-
if (!bytes) {
|
|
381
|
-
fail("missing_blob", `/stages/${index}/${field}`);
|
|
382
|
-
continue;
|
|
383
|
-
}
|
|
384
|
-
if (createHash("sha256").update(bytes).digest("hex") !== reference.sha256) {
|
|
385
|
-
fail("blob_checksum_mismatch", `/stages/${index}/${field}`);
|
|
386
|
-
continue;
|
|
387
|
-
}
|
|
388
|
-
if (field === "result_schema") {
|
|
389
|
-
try {
|
|
390
|
-
const schema = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
|
|
391
|
-
new Ajv2020({ strict: true, validateFormats: false }).compile(schema);
|
|
392
|
-
}
|
|
393
|
-
catch {
|
|
394
|
-
fail("invalid_result_schema", `/stages/${index}/${field}`);
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
if (!ids.has(descriptor.entry_stage))
|
|
400
|
-
fail("foreign_entry", "/entry_stage");
|
|
401
|
-
for (const terminal of descriptor.terminal_stages) {
|
|
402
|
-
if (!ids.has(terminal))
|
|
403
|
-
fail("foreign_terminal", "/terminal_stages");
|
|
404
|
-
}
|
|
405
|
-
const transitions = new Set();
|
|
406
|
-
for (const [index, edge] of descriptor.edges.entries()) {
|
|
407
|
-
const transition = `${edge.from}/${edge.on}`;
|
|
408
|
-
if (transitions.has(transition))
|
|
409
|
-
fail("ambiguous_transition", `/edges/${index}`);
|
|
410
|
-
transitions.add(transition);
|
|
411
|
-
if (!ids.has(edge.from) || !ids.has(edge.to)) {
|
|
412
|
-
fail("foreign_edge", `/edges/${index}`);
|
|
413
|
-
continue;
|
|
414
|
-
}
|
|
415
|
-
outgoing.get(edge.from).push(edge.to);
|
|
416
|
-
incoming.get(edge.to).push(edge.from);
|
|
417
|
-
if (descriptor.terminal_stages.includes(edge.from))
|
|
418
|
-
fail("terminal_outgoing", `/edges/${index}`);
|
|
419
|
-
}
|
|
420
|
-
const closure = (seeds, graph) => {
|
|
421
|
-
const seen = new Set(), pending = [...seeds];
|
|
422
|
-
while (pending.length) {
|
|
423
|
-
const id = pending.pop();
|
|
424
|
-
if (seen.has(id) || !ids.has(id))
|
|
425
|
-
continue;
|
|
426
|
-
seen.add(id);
|
|
427
|
-
pending.push(...(graph.get(id) ?? []));
|
|
428
|
-
}
|
|
429
|
-
return seen;
|
|
430
|
-
};
|
|
431
|
-
const reachable = closure([descriptor.entry_stage], outgoing);
|
|
432
|
-
const canFinish = closure(descriptor.terminal_stages, incoming);
|
|
433
|
-
for (const id of ids) {
|
|
434
|
-
if (!reachable.has(id))
|
|
435
|
-
fail("unreachable_stage", `/stages/${id}`);
|
|
436
|
-
if (!canFinish.has(id))
|
|
437
|
-
fail("no_terminal_path", `/stages/${id}`);
|
|
438
|
-
}
|
|
439
|
-
const degrees = new Map([...ids].map(id => [id, incoming.get(id).length]));
|
|
440
|
-
const ready = [...ids].filter(id => degrees.get(id) === 0);
|
|
441
|
-
let visited = 0;
|
|
442
|
-
while (ready.length) {
|
|
443
|
-
const id = ready.pop();
|
|
444
|
-
visited++;
|
|
445
|
-
for (const next of outgoing.get(id)) {
|
|
446
|
-
const count = degrees.get(next) - 1;
|
|
447
|
-
degrees.set(next, count);
|
|
448
|
-
if (count === 0)
|
|
449
|
-
ready.push(next);
|
|
450
|
-
}
|
|
451
|
-
}
|
|
452
|
-
if (visited !== ids.size)
|
|
453
|
-
fail("cyclic_graph", "/edges");
|
|
454
|
-
return issues;
|
|
455
|
-
}
|
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
import fs from "node:fs";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import os from "node:os";
|
|
4
|
-
import { randomUUID } from "node:crypto";
|
|
5
|
-
import { AppError } from "../shared/errors.js";
|
|
6
|
-
function refuse(reason) {
|
|
7
|
-
throw new AppError("installation_locked", "Installation owner requires reconciliation", 1, { reason, effect: "no_effect", help: "setup", next_action: "Replay the original exact request after its owner exits" });
|
|
8
|
-
}
|
|
9
|
-
function readOwner(file) {
|
|
10
|
-
if (!fs.lstatSync(file).isFile())
|
|
11
|
-
refuse("foreign_lock_object");
|
|
12
|
-
let owner;
|
|
13
|
-
try {
|
|
14
|
-
owner = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
15
|
-
}
|
|
16
|
-
catch {
|
|
17
|
-
return refuse("unreadable_lock_owner");
|
|
18
|
-
}
|
|
19
|
-
if (!owner || owner.schema_id !== "dd-flow/installation-lock@1" || !/^[a-f0-9]{64}$/.test(owner.identity) ||
|
|
20
|
-
owner.host !== os.hostname() || !Number.isSafeInteger(owner.pid) || owner.pid < 1 || !/^[a-f0-9-]{36}$/.test(owner.token))
|
|
21
|
-
refuse("unverified_lock_owner");
|
|
22
|
-
return owner;
|
|
23
|
-
}
|
|
24
|
-
function isDead(pid) {
|
|
25
|
-
try {
|
|
26
|
-
process.kill(pid, 0);
|
|
27
|
-
return false;
|
|
28
|
-
}
|
|
29
|
-
catch (error) {
|
|
30
|
-
return error.code === "ESRCH";
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
/** Publish a complete owner record atomically; only the same request can reclaim a kernel-confirmed dead owner. */
|
|
34
|
-
export function acquireInstallationLock(file, identity) {
|
|
35
|
-
if (!/^[a-f0-9]{64}$/.test(identity))
|
|
36
|
-
refuse("invalid_lock_identity");
|
|
37
|
-
const owner = { schema_id: "dd-flow/installation-lock@1", identity, host: os.hostname(), pid: process.pid, token: randomUUID() };
|
|
38
|
-
const temporary = path.join(path.dirname(file), `.lock-owner-${owner.token}.tmp`);
|
|
39
|
-
const fd = fs.openSync(temporary, "wx", 0o600);
|
|
40
|
-
try {
|
|
41
|
-
try {
|
|
42
|
-
fs.writeFileSync(fd, JSON.stringify(owner));
|
|
43
|
-
fs.fsyncSync(fd);
|
|
44
|
-
}
|
|
45
|
-
finally {
|
|
46
|
-
fs.closeSync(fd);
|
|
47
|
-
}
|
|
48
|
-
for (let attempt = 0; attempt < 3; attempt++) {
|
|
49
|
-
try {
|
|
50
|
-
fs.linkSync(temporary, file);
|
|
51
|
-
return { token: owner.token, release: () => {
|
|
52
|
-
const retained = readOwner(file);
|
|
53
|
-
if (retained.token !== owner.token || retained.pid !== process.pid || retained.identity !== identity)
|
|
54
|
-
refuse("lock_owner_changed");
|
|
55
|
-
fs.unlinkSync(file);
|
|
56
|
-
} };
|
|
57
|
-
}
|
|
58
|
-
catch (error) {
|
|
59
|
-
if (error.code !== "EEXIST")
|
|
60
|
-
throw error;
|
|
61
|
-
const previous = readOwner(file);
|
|
62
|
-
if (previous.identity !== identity)
|
|
63
|
-
refuse("different_request_owns_lock");
|
|
64
|
-
if (!isDead(previous.pid))
|
|
65
|
-
refuse("owner_process_alive_or_unknown");
|
|
66
|
-
// Atomic quarantine retains crash evidence. A competing reclaimer must retry, not overwrite an owner.
|
|
67
|
-
const quarantine = `${file}.abandoned-${previous.token}`;
|
|
68
|
-
if (fs.existsSync(quarantine))
|
|
69
|
-
refuse("ambiguous_abandoned_lock");
|
|
70
|
-
const before = fs.lstatSync(file);
|
|
71
|
-
const checked = readOwner(file);
|
|
72
|
-
if (checked.token !== previous.token || !isDead(checked.pid))
|
|
73
|
-
refuse("lock_owner_changed");
|
|
74
|
-
try {
|
|
75
|
-
fs.linkSync(file, quarantine);
|
|
76
|
-
}
|
|
77
|
-
catch {
|
|
78
|
-
return refuse("concurrent_lock_reconciliation");
|
|
79
|
-
}
|
|
80
|
-
const after = fs.lstatSync(file);
|
|
81
|
-
if (before.ino !== after.ino || before.dev !== after.dev || readOwner(file).token !== previous.token)
|
|
82
|
-
refuse("lock_owner_changed");
|
|
83
|
-
fs.unlinkSync(file);
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
return refuse("lock_contention");
|
|
87
|
-
}
|
|
88
|
-
finally {
|
|
89
|
-
if (fs.existsSync(temporary))
|
|
90
|
-
fs.unlinkSync(temporary);
|
|
91
|
-
}
|
|
92
|
-
}
|
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
import { AppError } from "../shared/errors.js";
|
|
2
|
-
import { validateHybridContract } from "./hybrid-contracts.js";
|
|
3
|
-
import { mergeModelListing } from "./model-discovery.js";
|
|
4
|
-
import { readBoundedReleaseFile } from "./release-source.js";
|
|
5
|
-
// Reviewed adapter fallbacks and retained eval configurations, not vendor
|
|
6
|
-
// availability promises or recommended defaults. Missing facts stay unknown;
|
|
7
|
-
// historical eval qualification/capacity claims are deliberately not imported.
|
|
8
|
-
const seed = {
|
|
9
|
-
schema_id: "dd-flow/model-catalog@1", version: "1.1.0",
|
|
10
|
-
entries: [{ harness: "agy", provider: "google", model: "gemini-3.1-pro-high",
|
|
11
|
-
settings: { reasoning: ["unknown"], mode: ["unknown"] },
|
|
12
|
-
provenance: { kind: "reviewed-seed", source: "https://github.com/deksden-com/dd-flow-cli/blob/2ba2ce2364705cf59fdc48bdd5dc5b37fb7cd784/src/harness-runtime/lib/dd-agy.mjs#L7",
|
|
13
|
-
reviewed_at: "2026-10-07T00:00:00Z" }, availability: "not-observed", qualification: { scope: "seed-only", evidence: null } },
|
|
14
|
-
{ harness: "grok", provider: "xai", model: "grok-4.6",
|
|
15
|
-
settings: { reasoning: ["high"], mode: ["bypassPermissions"] },
|
|
16
|
-
provenance: { kind: "reviewed-seed", source: "https://github.com/deksden-com/dd-flow-cli/blob/2ba2ce2364705cf59fdc48bdd5dc5b37fb7cd784/src/harness-runtime/lib/dd-grok.mjs#L48",
|
|
17
|
-
reviewed_at: "2026-10-07T00:00:00Z" }, availability: "not-observed", qualification: { scope: "seed-only", evidence: null } },
|
|
18
|
-
{ harness: "codex", provider: "unknown", model: "gpt-6.1-sol",
|
|
19
|
-
settings: { reasoning: ["high"], mode: ["unknown"] },
|
|
20
|
-
provenance: { kind: "reviewed-seed", source: "https://github.com/deksden-com/dd-eval/blob/9715970544096fc0288ac8e18f95d4e35997be54/profiles/codex-desktop-gpt-6-1-sol-high.json",
|
|
21
|
-
reviewed_at: "2026-10-07T00:00:00Z" }, availability: "not-observed", qualification: { scope: "seed-only", evidence: null } },
|
|
22
|
-
{ harness: "zcode", provider: "builtin:zai-coding-plan", model: "GLM-5.3",
|
|
23
|
-
settings: { reasoning: ["high"], mode: ["yolo"] },
|
|
24
|
-
provenance: { kind: "reviewed-seed", source: "https://github.com/deksden-com/dd-eval/blob/9715970544096fc0288ac8e18f95d4e35997be54/profiles/zcode-acp-zai-glm-5-3-high.json",
|
|
25
|
-
reviewed_at: "2026-10-07T00:00:00Z" }, availability: "not-observed", qualification: { scope: "seed-only", evidence: null } },
|
|
26
|
-
{ harness: "opencode", provider: "opencode", model: "big-pickle",
|
|
27
|
-
settings: { reasoning: ["default"], mode: ["build"] },
|
|
28
|
-
provenance: { kind: "reviewed-seed", source: "https://github.com/deksden-com/dd-eval/blob/9715970544096fc0288ac8e18f95d4e35997be54/profiles/opencode-server-opencode-big-pickle-default.json",
|
|
29
|
-
reviewed_at: "2026-10-07T00:00:00Z" }, availability: "not-observed", qualification: { scope: "seed-only", evidence: null } },
|
|
30
|
-
{ harness: "droid", provider: "openai", model: "gpt-5.6-sol",
|
|
31
|
-
settings: { reasoning: ["high"], mode: ["auto-high"] },
|
|
32
|
-
provenance: { kind: "reviewed-seed", source: "https://github.com/deksden-com/dd-eval/blob/9715970544096fc0288ac8e18f95d4e35997be54/profiles/droid-cli-openai-gpt-5-6-sol-high.json",
|
|
33
|
-
reviewed_at: "2026-10-07T00:00:00Z" }, availability: "not-observed", qualification: { scope: "seed-only", evidence: null } }]
|
|
34
|
-
};
|
|
35
|
-
const families = ["codex", "zcode", "agy", "opencode", "grok", "droid"];
|
|
36
|
-
const identifier = (value, max = 128) => typeof value === "string" && value.length <= max && /^[A-Za-z0-9][A-Za-z0-9._:/+-]*$/.test(value);
|
|
37
|
-
const providerId = (value) => typeof value === "string" && /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/.test(value);
|
|
38
|
-
function invalid() {
|
|
39
|
-
throw new AppError("model_catalog_input", "Model catalog input requires reviewed seed data and an explicit observation scope; see dd-flow models --help", 2, { effect: "no_effect", help: "models" });
|
|
40
|
-
}
|
|
41
|
-
function readJson(file) {
|
|
42
|
-
let bytes;
|
|
43
|
-
try {
|
|
44
|
-
bytes = readBoundedReleaseFile(file, 4 * 1024 * 1024);
|
|
45
|
-
}
|
|
46
|
-
catch {
|
|
47
|
-
throw new AppError("model_input_unavailable", "Model input must be a bounded readable regular file; see dd-flow models --help", 1, { effect: "no_effect", help: "models" });
|
|
48
|
-
}
|
|
49
|
-
try {
|
|
50
|
-
return JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
|
|
51
|
-
}
|
|
52
|
-
catch {
|
|
53
|
-
return invalid();
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
export function readModelSeed(file) {
|
|
57
|
-
const input = file === undefined ? structuredClone(seed) : readJson(file);
|
|
58
|
-
if (validateHybridContract("model-catalog", input).length)
|
|
59
|
-
invalid();
|
|
60
|
-
const catalog = input;
|
|
61
|
-
if (catalog.entries.length > 10_000 || catalog.entries.some(entry => {
|
|
62
|
-
let source;
|
|
63
|
-
try {
|
|
64
|
-
source = new URL(entry.provenance.source);
|
|
65
|
-
}
|
|
66
|
-
catch {
|
|
67
|
-
return true;
|
|
68
|
-
}
|
|
69
|
-
const reviewed = entry.provenance.reviewed_at;
|
|
70
|
-
return entry.qualification.scope !== "seed-only" || !providerId(entry.provider) || !identifier(entry.model, 256)
|
|
71
|
-
|| !/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/.test(reviewed) || !Number.isFinite(Date.parse(reviewed))
|
|
72
|
-
|| new Date(reviewed).toISOString() !== reviewed.replace(/Z$/, reviewed.includes(".") ? "Z" : ".000Z")
|
|
73
|
-
|| source.protocol !== "https:" || source.username !== "" || source.password !== "" || source.search !== ""
|
|
74
|
-
|| entry.provenance.source.length > 2048
|
|
75
|
-
|| [...entry.settings.reasoning, ...entry.settings.mode].some(value => !identifier(value, 64));
|
|
76
|
-
}))
|
|
77
|
-
invalid();
|
|
78
|
-
return structuredClone(catalog);
|
|
79
|
-
}
|
|
80
|
-
/** Offline reader only. A supplied snapshot is an observation, not account authorization. */
|
|
81
|
-
export function listOperatorModels(input) {
|
|
82
|
-
if (input.harness !== undefined && !families.includes(input.harness) || input.provider !== undefined && !providerId(input.provider)
|
|
83
|
-
|| input.model !== undefined && !identifier(input.model, 256)
|
|
84
|
-
|| input.accountSha256 !== undefined && !/^[a-f0-9]{64}$/.test(input.accountSha256)
|
|
85
|
-
|| (input.accountSha256 !== undefined || input.observationFile !== undefined) && (!input.harness || !input.provider || !input.accountSha256))
|
|
86
|
-
invalid();
|
|
87
|
-
// Validate all selectors before opening operator files.
|
|
88
|
-
const catalog = readModelSeed(input.seedFile);
|
|
89
|
-
let entries, reason, observedAt = null;
|
|
90
|
-
if (input.accountSha256) {
|
|
91
|
-
const scope = { harness: input.harness, provider: input.provider, account_sha256: input.accountSha256 };
|
|
92
|
-
const observation = input.observationFile === undefined ? undefined : readJson(input.observationFile);
|
|
93
|
-
const merged = mergeModelListing(catalog, scope, observation);
|
|
94
|
-
entries = merged.entries.map(entry => ({ harness: scope.harness, provider: scope.provider, ...entry }));
|
|
95
|
-
reason = merged.reason;
|
|
96
|
-
observedAt = merged.observed_at;
|
|
97
|
-
}
|
|
98
|
-
else {
|
|
99
|
-
entries = catalog.entries.filter(entry => (!input.harness || entry.harness === input.harness) && (!input.provider || entry.provider === input.provider))
|
|
100
|
-
.map(entry => ({ harness: entry.harness, provider: entry.provider, model: entry.model, seed: structuredClone(entry),
|
|
101
|
-
availability: "unknown", account_access: "unknown", qualified: "unknown" }));
|
|
102
|
-
reason = "account_scope_not_selected";
|
|
103
|
-
}
|
|
104
|
-
if (input.model)
|
|
105
|
-
entries = entries.filter(entry => entry.model === input.model);
|
|
106
|
-
return { catalog_version: catalog.version, observed_at: observedAt, reason, entries,
|
|
107
|
-
families: families.map(harness => ({ harness, seed_status: catalog.entries.some(entry => entry.harness === harness) ? "reviewed_entries" : "not_curated",
|
|
108
|
-
account_access: "unknown", qualified: "unknown" })) };
|
|
109
|
-
}
|