lastlight-shared 0.1.6 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,592 @@
1
+ /**
2
+ * The PURE half of the per-repository config layer (issue #180) — the schema,
3
+ * the operator bounds, and the validators/merger that enforce them.
4
+ *
5
+ * A managed repo may commit a `.lastlight/` directory that overrides a BOUNDED
6
+ * subset of Last Light's config for runs against that repo. The directory
7
+ * mirrors a deployment overlay's on-disk shape exactly:
8
+ *
9
+ * .lastlight/
10
+ * lastlight.yml # the config override
11
+ * workflows/prompts/*.md # prompt overrides
12
+ * skills/<name>/SKILL.md # skill overrides
13
+ * agent-context/*.md # persona/rules additions
14
+ *
15
+ * ── Why this lives in `lastlight-shared` ──────────────────────────────────
16
+ * Two consumers need exactly the same answers about a `.lastlight/` tree:
17
+ * - `lastlight-core` at runtime, after fetching the layer from GitHub
18
+ * (`apps/server/src/config/repo-config.ts`, which owns the impure half —
19
+ * the fetch, the TTL cache, the on-disk unpack — and re-exports everything
20
+ * here so its import surface is unchanged); and
21
+ * - the `lastlight` CLI, offline, inside a user's own code repo
22
+ * (`lastlight repo config validate`).
23
+ * The CLI must never gain a dependency edge to core, so the bounds logic sits
24
+ * here — the one package both already depend on. Nothing in this file touches
25
+ * the filesystem, the network, or runtime config: it is a function of its
26
+ * arguments, which is also what makes it directly unit-testable.
27
+ *
28
+ * ── The trust rule ────────────────────────────────────────────────────────
29
+ * The layer is ALWAYS read from the repo's **default branch**. Never a PR head.
30
+ * Never the sandbox checkout. That rule is enforced by the fetcher in core;
31
+ * this module only describes what may appear in the layer once it arrives.
32
+ *
33
+ * ── The failure rule ──────────────────────────────────────────────────────
34
+ * Warn, drop the bad bits, run anyway. A repo's config file must never fail a
35
+ * run. Invalid YAML drops the whole file; an unknown or out-of-bounds key drops
36
+ * just that key. Every rejection becomes a structured {@link RepoConfigWarning}
37
+ * so the dashboard/CLI can report it back to the repo's owners.
38
+ */
39
+ import { join, resolve, sep } from "node:path";
40
+ import { parse as parseYaml } from "yaml";
41
+ import { providerByPrefix, oauthProviderByModelPrefix } from "./providers.js";
42
+ // ---------------------------------------------------------------------------
43
+ // Bounds
44
+ // ---------------------------------------------------------------------------
45
+ /** Hard cap on the unpacked `.lastlight/` layer, in bytes. */
46
+ export const REPO_CONFIG_MAX_BYTES = 2 * 1024 * 1024;
47
+ /** Hard cap on the number of files in the unpacked layer. */
48
+ export const REPO_CONFIG_MAX_FILES = 200;
49
+ /** The config file inside `.lastlight/`. Exactly this name — no `.yaml` variant. */
50
+ export const REPO_CONFIG_FILE = "lastlight.yml";
51
+ /** Git filemode for a symlink blob. Rejected on sight — see {@link sanitizeRepoFiles}. */
52
+ const GIT_MODE_SYMLINK = "120000";
53
+ /**
54
+ * The allow-list a deployment gets when it says nothing. Kept as a constant so
55
+ * the normaliser, the docs, `config/default.yaml` and the CLI's offline
56
+ * validator can't drift apart.
57
+ *
58
+ * It MUST stay identical to `repoConfig.allowKeys` in
59
+ * `apps/server/config/default.yaml`: this list is what a deployment falls back
60
+ * to when config isn't in reach (`repoConfigPolicy()`'s no-config path, and the
61
+ * CLI's offline `lastlight repo config validate`), so a divergence tells repo
62
+ * owners their file is out of bounds when it isn't. Pinned by the
63
+ * `default allow-list` block in `apps/server/tests/config/repo-config-shared.test.ts`
64
+ * — the two drifted apart once already, silently.
65
+ */
66
+ export const DEFAULT_REPO_CONFIG_ALLOW_KEYS = [
67
+ "models",
68
+ "variants",
69
+ "crons",
70
+ "disabled.workflows",
71
+ "disabled.crons",
72
+ "approval",
73
+ ];
74
+ /**
75
+ * The bounds to assume when no deployment config is in reach — the shipped
76
+ * defaults. The offline CLI validator (`lastlight repo config validate`) uses
77
+ * this: it can't know the operator's narrowing, so it validates against the
78
+ * widest shipped policy and says so.
79
+ */
80
+ export function defaultRepoConfigPolicy() {
81
+ return {
82
+ enabled: true,
83
+ allowKeys: [...DEFAULT_REPO_CONFIG_ALLOW_KEYS],
84
+ allowedModels: null,
85
+ allowAssets: true,
86
+ };
87
+ }
88
+ /**
89
+ * Classify a path relative to `.lastlight/`, or `null` when it is not part of
90
+ * the layer at all.
91
+ *
92
+ * `null` is a routine answer, not an error: `.lastlight/` is shared real
93
+ * estate. With `buildAssets.location: repo` the build workflow commits its
94
+ * handoff docs to `.lastlight/<issueKey>/*.md`, and repos are free to keep
95
+ * other things there. Those are simply outside the layer — the warning path is
96
+ * reserved for files that LOOK like layer assets but have the wrong shape
97
+ * (see {@link sanitizeRepoFiles}).
98
+ */
99
+ export function repoLayerPathKind(path) {
100
+ if (path === REPO_CONFIG_FILE)
101
+ return "config";
102
+ if (/^workflows\/prompts\/(?:[^/]+\/)*[^/]+\.md$/.test(path))
103
+ return "prompt";
104
+ if (/^skills\/[^/]+\/(?:[^/]+\/)*[^/]+$/.test(path))
105
+ return "skill";
106
+ if (/^agent-context\/[^/]+\.md$/.test(path))
107
+ return "agent-context";
108
+ return null;
109
+ }
110
+ /** True when a path is a workflow DEFINITION — the one thing a repo may never contribute. */
111
+ export function isRepoWorkflowPath(path) {
112
+ return /^workflows\/[^/]+\.ya?ml$/.test(path);
113
+ }
114
+ /** Top-level directories inside `.lastlight/` that the layer claims. */
115
+ const LAYER_DIRS = ["workflows/", "skills/", "agent-context/"];
116
+ // ---------------------------------------------------------------------------
117
+ // File-level guards
118
+ // ---------------------------------------------------------------------------
119
+ /**
120
+ * Reject a relative path that can't be safely joined onto a root. Modelled on
121
+ * `assertSafeRelative` in `./workflow-loader.ts`, but returning a reason
122
+ * instead of throwing — a hostile repo must not be able to abort a run by
123
+ * committing a bad filename.
124
+ */
125
+ function unsafeRelativeReason(path) {
126
+ if (!path)
127
+ return "path is empty";
128
+ if (path.startsWith("/") || /^[A-Za-z]:/.test(path))
129
+ return "path is absolute";
130
+ if (path.includes("\0"))
131
+ return "path contains a NUL byte";
132
+ if (path.includes("\\"))
133
+ return "path contains a backslash";
134
+ const segments = path.split("/");
135
+ if (segments.some((s) => s === "" || s === "." || s === ".."))
136
+ return "path contains a traversal segment";
137
+ return null;
138
+ }
139
+ /** True when `filePath` resolves inside `root`. Same check as the workflow loader's `isInside`. */
140
+ function isInside(filePath, root) {
141
+ const r = resolve(root);
142
+ const f = resolve(filePath);
143
+ return f === r || f.startsWith(r + sep);
144
+ }
145
+ /**
146
+ * Apply every file-level bound to a `.lastlight/` subtree.
147
+ *
148
+ * Pure — takes the blobs, returns the ones that may be written to disk plus a
149
+ * warning per rejection. The caps are enforced here as well as at fetch time
150
+ * (the client stops downloading at its own limits) because this function is the
151
+ * last gate before anything touches the filesystem, and defence in depth is
152
+ * cheap.
153
+ *
154
+ * Generic in the file type so a caller carrying a richer blob shape (core's
155
+ * `RepoConfigFile`) gets its own type back rather than a widened one.
156
+ */
157
+ export function sanitizeRepoFiles(files, policy, repo) {
158
+ const warnings = [];
159
+ const accepted = [];
160
+ const warn = (code, path, message) => warnings.push({ code, repo, path, message });
161
+ let bytes = 0;
162
+ let unrecognised = 0;
163
+ let assetsDropped = 0;
164
+ for (const file of files) {
165
+ const path = file.path;
166
+ const unsafe = unsafeRelativeReason(path);
167
+ if (unsafe) {
168
+ warn("path-escape", path, `Ignored .lastlight/${path}: ${unsafe}.`);
169
+ continue;
170
+ }
171
+ // Second, independent check: even a segment-clean path must land under the
172
+ // layer root once joined. Cheap, and catches anything the string checks miss.
173
+ if (!isInside(join("/layer-root", path), "/layer-root")) {
174
+ warn("path-escape", path, `Ignored .lastlight/${path}: it escapes the layer directory.`);
175
+ continue;
176
+ }
177
+ // Symlinks are the classic unpack escape — a `skills/x/SKILL.md` symlink
178
+ // pointing at /etc/passwd or at the harness's own secrets would otherwise be
179
+ // read as layer content. Only regular files are ever materialized.
180
+ if (file.mode === GIT_MODE_SYMLINK) {
181
+ warn("symlink", path, `Ignored .lastlight/${path}: symlinks are not allowed in a repo config layer.`);
182
+ continue;
183
+ }
184
+ if (file.mode !== "100644" && file.mode !== "100755") {
185
+ warn("symlink", path, `Ignored .lastlight/${path}: only regular files are allowed (mode ${file.mode}).`);
186
+ continue;
187
+ }
188
+ // Repos may contribute PROMPTS, never workflows: a workflow YAML defines
189
+ // phases, skills and permission profiles, which is the operator's call.
190
+ if (isRepoWorkflowPath(path)) {
191
+ warn("workflow-not-allowed", path, `Ignored .lastlight/${path}: a repo may override prompts (workflows/prompts/*.md), not workflow definitions.`);
192
+ continue;
193
+ }
194
+ const kind = repoLayerPathKind(path);
195
+ if (!kind) {
196
+ // Inside a layer directory but the wrong shape → the repo probably meant
197
+ // it as an asset. Counted and reported once, so a big stray directory
198
+ // can't flood the warning list.
199
+ if (LAYER_DIRS.some((d) => path.startsWith(d)))
200
+ unrecognised++;
201
+ continue;
202
+ }
203
+ if (kind !== "config" && !policy.allowAssets) {
204
+ assetsDropped++;
205
+ continue;
206
+ }
207
+ if (accepted.length >= REPO_CONFIG_MAX_FILES) {
208
+ warn("file-count-cap", path, `Ignored .lastlight/${path}: the repo config layer is capped at ${REPO_CONFIG_MAX_FILES} files.`);
209
+ continue;
210
+ }
211
+ if (bytes + file.content.length > REPO_CONFIG_MAX_BYTES) {
212
+ warn("size-cap", path, `Ignored .lastlight/${path}: the repo config layer is capped at ${REPO_CONFIG_MAX_BYTES} bytes.`);
213
+ continue;
214
+ }
215
+ bytes += file.content.length;
216
+ accepted.push(file);
217
+ }
218
+ if (unrecognised > 0) {
219
+ warn("unrecognised-asset", ".lastlight", `Ignored ${unrecognised} file(s) under .lastlight/: a repo layer may contain ${REPO_CONFIG_FILE}, ` +
220
+ `workflows/prompts/*.md, skills/<name>/SKILL.md and agent-context/*.md.`);
221
+ }
222
+ if (assetsDropped > 0) {
223
+ warn("assets-not-allowed", ".lastlight", `Ignored ${assetsDropped} asset file(s) under .lastlight/: this deployment sets repoConfig.allowAssets: false.`);
224
+ }
225
+ return { accepted, warnings };
226
+ }
227
+ // ---------------------------------------------------------------------------
228
+ // Config-level bounds
229
+ // ---------------------------------------------------------------------------
230
+ /**
231
+ * Parse a repo's `lastlight.yml`. Malformed YAML, or YAML that isn't a mapping,
232
+ * drops the WHOLE file — a half-understood config file is more dangerous than
233
+ * none, and the repo gets a warning either way.
234
+ */
235
+ export function parseRepoConfigYaml(raw, repo) {
236
+ let parsed;
237
+ try {
238
+ parsed = parseYaml(raw);
239
+ }
240
+ catch (err) {
241
+ const message = err instanceof Error ? err.message : String(err);
242
+ return {
243
+ warnings: [
244
+ {
245
+ code: "invalid-yaml",
246
+ repo,
247
+ path: REPO_CONFIG_FILE,
248
+ message: `Ignored .lastlight/${REPO_CONFIG_FILE}: it is not valid YAML (${message}).`,
249
+ },
250
+ ],
251
+ };
252
+ }
253
+ // An empty file parses to null — legal, just carries nothing.
254
+ if (parsed === null || parsed === undefined)
255
+ return { warnings: [] };
256
+ if (!isPlainObject(parsed)) {
257
+ return {
258
+ warnings: [
259
+ {
260
+ code: "not-a-mapping",
261
+ repo,
262
+ path: REPO_CONFIG_FILE,
263
+ message: `Ignored .lastlight/${REPO_CONFIG_FILE}: the top level must be a mapping.`,
264
+ },
265
+ ],
266
+ };
267
+ }
268
+ return { config: parsed, warnings: [] };
269
+ }
270
+ /**
271
+ * True when `path` (a dotted config path) is admitted by the allow-list. An
272
+ * entry admits itself and everything beneath it — `models` admits
273
+ * `models.architect`; `disabled.workflows` does NOT admit `disabled.prompts`.
274
+ */
275
+ function isAllowedKey(path, allowKeys) {
276
+ return allowKeys.some((allowed) => path === allowed || path.startsWith(`${allowed}.`));
277
+ }
278
+ /**
279
+ * True when a dotted path is a PREFIX of some allow-list entry — i.e. the repo
280
+ * must be allowed to descend into it even though the container itself isn't
281
+ * directly settable (`disabled` for `disabled.workflows`).
282
+ */
283
+ function isAllowedPrefix(path, allowKeys) {
284
+ return allowKeys.some((allowed) => allowed.startsWith(`${path}.`));
285
+ }
286
+ /**
287
+ * Reduce a repo's raw `lastlight.yml` to the sub-tree it is actually allowed to
288
+ * contribute. Pure. Everything dropped produces a warning.
289
+ *
290
+ * `base` supplies the operator's current values, which the `approval` add-only
291
+ * rule needs: a repo may raise a gate, never lower one.
292
+ */
293
+ export function sanitizeRepoConfigLayer(raw, policy, base, repo) {
294
+ const warnings = [];
295
+ const layer = {};
296
+ if (!raw)
297
+ return { layer, warnings };
298
+ const warn = (code, path, message) => warnings.push({ code, repo, path, message });
299
+ for (const [key, value] of Object.entries(raw)) {
300
+ const allowed = isAllowedKey(key, policy.allowKeys);
301
+ const descendable = isAllowedPrefix(key, policy.allowKeys);
302
+ if (!allowed && !descendable) {
303
+ warn("key-not-allowed", key, `Ignored "${key}" in .lastlight/${REPO_CONFIG_FILE}: a repo may not set this key.`);
304
+ continue;
305
+ }
306
+ switch (key) {
307
+ case "models":
308
+ assignIfAny(layer, "models", sanitizeModels(value, policy, warn));
309
+ break;
310
+ case "variants":
311
+ assignIfAny(layer, "variants", sanitizeStringMap(value, "variants", policy, warn));
312
+ break;
313
+ case "crons":
314
+ // Accepted and deliberately NOT merged. Cron participation
315
+ // (`crons: {enable, disable}`) is read straight off the RAW layer by
316
+ // the scheduler at tick time — `repoCronPrefs` in
317
+ // `apps/server/src/cron/repo-crons.ts` — because it decides WHICH repos
318
+ // a cron fans out over, which happens before any run (and therefore
319
+ // before any merged per-run config) exists. So it is not part of
320
+ // {@link RepoMergedConfig} and has no merged-shape validator here; the
321
+ // `case` exists only to stop the `default:` branch reporting a
322
+ // "no repo-layer validator" warning for a block that is fully
323
+ // supported. Do NOT "fix" this by adding `crons` to the merged shape:
324
+ // that would give one block two owners with two bounds checks.
325
+ break;
326
+ case "disabled":
327
+ assignIfAny(layer, "disabled", sanitizeDisabled(value, policy, warn));
328
+ break;
329
+ case "approval":
330
+ assignIfAny(layer, "approval", sanitizeApproval(value, policy, base, warn));
331
+ break;
332
+ default:
333
+ // Allow-listed by the operator but not a key this module knows how to
334
+ // bound. Refusing is the safe direction: an unbounded pass-through
335
+ // would let an operator widen the layer past what's been reviewed.
336
+ warn("key-not-allowed", key, `Ignored "${key}" in .lastlight/${REPO_CONFIG_FILE}: it has no repo-layer validator.`);
337
+ }
338
+ }
339
+ return { layer, warnings };
340
+ }
341
+ /** Drop empty sub-trees so the merge never records a `repo` provenance for nothing. */
342
+ function assignIfAny(target, key, value) {
343
+ if (value && Object.keys(value).length > 0)
344
+ target[key] = value;
345
+ }
346
+ function sanitizeModels(raw, policy, warn) {
347
+ if (!isPlainObject(raw)) {
348
+ warn("invalid-value", "models", `Ignored "models" in .lastlight/${REPO_CONFIG_FILE}: it must be a mapping.`);
349
+ return undefined;
350
+ }
351
+ const out = {};
352
+ for (const [task, spec] of Object.entries(raw)) {
353
+ const path = `models.${task}`;
354
+ // Re-checked per leaf, not just on the `models` container: an operator can
355
+ // narrow the allow-list to a single task (`models.triage`), and the leaf is
356
+ // the only place that distinction exists.
357
+ if (!isAllowedKey(path, policy.allowKeys)) {
358
+ warn("key-not-allowed", path, `Ignored "${path}": a repo may not set this key.`);
359
+ continue;
360
+ }
361
+ if (typeof spec !== "string" || !spec.trim()) {
362
+ warn("invalid-value", path, `Ignored "${path}": a model must be a non-empty "provider/model" string.`);
363
+ continue;
364
+ }
365
+ const model = spec.trim();
366
+ const prefix = model.split("/")[0] ?? "";
367
+ if (!prefix || !model.includes("/") || (!providerByPrefix(prefix) && !oauthProviderByModelPrefix(prefix))) {
368
+ warn("unknown-provider", path, `Ignored "${path}": "${model}" is not a "provider/model" spec Last Light can wire.`);
369
+ continue;
370
+ }
371
+ // A non-null allowedModels is the operator saying "exactly these" — an
372
+ // exact-match list, never a prefix rule, so it can't be widened by a repo
373
+ // appending to a model id.
374
+ if (policy.allowedModels !== null && !policy.allowedModels.includes(model)) {
375
+ warn("model-not-allowed", path, `Ignored "${path}": "${model}" is not in this deployment's repoConfig.allowedModels.`);
376
+ continue;
377
+ }
378
+ out[task] = model;
379
+ }
380
+ return out;
381
+ }
382
+ function sanitizeStringMap(raw, path, policy, warn) {
383
+ if (!isPlainObject(raw)) {
384
+ warn("invalid-value", path, `Ignored "${path}" in .lastlight/${REPO_CONFIG_FILE}: it must be a mapping.`);
385
+ return undefined;
386
+ }
387
+ const out = {};
388
+ for (const [key, value] of Object.entries(raw)) {
389
+ const leaf = `${path}.${key}`;
390
+ if (!isAllowedKey(leaf, policy.allowKeys)) {
391
+ warn("key-not-allowed", leaf, `Ignored "${leaf}": a repo may not set this key.`);
392
+ continue;
393
+ }
394
+ if (typeof value !== "string" || !value.trim()) {
395
+ warn("invalid-value", leaf, `Ignored "${leaf}": it must be a non-empty string.`);
396
+ continue;
397
+ }
398
+ out[key] = value.trim();
399
+ }
400
+ return out;
401
+ }
402
+ function sanitizeDisabled(raw, policy, warn) {
403
+ if (!isPlainObject(raw)) {
404
+ warn("invalid-value", "disabled", `Ignored "disabled" in .lastlight/${REPO_CONFIG_FILE}: it must be a mapping.`);
405
+ return undefined;
406
+ }
407
+ const out = {};
408
+ for (const [key, value] of Object.entries(raw)) {
409
+ const path = `disabled.${key}`;
410
+ if (!isAllowedKey(path, policy.allowKeys)) {
411
+ warn("key-not-allowed", path, `Ignored "${path}": a repo may not set this key.`);
412
+ continue;
413
+ }
414
+ if (!Array.isArray(value) || value.some((v) => typeof v !== "string" || !v.trim())) {
415
+ warn("invalid-value", path, `Ignored "${path}": it must be an array of non-empty strings.`);
416
+ continue;
417
+ }
418
+ // Same unsafe-name check `validateAssets` applies to the operator's own
419
+ // disabled lists — a name is a logical workflow/cron name, never a path.
420
+ const names = value.map((v) => v.trim());
421
+ const bad = names.find((n) => n.includes("/") || n.includes(".."));
422
+ if (bad) {
423
+ warn("invalid-value", path, `Ignored "${path}": "${bad}" is not a valid workflow/cron name.`);
424
+ continue;
425
+ }
426
+ out[key] = names;
427
+ }
428
+ return out;
429
+ }
430
+ function sanitizeApproval(raw, policy, base, warn) {
431
+ if (!isPlainObject(raw)) {
432
+ warn("invalid-value", "approval", `Ignored "approval" in .lastlight/${REPO_CONFIG_FILE}: it must be a mapping.`);
433
+ return undefined;
434
+ }
435
+ const baseApproval = isPlainObject(base.value.approval) ? base.value.approval : {};
436
+ const out = {};
437
+ for (const [gate, value] of Object.entries(raw)) {
438
+ const path = `approval.${gate}`;
439
+ if (!isAllowedKey(path, policy.allowKeys)) {
440
+ warn("key-not-allowed", path, `Ignored "${path}": a repo may not set this key.`);
441
+ continue;
442
+ }
443
+ if (typeof value !== "boolean") {
444
+ warn("invalid-value", path, `Ignored "${path}": an approval gate must be true or false.`);
445
+ continue;
446
+ }
447
+ // ADD-ONLY. A repo may demand more human oversight of runs against itself;
448
+ // it may never remove oversight the operator asked for. Clearing a gate the
449
+ // operator never set is a no-op, so every `false` is simply dropped — with a
450
+ // warning, so the repo learns why nothing happened.
451
+ if (value === false) {
452
+ const code = baseApproval[gate] === true ? "approval-downgrade" : "invalid-value";
453
+ warn(code, path, `Ignored "${path}: false": the repo approval layer is add-only — a repo can raise an approval gate, never clear one.`);
454
+ continue;
455
+ }
456
+ out[gate] = true;
457
+ }
458
+ return out;
459
+ }
460
+ // ---------------------------------------------------------------------------
461
+ // Resolution
462
+ // ---------------------------------------------------------------------------
463
+ /**
464
+ * Apply a repo's layer on top of the boot config and report what happened.
465
+ *
466
+ * PURE — no fs, no network, no runtime-config reads. Plain objects deep-merge
467
+ * key-by-key while arrays and scalars replace wholesale — byte-for-byte the
468
+ * semantics of the boot layers (see {@link mergeLayer}), so the repo layer can
469
+ * never acquire semantics the operator's layers don't have.
470
+ *
471
+ * Note on `disabled.*`: those are arrays, so a repo's list REPLACES the
472
+ * operator's rather than adding to it (locked precedence). Operators who don't
473
+ * want that remove `disabled.workflows` / `disabled.crons` from
474
+ * `repoConfig.allowKeys`.
475
+ *
476
+ * Passing `undefined` for `repoLayer` (no `.lastlight/`, fetch failed, feature
477
+ * disabled) returns the base unchanged with no warnings — the inert path.
478
+ */
479
+ export function resolveRepoConfig(base, policy, repoLayer) {
480
+ const warnings = repoLayer ? [...repoLayer.warnings] : [];
481
+ let layer = {};
482
+ if (repoLayer && policy.enabled) {
483
+ const sanitized = sanitizeRepoConfigLayer(repoLayer.config, policy, base, repoLayer.repo);
484
+ layer = sanitized.layer;
485
+ warnings.push(...sanitized.warnings);
486
+ }
487
+ const value = structuredClone(base.value);
488
+ const sources = structuredClone(base.sources);
489
+ mergeLayer(value, sources, layer, "repo");
490
+ return {
491
+ merged: shapeMerged(value),
492
+ sources: shapeSources(sources),
493
+ warnings,
494
+ };
495
+ }
496
+ /**
497
+ * Merge one layer INTO `value`/`sources` in place, tagging every leaf it
498
+ * supplies with `source`.
499
+ *
500
+ * THE single definition of Last Light's config-merge semantics: plain objects
501
+ * deep-merge key-by-key so each leaf resolves (and is attributed) on its own;
502
+ * arrays and scalars replace wholesale. Core's boot-layer resolver
503
+ * (`apps/server/src/config/config-resolve.ts`) re-exports this rather than
504
+ * carrying its own — the repo layer must merge exactly the way default/overlay/
505
+ * env do, or a repo could acquire precedence the operator's own layers don't
506
+ * have, and two implementations is exactly how that drift starts.
507
+ *
508
+ * It lives HERE, in the leaf package, because the direction of the dependency
509
+ * edge only permits it here: `lastlight-shared` may never depend on core.
510
+ */
511
+ export function mergeLayer(value, sources, layer, source) {
512
+ for (const [key, incoming] of Object.entries(layer)) {
513
+ if (isPlainObject(incoming)) {
514
+ const childValue = isPlainObject(value[key]) ? value[key] : {};
515
+ const childSources = isPlainObject(sources[key]) ? sources[key] : {};
516
+ mergeLayer(childValue, childSources, incoming, source);
517
+ value[key] = childValue;
518
+ sources[key] = childSources;
519
+ }
520
+ else {
521
+ value[key] = incoming;
522
+ sources[key] = source;
523
+ }
524
+ }
525
+ }
526
+ function shapeMerged(value) {
527
+ const disabled = isPlainObject(value.disabled) ? value.disabled : {};
528
+ return {
529
+ models: stringMap(value.models),
530
+ variants: stringMap(value.variants),
531
+ disabled: {
532
+ workflows: stringList(disabled.workflows),
533
+ crons: stringList(disabled.crons),
534
+ prompts: stringList(disabled.prompts),
535
+ skills: stringList(disabled.skills),
536
+ agentContext: stringList(disabled.agentContext),
537
+ },
538
+ approval: boolMap(value.approval),
539
+ };
540
+ }
541
+ function shapeSources(sources) {
542
+ const disabled = isPlainObject(sources.disabled) ? sources.disabled : {};
543
+ const pick = (key) => (isConfigSource(disabled[key]) ? disabled[key] : "default");
544
+ return {
545
+ models: sourceMap(sources.models),
546
+ variants: sourceMap(sources.variants),
547
+ disabled: {
548
+ workflows: pick("workflows"),
549
+ crons: pick("crons"),
550
+ prompts: pick("prompts"),
551
+ skills: pick("skills"),
552
+ agentContext: pick("agentContext"),
553
+ },
554
+ approval: sourceMap(sources.approval),
555
+ };
556
+ }
557
+ /** Narrow an unknown provenance leaf to a {@link ConfigSource}. */
558
+ export function isConfigSource(value) {
559
+ return value === "default" || value === "overlay" || value === "env" || value === "repo";
560
+ }
561
+ function stringMap(raw) {
562
+ const out = {};
563
+ if (isPlainObject(raw))
564
+ for (const [k, v] of Object.entries(raw))
565
+ if (typeof v === "string")
566
+ out[k] = v;
567
+ return out;
568
+ }
569
+ function boolMap(raw) {
570
+ const out = {};
571
+ if (isPlainObject(raw))
572
+ for (const [k, v] of Object.entries(raw))
573
+ out[k] = v === true;
574
+ return out;
575
+ }
576
+ function sourceMap(raw) {
577
+ const out = {};
578
+ if (isConfigSource(raw))
579
+ return out;
580
+ if (isPlainObject(raw))
581
+ for (const [k, v] of Object.entries(raw))
582
+ if (isConfigSource(v))
583
+ out[k] = v;
584
+ return out;
585
+ }
586
+ function stringList(raw) {
587
+ return Array.isArray(raw) ? raw.filter((v) => typeof v === "string") : [];
588
+ }
589
+ function isPlainObject(value) {
590
+ return typeof value === "object" && value !== null && !Array.isArray(value);
591
+ }
592
+ //# sourceMappingURL=repo-config-schema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"repo-config-schema.js","sourceRoot":"","sources":["../src/repo-config-schema.ts"],"names":[],"mappings":"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"}