bareloop 0.23.0 → 0.25.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.
- package/CHANGELOG.md +218 -0
- package/bareloop.context.md +233 -19
- package/package.json +1 -1
- package/src/authorflow.js +17 -7
- package/src/authoring.js +32 -0
- package/src/authorjob.js +108 -36
- package/src/calibrate.js +33 -12
- package/src/cardauthor.js +23 -16
- package/src/cli.js +36 -5
- package/src/declaredclose.js +17 -10
- package/src/index.js +17 -5
- package/src/job.js +58 -3
- package/src/judged.js +132 -14
- package/src/kinds.js +84 -13
- package/src/planrun.js +14 -2
- package/src/providers.js +143 -18
- package/src/run.js +9 -4
- package/src/source.js +849 -0
- package/src/validate.js +19 -0
- package/types/authoring.d.ts +30 -0
- package/types/authorjob.d.ts +10 -6
- package/types/calibrate.d.ts +9 -2
- package/types/cardauthor.d.ts +13 -11
- package/types/cli.d.ts +2 -1
- package/types/declaredclose.d.ts +2 -0
- package/types/index.d.ts +2 -1
- package/types/job.d.ts +22 -1
- package/types/judged.d.ts +123 -14
- package/types/kinds.d.ts +66 -3
- package/types/planrun.d.ts +10 -2
- package/types/providers.d.ts +73 -11
- package/types/run.d.ts +9 -3
- package/types/source.d.ts +206 -0
- package/types/validate.d.ts +7 -0
package/types/providers.d.ts
CHANGED
|
@@ -31,23 +31,31 @@ export function makeProvider(providerName: string, { apiKey, model, baseUrl }?:
|
|
|
31
31
|
* before this factory existed — that decision stays where each runner's own
|
|
32
32
|
* `--model` flag/spec precedence lives, not duplicated in here).
|
|
33
33
|
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
34
|
+
* THE JUDGE IS NO LONGER PINNED TO ANTHROPIC (PRD item 32.2, hamr 2026-09-10:
|
|
35
|
+
* *"what i care about is that judge becomes llm agnostic and not set to one
|
|
36
|
+
* model or provider"*). It is constructed through this same factory, from a
|
|
37
|
+
* `judgeProviderName`/`judgeModel` pair the CALLER resolved (`resolveJudge`,
|
|
38
|
+
* src/judged.js — the spec's signed `judge` override, else the job's own worker
|
|
39
|
+
* provider and model). Item 28's pin, and item 28 part (2)'s deferral of a
|
|
40
|
+
* signed `judge:{provider,model}` field, both close here.
|
|
41
|
+
*
|
|
42
|
+
* `judgeApiKey` stays a SEPARATE argument rather than being derived: the judge
|
|
43
|
+
* may legitimately be a different provider from the worker, and reading its key
|
|
44
|
+
* for it would put a second env-var reader in a module whose whole job is to
|
|
45
|
+
* have exactly one. The caller reads the resolved judge provider's own `envKey`
|
|
46
|
+
* and hands the key in — the same contract the worker key already has here.
|
|
41
47
|
* @param {object} args
|
|
42
48
|
* @param {string} args.providerName spec.provider (or the anthropic-api default)
|
|
43
49
|
* @param {string|undefined} args.apiKey the WORKER's key, read from the provider's own `envKey`
|
|
44
50
|
* @param {string} args.model the already-resolved worker model id
|
|
45
51
|
* @param {Readonly<Record<string, string>>} args.tierModels the already-resolved tier -> model map (spec override folded in by the caller)
|
|
46
52
|
* @param {string|undefined} [args.baseUrl] spec.baseUrl, if any
|
|
47
|
-
* @param {string|undefined} args.judgeApiKey the judge's own key
|
|
48
|
-
* @param {string} args.judgeModel
|
|
53
|
+
* @param {string|undefined} args.judgeApiKey the judge's own key, read by the caller from the RESOLVED judge provider's `envKey`
|
|
54
|
+
* @param {string} args.judgeModel the resolved judge model id
|
|
55
|
+
* @param {string} [args.judgeProviderName] the resolved judge provider name; defaults to `providerName` (the job's own worker provider), which is `resolveJudge`'s own default
|
|
56
|
+
* @param {string|undefined} [args.judgeBaseUrl] the judge provider's endpoint, when it takes one
|
|
49
57
|
*/
|
|
50
|
-
export function buildRunnerProviders({ providerName, apiKey, model, tierModels, baseUrl, judgeApiKey, judgeModel, }: {
|
|
58
|
+
export function buildRunnerProviders({ providerName, apiKey, model, tierModels, baseUrl, judgeApiKey, judgeModel, judgeProviderName, judgeBaseUrl, }: {
|
|
51
59
|
providerName: string;
|
|
52
60
|
apiKey: string | undefined;
|
|
53
61
|
model: string;
|
|
@@ -55,11 +63,32 @@ export function buildRunnerProviders({ providerName, apiKey, model, tierModels,
|
|
|
55
63
|
baseUrl?: string | undefined;
|
|
56
64
|
judgeApiKey: string | undefined;
|
|
57
65
|
judgeModel: string;
|
|
66
|
+
judgeProviderName?: string | undefined;
|
|
67
|
+
judgeBaseUrl?: string | undefined;
|
|
58
68
|
}): {
|
|
59
69
|
provider: any;
|
|
60
70
|
providerFor: (tier: string) => any;
|
|
61
71
|
judgeProvider: any;
|
|
62
72
|
};
|
|
73
|
+
/**
|
|
74
|
+
* The launch-time warning lines for an ADMITTED-PENDING-PROBE provider, or
|
|
75
|
+
* `null` when the provider has paid for its own end-to-end probe (and for an
|
|
76
|
+
* off-table name — an unknown provider is `resolveProvider`'s named throw to
|
|
77
|
+
* report, never this function's warning to soften).
|
|
78
|
+
*
|
|
79
|
+
* This lives here, next to `PROBE_STATUS`, rather than inline in the runner,
|
|
80
|
+
* for one reason: inline it was UNTESTABLE. `scripts/run-u.mjs` reaches this
|
|
81
|
+
* point only with a real job row (a machine-local patient path) and a real
|
|
82
|
+
* signed spec, so nothing in the suite could reach the branch, and a warning
|
|
83
|
+
* nothing executes drifts exactly the way the green-only prose did (item 31.2).
|
|
84
|
+
*
|
|
85
|
+
* It never REFUSES, and it never returns a shape a caller can mistake for a
|
|
86
|
+
* refusal: hamr admitted `gemini-api` to the menu, and a probe table that
|
|
87
|
+
* quietly withheld it would be a second ruling nobody made.
|
|
88
|
+
* @param {string} providerName
|
|
89
|
+
* @returns {string[]|null} lines to print, in order, or null when there is nothing to say
|
|
90
|
+
*/
|
|
91
|
+
export function probeWarningLines(providerName: string): string[] | null;
|
|
63
92
|
/**
|
|
64
93
|
* Today's anthropic-api tier table — EXACTLY the `DEFAULT_TIER_MODELS` both
|
|
65
94
|
* runners hardcoded before this factory existed. Any change to these two
|
|
@@ -84,6 +113,35 @@ export const OPENAI_TIER_MODELS: Readonly<{
|
|
|
84
113
|
sonnet: "deepseek-chat";
|
|
85
114
|
haiku: "deepseek-chat";
|
|
86
115
|
}>;
|
|
116
|
+
/**
|
|
117
|
+
* Gemini (PRD item 31.3, hamr 2026-09-09: "anthropic, openai, gemini drop
|
|
118
|
+
* ollama for now"). Two real tiers, unlike DeepSeek's single model: `flash` is
|
|
119
|
+
* the cheap tier by design, so `haiku` maps to it honestly rather than
|
|
120
|
+
* repeating one id.
|
|
121
|
+
*
|
|
122
|
+
* ADMITTED-PENDING-PROBE. The probe rule stands (PRD item 28 ruling (d),
|
|
123
|
+
* 2026-09-09): no endpoint is in the menu without its own clean paid probe,
|
|
124
|
+
* and `gemini-api` has ZERO runs. `anthropic-api` is the original (~167
|
|
125
|
+
* archived runs) and `openai-api` was probed green through the shipped runner
|
|
126
|
+
* (run mtu12vks, $1.64/$4, F157); gemini owes that fire and has not paid it.
|
|
127
|
+
* `PROBE_STATUS` below is the machine-readable form of that debt — nothing in
|
|
128
|
+
* here silently pretends the provider is proven.
|
|
129
|
+
*/
|
|
130
|
+
export const GEMINI_TIER_MODELS: Readonly<{
|
|
131
|
+
sonnet: "gemini-2.5-pro";
|
|
132
|
+
haiku: "gemini-2.5-flash";
|
|
133
|
+
}>;
|
|
134
|
+
/**
|
|
135
|
+
* Which admitted providers have paid for their own end-to-end probe, and which
|
|
136
|
+
* have not. Read by the runner to print a loud marker; never read to REFUSE —
|
|
137
|
+
* hamr admitted gemini to the menu, and a table that quietly withheld it would
|
|
138
|
+
* be a second, invisible ruling. An unprobed provider runs, and says so.
|
|
139
|
+
* @type {Readonly<Record<string, {probed: boolean, evidence: string}>>}
|
|
140
|
+
*/
|
|
141
|
+
export const PROBE_STATUS: Readonly<Record<string, {
|
|
142
|
+
probed: boolean;
|
|
143
|
+
evidence: string;
|
|
144
|
+
}>>;
|
|
87
145
|
export type ProviderTableEntry = {
|
|
88
146
|
/**
|
|
89
147
|
* the bare-agent provider class
|
|
@@ -98,7 +156,11 @@ export type ProviderTableEntry = {
|
|
|
98
156
|
*/
|
|
99
157
|
tiers: Readonly<Record<string, string>>;
|
|
100
158
|
/**
|
|
101
|
-
*
|
|
159
|
+
* the CONSTRUCTOR OPTION NAME this provider reads its endpoint from — NOT assumed to be `baseUrl` (see below)
|
|
160
|
+
*/
|
|
161
|
+
endpointKey: string;
|
|
162
|
+
/**
|
|
163
|
+
* per-model request-key/param gating, applied on TOP of {exposeErrorBody:true, apiKey, model, <endpointKey>?}
|
|
102
164
|
*/
|
|
103
165
|
paramsFor: (model: string | undefined) => Record<string, any>;
|
|
104
166
|
};
|
package/types/run.d.ts
CHANGED
|
@@ -34,9 +34,14 @@
|
|
|
34
34
|
* metered claude-json text provider for the toolless drafter); ignored on every Loop-driven provider
|
|
35
35
|
* @param {(type: string, data?: object) => object} opts.emit spine emitter
|
|
36
36
|
* @param {(tier: string) => any} [opts.providerFor] P: per-step model-tier provider factory (forwarded to the plan flow)
|
|
37
|
+
* @param {string|null} [opts.judgeModel] SOFTGREEN — WHICH model the judge provider drives
|
|
38
|
+
* (PRD item 32.1), resolved by the CALLER (`resolveJudge`, src/judged.js) and passed
|
|
39
|
+
* through untouched. Travels with `judgeProvider`; absent, a judged stage stops as a
|
|
40
|
+
* wiring gap rather than grading under an identity nobody named.
|
|
37
41
|
* @param {any} [opts.judgeProvider] SOFTGREEN — the provider a JUDGED close stage runs its
|
|
38
|
-
* locate call through,
|
|
39
|
-
*
|
|
42
|
+
* locate call through, built by the CALLER from the RESOLVED judge identity (PRD item
|
|
43
|
+
* 32.1: `resolveJudge`/`resolveJobJudge`, src/judged.js — the spec's signed `judge`
|
|
44
|
+
* override, else the job's own worker provider and model), never pinned. Forwarded to the plan
|
|
40
45
|
* flow. It is its own seam and not `provider`/`providerFor`, because the judge tier is
|
|
41
46
|
* never a step knob and never agent-selectable; absent, a judged stage instrument-stops as
|
|
42
47
|
* a wiring gap rather than grading on whatever binding happened to be at hand. Its spend
|
|
@@ -176,7 +181,7 @@
|
|
|
176
181
|
* deliberately NOT in the resumable/checkpoint set (`src/reuse.js`) — an
|
|
177
182
|
* unknown in-flight state is not a known-safe resume point.
|
|
178
183
|
*/
|
|
179
|
-
export function runJob(rawSpec: object | string, { approvals, workdir, provider, nativeProvider, providerFor, judgeProvider, emit, capRuns, strikeLimit, shellCapUsd, closeTimeoutMs, closeDir, layerRoot, readShim, scout, bridge, priorSpentUsd, priorSpendComplete, priorWallMs, resumeSeed, resumeGrades, resumeReplans, resumeBranch, humanRuling, heldRuling, reviewDoor, doorRerun, resumable }: {
|
|
184
|
+
export function runJob(rawSpec: object | string, { approvals, workdir, provider, nativeProvider, providerFor, judgeProvider, judgeModel, emit, capRuns, strikeLimit, shellCapUsd, closeTimeoutMs, closeDir, layerRoot, readShim, scout, bridge, priorSpentUsd, priorSpendComplete, priorWallMs, resumeSeed, resumeGrades, resumeReplans, resumeBranch, humanRuling, heldRuling, reviewDoor, doorRerun, resumable }: {
|
|
180
185
|
approvals: unknown;
|
|
181
186
|
workdir: string;
|
|
182
187
|
provider: any;
|
|
@@ -188,6 +193,7 @@ export function runJob(rawSpec: object | string, { approvals, workdir, provider,
|
|
|
188
193
|
}) => any) | undefined;
|
|
189
194
|
emit: (type: string, data?: object) => object;
|
|
190
195
|
providerFor?: ((tier: string) => any) | undefined;
|
|
196
|
+
judgeModel?: string | null | undefined;
|
|
191
197
|
judgeProvider?: any;
|
|
192
198
|
capRuns?: number | undefined;
|
|
193
199
|
strikeLimit?: number | undefined;
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build the frozen source tree — `<into>/tree/input/…` (the copy), `<into>/tree/output/`
|
|
3
|
+
* (empty, where the run writes), `<into>/source.json` (the manifest, OUTSIDE
|
|
4
|
+
* the tree so no worker can read or edit it). One hidden git commit seeds the
|
|
5
|
+
* tree with a neutralized identity; the seed sha rides in the manifest and is
|
|
6
|
+
* what every close in this run measures against (`seedAtHead`, src/kinds.js).
|
|
7
|
+
*
|
|
8
|
+
* `destination`, when given, is recorded in the manifest (never in a signed
|
|
9
|
+
* job spec — a job spec is a repeatable SHAPE, source/destination a PER-RUN
|
|
10
|
+
* value, see `readSourceManifest`'s own comment) as the run's declared
|
|
11
|
+
* drop-off point. D3 rework (hamr's ruling, 2026-09-12, condensed in
|
|
12
|
+
* `docs/product/ITEM33-BUILD.md`): **destination is a DIRECTORY, never a
|
|
13
|
+
* filename** — it may already exist (need not be empty), and it may sit
|
|
14
|
+
* inside the source itself (the same-dir case: "i can limit your actions to
|
|
15
|
+
* a certain fix in a certain dir and your output will also be there"). A job
|
|
16
|
+
* may produce more than one file; the agent names them, and `copyOut`
|
|
17
|
+
* delivers every non-empty file it finds under `output/`, each under its own
|
|
18
|
+
* dated name. There is therefore no separate `output` field any more — the
|
|
19
|
+
* old single-file `output-invalid` guard existed only to stop a
|
|
20
|
+
* manifest-declared path from escaping the tree, and that vector is closed
|
|
21
|
+
* structurally now: `copyOut` always reads `output/` itself, never a path a
|
|
22
|
+
* manifest could have been hand-edited to name.
|
|
23
|
+
*
|
|
24
|
+
* A `kind: 'repo'` source's `destination` is a DIFFERENT thing entirely
|
|
25
|
+
* (hamr: "destination if for code on a green then that would be the place
|
|
26
|
+
* agent allowed to do changes") — it names the WRITE FENCE inside the copied
|
|
27
|
+
* repo, the signed `writeScope` field's job (`src/job.js:363`), not a
|
|
28
|
+
* filesystem drop-off point this door proves or copies into. It is recorded
|
|
29
|
+
* as declared, unvalidated by `proveDestination`, and `frontDoorFromManifest`
|
|
30
|
+
* never hands a repo source's destination to `copyOut` — wiring it into
|
|
31
|
+
* `writeScope` is M3/M4's job, not this one.
|
|
32
|
+
* @param {{source: string, into: string, destination?: string,
|
|
33
|
+
* fetchTimeoutMs?: number}} args `fetchTimeoutMs` is test-only — production
|
|
34
|
+
* callers omit it and get `PROVIDER_TIMEOUT_MS`.
|
|
35
|
+
* @returns {Promise<{stop: null, into: string, tree: string, manifestPath: string, manifest: object}|SourceRefusal>}
|
|
36
|
+
*/
|
|
37
|
+
export function prepareSource({ source, into, destination, fetchTimeoutMs }: {
|
|
38
|
+
source: string;
|
|
39
|
+
into: string;
|
|
40
|
+
destination?: string;
|
|
41
|
+
fetchTimeoutMs?: number;
|
|
42
|
+
}): Promise<{
|
|
43
|
+
stop: null;
|
|
44
|
+
into: string;
|
|
45
|
+
tree: string;
|
|
46
|
+
manifestPath: string;
|
|
47
|
+
manifest: object;
|
|
48
|
+
} | SourceRefusal>;
|
|
49
|
+
/**
|
|
50
|
+
* The delivered name (M2b fix 4, kept under D3): a destination FILE of
|
|
51
|
+
* `/home/me/profile.md` lands as `/home/me/profile-2026-09-12.md`, and a
|
|
52
|
+
* SECOND delivery of the same name the same day as
|
|
53
|
+
* `profile-2026-09-12-2.md`, `-3`, … (the work-branch collision rule, the same
|
|
54
|
+
* spelling `prepareWorkBranch` uses). The date is the delivery's, not the
|
|
55
|
+
* run's: what a person wants to know opening the folder later is when the file
|
|
56
|
+
* arrived. D3: `destination` here is a FULL FILE PATH inside the destination
|
|
57
|
+
* DIRECTORY (`copyOut` builds one per output file it delivers) — this
|
|
58
|
+
* function itself is unchanged from M2b, it is just no longer handed the
|
|
59
|
+
* bare top-level destination directly.
|
|
60
|
+
*
|
|
61
|
+
* Nothing is ever overwritten, which is why the name is computed rather than
|
|
62
|
+
* taken: a plain `profile.md` would eventually collide with the person's own
|
|
63
|
+
* file or a previous run's, and the only honest answers to a collision are
|
|
64
|
+
* "refuse" or "a new name" — hamr picked a new name.
|
|
65
|
+
* @param {string} destination the absolute FILE path as declared
|
|
66
|
+
* @param {Date} now
|
|
67
|
+
* @param {number} n 1 = the plain dated name, 2+ = the same-day suffix
|
|
68
|
+
* @returns {string}
|
|
69
|
+
*/
|
|
70
|
+
export function datedDestination(destination: string, now: Date, n: number): string;
|
|
71
|
+
/**
|
|
72
|
+
* The first dated name that is free, or a refusal when every same-day slot up
|
|
73
|
+
* to `MAX_SAME_DAY` is taken. Used by `copyOut` at delivery time (per output
|
|
74
|
+
* file) so a same-day repeat delivery of the same name never disagrees with
|
|
75
|
+
* what "landable" means.
|
|
76
|
+
* @param {string} destination an absolute FILE path
|
|
77
|
+
* @param {Date} [now]
|
|
78
|
+
* @returns {{stop: null, path: string}|SourceRefusal}
|
|
79
|
+
*/
|
|
80
|
+
export function pickDelivery(destination: string, now?: Date): {
|
|
81
|
+
stop: null;
|
|
82
|
+
path: string;
|
|
83
|
+
} | SourceRefusal;
|
|
84
|
+
/**
|
|
85
|
+
* Prove a destination DIRECTORY is landable — $0, mechanical, before any
|
|
86
|
+
* token spends (PRD item 33 ruling: "source and destination are proven at
|
|
87
|
+
* job start"). D3 rework (hamr's ruling, 2026-09-12): **destination is a
|
|
88
|
+
* directory, never a filename** — it may already exist, and need not be
|
|
89
|
+
* empty (a job may write into a folder that already holds other files, or
|
|
90
|
+
* even sit inside the source itself, the same-dir case). What this proves is
|
|
91
|
+
* narrower than the M2/M2b version: only that the directory is USABLE
|
|
92
|
+
* (exists as a directory and is writable, or can be created) — it can say
|
|
93
|
+
* nothing about individual delivered FILE names, because those are not known
|
|
94
|
+
* until the run actually produces them (the agent names its own output
|
|
95
|
+
* files); per-file same-day collisions are handled at delivery time, inside
|
|
96
|
+
* `copyOut`, via `pickDelivery`.
|
|
97
|
+
* @param {unknown} destination absolute directory path
|
|
98
|
+
* @param {{into: string}} o `into` — the run's own scratch root (prepared by
|
|
99
|
+
* `prepareSource`, or a run's own `workdir` when there is no source door);
|
|
100
|
+
* the destination may never sit inside it.
|
|
101
|
+
* @returns {Promise<{stop: null}|SourceRefusal>}
|
|
102
|
+
*/
|
|
103
|
+
export function proveDestination(destination: unknown, { into }: {
|
|
104
|
+
into: string;
|
|
105
|
+
}): Promise<{
|
|
106
|
+
stop: null;
|
|
107
|
+
} | SourceRefusal>;
|
|
108
|
+
/**
|
|
109
|
+
* Copy the run's own output to the proven destination DIRECTORY — once per
|
|
110
|
+
* file, never overwriting (`COPYFILE_EXCL`, so even a file that appears
|
|
111
|
+
* between `proveDestination` and here refuses rather than clobbers), on a
|
|
112
|
+
* minted green only. This function renders no verdict and changes none: a
|
|
113
|
+
* refusal here is reported beside the green it could not deliver, never
|
|
114
|
+
* folded back into it.
|
|
115
|
+
*
|
|
116
|
+
* D3 rework (hamr's ruling, 2026-09-12): a job may produce MORE THAN ONE
|
|
117
|
+
* file (his example: a flight search producing one sheet for "SFO→LAX
|
|
118
|
+
* red-eye" and another for "SFO under $700") — the agent names them
|
|
119
|
+
* meaningfully, and EVERY non-empty file under `tree/output/` (excluding
|
|
120
|
+
* `output/.gitkeep`) is delivered, each under its own dated name
|
|
121
|
+
* (`datedDestination`/`pickDelivery`, M2b fix 4, unchanged). An empty file is
|
|
122
|
+
* silently skipped (not a delivered result, same judgment M2b made for the
|
|
123
|
+
* single-file case) rather than refusing the whole batch over one empty
|
|
124
|
+
* file; the refusal is reserved for the case where NOTHING is left to
|
|
125
|
+
* deliver at all.
|
|
126
|
+
*
|
|
127
|
+
* `into`, when given, is the run's own SCRATCH ROOT (`prepareSource`'s
|
|
128
|
+
* `into`, of which `tree` is the `tree/` subdirectory) — review finding #3:
|
|
129
|
+
* a destination sitting inside `<into>` but outside `tree` (e.g. dropped
|
|
130
|
+
* straight in the scratch area, never inside the frozen tree) must still
|
|
131
|
+
* refuse `destination-contained`, which a containment check against `tree`
|
|
132
|
+
* alone cannot see. Omitted, it defaults to `tree` (the pre-fix behaviour),
|
|
133
|
+
* for direct callers that only ever had a tree, never an `into`.
|
|
134
|
+
* @param {{tree: string, into?: string, destination: string}} o
|
|
135
|
+
* @returns {Promise<{stop: null, files: {path: string, bytes: number, sha256: string}[]}|SourceRefusal>}
|
|
136
|
+
*/
|
|
137
|
+
export function copyOut({ tree, into, destination }: {
|
|
138
|
+
tree: string;
|
|
139
|
+
into?: string;
|
|
140
|
+
destination: string;
|
|
141
|
+
}): Promise<{
|
|
142
|
+
stop: null;
|
|
143
|
+
files: {
|
|
144
|
+
path: string;
|
|
145
|
+
bytes: number;
|
|
146
|
+
sha256: string;
|
|
147
|
+
}[];
|
|
148
|
+
} | SourceRefusal>;
|
|
149
|
+
/**
|
|
150
|
+
* Read a source-door manifest, if one is there. mid-build spec correction
|
|
151
|
+
* (2026-09-11, the main session, on hamr's question about export): a job
|
|
152
|
+
* spec is a SHAPE, signed once — source and destination are a PER-RUN value,
|
|
153
|
+
* the way `bareloop run <bundle> --repo <path>` varies the repo instance
|
|
154
|
+
* without touching the bundle's signature. Baking an absolute destination
|
|
155
|
+
* path into `job.js`/`jobSpecHash` would sign one instance where the spec is
|
|
156
|
+
* meant to describe a repeatable shape, so the run instance lives HERE
|
|
157
|
+
* instead — the manifest `prepareSource` already writes outside the tree —
|
|
158
|
+
* and nothing was added to the job schema.
|
|
159
|
+
*
|
|
160
|
+
* Absence is reported as absence: a patient the JOBS table points at
|
|
161
|
+
* directly, never having gone through `prepareSource`, has no manifest, and
|
|
162
|
+
* that IS "repo jobs untouched" — nothing here invents a destination for a
|
|
163
|
+
* job that never declared one. A manifest that EXISTS but cannot be read or
|
|
164
|
+
* parsed is a named stop, never a silent skip: the run has evidence
|
|
165
|
+
* something is there and wrong, and hiding that is worse than refusing on it.
|
|
166
|
+
* @param {string} into the manifest's own directory — `<into>/source.json`
|
|
167
|
+
* (a run-u-style caller passes `dirname(workdir)`, since `workdir` IS
|
|
168
|
+
* `<into>/tree`)
|
|
169
|
+
* @returns {Promise<{stop: null, present: false, manifest: null}|{stop: null, present: true, manifest: Record<string, any>}|SourceRefusal>}
|
|
170
|
+
*/
|
|
171
|
+
export function readSourceManifest(into: string): Promise<{
|
|
172
|
+
stop: null;
|
|
173
|
+
present: false;
|
|
174
|
+
manifest: null;
|
|
175
|
+
} | {
|
|
176
|
+
stop: null;
|
|
177
|
+
present: true;
|
|
178
|
+
manifest: Record<string, any>;
|
|
179
|
+
} | SourceRefusal>;
|
|
180
|
+
/**
|
|
181
|
+
* Reduce a `readSourceManifest` success result to the `{destination}` a
|
|
182
|
+
* runner acts on, or `null` when there is none to act on (no manifest, one
|
|
183
|
+
* that never declared a destination, or — D3 — a `kind: 'repo'` manifest,
|
|
184
|
+
* whose `destination` names the WRITE FENCE inside the copied repo, never a
|
|
185
|
+
* filesystem drop-off point this door proves or copies into: "repo jobs
|
|
186
|
+
* UNCHANGED", `writeScope` already does that job, `src/job.js:363`). Pure
|
|
187
|
+
* extraction of the one piece of `run-u.mjs`'s front-door reading that has
|
|
188
|
+
* any logic in it (the rest is spine emits and control flow) —
|
|
189
|
+
* unit-testable apart from the script, which is otherwise unreachable
|
|
190
|
+
* without a live provider key.
|
|
191
|
+
* @param {{present: boolean, manifest: Record<string, any>|null}} read
|
|
192
|
+
* @returns {{destination: string}|null}
|
|
193
|
+
*/
|
|
194
|
+
export function frontDoorFromManifest(read: {
|
|
195
|
+
present: boolean;
|
|
196
|
+
manifest: Record<string, any> | null;
|
|
197
|
+
}): {
|
|
198
|
+
destination: string;
|
|
199
|
+
} | null;
|
|
200
|
+
/**
|
|
201
|
+
* a named, non-throwing stop
|
|
202
|
+
*/
|
|
203
|
+
export type SourceRefusal = {
|
|
204
|
+
stop: string;
|
|
205
|
+
code: string;
|
|
206
|
+
};
|
package/types/validate.d.ts
CHANGED
|
@@ -226,3 +226,10 @@ export function sweepNestedQuantifiers(root: any, red: (code: string, path: stri
|
|
|
226
226
|
*/
|
|
227
227
|
export const CLOSE_SCRIPT_INTERPRETERS: readonly string[];
|
|
228
228
|
export const SECRET_PATTERNS: RegExp[];
|
|
229
|
+
/** Human-readable name for each entry in `SECRET_PATTERNS`, SAME order, SAME
|
|
230
|
+
* length — for a caller that must name WHICH shape matched (a refusal
|
|
231
|
+
* message, an audit line) without ever printing the matched text itself. Kept
|
|
232
|
+
* beside the inventory it describes rather than hand-typed at each call site,
|
|
233
|
+
* for the same reason `scanSecrets`/`redactSecrets` are: a second, drifting
|
|
234
|
+
* copy is how a name and its pattern come apart. */
|
|
235
|
+
export const SECRET_PATTERN_NAMES: string[];
|