bareloop 0.20.0 → 0.21.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 +67 -0
- package/README.md +14 -0
- package/bareloop.context.md +215 -0
- package/bin/bareloop.mjs +24 -0
- package/package.json +6 -2
- package/src/bundle.js +719 -0
- package/src/cli.js +460 -0
- package/src/index.js +17 -1
- package/src/reuse.js +1 -1
- package/types/bundle.d.ts +199 -0
- package/types/cli.d.ts +19 -0
- package/types/index.d.ts +3 -1
- package/types/reuse.d.ts +14 -0
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Recompute the bundle hash from what is ACTUALLY on disk right now (never
|
|
3
|
+
* trusts the stored manifest). Throws only if `dir` itself does not exist —
|
|
4
|
+
* every other absence (a missing spec.json, an empty close/) folds into the
|
|
5
|
+
* hash as an omitted file, which is exactly what makes a swapped/deleted
|
|
6
|
+
* script change the hash.
|
|
7
|
+
* @param {string} dir bundle directory
|
|
8
|
+
* @returns {string} sha256 hex
|
|
9
|
+
*/
|
|
10
|
+
export function bundleHash(dir: string): string;
|
|
11
|
+
/**
|
|
12
|
+
* EXPORT — mints a bundle directory from a signed job spec (`docs/product/
|
|
13
|
+
* EXPORT-BUILD.md`, "The bundle"). Every check below runs BEFORE anything
|
|
14
|
+
* touches disk (typed reds, nothing written): a partially-written bundle
|
|
15
|
+
* would be worse than no bundle, since `readBundle`'s tamper check has
|
|
16
|
+
* nothing trustworthy to compare against yet.
|
|
17
|
+
*
|
|
18
|
+
* Refusals, each a distinct red code:
|
|
19
|
+
* - `no-bridge-at-hash` — neither of the job's two possible bridge names —
|
|
20
|
+
* `spec.job` itself, or its SHAPE FORK `shapeForkName(spec.job,
|
|
21
|
+
* closeStagesOf(spec))` (the name `writeGreenRow`/`writeRunGreenRow` in
|
|
22
|
+
* `src/reuse.js` mint whenever a run's close-stage set differs from the
|
|
23
|
+
* base bridge's `closeStageNames` — same derivation, imported rather than
|
|
24
|
+
* re-spelled, so the two names cannot drift) — carries a recorded specHash
|
|
25
|
+
* (bridge-level or on some version) equal to `jobSpecHash(spec)`: nothing
|
|
26
|
+
* has ever greened THIS exact spec, so there is nothing proven to ship.
|
|
27
|
+
* Every candidate bridge that DOES exist (base and/or fork) still ships
|
|
28
|
+
* into `bridges/` — the spec says "the job's WHOLE registry history
|
|
29
|
+
* (greens and reds)", not just the one that matched.
|
|
30
|
+
* - `close-cmd-unrelocatable` — a `close[].cmd` is not `node <abs .mjs> …`.
|
|
31
|
+
* - `close-script-missing` — a cmd names a script `closeScripts` does not
|
|
32
|
+
* carry the source for.
|
|
33
|
+
* - `close-script-collision` — two different script paths share a basename;
|
|
34
|
+
* copying both into one flat `close/` directory would silently clobber one.
|
|
35
|
+
* - `close-import-unexported` — a close script imports a name from
|
|
36
|
+
* bareloop's own src that `src/index.js` does not export.
|
|
37
|
+
* - `close-import-unparsed` — a close script's import is a shape this
|
|
38
|
+
* module cannot verify at all (default import, `* as ns`, mixed
|
|
39
|
+
* default+named/namespace, a bare side-effect import, or a dynamic
|
|
40
|
+
* `import()`), or is a relative import that does not point into
|
|
41
|
+
* `src/` (a sibling file `close/` never ships, so it would be missing
|
|
42
|
+
* from the bundle). Fails safe rather than silently accepting what it
|
|
43
|
+
* cannot read.
|
|
44
|
+
* - `close-absolute-path` — a close script's source bakes in a string
|
|
45
|
+
* literal that is an absolute POSIX path which exists on the exporting
|
|
46
|
+
* machine and is outside the system-prefix allow-list (see
|
|
47
|
+
* `absolutePathLiteralsOf`): a close judges the cwd the runner gives it,
|
|
48
|
+
* never a path baked into the script — F8/F129, the live defect this
|
|
49
|
+
* guard would have refused at export time before any run.
|
|
50
|
+
* - `outdir-not-empty` — `outDir` exists and already holds files.
|
|
51
|
+
* - `registry-unreadable` — `registryDir` itself could not be read
|
|
52
|
+
* (bubbled from `loadRegistry`).
|
|
53
|
+
* @param {{ spec: any, closeScripts: Record<string, string>, registryDir: string, outDir: string, bareloopVersion: string }} o
|
|
54
|
+
* @returns {{ ok: boolean, reds: Red[], dir: string|null, bundleHash: string|null, manifest: any|null }}
|
|
55
|
+
*/
|
|
56
|
+
export function exportBundle({ spec, closeScripts, registryDir, outDir, bareloopVersion }: {
|
|
57
|
+
spec: any;
|
|
58
|
+
closeScripts: Record<string, string>;
|
|
59
|
+
registryDir: string;
|
|
60
|
+
outDir: string;
|
|
61
|
+
bareloopVersion: string;
|
|
62
|
+
}): {
|
|
63
|
+
ok: boolean;
|
|
64
|
+
reds: Red[];
|
|
65
|
+
dir: string | null;
|
|
66
|
+
bundleHash: string | null;
|
|
67
|
+
manifest: any | null;
|
|
68
|
+
};
|
|
69
|
+
/**
|
|
70
|
+
* READ a bundle directory back: the manifest/spec, tamper-checked against
|
|
71
|
+
* what is actually on disk, plus the record-only bridges/blessing/history a
|
|
72
|
+
* runner needs beside them. Never throws — every failure is a typed red.
|
|
73
|
+
*
|
|
74
|
+
* `bundle-tampered` (N4) is a RED, never a warning, and runs whenever both a
|
|
75
|
+
* manifest and a spec could be read at all — a swapped close script must be
|
|
76
|
+
* caught here, before any caller reaches `runJob`.
|
|
77
|
+
* @param {string} dir bundle directory
|
|
78
|
+
* @returns {{ ok: boolean, reds: Red[], spec: any, manifest: any, bridges: any[], blessing: any, history: any[] }}
|
|
79
|
+
*/
|
|
80
|
+
export function readBundle(dir: string): {
|
|
81
|
+
ok: boolean;
|
|
82
|
+
reds: Red[];
|
|
83
|
+
spec: any;
|
|
84
|
+
manifest: any;
|
|
85
|
+
bridges: any[];
|
|
86
|
+
blessing: any;
|
|
87
|
+
history: any[];
|
|
88
|
+
};
|
|
89
|
+
/**
|
|
90
|
+
* The IN-MEMORY `$BARELOOP_BUNDLE` substitution (the frozen spec's "two
|
|
91
|
+
* facts": the runner never touches the spec on disk, it substitutes the
|
|
92
|
+
* absolute bundle directory into a copy before `runJob`, and signs THAT
|
|
93
|
+
* substituted spec's hash — not the on-disk one). `approveHash` is the value
|
|
94
|
+
* an `--approve` flag / a `history.jsonl` row must record.
|
|
95
|
+
* @param {{ spec: any }} bundle a `readBundle` result (or anything carrying `.spec`)
|
|
96
|
+
* @param {string} bundleDir absolute or relative bundle directory
|
|
97
|
+
* @returns {{ spec: any, approveHash: string }}
|
|
98
|
+
*/
|
|
99
|
+
export function resolveBundleSpec(bundle: {
|
|
100
|
+
spec: any;
|
|
101
|
+
}, bundleDir: string): {
|
|
102
|
+
spec: any;
|
|
103
|
+
approveHash: string;
|
|
104
|
+
};
|
|
105
|
+
/**
|
|
106
|
+
* TIGHTEN-ONLY envelope check (hamr Q8: raising the ceiling is a re-export,
|
|
107
|
+
* never a runner flag). Each GIVEN number must be ≤ the bundle spec's own —
|
|
108
|
+
* an absent field in the envelope trivially passes (nothing was asked to
|
|
109
|
+
* tighten), and a spec with no ceiling of its own for that field (only
|
|
110
|
+
* `maxWallMs` is ever absent — `budgetUsd` is required) has nothing to
|
|
111
|
+
* widen, so a given number passes there too.
|
|
112
|
+
* @param {any} spec the (already `$BARELOOP_BUNDLE`-resolved) bundle spec
|
|
113
|
+
* @param {{ budgetUsd?: number, maxWallMs?: number }} envelope
|
|
114
|
+
* @returns {{ ok: boolean, reds: Red[] }}
|
|
115
|
+
*/
|
|
116
|
+
export function checkEnvelope(spec: any, envelope?: {
|
|
117
|
+
budgetUsd?: number;
|
|
118
|
+
maxWallMs?: number;
|
|
119
|
+
}): {
|
|
120
|
+
ok: boolean;
|
|
121
|
+
reds: Red[];
|
|
122
|
+
};
|
|
123
|
+
/**
|
|
124
|
+
* Write `blessing.json`: the FIRST GREEN RUN on the importer's own machine
|
|
125
|
+
* signs the bundle as trustworthy (hamr Q2), so later runs need no
|
|
126
|
+
* `--approve` (hamr: "no-resign"). Overwrites any prior blessing — there is
|
|
127
|
+
* only ever one, for the bundle's current `bundleHash`.
|
|
128
|
+
* @param {string} dir bundle directory
|
|
129
|
+
* @param {{ bundleHash: string, runid: string, outcome: string, host?: string, blessedAt?: string }} record
|
|
130
|
+
* @returns {any} the blessing object written
|
|
131
|
+
*/
|
|
132
|
+
export function bless(dir: string, record: {
|
|
133
|
+
bundleHash: string;
|
|
134
|
+
runid: string;
|
|
135
|
+
outcome: string;
|
|
136
|
+
host?: string;
|
|
137
|
+
blessedAt?: string;
|
|
138
|
+
}): any;
|
|
139
|
+
/**
|
|
140
|
+
* Read `blessing.json` back and judge it against the bundle's CURRENT
|
|
141
|
+
* manifest hash. Three outcomes, never conflated: no blessing at all
|
|
142
|
+
* (`unblessed: true` — the bundle has simply never greened here, not a red);
|
|
143
|
+
* a blessing whose hash no longer matches the manifest (`blessing-stale` —
|
|
144
|
+
* the bundle was re-exported/edited since the run that blessed it); a match.
|
|
145
|
+
* @param {{ manifest: any, blessing: any }} bundle a `readBundle` result
|
|
146
|
+
* @returns {{ ok: boolean, unblessed: boolean, reds: Red[] }}
|
|
147
|
+
*/
|
|
148
|
+
export function verifyBlessing(bundle: {
|
|
149
|
+
manifest: any;
|
|
150
|
+
blessing: any;
|
|
151
|
+
}): {
|
|
152
|
+
ok: boolean;
|
|
153
|
+
unblessed: boolean;
|
|
154
|
+
reds: Red[];
|
|
155
|
+
};
|
|
156
|
+
/**
|
|
157
|
+
* F128 preflight — can this bundle's close scripts actually resolve `import
|
|
158
|
+
* ... from 'bareloop'` at runtime? The bundle's own `package.json` declares
|
|
159
|
+
* `bareloop` as ITS dependency (`npm install` must be run INSIDE the bundle
|
|
160
|
+
* directory — validation step 2 was corrected 2026-09-06, see the dated
|
|
161
|
+
* addendum in `docs/product/EXPORT-BUILD.md`); nothing installs it for the
|
|
162
|
+
* bundle as a side effect of the bundle being installed as someone else's
|
|
163
|
+
* dependency. Left unchecked, a missing `node_modules` crashes every close
|
|
164
|
+
* stage deep inside the close-first precheck with a bare
|
|
165
|
+
* `ERR_MODULE_NOT_FOUND`, which surfaces as a generic `close-red` with no
|
|
166
|
+
* cure line (the live defect this fixes) — this check runs first and fails
|
|
167
|
+
* fast with the exact command to run.
|
|
168
|
+
*
|
|
169
|
+
* Resolves the bare specifier `'bareloop'` exactly as a close script would,
|
|
170
|
+
* from inside the bundle's own `close/` directory (the probe path itself
|
|
171
|
+
* need not exist — `createRequire`/`resolve` only use it to anchor the
|
|
172
|
+
* `node_modules` walk). Going through `require.resolve` also walks
|
|
173
|
+
* `bareloop`'s own `exports` map (the package root, `"."` -> `./src/
|
|
174
|
+
* index.js`), so a bare successful resolve already proves a real,
|
|
175
|
+
* exports-map-shaped `bareloop` package is reachable from THIS bundle's own
|
|
176
|
+
* dependency chain — not some unrelated global install. Any resolution
|
|
177
|
+
* counts (a direct `node_modules/bareloop`, a symlink into it, or a parent
|
|
178
|
+
* `node_modules/bareloop` up the directory chain) — it only has to resolve.
|
|
179
|
+
* @param {string} bundleDir
|
|
180
|
+
* @returns {{ ok: boolean, reds: Red[] }}
|
|
181
|
+
*/
|
|
182
|
+
export function checkBundleDeps(bundleDir: string): {
|
|
183
|
+
ok: boolean;
|
|
184
|
+
reds: Red[];
|
|
185
|
+
};
|
|
186
|
+
/**
|
|
187
|
+
* Append one row to `history.jsonl` — one `bareloop run` on this machine per
|
|
188
|
+
* line. Same convention as `spine.js`/`ledger.js`: `appendFileSync` opens
|
|
189
|
+
* with O_APPEND, so one line under PIPE_BUF is written atomically with
|
|
190
|
+
* respect to other appenders; there is no read-modify-write here to race.
|
|
191
|
+
* @param {string} dir bundle directory
|
|
192
|
+
* @param {any} row
|
|
193
|
+
*/
|
|
194
|
+
export function appendHistory(dir: string, row: any): void;
|
|
195
|
+
export type Red = {
|
|
196
|
+
code: string;
|
|
197
|
+
path: string;
|
|
198
|
+
detail?: string;
|
|
199
|
+
};
|
package/types/cli.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one entry point. Never calls `process.exit` — always returns an exit
|
|
3
|
+
* code, so `bin/bareloop.mjs` can set `process.exitCode` and let node flush
|
|
4
|
+
* queued stdout on its own (F-something: `process.exit()` can discard it).
|
|
5
|
+
* @param {string[]} argv
|
|
6
|
+
* @param {{ env?: Record<string,string|undefined>, stdout?: any, stderr?: any, cwd?: string, provider?: any, providerFor?: any, judgeProvider?: any, now?: () => number, stdin?: any }} deps
|
|
7
|
+
* @returns {Promise<number>}
|
|
8
|
+
*/
|
|
9
|
+
export function main(argv: string[], deps?: {
|
|
10
|
+
env?: Record<string, string | undefined>;
|
|
11
|
+
stdout?: any;
|
|
12
|
+
stderr?: any;
|
|
13
|
+
cwd?: string;
|
|
14
|
+
provider?: any;
|
|
15
|
+
providerFor?: any;
|
|
16
|
+
judgeProvider?: any;
|
|
17
|
+
now?: () => number;
|
|
18
|
+
stdin?: any;
|
|
19
|
+
}): Promise<number>;
|
package/types/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { makeSpine } from "./spine.js";
|
|
2
2
|
export { runPlan } from "./planrun.js";
|
|
3
|
+
export { main as cliMain } from "./cli.js";
|
|
3
4
|
export { ralph, runClose, runStages, CLOSE_FAULTS } from "./ralph.js";
|
|
4
5
|
export { globToPrefix, scanSecrets } from "./validate.js";
|
|
5
6
|
export { validateJob, jobSpecHash, checkApproval, checkMenu, resolveWorkerModel, CLOSE_TYPES, CLASS_BY_CLOSE, GOLD_COMPARE, CADENCE_UNITS, PROVIDERS, CONDITION_KEYS, TOOL_MENU, LOCKED_TOOLS, STORE_VERBS, VERDICT_TYPES, LOCKED_VERDICTS } from "./job.js";
|
|
@@ -8,7 +9,7 @@ export { snapshotScope, evalExits } from "./exits.js";
|
|
|
8
9
|
export { runJob, ACCOUNTED_ROUND_TYPES } from "./run.js";
|
|
9
10
|
export { BRIDGE_SCHEMA, validateBridge, deriveStatus, listingRow, loadGate, mintBridge, appendGreen, appendRed, loadBridge, loadRegistry, saveBridge, makeRegistry, registryExists, QUARANTINED_VERDICTS, QUARANTINED_CODE, quarantinesCredit, newestEligibleVersion, reuseEligibility, recordDoor } from "./bridges.js";
|
|
10
11
|
export { renderListing, selectionPrompt } from "./selection.js";
|
|
11
|
-
export { validateEnvelope, resolveTrySpec, resolveReuse, reuseSpecHash, selectBridge, runReuse, REUSE_GRADED_RED, readResume, resumeTreeGate, CHECKPOINT_OUTCOMES, PAUSE_TTL_MS, checkpointAgeGate, applyDoorDecision, writeRunGreenRow } from "./reuse.js";
|
|
12
|
+
export { validateEnvelope, resolveTrySpec, resolveReuse, reuseSpecHash, selectBridge, runReuse, REUSE_GRADED_RED, readResume, resumeTreeGate, CHECKPOINT_OUTCOMES, PAUSE_TTL_MS, checkpointAgeGate, applyDoorDecision, writeRunGreenRow, shapeForkName } from "./reuse.js";
|
|
12
13
|
export { classifyIncidents, foldLedger, ledgerDeltas, updateLedger, LEDGER_CLASSES, VOUCHED_RATE_SOURCES, rateProvenance, spendProvenance } from "./ledger.js";
|
|
13
14
|
export { workBranchName, prepareWorkBranch, WORK_BRANCH_PREFIX, WORK_BRANCH_RE } from "./workbranch.js";
|
|
14
15
|
export { createReadShim, wrapReadTool, readShimArm, readShimStrategy, READ_SHIM_CAP, READ_SHIM_STRATEGY, READ_SHIM_DIFF_STRATEGY, READ_SHIM_ARMS } from "./readshim.js";
|
|
@@ -26,3 +27,4 @@ export { runBehaviour, formatBehaviour } from "./behaviour.js";
|
|
|
26
27
|
export { replayRun, formatReplay } from "./replay.js";
|
|
27
28
|
export { runInterview, authorCloseForJob, assembleSpec, prepareSigning, refusalEvents, refuseLockedKind, GENRE, REFUSAL_LIB, REFUSAL_CATEGORY, AUTHORED_SPEC_FIELDS } from "./authorjob.js";
|
|
28
29
|
export { PROMPT_REGISTERS, isPromptFile } from "./promptregisters.js";
|
|
30
|
+
export { exportBundle, bundleHash, readBundle, resolveBundleSpec, checkEnvelope, bless, verifyBlessing, appendHistory, checkBundleDeps } from "./bundle.js";
|
package/types/reuse.d.ts
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The name a cold green FORKS to when the entry of the job's own name greened a
|
|
3
|
+
* different close. Derived from the shape itself, so it is deterministic: the same
|
|
4
|
+
* close always lands in the same file, and the next green of that shape appends there
|
|
5
|
+
* instead of minting a third entry.
|
|
6
|
+
*
|
|
7
|
+
* The separator is a HYPHEN, not the `~` first proposed: the registry name IS the
|
|
8
|
+
* filename and `validateBridge` holds it to the same kebab alphabet the job validator
|
|
9
|
+
* uses (`^[a-z0-9][a-z0-9-]*$`), so a `~` name would be refused by the entry's own
|
|
10
|
+
* validator and the green would be lost — which is the exact failure this fork exists
|
|
11
|
+
* to prevent.
|
|
12
|
+
* @param {string} name @param {string[]} stageNames @returns {string}
|
|
13
|
+
*/
|
|
14
|
+
export function shapeForkName(name: string, stageNames: string[]): string;
|
|
1
15
|
/**
|
|
2
16
|
* Validate the reuse envelope — the three numbers the operator signs before the run.
|
|
3
17
|
*
|