bareloop 0.24.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 +97 -1
- package/bareloop.context.md +177 -0
- package/package.json +1 -1
- package/src/index.js +9 -0
- package/src/kinds.js +7 -1
- package/src/source.js +849 -0
- package/src/validate.js +15 -0
- package/types/index.d.ts +1 -0
- package/types/kinds.d.ts +24 -0
- package/types/source.d.ts +206 -0
- package/types/validate.d.ts +7 -0
package/CHANGELOG.md
CHANGED
|
@@ -5,7 +5,103 @@ All notable changes to bareloop are documented here. Format:
|
|
|
5
5
|
[SemVer](https://semver.org/spec/v2.0.0.html). Pre-1.0: **minor** = a ladder rung or
|
|
6
6
|
feature lands, **patch** = docs, fixes, scaffolding.
|
|
7
7
|
|
|
8
|
-
## [
|
|
8
|
+
## [0.25.0] — 2026-09-12
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **The source front door** (PRD item 33/M2) — a plain folder, a plain file, or one
|
|
13
|
+
`http(s)` URL now runs without a `--patient` git checkout: `prepareSource`
|
|
14
|
+
(`src/source.js`) freezes it into a hidden, scratch git tree bareloop owns (the person
|
|
15
|
+
never sees the repo), `proveDestination` proves a drop-off file is landable at $0 before
|
|
16
|
+
any token spends, and `copyOut` delivers the run's own output there once, on a minted
|
|
17
|
+
green, never overwriting. New CLI `scripts/prep-source.mjs`. `scripts/run-u.mjs` wires
|
|
18
|
+
both: a manifest-declared destination refuses before any token, and copies out on a
|
|
19
|
+
green (a refused copy-out never changes the verdict). The destination is a PER-RUN value
|
|
20
|
+
recorded in the manifest (`<into>/source.json`), never a signed job-spec field — a job
|
|
21
|
+
spec is a repeatable shape, signed once; nothing was added to `job.js`/`jobSpecHash`.
|
|
22
|
+
Exported: `prepareSource`, `proveDestination`, `copyOut`, `readSourceManifest`,
|
|
23
|
+
`frontDoorFromManifest`. `git()` (`src/kinds.js`) is now exported for reuse by this
|
|
24
|
+
module rather than re-spawned a second way.
|
|
25
|
+
|
|
26
|
+
### Fixed
|
|
27
|
+
|
|
28
|
+
- **Source front door (PRD item 33/M2 review):** `output` is now validated the ONE way
|
|
29
|
+
everywhere it is read or written (new `output-invalid` refusal) — a bare
|
|
30
|
+
`resolve(tree, output)` previously let a shape like `output/../../x` escape the frozen
|
|
31
|
+
tree; a hand-edited manifest carrying such a value is now treated as no front door at
|
|
32
|
+
all by `frontDoorFromManifest`, never handed to `copyOut` unvalidated. `prepareSource`
|
|
33
|
+
now proves the destination (`proveDestination`) BEFORE creating `into`, so a refused
|
|
34
|
+
destination leaves nothing on disk. `copyOut` takes an optional `into` (the run's
|
|
35
|
+
scratch root); `scripts/run-u.mjs`'s two call sites now pass `dirname(wd)` instead of
|
|
36
|
+
`wd`, so a destination inside the scratch area but outside the frozen tree is caught as
|
|
37
|
+
`destination-contained` instead of slipping through.
|
|
38
|
+
- **Source front door hard-line defect (PRD item 33/M2, live-proven):** every frozen
|
|
39
|
+
file's content — a folder, a single file, or a fetched URL body — is now scanned for a
|
|
40
|
+
known secret shape (the same `scanSecrets` inventory, `src/validate.js`) BEFORE
|
|
41
|
+
anything is written under `into`; a hit refuses `source-carries-secret`, naming the
|
|
42
|
+
file(s) and pattern name(s) only, never the matched text, and leaves nothing on disk.
|
|
43
|
+
Previously the door scanned only the URL string, so a plain folder or file carrying a
|
|
44
|
+
real API key (e.g. an `.env`) was frozen into the tree and committed to the hidden git
|
|
45
|
+
seed untouched.
|
|
46
|
+
- **Source front door, M2b review fixes 2–8 (PRD item 33):** `.env`/`.env.*` refused by
|
|
47
|
+
NAME whatever it contains (`source-env-file`), belt-and-braces beside the content scan;
|
|
48
|
+
a per-file `MAX_BUFFER` ceiling for folder and single-file sources (`source-file-
|
|
49
|
+
oversize`; repo sources exempt); the delivered file now carries the date
|
|
50
|
+
(`profile-2026-09-12.md`, `-2`/`-3` the same day, cap 99) — new exports
|
|
51
|
+
`datedDestination`/`pickDelivery`, used by both `proveDestination` and `copyOut` so the
|
|
52
|
+
two can never disagree; `copyOut` returns `{bytes, sha256, path}` and `run-u.mjs`
|
|
53
|
+
spine-records the real landed path (`destination-written {path, declared, bytes,
|
|
54
|
+
sha256}`); a URL's redirects are followed but never invisible — the final URL rides in
|
|
55
|
+
the manifest (`finalUrl`) and is printed by `prep-source`, and is secret-scanned like
|
|
56
|
+
the typed one; a nested `.git` anywhere below the root refuses `source-nested-repo`,
|
|
57
|
+
`git add` runs `-f` so a source `.gitignore` cannot drop a file from the seed, and the
|
|
58
|
+
seed is read back with `git ls-tree` and diffed against the manifest's file list
|
|
59
|
+
(`source-seed-incomplete` on a shortfall); a git repo root is now an allowed source
|
|
60
|
+
(`kind: 'repo'`) — copied with its history, working files at the tree root, seed on top
|
|
61
|
+
of existing history; a `.git` FILE (linked worktree/submodule) refuses
|
|
62
|
+
`source-is-linked-worktree`; `prep-source` prints a folder-blast-radius note (mirrored
|
|
63
|
+
in `bareloop.context.md`).
|
|
64
|
+
- **Repo-source hard-line defect (live-proven during the M2b 7 review):** a copied
|
|
65
|
+
`.git/hooks` carried the SOURCE repo's own `pre-commit`/`commit-msg`/`post-commit`
|
|
66
|
+
scripts, and `git commit` ran them — arbitrary code from the source repo, executed
|
|
67
|
+
inside bareloop's own process, before a single token spent. Fixed: the copied hooks
|
|
68
|
+
directory is stripped after the `.git` copy, and every git call the door makes pins
|
|
69
|
+
`core.hooksPath` to a path that is never created, so a `core.hooksPath` set in the
|
|
70
|
+
copied `.git/config` cannot reopen the hole either.
|
|
71
|
+
|
|
72
|
+
- **Source front door D1/D2/D3 rework (PRD item 33, hamr's rulings, 2026-09-12):**
|
|
73
|
+
- **D1 — repo sources copy only what git tracks** (hamr, verbatim: "copy only what git
|
|
74
|
+
tracks"; closes F164/F165): repo sources are enumerated with `git ls-files --stage` in
|
|
75
|
+
the source, never a filesystem walk; each tracked path's working-tree content is
|
|
76
|
+
frozen, and nothing untracked or gitignored is ever a candidate (npm
|
|
77
|
+
`node_modules/.bin/*` symlinks, this repo's own `.claude/`, a gitignored `.env`,
|
|
78
|
+
none of it reaches the tree or the seed). `git add -A` drops `-f` for repo sources
|
|
79
|
+
(kept for plain folder/file/URL sources). A gitlink (a real submodule) refuses
|
|
80
|
+
`source-nested-repo`; a tracked symlink refuses `source-symlink` only when it
|
|
81
|
+
resolves outside the source root — one staying inside is copied verbatim as a link.
|
|
82
|
+
- **D2 — secrets scanned inside binary content too** (closes the F166 residual): every
|
|
83
|
+
frozen file's content is scanned for secrets, binary files included, decoded `latin1`
|
|
84
|
+
(byte-preserving) instead of `utf8` — the same `SECRET_PATTERNS` inventory, no second
|
|
85
|
+
pattern list.
|
|
86
|
+
- **D3 — destination is a DIRECTORY, never a filename** (hamr's ruling, 2026-09-12): it
|
|
87
|
+
may already exist, need not be empty, and may sit inside the source itself
|
|
88
|
+
(`destination-in-source` is gone). A job may produce more than one file; `copyOut`
|
|
89
|
+
delivers every non-empty file under `output/` (excluding `output/.gitkeep`), each
|
|
90
|
+
under its own dated name, and returns `{files: [{path, bytes, sha256}]}` instead of a
|
|
91
|
+
single object. The single-file `output` manifest field and `output-invalid` validator
|
|
92
|
+
are removed — the escape vector they guarded is now closed structurally, since
|
|
93
|
+
`copyOut` reads `tree/output/` itself rather than a manifest-declared path. A repo
|
|
94
|
+
source's `destination` is recorded as the declared write fence and never proven or
|
|
95
|
+
copied into (`frontDoorFromManifest` returns `null` for a repo manifest) — wiring it
|
|
96
|
+
into the signed `writeScope` field is left to a later milestone. New refusal codes:
|
|
97
|
+
`destination-not-directory`, `destination-not-writable`, `destination-invalid`
|
|
98
|
+
(repo-destination type guard); retired: `output-invalid`,
|
|
99
|
+
`destination-output-required`, `destination-in-source`. `scripts/prep-source.mjs`
|
|
100
|
+
drops `--output`; `scripts/run-u.mjs`'s copy-out call site emits one
|
|
101
|
+
`destination-written` per delivered file.
|
|
102
|
+
- **D4 — measured a real 15 MB repo** (`~/PycharmProjects/adaptlearn`, the repo that
|
|
103
|
+
broke F164): 1296 files, 3.16 MB copied, 0.89s elapsed, ~84 MB maxRSS, no gitignored
|
|
104
|
+
or untracked content reached the tree or the seed. No size cap added.
|
|
9
105
|
|
|
10
106
|
## [0.24.0] — 2026-09-10
|
|
11
107
|
|
package/bareloop.context.md
CHANGED
|
@@ -2907,6 +2907,183 @@ provider but `anthropic-api`, spine bundling for `formatReplay` on the minting r
|
|
|
2907
2907
|
`runid` is noted), and tarball packing — a bundle is a plain directory; use `npm pack` on it
|
|
2908
2908
|
yourself if you want a tarball.
|
|
2909
2909
|
|
|
2910
|
+
### The source front door — a plain folder/file/URL, no `--patient` (PRD item 33/M2, `src/source.js`)
|
|
2911
|
+
|
|
2912
|
+
A repo job still gives `runJob` a `--patient` checkout it owns. A NON-code job (a resume, a
|
|
2913
|
+
CSV, a page) has no repo to point at — this door is what lets it run anyway, WITHOUT the
|
|
2914
|
+
person ever seeing a git repo (H1's fix): the source is frozen into a hidden, scratch git
|
|
2915
|
+
tree bareloop creates and owns, before any token spends, so the run and its close judge the
|
|
2916
|
+
SAME bytes.
|
|
2917
|
+
|
|
2918
|
+
```js
|
|
2919
|
+
import { prepareSource, proveDestination, copyOut, readSourceManifest, frontDoorFromManifest } from 'bareloop';
|
|
2920
|
+
|
|
2921
|
+
const prepared = await prepareSource({
|
|
2922
|
+
source: '/home/me/resume.md', // an http(s) URL, an existing file, or an existing plain folder/repo
|
|
2923
|
+
into: '/home/me/.bareloop-jobs/j1', // must NOT already exist — fresh, never reused
|
|
2924
|
+
destination: '/home/me/profile-drafts', // optional — a DIRECTORY (D3), need not exist or be empty
|
|
2925
|
+
});
|
|
2926
|
+
// prepared.tree === '/home/me/.bareloop-jobs/j1/tree' — hand THIS to runJob as `workdir`
|
|
2927
|
+
// prepared.manifest.seed — the commit `seedAtHead` will read at run start
|
|
2928
|
+
```
|
|
2929
|
+
|
|
2930
|
+
Or the CLI: `node scripts/prep-source.mjs --source <path-or-url> --into <dir> [--destination
|
|
2931
|
+
<absolute-directory>]` — prints the tree path, the seed, and the exact next command.
|
|
2932
|
+
|
|
2933
|
+
> **If your source is a folder, read this first (hamr's ruling):** make a new folder, put
|
|
2934
|
+
> only the file(s) this job needs in it, point bareloop at that — never your original
|
|
2935
|
+
> folder. Everything in the folder you name is frozen, read, and paid for. `prep-source`
|
|
2936
|
+
> prints the same line on every folder prep, and the panel (N6) shows it beside the Source
|
|
2937
|
+
> field.
|
|
2938
|
+
|
|
2939
|
+
**Layout.** `<into>/tree/input/…` is the frozen copy; `<into>/tree/output/` is empty (a
|
|
2940
|
+
`.gitkeep` seeds it) and is where the run writes; `<into>/source.json` is the manifest,
|
|
2941
|
+
OUTSIDE the tree — no worker verb can ever read or edit it. One hidden commit, authored
|
|
2942
|
+
`bareloop <bareloop@localhost>` with `commit.gpgsign` pinned off — never the operator's
|
|
2943
|
+
identity, never global git config (this repo's own tests run with an empty `HOME`).
|
|
2944
|
+
|
|
2945
|
+
**What is admitted today (hamr: "start with already supported files").** A URL is fetched
|
|
2946
|
+
ONCE with plain `fetch` — no credentials, no cookies, no custom headers — under
|
|
2947
|
+
`PROVIDER_TIMEOUT_MS` and capped at `MAX_BUFFER`, both reused from elsewhere in this library,
|
|
2948
|
+
never a second number. Content-type must be `text/*`, `application/json`, or
|
|
2949
|
+
`application/xml`. A local file or folder must be plain text (a NUL byte in the first 8KB of
|
|
2950
|
+
any file refuses the whole prep, naming every offending file) and must contain no symlinks
|
|
2951
|
+
(named, never followed). Each file has its OWN `MAX_BUFFER` ceiling (`source-file-oversize`)
|
|
2952
|
+
— there is no folder TOTAL cap, hamr's call. A file named `.env` or `.env.*` is refused by
|
|
2953
|
+
NAME whatever it contains (`source-env-file`), belt-and-braces beside the content scan below.
|
|
2954
|
+
A `.git` found anywhere BELOW the root is a nested repo and refuses (`source-nested-repo`):
|
|
2955
|
+
its objects are a second history nobody declared, and the seed could not hold them honestly.
|
|
2956
|
+
PDF and Word input have no reader here (hole H7, PRD item 33) — logged, not built.
|
|
2957
|
+
|
|
2958
|
+
**A git repo IS an allowed source** (hamr: yes — the PR-review job). It is COPIED with its
|
|
2959
|
+
history, never used in place (the patients-are-copies rule): the working files land at the
|
|
2960
|
+
TREE ROOT rather than under `input/`, `.git` is copied verbatim, `output/` is added, and the
|
|
2961
|
+
seed commit goes on TOP of the existing history — so `manifest.kind === 'repo'` and the
|
|
2962
|
+
worker gets a real repo to review. Its guards (the input stays untouched) arrive with M4. A
|
|
2963
|
+
`.git` that is a FILE, not a directory — a linked worktree or a submodule — refuses
|
|
2964
|
+
`source-is-linked-worktree`: its real git directory lives elsewhere, and a copy would
|
|
2965
|
+
silently commit into the original. Text-only and the per-file ceiling do not apply to a repo
|
|
2966
|
+
source (a real repo legitimately carries binaries).
|
|
2967
|
+
|
|
2968
|
+
**A repo source copies only what git tracks** (D1 rework, hamr's ruling verbatim: "copy only
|
|
2969
|
+
what git tracks" — closes F164/F165). Files are enumerated with `git ls-files --stage` in the
|
|
2970
|
+
SOURCE, never a filesystem walk: each tracked path's WORKING-TREE content is frozen (so
|
|
2971
|
+
uncommitted edits to a tracked file come along), and nothing untracked or gitignored is ever a
|
|
2972
|
+
candidate — real npm `node_modules/.bin/*` symlinks, this repo's own `.claude/`, a gitignored
|
|
2973
|
+
`.env`, none of it is read or written. `git add -A` drops `-f` for a repo source (kept for a
|
|
2974
|
+
plain folder/file/URL, where a `.gitignore` has no honest meaning yet). A gitlink entry (a real
|
|
2975
|
+
submodule) refuses `source-nested-repo`; a tracked symlink refuses `source-symlink` only when
|
|
2976
|
+
it resolves OUTSIDE the source root — one that stays inside is legal tracked content, copied
|
|
2977
|
+
verbatim as a link, never dereferenced. Measured live against a real 15 MB repo carrying
|
|
2978
|
+
`node_modules` (`~/PycharmProjects/adaptlearn`, the repo that broke F164): 1296 files, 3.16 MB,
|
|
2979
|
+
0.89s elapsed, ~84 MB maxRSS — no gitignored or untracked content reached the tree or the seed.
|
|
2980
|
+
|
|
2981
|
+
**Every frozen file's content is secret-scanned, binary files included** (D2, closes the F166
|
|
2982
|
+
residual). A binary file (a NUL byte in its first 8KB) used to be hashed but never scanned —
|
|
2983
|
+
now it runs through the SAME `SECRET_PATTERNS` inventory, decoded `latin1` (byte-preserving)
|
|
2984
|
+
instead of `utf8`, so an ASCII key embedded in a compiled artifact is still found. This only
|
|
2985
|
+
matters where binaries are admitted at all (a repo source today; a plain folder/file still
|
|
2986
|
+
refuses binary content outright via `source-not-text`, unchanged).
|
|
2987
|
+
|
|
2988
|
+
**A URL's redirects are followed but never invisible.** The URL the body actually came from
|
|
2989
|
+
rides into the manifest as `finalUrl` and is printed by `prep-source` whenever it differs
|
|
2990
|
+
from what was typed, so a link that quietly lands on a login or error page is something a
|
|
2991
|
+
person can see before a token spends. The final URL is secret-scanned the same way the typed
|
|
2992
|
+
one is.
|
|
2993
|
+
|
|
2994
|
+
**The seed holds every frozen file, and that is CHECKED, not hoped.** `git add` runs with
|
|
2995
|
+
`-f`, so a `.gitignore` inside the source cannot drop files from the seed (a file present on
|
|
2996
|
+
disk but absent from the seed would read to a close as the worker having written it); then
|
|
2997
|
+
the seed is read back with `git ls-tree -r --name-only HEAD` and diffed against the manifest's
|
|
2998
|
+
own file list by name. A shortfall refuses `source-seed-incomplete`.
|
|
2999
|
+
|
|
3000
|
+
**Every frozen file's content is scanned for a known secret shape before anything is
|
|
3001
|
+
written under `into`** (a folder, a single file, or a fetched URL body — the SAME inventory
|
|
3002
|
+
`scanSecrets` reads, `src/validate.js`; live-proven fix — the door used to scan only the URL
|
|
3003
|
+
string, so a plain `.env` with a real API key was frozen into the tree and committed to the
|
|
3004
|
+
hidden git seed untouched). A hit refuses `source-carries-secret`, naming the file path(s)
|
|
3005
|
+
and the pattern name(s) only — the matched text itself never appears in the refusal, the
|
|
3006
|
+
manifest, or any file, and nothing is written to disk (`into` does not exist afterward). This
|
|
3007
|
+
is a shape-based check, not a filename denylist: a secret whose shape is not in the inventory
|
|
3008
|
+
(a plain database password in a `.env`, say) still passes through — a named, accepted
|
|
3009
|
+
residual, not this fix's job to close.
|
|
3010
|
+
|
|
3011
|
+
**Every refusal is a named `{stop, code}`, never a throw and never silent:** `source-
|
|
3012
|
+
unreadable`, `source-symlink`, `source-env-file`, `source-file-oversize`,
|
|
3013
|
+
`source-nested-repo`, `source-is-linked-worktree`, `source-seed-incomplete`, `source-git-failed`, `source-not-text`, `source-fetch-failed`,
|
|
3014
|
+
`source-fetch-timeout`, `source-fetch-oversize`, `source-carries-secret`, `into-exists`,
|
|
3015
|
+
`destination-invalid`, `destination-not-absolute`, `destination-not-directory`,
|
|
3016
|
+
`destination-not-writable`, `destination-contained`, `destination-parent-missing`,
|
|
3017
|
+
`destination-parent-unwritable`, `destination-exists`, `destination-output-missing`,
|
|
3018
|
+
`destination-write-failed`, `source-manifest-unreadable`, `source-manifest-invalid`. A
|
|
3019
|
+
refusal never throws, never overwrites a person's file, and never lands a destination inside
|
|
3020
|
+
the run's own scratch tree.
|
|
3021
|
+
|
|
3022
|
+
**Destination is a DIRECTORY, never a filename** (D3 rework, hamr's ruling, 2026-09-12,
|
|
3023
|
+
condensed in `docs/product/ITEM33-BUILD.md`). It may already exist and need not be empty; it
|
|
3024
|
+
may sit inside the source itself (the same-dir case: "i can limit your actions to a certain
|
|
3025
|
+
fix in a certain dir and your output will also be there" — `destination-in-source` is gone). A
|
|
3026
|
+
job may produce MORE THAN ONE file (his example: a flight search producing one sheet for
|
|
3027
|
+
"SFO→LAX red-eye" and another for "SFO under $700"); the agent names them, and there is no
|
|
3028
|
+
`output` field in the manifest any more — `copyOut` always reads `tree/output/` itself, never
|
|
3029
|
+
a manifest-declared path, which closes the old escape vector (`output/../../x`) structurally
|
|
3030
|
+
rather than by validation.
|
|
3031
|
+
|
|
3032
|
+
**Destination is proven at prepare time, not just in-run** (hamr's ruling: source AND
|
|
3033
|
+
destination are proven at job start, $0): when `destination` is given, `prepareSource` calls
|
|
3034
|
+
`proveDestination` BEFORE creating `into` — a refused destination leaves nothing on disk.
|
|
3035
|
+
`proveDestination` only proves the directory is USABLE (exists as a directory and is writable,
|
|
3036
|
+
or its parent exists and is writable so it can be created) — it cannot foresee individual
|
|
3037
|
+
delivered FILE names, since those are not known until the run actually produces them.
|
|
3038
|
+
`copyOut` takes an optional `into` (the run's scratch root, `<into>`, of which `tree` is the
|
|
3039
|
+
`tree/` subdirectory; defaults to `tree` for direct callers with no `into`) so a destination
|
|
3040
|
+
sitting inside the scratch area but outside the frozen tree — e.g. `<into>/profile.md` — is
|
|
3041
|
+
also caught as `destination-contained`, not just one strictly inside `tree`. `run-u.mjs`'s two
|
|
3042
|
+
call sites pass `dirname(wd)` (`wd` IS `<into>/tree`) as `into` for exactly this reason.
|
|
3043
|
+
|
|
3044
|
+
**A `kind: 'repo'` source's destination is a DIFFERENT thing** (hamr: "destination if for code
|
|
3045
|
+
on a green then that would be the place agent allowed to do changes") — it names the WRITE
|
|
3046
|
+
FENCE inside the copied repo, the signed `writeScope` field's job (`src/job.js:363`), never a
|
|
3047
|
+
filesystem drop-off point. It is recorded as declared, never proven by `proveDestination` and
|
|
3048
|
+
never handed to `copyOut` (`frontDoorFromManifest` returns `null` for a repo manifest).
|
|
3049
|
+
Wiring it into `writeScope` is M3/M4's job, not this door's.
|
|
3050
|
+
|
|
3051
|
+
**Every non-empty file under `output/` is delivered, each under its OWN dated name, and
|
|
3052
|
+
nothing is ever overwritten.** A file named `profile.md` LANDS as `profile-2026-09-12.md`
|
|
3053
|
+
inside the destination directory; a second delivery of the same name the same day lands as
|
|
3054
|
+
`profile-2026-09-12-2.md`, then `-3` (the work-branch collision rule), up to `-99` before
|
|
3055
|
+
`destination-exists`. An empty file is silently skipped (not a delivered result); the batch
|
|
3056
|
+
refuses `destination-output-missing` only when NOTHING non-empty exists under `output/` at
|
|
3057
|
+
all (excluding the `output/.gitkeep` seed placeholder). `datedDestination(destination, now,
|
|
3058
|
+
n)` and `pickDelivery(destination, now?)` are exported and unchanged from M2b — `copyOut`
|
|
3059
|
+
calls them per file now, at delivery time, rather than once against a single declared path.
|
|
3060
|
+
`copyOut` returns `{files: [{path, bytes, sha256}]}` and `run-u.mjs` spine-records ONE
|
|
3061
|
+
`destination-written` per delivered file — the declared directory is not the path anything
|
|
3062
|
+
wrote.
|
|
3063
|
+
|
|
3064
|
+
**The destination is a PER-RUN value, never a signed job-spec field.** A job spec is a
|
|
3065
|
+
repeatable SHAPE (goal, checks, judge rules), signed once; source and destination vary per
|
|
3066
|
+
run — the same way `bareloop run <bundle> --repo <path>` varies the repo instance without
|
|
3067
|
+
touching the bundle's own signature. So `job.js`/`jobSpecHash` carry nothing about it: the
|
|
3068
|
+
run's own instance lives entirely in the manifest `prepareSource` writes.
|
|
3069
|
+
|
|
3070
|
+
`scripts/run-u.mjs` reads that manifest via `readSourceManifest(dirname(workdir))` (`workdir`
|
|
3071
|
+
IS `<into>/tree`, so its parent is `<into>`) and reduces it with `frontDoorFromManifest`. No
|
|
3072
|
+
manifest → no front-door behaviour at all — every existing repo-patient job is untouched. A
|
|
3073
|
+
manifest that exists but cannot be read or parsed is a named stop, never a silent skip. When
|
|
3074
|
+
a manifest declares a destination, `proveDestination` runs BEFORE any token spends (a job
|
|
3075
|
+
that cannot land its output should never buy a worker turn first), and `copyOut` runs once,
|
|
3076
|
+
on a minted `green` — the ONE outcome string a graded close ever mints; `soft-green` jobs
|
|
3077
|
+
mint the same string, since the class lives on `spec.verdictType`, never on `runJob`'s
|
|
3078
|
+
return value. A refused copy-out is spine-recorded (`destination-refused {code, detail}`)
|
|
3079
|
+
beside the green it could not deliver and NEVER changes that verdict — a delivery failure is
|
|
3080
|
+
not a grading failure.
|
|
3081
|
+
|
|
3082
|
+
Not wired yet, named rather than silently absent: `bareloop run` (the exported bundle CLI,
|
|
3083
|
+
`src/cli.js`) still requires `--repo`; a bundled front-door job is parked past PRD item
|
|
3084
|
+
33/M5. The intake form that lets a person fill in Source/Destination through the authoring
|
|
3085
|
+
interview (rather than the CLI above) is item 33/M3, not yet built.
|
|
3086
|
+
|
|
2910
3087
|
## Architecture
|
|
2911
3088
|
|
|
2912
3089
|
Three layers. An **outer shell** (dumb, permanent): per-run budget cap via bareguard,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bareloop",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.25.0",
|
|
4
4
|
"description": "Workflows that earn their own design, with receipts — agent-authored workflows for repeated, long, verifiable jobs that improve across runs under an un-gameable gate.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.js",
|
package/src/index.js
CHANGED
|
@@ -285,3 +285,12 @@ export {
|
|
|
285
285
|
// exists so an adopter driving `bareloop run` from their own script can do so
|
|
286
286
|
// without spawning `bin/bareloop.mjs` as a child process.
|
|
287
287
|
export { main as cliMain } from './cli.js';
|
|
288
|
+
// PRD item 33/M2 (`docs/product/ITEM33-BUILD.md`) — the source front door. A
|
|
289
|
+
// plain folder, a plain file, or one URL frozen into a hidden git tree before
|
|
290
|
+
// any token spends (H1's fix: the person never sees the repo), and the
|
|
291
|
+
// destination proven/copied at the two moments a runner needs them ($0
|
|
292
|
+
// preflight, and once on a minted green). Exported because `scripts/prep-
|
|
293
|
+
// source.mjs` and `scripts/run-u.mjs` are two separate call sites reaching
|
|
294
|
+
// for the SAME functions — an adopter building a third has to reach the same
|
|
295
|
+
// module, not a re-derivation of it.
|
|
296
|
+
export { prepareSource, proveDestination, copyOut, readSourceManifest, frontDoorFromManifest } from './source.js';
|
package/src/kinds.js
CHANGED
|
@@ -485,9 +485,15 @@ async function sh(cmd, args, { cwd, env: declared = {}, timeoutMs = DEFAULT_TIME
|
|
|
485
485
|
/**
|
|
486
486
|
* git, read-only, in a repository. Faults come back as `ok:false` with the
|
|
487
487
|
* stderr — a caller decides whether that is a stop.
|
|
488
|
+
*
|
|
489
|
+
* Exported (PRD item 33/M2, `src/source.js`) so the hidden-git seed a plain
|
|
490
|
+
* folder/file/URL job runs against goes through the SAME spawn primitive as
|
|
491
|
+
* every other git read in this file — a second `spawn('git', …)` for the
|
|
492
|
+
* source front door would be a second instrument with its own timeout and
|
|
493
|
+
* buffer ceiling to keep in sync with this one.
|
|
488
494
|
* @param {string} cwd @param {string[]} args
|
|
489
495
|
*/
|
|
490
|
-
async function git(cwd, args) {
|
|
496
|
+
export async function git(cwd, args) {
|
|
491
497
|
const r = await sh('git', args, { cwd, timeoutMs: 120_000 });
|
|
492
498
|
// git refusing IS "the close cannot run": the changed-set primitive is not an
|
|
493
499
|
// optional enrichment, so a non-zero git carries FAILED rather than inheriting
|