@bongos/core 1.19.6 → 1.19.7
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/.bongos-core.json +22 -17
- package/.gitattributes +1 -0
- package/README.md +2 -2
- package/docs/adr/0043-git-ssh-trust-boundary-and-rank-floor-on-permission-paths.md +38 -0
- package/docs/canonical-permissions.md +3 -1
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/scripts/gds/hooks/pre-push.js +6 -2
- package/scripts/gds/install-git-hooks.js +13 -10
- package/scripts/gds/setup.js +7 -5
- package/src/module-api.js +3 -1
- package/tests/pre_push_hook.mjs +77 -0
package/.bongos-core.json
CHANGED
|
@@ -2,22 +2,22 @@
|
|
|
2
2
|
"artifact": "bongos-core",
|
|
3
3
|
"manifest_schema": 1,
|
|
4
4
|
"generator": "scripts/gds/package-core.js",
|
|
5
|
-
"core_version": "1.19.
|
|
6
|
-
"core_contract": "1.19.
|
|
7
|
-
"source_commit": "
|
|
5
|
+
"core_version": "1.19.7",
|
|
6
|
+
"core_contract": "1.19.7",
|
|
7
|
+
"source_commit": "4c4a86f4d9e8987627774c5a8e5060f3f61b0bc0",
|
|
8
8
|
"source_ref": "HEAD",
|
|
9
|
-
"built_at": "2026-08-08T20:
|
|
9
|
+
"built_at": "2026-08-08T20:39:12.129Z",
|
|
10
10
|
"redaction": {
|
|
11
11
|
"model": "docs-redacted+functional-verbatim",
|
|
12
12
|
"docs_redacted": 284,
|
|
13
13
|
"agent_docs_stubbed": 21,
|
|
14
|
-
"functional_verbatim":
|
|
14
|
+
"functional_verbatim": 1203,
|
|
15
15
|
"rules": 3,
|
|
16
16
|
"gate_literals": 3,
|
|
17
17
|
"gate": "passed"
|
|
18
18
|
},
|
|
19
|
-
"file_count":
|
|
20
|
-
"tree_sha256": "
|
|
19
|
+
"file_count": 1508,
|
|
20
|
+
"tree_sha256": "1ae72bc6a69eda4c6cf29ec3a5e9ba8598b43a5b73aba6abe44bbd7da45dfdbc",
|
|
21
21
|
"files": [
|
|
22
22
|
{
|
|
23
23
|
"path": ".claude/skills/blocker-review/SKILL.md",
|
|
@@ -287,7 +287,7 @@
|
|
|
287
287
|
{
|
|
288
288
|
"path": ".gitattributes",
|
|
289
289
|
"mode": "0000644",
|
|
290
|
-
"sha256": "
|
|
290
|
+
"sha256": "106e3a8a087cf1a9dd932513115d16c387cfeebea0e22ed269a672f95efdac1e"
|
|
291
291
|
},
|
|
292
292
|
{
|
|
293
293
|
"path": "CLAUDE.md",
|
|
@@ -322,7 +322,7 @@
|
|
|
322
322
|
{
|
|
323
323
|
"path": "README.md",
|
|
324
324
|
"mode": "0000644",
|
|
325
|
-
"sha256": "
|
|
325
|
+
"sha256": "f0358ea7bfffc17c7f1e4ead3bb223117787ba6f88c1ae357150e4f6a59e70a8"
|
|
326
326
|
},
|
|
327
327
|
{
|
|
328
328
|
"path": "bin/bongos.js",
|
|
@@ -627,7 +627,7 @@
|
|
|
627
627
|
{
|
|
628
628
|
"path": "docs/adr/0043-git-ssh-trust-boundary-and-rank-floor-on-permission-paths.md",
|
|
629
629
|
"mode": "0000644",
|
|
630
|
-
"sha256": "
|
|
630
|
+
"sha256": "9feebaa2a6e1133f6eae91e3e8d9bf85fd1e9b3bc276fb862b9097f72fc57621"
|
|
631
631
|
},
|
|
632
632
|
{
|
|
633
633
|
"path": "docs/adr/0044-mediterranean-palette-replacement.md",
|
|
@@ -1297,7 +1297,7 @@
|
|
|
1297
1297
|
{
|
|
1298
1298
|
"path": "docs/canonical-permissions.md",
|
|
1299
1299
|
"mode": "0000644",
|
|
1300
|
-
"sha256": "
|
|
1300
|
+
"sha256": "f57f118f5b863a82895129c70311a7c87e5424e22a793b82c637db2ee7e345a0"
|
|
1301
1301
|
},
|
|
1302
1302
|
{
|
|
1303
1303
|
"path": "docs/design-contract.md",
|
|
@@ -3997,12 +3997,12 @@
|
|
|
3997
3997
|
{
|
|
3998
3998
|
"path": "package-lock.json",
|
|
3999
3999
|
"mode": "0000644",
|
|
4000
|
-
"sha256": "
|
|
4000
|
+
"sha256": "5dca6505b577478929f260ec88895fe954a8ad3dd4ceb3a065d21fa1edaa60e1"
|
|
4001
4001
|
},
|
|
4002
4002
|
{
|
|
4003
4003
|
"path": "package.json",
|
|
4004
4004
|
"mode": "0000644",
|
|
4005
|
-
"sha256": "
|
|
4005
|
+
"sha256": "e70c0d0d826a15558627693fdb4d75b18e6c418ec4f0e7d65bd7847c1a17a7bf"
|
|
4006
4006
|
},
|
|
4007
4007
|
{
|
|
4008
4008
|
"path": "public-docs/index.html",
|
|
@@ -4772,7 +4772,7 @@
|
|
|
4772
4772
|
{
|
|
4773
4773
|
"path": "scripts/gds/hooks/pre-push.js",
|
|
4774
4774
|
"mode": "0000644",
|
|
4775
|
-
"sha256": "
|
|
4775
|
+
"sha256": "605b6551ca630222b671b74c08fa1b69d487614359fad327a93c2ac17e8a2021"
|
|
4776
4776
|
},
|
|
4777
4777
|
{
|
|
4778
4778
|
"path": "scripts/gds/http-api-client.js",
|
|
@@ -4787,7 +4787,7 @@
|
|
|
4787
4787
|
{
|
|
4788
4788
|
"path": "scripts/gds/install-git-hooks.js",
|
|
4789
4789
|
"mode": "0000644",
|
|
4790
|
-
"sha256": "
|
|
4790
|
+
"sha256": "668d97543e863394527bad2a629037bfc81eea2f8244f53eb5e79fd43f7485c0"
|
|
4791
4791
|
},
|
|
4792
4792
|
{
|
|
4793
4793
|
"path": "scripts/gds/key-card.js",
|
|
@@ -5182,7 +5182,7 @@
|
|
|
5182
5182
|
{
|
|
5183
5183
|
"path": "scripts/gds/setup.js",
|
|
5184
5184
|
"mode": "0000644",
|
|
5185
|
-
"sha256": "
|
|
5185
|
+
"sha256": "8e512a07ed885666722a216951dfad7f57fbfd92923bdf5079ce1c6f7b5c0abc"
|
|
5186
5186
|
},
|
|
5187
5187
|
{
|
|
5188
5188
|
"path": "scripts/gds/shell.js",
|
|
@@ -5622,7 +5622,7 @@
|
|
|
5622
5622
|
{
|
|
5623
5623
|
"path": "src/module-api.js",
|
|
5624
5624
|
"mode": "0000644",
|
|
5625
|
-
"sha256": "
|
|
5625
|
+
"sha256": "c60cd1bedb3f71a544c913e8859082a392e2e96e17927cff8c6aa928805634a1"
|
|
5626
5626
|
},
|
|
5627
5627
|
{
|
|
5628
5628
|
"path": "src/module-loader/loader.js",
|
|
@@ -7034,6 +7034,11 @@
|
|
|
7034
7034
|
"mode": "0000644",
|
|
7035
7035
|
"sha256": "c9604d5184f2f1243c4bfab594e3f0e2c04060aadc2d726c923e72d38e276577"
|
|
7036
7036
|
},
|
|
7037
|
+
{
|
|
7038
|
+
"path": "tests/pre_push_hook.mjs",
|
|
7039
|
+
"mode": "0000644",
|
|
7040
|
+
"sha256": "cdd47e5e709590d1f8bb604b9ad98b13857d0efc196c8ab63728f00f62b999b5"
|
|
7041
|
+
},
|
|
7037
7042
|
{
|
|
7038
7043
|
"path": "tests/preflight_checks.mjs",
|
|
7039
7044
|
"mode": "0000644",
|
package/.gitattributes
CHANGED
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
*.sh text eol=lf
|
|
18
18
|
*.bash text eol=lf
|
|
19
19
|
.husky/pre-commit text eol=lf
|
|
20
|
+
.husky/pre-push text eol=lf
|
|
20
21
|
|
|
21
22
|
# Node / Python sources: keep LF everywhere too. Node tolerates CRLF, but LF
|
|
22
23
|
# keeps diffs clean and avoids surprises when these are sourced or piped.
|
package/README.md
CHANGED
|
@@ -48,7 +48,7 @@ When it finishes, open Claude Code and run `/builder-start` to see what tasks ar
|
|
|
48
48
|
|
|
49
49
|
## Secrets scanning
|
|
50
50
|
|
|
51
|
-
We keep secrets (`.env` files, API keys, private keys) out of the repo with two layers. Both are **instance topology** — the git
|
|
51
|
+
We keep secrets (`.env` files, API keys, private keys) out of the repo with two layers. Both are **instance topology** — the git hooks (`.husky/`) and CI (`.github/`) are excluded from the published core artifact, and a secrets pre-commit hook is an instance addition (nothing provisions one automatically). This repo tracks `.husky/pre-push` — the ADR 0043 permission gate, a separate wall from secrets scanning. The pattern below is the recommended posture:
|
|
52
52
|
|
|
53
53
|
- **Pre-commit hook** (`.husky/pre-commit`) — scans your *staged* changes before each commit and aborts if it finds a secret. On an instance that ships `.husky/`, activate it **once per clone**:
|
|
54
54
|
|
|
@@ -56,7 +56,7 @@ We keep secrets (`.env` files, API keys, private keys) out of the repo with two
|
|
|
56
56
|
node scripts/gds/install-git-hooks.js
|
|
57
57
|
```
|
|
58
58
|
|
|
59
|
-
Prefer that over setting `core.hooksPath` by hand: it no-ops cleanly on a checkout without a `.husky/` (
|
|
59
|
+
Prefer that over setting `core.hooksPath` by hand: it no-ops cleanly on a checkout without a `.husky/` (e.g. a scaffolded instance that hasn't added hooks), whereas pointing `core.hooksPath` at a directory that isn't there leaves git with a dangling hooks path and no gate. `node scripts/gds/doctor.js` reports which case you're in.
|
|
60
60
|
|
|
61
61
|
It always runs a built-in guard (blocks `.env`, `*.pem`, credential files, obvious key patterns). If [TruffleHog](https://github.com/trufflesecurity/trufflehog) is installed (`brew install trufflehog` on macOS; `scoop install trufflehog` / `choco install trufflehog` or the GitHub release on Windows) it also runs a deep scan; if not, it warns and lets the commit through — CI is the real gate.
|
|
62
62
|
|
|
@@ -93,6 +93,44 @@ enforcement**, not a redundant second net. Option B (blocker
|
|
|
93
93
|
Treat this as the freshness stamp: re-run the probes above before asserting the claim has changed —
|
|
94
94
|
a plan upgrade or a new ruleset would flip it, and this section is what a future sweep should update.
|
|
95
95
|
|
|
96
|
+
## G-2/G-17 resolution: Layer 1 delivered; the "path bug" refuted (2026-08-08, task 1002608)
|
|
97
|
+
|
|
98
|
+
The 2026-08-06 rank choke-point map (`docs/governance/<redacted>.md` §6) filed
|
|
99
|
+
two findings against Layer 1. Re-verified empirically before acting, per the map's own rule:
|
|
100
|
+
|
|
101
|
+
**G-17 — "the hook could not work even if it were installed" — is REFUTED.** The claim was that
|
|
102
|
+
`pre-push.js:30`'s `path.join(__dirname, '..', '..', '..', 'src', 'bongos', …)` resolves one
|
|
103
|
+
directory *above* the repo root. It does not: `__dirname` is `<root>/scripts/gds/hooks`, and three
|
|
104
|
+
`..` segments land exactly at `<root>` (hooks → gds → scripts → root). Verified live: the require
|
|
105
|
+
resolves and loads (all 15 exports), and the hook fed a `refs/heads/main` line on stdin blocks with
|
|
106
|
+
exit 1. The finding's fail-open claim is also inverted for this failure class: a module-scope
|
|
107
|
+
`require` throw is **not** caught by the `run().catch` crash handler (it wraps `run()` only) — node
|
|
108
|
+
would exit nonzero before any handler installs, and git **aborts** the push. Fail *closed*. The
|
|
109
|
+
runtime crash handler does fail open **by design**, documented in place: Layer 1 is cooperative and
|
|
110
|
+
bypassable (`--no-verify`) regardless, so bricking every push on an unrelated runtime bug buys no
|
|
111
|
+
security — Layer 3 is the net.
|
|
112
|
+
|
|
113
|
+
**G-2 — "no delivery path, and never did" — was real and is now fixed.** `.husky/` had never been
|
|
114
|
+
tracked on any branch, so `install-git-hooks.js` (wired into `/builder-setup` since #863) always
|
|
115
|
+
no-opped with `no-husky-dir`, and `pre-push.js`'s header claimed a `.git/hooks/` install that no
|
|
116
|
+
code performed. The repo now tracks `.husky/pre-push`, a two-line wrapper exec'ing
|
|
117
|
+
`scripts/gds/hooks/pre-push.js` — so the existing activation path (`core.hooksPath=.husky`, set
|
|
118
|
+
per-clone by `/builder-setup` or a standalone `node scripts/gds/install-git-hooks.js`) finally has
|
|
119
|
+
something to activate. This follows the `.github/` precedent from tasks 1002610–1002612: README
|
|
120
|
+
calls both instance topology, and both stay excluded from the **published core artifact**
|
|
121
|
+
(`publish-manifest.js` `PROTECTED_TOPOLOGY_EXCLUDE`, test-asserted) — but this repo is itself the
|
|
122
|
+
live reference instance where the Builder 25 incident happened, so it carries its own walls.
|
|
123
|
+
|
|
124
|
+
**Honest limits, unchanged and new:**
|
|
125
|
+
- Activation is per-clone and best-effort. A clone that never runs `/builder-setup` or
|
|
126
|
+
`install-git-hooks.js` has no Layer 1; whether the operator's own clone is activated cannot be
|
|
127
|
+
settled from the repo.
|
|
128
|
+
- Standalone instances scaffolded from the published core still receive no `.husky/` — README
|
|
129
|
+
§"Secrets scanning" says hooks are "provided when you stand up an instance," but no provisioning
|
|
130
|
+
code materializes them. Captured for the post-wave bug phase (idea inbox) rather than fixed here.
|
|
131
|
+
- The layer table above stands: Layer 1 is still not a trust boundary; the post-push main audit —
|
|
132
|
+
since task 1002612 a CI job on every push to `main` — remains the authoritative layer.
|
|
133
|
+
|
|
96
134
|
## Notes
|
|
97
135
|
|
|
98
136
|
We did **not** take immediate action against Builder 25 — Lars was overseeing the session and the change itself was a legitimate bug fix. The point of this ADR is the *mechanism*, not the person: the system must make the bypass hard and visible regardless of intent.
|
|
@@ -260,7 +260,9 @@ for the probes. `main-audit.js` is therefore still the *authoritative* layer, no
|
|
|
260
260
|
rank/authz machinery, the ship/grade/deploy pipeline, DB `migrations/`, `infra/`, and the
|
|
261
261
|
trust-boundary docs. The protected set is the glob list in
|
|
262
262
|
[`src/bongos/permission-path-check.js`](../src/bongos/permission-path-check.js) (source of truth).
|
|
263
|
-
- **Nobody pushes `main` directly.** The pre-push hook (`.husky/pre-push
|
|
263
|
+
- **Nobody pushes `main` directly.** The pre-push hook (`.husky/pre-push`, tracked in this repo and
|
|
264
|
+
activated per-clone by `/builder-setup` via `install-git-hooks.js` — inert on a clone that never
|
|
265
|
+
activated it; ADR 0043 §"G-2/G-17 resolution") blocks it; the graded
|
|
264
266
|
pipeline pushes only via `scripts/gds/push-main.js` (which carries the `OTB_ALLOW_MAIN_PUSH`
|
|
265
267
|
override). A raw push is caught and reverted by the **post-push main audit**
|
|
266
268
|
(`scripts/gds/main-audit.js`) — nightly and as the `~/deploy.sh` deploy gate — which is the
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.7",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@bongos/core",
|
|
9
|
-
"version": "1.19.
|
|
9
|
+
"version": "1.19.7",
|
|
10
10
|
"license": "AGPL-3.0-or-later",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"express": "^4.21.2",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.7",
|
|
4
4
|
"description": "Cloud Bongos — the AI-first build platform core (GDS + platform surfaces + module system), installed as a versioned dependency (ADR 0108).",
|
|
5
5
|
"license": "AGPL-3.0-or-later",
|
|
6
6
|
"main": "src/platform-server.js",
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// scripts/gds/hooks/pre-push.js — client-side push gate (SEC #863).
|
|
3
3
|
//
|
|
4
|
-
//
|
|
5
|
-
// /builder-setup)
|
|
4
|
+
// Invoked via the tracked .husky/pre-push wrapper. Inert until a clone runs
|
|
5
|
+
// scripts/gds/install-git-hooks.js (from /builder-setup), which points
|
|
6
|
+
// core.hooksPath at .husky/ — activation is per-clone (task 1002608; ADR 0043
|
|
7
|
+
// §"G-2/G-17 resolution"). The published core artifact excludes .husky/
|
|
8
|
+
// (publish-manifest.js PROTECTED_TOPOLOGY_EXCLUDE) — instance topology.
|
|
9
|
+
// This is LAYER 1 of the #863 fix — fast, cooperative, blocks
|
|
6
10
|
// the honest builder before a bad push ever leaves the machine. It is NOT a
|
|
7
11
|
// trust boundary on its own (a builder can `git push --no-verify` or delete the
|
|
8
12
|
// hook); the authoritative net is the post-push main audit + the deploy-side
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// scripts/gds/install-git-hooks.js — activate the project's git hooks (SEC #863).
|
|
3
3
|
//
|
|
4
|
-
// The
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
4
|
+
// The hooks are TRACKED, committed files under .husky/ — this repo ships
|
|
5
|
+
// .husky/pre-push (the ADR 0043 Layer 1 permission gate, task 1002608); an
|
|
6
|
+
// instance may add more (e.g. a pre-commit secrets guard). Git only runs them
|
|
7
|
+
// when the clone has core.hooksPath pointed at .husky — easy to forget, which
|
|
8
|
+
// means a builder could run with NO push gate.
|
|
9
9
|
//
|
|
10
10
|
// This script makes activation automatic (called by /builder-setup). It does
|
|
11
11
|
// NOT write any hook file — the hooks ship via git. It only ensures
|
|
12
|
-
// core.hooksPath is set so
|
|
12
|
+
// core.hooksPath is set so whatever hooks .husky/ tracks actually fire.
|
|
13
13
|
//
|
|
14
14
|
// Idempotent and cross-platform. Runnable standalone:
|
|
15
15
|
// node scripts/gds/install-git-hooks.js
|
|
@@ -68,6 +68,8 @@ function activate() {
|
|
|
68
68
|
// clones pick it up on any re-run of this script.
|
|
69
69
|
const mergeDriver = registerMergeDriver(root);
|
|
70
70
|
|
|
71
|
+
const hooks = fs.readdirSync(huskyDir).filter((f) => !f.startsWith('.') && !f.startsWith('_'));
|
|
72
|
+
|
|
71
73
|
let current = '';
|
|
72
74
|
try { current = git('config --get core.hooksPath'); } catch (_) { current = ''; }
|
|
73
75
|
|
|
@@ -77,13 +79,13 @@ function activate() {
|
|
|
77
79
|
const points = configuredHooksDir();
|
|
78
80
|
const alreadyHusky = current && points && path.basename(points) === '.husky';
|
|
79
81
|
if (alreadyHusky) {
|
|
80
|
-
return { status: 'already-active', hooksPath: current, dir: points, mergeDriver };
|
|
82
|
+
return { status: 'already-active', hooksPath: current, dir: points, mergeDriver, hooks };
|
|
81
83
|
}
|
|
82
84
|
|
|
83
85
|
// Not set (or set elsewhere): activate the project hooks. Relative '.husky'
|
|
84
86
|
// is husky-standard and resolves per-worktree.
|
|
85
87
|
git('config core.hooksPath .husky');
|
|
86
|
-
return { status: 'activated', hooksPath: '.husky', dir: huskyDir, mergeDriver };
|
|
88
|
+
return { status: 'activated', hooksPath: '.husky', dir: huskyDir, mergeDriver, hooks };
|
|
87
89
|
}
|
|
88
90
|
|
|
89
91
|
if (require.main === module) {
|
|
@@ -93,10 +95,11 @@ if (require.main === module) {
|
|
|
93
95
|
console.error(`⚠ ${r.message}`);
|
|
94
96
|
process.exit(0);
|
|
95
97
|
}
|
|
98
|
+
const live = (r.hooks && r.hooks.length) ? r.hooks.join(', ') : 'none tracked';
|
|
96
99
|
if (r.status === 'already-active') {
|
|
97
|
-
console.log(`✓ git hooks already active (core.hooksPath=${r.hooksPath});
|
|
100
|
+
console.log(`✓ git hooks already active (core.hooksPath=${r.hooksPath}); live hooks: ${live}`);
|
|
98
101
|
} else {
|
|
99
|
-
console.log(`✓ git hooks activated (core.hooksPath=.husky);
|
|
102
|
+
console.log(`✓ git hooks activated (core.hooksPath=.husky); live hooks: ${live}`);
|
|
100
103
|
}
|
|
101
104
|
if (r.mergeDriver && r.mergeDriver.ok) {
|
|
102
105
|
console.log(`✓ merge driver '${MERGE_DRIVER_NAME}' registered; generated-file conflicts (docs/repo-map.md, nested CLAUDE.md) auto-regenerate on merge`);
|
package/scripts/gds/setup.js
CHANGED
|
@@ -515,14 +515,16 @@ async function promptOnboardingConsent() {
|
|
|
515
515
|
console.log(`Total credits: ${builder.total_credits}`);
|
|
516
516
|
console.log(`Session saved to ${SESSION_PATH}`);
|
|
517
517
|
// SEC #863: activate the project git hooks (core.hooksPath → .husky) so
|
|
518
|
-
// the
|
|
519
|
-
//
|
|
520
|
-
//
|
|
518
|
+
// the tracked hooks run — here that's the pre-push permission gate
|
|
519
|
+
// (task 1002608); an instance may track more. Best effort — an activation
|
|
520
|
+
// hiccup must never fail an otherwise-good setup; the post-push main
|
|
521
|
+
// audit is the fail-closed net regardless.
|
|
521
522
|
try {
|
|
522
523
|
const { activate } = require('./install-git-hooks');
|
|
523
524
|
const r = activate();
|
|
524
|
-
|
|
525
|
-
|
|
525
|
+
const live = (r.hooks && r.hooks.length) ? r.hooks.join(', ') : 'none tracked';
|
|
526
|
+
if (r.status === 'activated') console.log(`Git hooks activated (live hooks: ${live})`);
|
|
527
|
+
else if (r.status === 'already-active') console.log(`Git hooks already active (live hooks: ${live})`);
|
|
526
528
|
else console.log(`Note: ${r.message}`);
|
|
527
529
|
} catch (e) {
|
|
528
530
|
console.log(`Note: could not activate git hooks (${e.message}); run: git config core.hooksPath .husky`);
|
package/src/module-api.js
CHANGED
|
@@ -439,8 +439,10 @@ const { validateOrRespond, LIMITS, parseId, asyncHandler, corsPublicGet, parsePa
|
|
|
439
439
|
// landed since 1.19.4 with no explicit bump. run 31276103393. (task 1002620)
|
|
440
440
|
// 1.19.6 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
441
441
|
// landed since 1.19.5 with no explicit bump. run 31277243929. (task 1002620)
|
|
442
|
+
// 1.19.7 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
443
|
+
// landed since 1.19.6 with no explicit bump. run 31277387539. (task 1002620)
|
|
442
444
|
// ---------------------------------------------------------------------------
|
|
443
|
-
const CORE_VERSION = '1.19.
|
|
445
|
+
const CORE_VERSION = '1.19.7'; // CI auto-patch carrier (ADR 0161); see changelog above
|
|
444
446
|
|
|
445
447
|
// A namespaced logger so a module's log lines are attributable + consistent.
|
|
446
448
|
// Usage: const log = api.logger('dev-box'); log.info('mounted');
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
// tests/pre_push_hook.mjs — ADR 0043 Layer 1 delivery + behavior (task 1002608, G-2/G-17).
|
|
2
|
+
//
|
|
3
|
+
// Pins three facts the 2026-08-06 choke-point map litigated:
|
|
4
|
+
// 1. G-17 refutation: pre-push.js's dependency path resolves INSIDE the repo
|
|
5
|
+
// (three `..` from scripts/gds/hooks/ is the root, not one above it).
|
|
6
|
+
// 2. The hook actually blocks a direct push to refs/heads/main (exit 1),
|
|
7
|
+
// and allows a branch deletion (exit 0) — both offline-deterministic.
|
|
8
|
+
// 3. G-2 fix: the tracked .husky/pre-push wrapper exists, execs the real
|
|
9
|
+
// gate, and is LF + executable in the git index (CRLF or mode 100644
|
|
10
|
+
// would silently kill the hook on Unix clones).
|
|
11
|
+
//
|
|
12
|
+
// DB-free, no network: the main-push block fires before any API call.
|
|
13
|
+
// Run: node --test tests/pre_push_hook.mjs
|
|
14
|
+
|
|
15
|
+
import assert from 'node:assert/strict';
|
|
16
|
+
import { test } from 'node:test';
|
|
17
|
+
import { createRequire } from 'node:module';
|
|
18
|
+
import { spawnSync } from 'node:child_process';
|
|
19
|
+
import fs from 'node:fs';
|
|
20
|
+
import path from 'node:path';
|
|
21
|
+
import { fileURLToPath } from 'node:url';
|
|
22
|
+
|
|
23
|
+
const require = createRequire(import.meta.url);
|
|
24
|
+
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
25
|
+
const HOOK = path.join(ROOT, 'scripts', 'gds', 'hooks', 'pre-push.js');
|
|
26
|
+
const WRAPPER = path.join(ROOT, '.husky', 'pre-push');
|
|
27
|
+
const SHA = 'a'.repeat(40);
|
|
28
|
+
const ZEROS = '0'.repeat(40);
|
|
29
|
+
|
|
30
|
+
function runHook(stdinLine) {
|
|
31
|
+
return spawnSync(process.execPath, [HOOK, 'origin', 'https://example.invalid/repo.git'], {
|
|
32
|
+
cwd: ROOT,
|
|
33
|
+
input: stdinLine,
|
|
34
|
+
encoding: 'utf8',
|
|
35
|
+
env: { ...process.env, OTB_ALLOW_MAIN_PUSH: '' },
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
test('G-17 refuted: the line-30 dependency path resolves inside the repo and loads', () => {
|
|
40
|
+
const hooksDir = path.dirname(HOOK);
|
|
41
|
+
const resolved = path.join(hooksDir, '..', '..', '..', 'src', 'bongos', 'permission-path-check.js');
|
|
42
|
+
assert.equal(path.resolve(resolved), path.join(ROOT, 'src', 'bongos', 'permission-path-check.js'));
|
|
43
|
+
assert.ok(fs.existsSync(resolved), 'permission-path-check.js must exist where pre-push.js requires it');
|
|
44
|
+
const ppc = require(resolved);
|
|
45
|
+
assert.equal(typeof ppc.checkPermissionPaths, 'function');
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test('hook blocks a direct push to refs/heads/main with exit 1', () => {
|
|
49
|
+
const r = runHook(`refs/heads/main ${SHA} refs/heads/main ${SHA}\n`);
|
|
50
|
+
assert.equal(r.status, 1, `expected exit 1, got ${r.status}; stderr: ${r.stderr}`);
|
|
51
|
+
assert.match(r.stderr, /push blocked/i);
|
|
52
|
+
assert.match(r.stderr, /Direct pushes to `main`/);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test('hook allows a branch deletion (all-zero local sha) with exit 0', () => {
|
|
56
|
+
const r = runHook(`refs/heads/some-branch ${ZEROS} refs/heads/some-branch ${SHA}\n`);
|
|
57
|
+
assert.equal(r.status, 0, `expected exit 0, got ${r.status}; stderr: ${r.stderr}`);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test('requiring the hook as a module neither reads stdin nor exits', () => {
|
|
61
|
+
const mod = require(HOOK);
|
|
62
|
+
assert.equal(typeof mod.run, 'function');
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test('G-2 fixed: tracked .husky/pre-push wrapper execs the real gate', () => {
|
|
66
|
+
assert.ok(fs.existsSync(WRAPPER), '.husky/pre-push must be tracked (ADR 0043 Layer 1 delivery)');
|
|
67
|
+
const body = fs.readFileSync(WRAPPER, 'utf8');
|
|
68
|
+
assert.ok(body.startsWith('#!/bin/sh'), 'wrapper must start with a sh shebang');
|
|
69
|
+
assert.match(body, /scripts\/gds\/hooks\/pre-push\.js/, 'wrapper must invoke the real gate');
|
|
70
|
+
assert.ok(!body.includes('\r'), 'wrapper must be LF-only (CRLF breaks the shebang on Unix)');
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test('wrapper is executable (mode 100755) in the git index', () => {
|
|
74
|
+
const r = spawnSync('git', ['ls-files', '-s', '--', '.husky/pre-push'], { cwd: ROOT, encoding: 'utf8' });
|
|
75
|
+
assert.equal(r.status, 0, `git ls-files failed: ${r.stderr}`);
|
|
76
|
+
assert.match(r.stdout, /^100755 /, `expected index mode 100755, got: ${r.stdout.trim() || '(untracked)'}`);
|
|
77
|
+
});
|