@bongos/core 1.21.176 → 1.21.178
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 +186 -496
- package/.claude/skills/planning-session/SKILL.md +2 -2
- package/clients/bongos-client/README.md +1 -1
- package/clients/bongos-client/bongos-client.global.js +0 -6
- package/clients/bongos-client/index.cjs +0 -6
- package/clients/bongos-client/index.d.ts +0 -6
- package/clients/bongos-client/index.mjs +0 -6
- package/config/scheduled-routines.json +3 -8
- package/docs/adr/0042-builder-self-deploy-ci-auto-merge.md +3 -2
- package/docs/adr/0091-bounding-the-kernel-and-db-carve.md +1 -1
- package/docs/adr/0093-tranche-2-core-carve-sequence.md +1 -1
- package/docs/api/openapi.json +1 -87
- package/docs/api-reference.md +1 -8
- package/docs/canonical-permissions.md +4 -4
- package/docs/copy-inventory.md +25 -39
- package/docs/copy-registry.json +73 -200
- package/docs/file-map.md +6 -11
- package/docs/handoff-template.md +1 -1
- package/docs/module-api-changelog.md +4 -0
- package/docs/modules-contract.md +2 -2
- package/docs/onboarding/diagrams/README.md +3 -7
- package/docs/onboarding/diagrams/assertions.json +3 -3
- package/docs/page-inventory.json +4 -7
- package/docs/page-readings.json +1520 -1569
- package/docs/recipes/ops-gotchas.md +3 -1
- package/modules/autonomy/db.js +9 -0
- package/modules/autonomy/module.json +2 -2
- package/modules/autonomy/pollers/daily-checks.js +262 -0
- package/modules/autonomy/pollers/routine-timers.js +7 -5
- package/modules/autonomy/rank-roles.js +64 -0
- package/modules/autonomy/server-schedule.js +37 -0
- package/modules/government/catalog.js +10 -7
- package/modules/government/decision-board.js +0 -1
- package/modules/government/decision-rules.js +0 -1
- package/modules/government/docket.js +0 -1
- package/modules/government/protected-surfaces.js +1 -1
- package/modules/government/protected-surfaces.json +2 -3
- package/modules/hall-ui/public/atlas.html +1 -1
- package/modules/hall-ui/public/blockers.html +1 -1
- package/modules/hall-ui/public/board-room.html +1 -1
- package/modules/hall-ui/public/city.html +1 -1
- package/modules/hall-ui/public/collab.html +1 -1
- package/modules/hall-ui/public/commands.html +1 -1
- package/modules/hall-ui/public/copy-desk.html +1 -1
- package/modules/hall-ui/public/deploy.html +1 -1
- package/modules/hall-ui/public/diagrams.html +1 -1
- package/modules/hall-ui/public/drachmae.html +1 -1
- package/modules/hall-ui/public/fleet.html +1 -1
- package/modules/hall-ui/public/gate.css +4 -26
- package/modules/hall-ui/public/gate.html +12 -27
- package/modules/hall-ui/public/gate.js +26 -176
- package/modules/hall-ui/public/gate.states.json +4 -22
- package/modules/hall-ui/public/goal-map.html +1 -1
- package/modules/hall-ui/public/goals.html +1 -1
- package/modules/hall-ui/public/government.html +1 -1
- package/modules/hall-ui/public/idea.html +1 -1
- package/modules/hall-ui/public/ideas.html +1 -1
- package/modules/hall-ui/public/index.html +1 -1
- package/modules/hall-ui/public/modules.html +1 -1
- package/modules/hall-ui/public/my-agents.html +1 -1
- package/modules/hall-ui/public/primer.html +1 -1
- package/modules/hall-ui/public/profile.html +1 -1
- package/modules/hall-ui/public/project-settings.html +1 -1
- package/modules/hall-ui/public/ranks.html +1 -1
- package/modules/hall-ui/public/roadmap.html +1 -1
- package/modules/hall-ui/public/roster.html +1 -1
- package/modules/hall-ui/public/sessions.html +1 -1
- package/modules/hall-ui/public/settings.html +1 -1
- package/modules/hall-ui/public/shell.js +1 -1
- package/modules/hall-ui/public/street-card.js +5 -9
- package/modules/hall-ui/public/street-desk.js +2 -2
- package/modules/hall-ui/public/street-inside.js +1 -1
- package/modules/hall-ui/public/street.html +1 -1
- package/modules/hall-ui/public/studio.html +1 -1
- package/modules/hall-ui/public/task.html +1 -1
- package/modules/hall-ui/public/thinking.html +1 -1
- package/modules/hall-ui/public/tweak-editor.html +1 -1
- package/modules/hall-ui/public/watch.html +1 -1
- package/modules/hall-ui/public/work.html +1 -1
- package/modules/lifecycle/matter-acts.js +0 -6
- package/modules/lifecycle/module.json +0 -1
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +36 -0
- package/scripts/bongos/adopt-preflight.js +3 -3
- package/scripts/bongos/api-alias-caller-scan.js +2 -2
- package/scripts/bongos/audit-authorship.sh +1 -8
- package/scripts/bongos/audit-rank-roles.js +15 -56
- package/scripts/bongos/bump-version.js +46 -9
- package/scripts/bongos/codemod-rename-scripts-gds.js +0 -1
- package/scripts/bongos/diagram-facts.js +1 -1
- package/scripts/bongos/do-api.js +1 -1
- package/scripts/bongos/doc-cli-guard.js +2 -0
- package/scripts/bongos/doc-control-claims.js +2 -2
- package/scripts/bongos/fitness-checks-identity.js +4 -15
- package/scripts/bongos/gen-diagrams.js +3 -3
- package/scripts/bongos/government-walk.js +3 -9
- package/scripts/bongos/hard-floor.js +52 -0
- package/scripts/bongos/publish-manifest.js +3 -3
- package/scripts/bongos/pulse.js +5 -3
- package/scripts/bongos/routine-schedule.js +38 -3
- package/scripts/bongos/run-unit-tests.js +17 -5
- package/scripts/bongos/ship-regen.js +2 -3
- package/scripts/gds/{goal-names.js → hard-floor.js} +3 -4
- package/scripts/security/dep-audit.js +50 -124
- package/src/bongos/module-scope-map.js +2 -2
- package/src/bongos/permission-path-check.js +1 -1
- package/src/bongos/route-rank-check.js +1 -8
- package/src/bongos/routes.js +1 -2
- package/src/module-api.js +1 -1
- package/src/modules.js +1 -1
- package/tests/acclaim_feed_evidence_db.mjs +42 -23
- package/tests/adr_0089_scope.mjs +7 -5
- package/tests/autonomy_daily_checks.mjs +201 -0
- package/tests/bump_version.mjs +52 -18
- package/tests/claude_materialize.mjs +12 -12
- package/tests/copy_desk_flags_db.mjs +22 -14
- package/tests/effective_visibility_predicate.mjs +0 -18
- package/tests/gating_review_retired.mjs +1 -1
- package/tests/government_act_alone_buildings.mjs +1 -3
- package/tests/government_board_membership_db.mjs +35 -22
- package/tests/government_protected_surfaces.mjs +29 -13
- package/tests/governor_docket.mjs +0 -2
- package/tests/governor_matter_acts.mjs +6 -8
- package/tests/hall_city.mjs +1 -1
- package/tests/hall_matter_card.mjs +2 -2
- package/tests/hall_oversight_world.mjs +16 -12
- package/tests/hall_street_desk.mjs +4 -3
- package/tests/hall_street_inside.mjs +1 -1
- package/tests/helpers.mjs +34 -0
- package/tests/howto_state.mjs +33 -56
- package/tests/howto_state_db.mjs +68 -0
- package/tests/idea_credit_streams_db.mjs +11 -3
- package/tests/integration_parked_ratchet.mjs +7 -1
- package/tests/nav_permission_atoms.mjs +1 -1
- package/tests/page_notes_db.mjs +12 -8
- package/tests/page_tweak_no_rearm.mjs +50 -0
- package/tests/publish_manifest.mjs +64 -0
- package/tests/routine_capture_kind.mjs +18 -7
- package/tests/routine_schedule.mjs +42 -0
- package/tests/search_isolation.mjs +5 -103
- package/tests/search_isolation_db.mjs +140 -0
- package/tests/session_earnings_mirror_db.mjs +2 -0
- package/tests/workflow_exec_paths.mjs +9 -11
- package/modules/lifecycle/routes/gate-approvals.js +0 -299
- package/scripts/bongos/artifact-staleness.js +0 -267
- package/scripts/bongos/ci-grade.js +0 -82
- package/scripts/bongos/classify-discipline.js +0 -153
- package/scripts/bongos/codemod-rename-src-gds.js +0 -145
- package/scripts/bongos/coreB-carve-proof.sh +0 -725
- package/scripts/bongos/gate-review.js +0 -330
- package/scripts/bongos/goal-names.js +0 -102
- package/scripts/bongos/rename-goals.js +0 -109
- package/scripts/bongos/seed-adopt-tasks.js +0 -75
- package/scripts/bongos/seed-api-contract-goal-tasks.js +0 -141
- package/scripts/bongos/seed-board-room-tasks.js +0 -196
- package/scripts/bongos/seed-bongos-app-tasks.js +0 -294
- package/scripts/bongos/seed-bongos-coreB-tranche1-tasks.js +0 -81
- package/scripts/bongos/seed-bongos-coreB-tranche2-tasks.js +0 -100
- package/scripts/bongos/seed-bongos-goal-hierarchy-tasks.js +0 -307
- package/scripts/bongos/seed-bongos-module-system-tasks.js +0 -346
- package/scripts/bongos/seed-bongos-tasks.js +0 -214
- package/scripts/bongos/seed-bongos-v1-seo-tasks.js +0 -104
- package/scripts/bongos/seed-bv1-community-tasks.js +0 -131
- package/scripts/bongos/seed-bv1-privacy-invites-tasks.js +0 -133
- package/scripts/bongos/seed-bv1-profile-page-tasks.js +0 -161
- package/scripts/bongos/seed-bv1-r01-delta-tasks.js +0 -154
- package/scripts/bongos/seed-bv2-tweak-mode-tasks.js +0 -277
- package/scripts/bongos/seed-gds-v4-tasks.js +0 -624
- package/scripts/bongos/seed-governance-tasks.js +0 -340
- package/scripts/bongos/seed-medusa-tasks.js +0 -440
- package/scripts/bongos/seed-module-upstreaming.js +0 -219
- package/scripts/bongos/seed-productization-tasks.js +0 -235
- package/scripts/bongos/seed-project-admin-console-tasks.js +0 -88
- package/scripts/bongos/seed-provisioning-tasks.js +0 -83
- package/scripts/bongos/seed-strict-versioning-tasks.js +0 -275
- package/scripts/bongos/seed-v2-world-tasks.js +0 -283
- package/scripts/gds/artifact-staleness.js +0 -7
- package/scripts/gds/ci-grade.js +0 -7
- package/scripts/gds/classify-discipline.js +0 -7
- package/scripts/gds/codemod-rename-src-gds.js +0 -7
- package/scripts/gds/coreB-carve-proof.sh +0 -8
- package/scripts/gds/gate-review.js +0 -7
- package/scripts/gds/rename-goals.js +0 -7
- package/scripts/gds/seed-adopt-tasks.js +0 -7
- package/scripts/gds/seed-api-contract-goal-tasks.js +0 -7
- package/scripts/gds/seed-board-room-tasks.js +0 -6
- package/scripts/gds/seed-bongos-app-tasks.js +0 -7
- package/scripts/gds/seed-bongos-coreB-tranche1-tasks.js +0 -7
- package/scripts/gds/seed-bongos-coreB-tranche2-tasks.js +0 -7
- package/scripts/gds/seed-bongos-goal-hierarchy-tasks.js +0 -7
- package/scripts/gds/seed-bongos-module-system-tasks.js +0 -7
- package/scripts/gds/seed-bongos-tasks.js +0 -7
- package/scripts/gds/seed-bongos-v1-seo-tasks.js +0 -6
- package/scripts/gds/seed-bv1-community-tasks.js +0 -6
- package/scripts/gds/seed-bv1-privacy-invites-tasks.js +0 -6
- package/scripts/gds/seed-bv1-profile-page-tasks.js +0 -6
- package/scripts/gds/seed-bv1-r01-delta-tasks.js +0 -6
- package/scripts/gds/seed-bv2-tweak-mode-tasks.js +0 -6
- package/scripts/gds/seed-gds-v4-tasks.js +0 -7
- package/scripts/gds/seed-governance-tasks.js +0 -7
- package/scripts/gds/seed-medusa-tasks.js +0 -7
- package/scripts/gds/seed-module-upstreaming.js +0 -7
- package/scripts/gds/seed-productization-tasks.js +0 -7
- package/scripts/gds/seed-project-admin-console-tasks.js +0 -6
- package/scripts/gds/seed-provisioning-tasks.js +0 -7
- package/scripts/gds/seed-strict-versioning-tasks.js +0 -7
- package/scripts/gds/seed-v2-world-tasks.js +0 -7
- package/scripts/migrate_cost_ledger.sh +0 -178
- package/tests/artifact_staleness.mjs +0 -207
- package/tests/gate_approvals.mjs +0 -611
- package/tests/gate_review.mjs +0 -152
- package/tests/goal_names.mjs +0 -120
- package/tests/newcomer_generator.mjs +0 -48
|
@@ -1,330 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
'use strict';
|
|
3
|
-
|
|
4
|
-
// scripts/bongos/gate-review.js — the automatic gate-surface gatekeeper (ADR 0058).
|
|
5
|
-
//
|
|
6
|
-
// WHY. In deploy mode 'ci' (config/deploy.json) PRs auto-merge on green checks and
|
|
7
|
-
// main auto-deploys to prod. CODEOWNERS (owner review on gate surfaces) was the ONLY
|
|
8
|
-
// human in that loop — and the bottleneck. The owner chose a RANK-BASED trust model
|
|
9
|
-
// (2026-06-15): trust senior builders to auto-merge routine sensitive work, but keep a
|
|
10
|
-
// HARD, rank-independent floor for the gate's own machinery + deploy switch.
|
|
11
|
-
//
|
|
12
|
-
// WHAT. This is a PURE, deterministic classifier over the PR diff-as-DATA. It runs in
|
|
13
|
-
// CI (.github/workflows/gate-review.yml) from the TRUSTED BASE copy (the workflow
|
|
14
|
-
// overlays scripts/+src/ from origin/main before running, so a PR cannot edit the
|
|
15
|
-
// analyzer to pass itself). It emits one of three verdicts:
|
|
16
|
-
//
|
|
17
|
-
// PASS — no gate surface touched, OR only AUTO pure-removal surfaces
|
|
18
|
-
// (un-mute a test, narrow the secret-scanner allowlist). Auto-merge.
|
|
19
|
-
// NEEDS_TRUST — a sensitive-but-routine gate surface (migrations, deps, routes,
|
|
20
|
-
// server, ship plumbing, boxes, auth-adjacent, doctrine docs). The
|
|
21
|
-
// workflow auto-merges this IFF the author is Metic+ (rank trust) or
|
|
22
|
-
// the owner approved; otherwise it escalates.
|
|
23
|
-
// ESCALATE — the HARD FLOOR: the gate's own machinery, the deploy switch, branch
|
|
24
|
-
// protection, CI workflows, the rank/permission enforcement, the
|
|
25
|
-
// grader, the deploy-time audit, or the hooks the gate ships through.
|
|
26
|
-
// Rank-trust does NOT apply — this ALWAYS pings the owner. Also any
|
|
27
|
-
// pre-flight rejection (symlink / non-ASCII path / oversize / analyzer
|
|
28
|
-
// error) → ESCALATE, fail-closed.
|
|
29
|
-
//
|
|
30
|
-
// The gate-surface SET is the authoritative src/bongos/permission-path-check.PROTECTED_GLOBS
|
|
31
|
-
// (imported, NEVER hand-retyped — the red-team's #1 finding). The classifier only
|
|
32
|
-
// decides, per touched surface, whether it is HARD-FLOOR, NEEDS_TRUST, or AUTO. Anything
|
|
33
|
-
// matched-protected-but-unclassified defaults to ESCALATE (no allow-by-omission).
|
|
34
|
-
//
|
|
35
|
-
// The pure core (classifyChanges) takes diff data as arguments and is exhaustively
|
|
36
|
-
// unit-tested (tests/gate_review.mjs) with the red-team evasion matrix. The CLI wrapper
|
|
37
|
-
// (main) gathers the real diff via git and applies GATE_REPORT_ONLY / exit codes.
|
|
38
|
-
|
|
39
|
-
const { execFileSync } = require('node:child_process');
|
|
40
|
-
const fs = require('node:fs');
|
|
41
|
-
// Destructured AT the require, not off a held module object: knip's static pass
|
|
42
|
-
// cannot follow `const x = require(...); const { y } = x;` and reports `y` as an
|
|
43
|
-
// unused export, which reds the CI knip step (this file's own CLAUDE.md records
|
|
44
|
-
// the same trap for src/instance-config.js).
|
|
45
|
-
const { PROTECTED_GLOBS, compileGlobMatcher } = require('../../src/bongos/permission-path-check');
|
|
46
|
-
|
|
47
|
-
// --- HARD FLOOR (rank-independent ESCALATE) -------------------------------------------
|
|
48
|
-
// A subset of PROTECTED_GLOBS: the surfaces that, if a compromised/injected senior
|
|
49
|
-
// session auto-merged them, would DISABLE the safety system itself or the deploy gate.
|
|
50
|
-
// Kept as globs so the matcher is identical to permission-path-check (trailing '/' =
|
|
51
|
-
// subtree). The owner can promote a NEEDS_TRUST surface here with a one-line edit (which
|
|
52
|
-
// is itself a hard-floor change to this file → escalates — correct).
|
|
53
|
-
const HARD_FLOOR_GLOBS = [
|
|
54
|
-
'scripts/bongos/gate-review.js', // self
|
|
55
|
-
'src/bongos/permission-path-check.js', // defines the protected set this gate reads
|
|
56
|
-
'src/bongos/route-rank-check.js', // the route-gate auditor
|
|
57
|
-
'src/bongos/auth.js', // requireRank / requireBuilder — the enforcement point
|
|
58
|
-
'modules/grading/grader.js', // BV1.R73: grading carved to modules/grading/
|
|
59
|
-
'modules/grading/grader-workers/',
|
|
60
|
-
'modules/grading/grader-rubric.json',
|
|
61
|
-
'scripts/bongos/ci-grade.js', // CI grade-gate driver (task 1143 — red-team #3)
|
|
62
|
-
'scripts/bongos/grade-smoke.js', // grade-smoke driver
|
|
63
|
-
'scripts/bongos/conductor-dispatch.js', // mid-flight grader dispatch
|
|
64
|
-
'scripts/bongos/main-audit.js', // the authoritative deploy-time audit (the backstop)
|
|
65
|
-
'scripts/bongos/push-main.js', // the sanctioned main-push carrying the override
|
|
66
|
-
'scripts/bongos/run-unit-tests.js', // the test selector (AUTO pure-removal handled first)
|
|
67
|
-
'scripts/bongos/install-git-hooks.js',
|
|
68
|
-
'scripts/bongos/hooks/',
|
|
69
|
-
'scripts/migrate.sh',
|
|
70
|
-
'scripts/deploy/',
|
|
71
|
-
'config/deploy.json', // the laptop/ci/bypass deploy switch
|
|
72
|
-
'infra/', // branch-protection.json + deploy substrate
|
|
73
|
-
'.github/', // CI gates: gate-review.yml, deploy-prod.yml, secret scan, grade gate
|
|
74
|
-
'CODEOWNERS',
|
|
75
|
-
'.husky/', // pre-push/pre-commit gate delivery (secrets-allowlist.txt is AUTO, checked first)
|
|
76
|
-
'.claude/hooks/',
|
|
77
|
-
'.claude/settings.json',
|
|
78
|
-
];
|
|
79
|
-
|
|
80
|
-
// db.js is HARD-FLOOR only for the rank-tier brain (REQUIRES_RANK_TIER); every other
|
|
81
|
-
// db.js change is NEEDS_TRUST (it is a huge, routinely-edited module — file-level
|
|
82
|
-
// hard-flooring it would ping the owner for ordinary query work). This is the one
|
|
83
|
-
// content-level rule; everything else is path-level.
|
|
84
|
-
const DB_JS = 'src/bongos/db.js';
|
|
85
|
-
const RANK_TIER_RE = /REQUIRES_RANK_TIER\s*=\s*\{[\s\S]*?\}/;
|
|
86
|
-
|
|
87
|
-
// AUTO pure-removal surfaces — a NARROWING-only edit can only STRENGTHEN the gate, so
|
|
88
|
-
// it may auto-pass for ANY author. Any non-narrowing edit falls through to its glob
|
|
89
|
-
// classification (run-unit-tests.js → HARD FLOOR; secrets-allowlist.txt → .husky/ HARD FLOOR).
|
|
90
|
-
const AUTO_NARROWING = {
|
|
91
|
-
'scripts/bongos/run-unit-tests.js': certifyRunUnitTestsNarrowing,
|
|
92
|
-
'.husky/secrets-allowlist.txt': certifyAllowlistNarrowing,
|
|
93
|
-
};
|
|
94
|
-
|
|
95
|
-
// task 1002874: compile through the authoritative module's FOLDING matcher, not
|
|
96
|
-
// globToRegExp directly. Both of these used to build their own byte-exact regexes
|
|
97
|
-
// from the raw globs and test raw candidates, so after protected-path matching
|
|
98
|
-
// gained NFC + case-folding, an uppercased spelling of a gate surface still walked
|
|
99
|
-
// past THIS classifier — the CI auto-merge gate — which is the bypass the fix
|
|
100
|
-
// exists to close. isCanonicalPath below rejects non-NFC and non-ASCII, but it
|
|
101
|
-
// does not case-fold, so it never covered this.
|
|
102
|
-
const matchesHardFloor = compileGlobMatcher(HARD_FLOOR_GLOBS);
|
|
103
|
-
|
|
104
|
-
// A path is "protected" per the authoritative module.
|
|
105
|
-
const matchesProtected = compileGlobMatcher(PROTECTED_GLOBS);
|
|
106
|
-
|
|
107
|
-
// Only [\x20-\x7e] path chars, NFC-stable, no leading/trailing whitespace. Defeats the
|
|
108
|
-
// unicode-homoglyph + whitespace-alias evasions (red-team Holes 6/8).
|
|
109
|
-
function isCanonicalPath(p) {
|
|
110
|
-
return typeof p === 'string' && p.length > 0 && /^[\x20-\x7e]+$/.test(p)
|
|
111
|
-
&& p === p.normalize('NFC') && p === p.trim();
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// AUTO certifier: run-unit-tests.js may auto-pass ONLY if its sole semantic change is
|
|
115
|
-
// REMOVING entries from INTEGRATION/KNOWN_FAILING (un-muting tests) — never adding, and
|
|
116
|
-
// the rest of the file byte-identical modulo those two set literals.
|
|
117
|
-
function certifyRunUnitTestsNarrowing(base, head) {
|
|
118
|
-
if (base == null || head == null) return { pass: false, reason: 'added/removed wholesale — not a narrowing edit' };
|
|
119
|
-
const setOf = (t, name) => {
|
|
120
|
-
const m = new RegExp(`const ${name} = new Set\\(\\[([\\s\\S]*?)\\]\\)`).exec(t);
|
|
121
|
-
if (!m) return null;
|
|
122
|
-
return new Set([...m[1].matchAll(/['"]([^'"]+)['"]/g)].map((x) => x[1]));
|
|
123
|
-
};
|
|
124
|
-
const strip = (t) => t.replace(/const (INTEGRATION|KNOWN_FAILING) = new Set\(\[[\s\S]*?\]\)/g, 'SET');
|
|
125
|
-
if (strip(base) !== strip(head)) return { pass: false, reason: 'non-set code changed in run-unit-tests.js' };
|
|
126
|
-
for (const name of ['INTEGRATION', 'KNOWN_FAILING']) {
|
|
127
|
-
const b = setOf(base, name), h = setOf(head, name);
|
|
128
|
-
if (!b || !h) return { pass: false, reason: `cannot parse ${name} set — fail-closed` };
|
|
129
|
-
for (const x of h) if (!b.has(x)) return { pass: false, reason: `${name} ADDED '${x}' (muting a test)` };
|
|
130
|
-
}
|
|
131
|
-
return { pass: true };
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// AUTO certifier: secrets-allowlist.txt may auto-pass ONLY if its head line set is a
|
|
135
|
-
// subset of the base (removing/narrowing allowlist entries) — never adding a new exclude.
|
|
136
|
-
function certifyAllowlistNarrowing(base, head) {
|
|
137
|
-
if (base == null || head == null) return { pass: false, reason: 'added/removed wholesale — not a narrowing edit' };
|
|
138
|
-
const norm = (t) => new Set(t.split('\n').map((l) => l.trim()).filter((l) => l && !l.startsWith('#')));
|
|
139
|
-
const b = norm(base), h = norm(head);
|
|
140
|
-
for (const line of h) if (!b.has(line)) return { pass: false, reason: `allowlist ADDED/BROADENED: ${line}` };
|
|
141
|
-
return { pass: true };
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
// ----------------------------------------------------------------------------------------
|
|
145
|
-
// PURE CORE. Inputs are diff DATA (no git, no fs) so the whole policy is unit-tested:
|
|
146
|
-
// files: [{ path, mode, deleted }] — changed paths + git new-mode + delete flag
|
|
147
|
-
// blobAt: (which:'base'|'head', path) => string|null — file content at base/head
|
|
148
|
-
// diffBytes: number — total textual diff size
|
|
149
|
-
// Returns { verdict:'pass'|'needs_trust'|'escalate', escalations:[{path,reason}],
|
|
150
|
-
// needsTrust:[{path,reason}], autoPassed:[path], surfaces:[path] }.
|
|
151
|
-
// ----------------------------------------------------------------------------------------
|
|
152
|
-
const MAX_DIFF_BYTES = Number(process.env.GATE_MAX_DIFF_BYTES || 2 * 1024 * 1024);
|
|
153
|
-
|
|
154
|
-
function classifyChanges({ files, blobAt, diffBytes = 0 } = {}) {
|
|
155
|
-
const escalations = [];
|
|
156
|
-
const needsTrust = [];
|
|
157
|
-
const autoPassed = [];
|
|
158
|
-
const surfaces = [];
|
|
159
|
-
|
|
160
|
-
// --- Pre-flight, fail-closed (whole changed set) ---
|
|
161
|
-
for (const f of files || []) {
|
|
162
|
-
if (f.mode === '120000') escalations.push({ path: f.path, reason: 'symlink in diff — refused (alias evasion)' });
|
|
163
|
-
if (!isCanonicalPath(f.path)) escalations.push({ path: String(f.path), reason: 'non-ASCII/whitespace path — refused' });
|
|
164
|
-
}
|
|
165
|
-
if (diffBytes > MAX_DIFF_BYTES) {
|
|
166
|
-
escalations.push({ path: '<diff>', reason: `diff ${diffBytes}B exceeds ${MAX_DIFF_BYTES}B — too large to certify` });
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
for (const f of files || []) {
|
|
170
|
-
const p = f.path;
|
|
171
|
-
if (!isCanonicalPath(p)) continue; // already escalated above
|
|
172
|
-
// A gate surface = the authoritative PROTECTED_GLOBS, OR a hard-floor glob (some
|
|
173
|
-
// floor paths — gate-review.js itself, config/deploy.json, CODEOWNERS — are also
|
|
174
|
-
// added to PROTECTED_GLOBS, but OR-ing keeps the gatekeeper correct even if a
|
|
175
|
-
// future hard-floor entry isn't yet in the shared list), OR a tests/*.mjs file.
|
|
176
|
-
const isProtected = matchesProtected(p) || matchesHardFloor(p);
|
|
177
|
-
const isTest = /^tests\/.+\.mjs$/.test(p);
|
|
178
|
-
if (!isProtected && !isTest) continue; // not a gate surface
|
|
179
|
-
|
|
180
|
-
surfaces.push(p);
|
|
181
|
-
|
|
182
|
-
// tests/*.mjs: deleting or shrinking a test silently weakens the suite → NEEDS_TRUST
|
|
183
|
-
// (rank-trustable per the owner's model); adding/expanding is fine → PASS.
|
|
184
|
-
if (isTest && !isProtected) {
|
|
185
|
-
if (f.deleted) { needsTrust.push({ path: p, reason: 'test file DELETED — suite shrink' }); continue; }
|
|
186
|
-
const base = blobAt('base', p), head = blobAt('head', p);
|
|
187
|
-
if (base != null && head != null && head.length < base.length) {
|
|
188
|
-
needsTrust.push({ path: p, reason: 'test file SHRANK — possible muting' });
|
|
189
|
-
}
|
|
190
|
-
continue;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
// AUTO pure-removal surfaces: certify a narrowing-only edit (any author).
|
|
194
|
-
if (Object.prototype.hasOwnProperty.call(AUTO_NARROWING, p)) {
|
|
195
|
-
const cert = AUTO_NARROWING[p](blobAt('base', p), blobAt('head', p));
|
|
196
|
-
if (cert.pass) { autoPassed.push(p); continue; }
|
|
197
|
-
// not a narrowing edit → fall through to HARD FLOOR (both AUTO files are floor globs)
|
|
198
|
-
escalations.push({ path: p, reason: `AUTO surface failed narrowing cert (${cert.reason}) — root-of-trust` });
|
|
199
|
-
continue;
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
// db.js: HARD FLOOR only when the rank-tier brain changed; else NEEDS_TRUST.
|
|
203
|
-
if (p === DB_JS) {
|
|
204
|
-
const base = blobAt('base', p), head = blobAt('head', p);
|
|
205
|
-
const region = (t) => { const m = t == null ? null : RANK_TIER_RE.exec(t); return m ? m[0] : (t == null ? '<absent>' : '<no-region>'); };
|
|
206
|
-
if (base == null || head == null) { needsTrust.push({ path: p, reason: 'db.js added/removed' }); continue; }
|
|
207
|
-
if (region(base) !== region(head)) {
|
|
208
|
-
escalations.push({ path: p, reason: 'db.js REQUIRES_RANK_TIER (rank brain) changed — root-of-trust' });
|
|
209
|
-
} else {
|
|
210
|
-
needsTrust.push({ path: p, reason: 'db.js change outside the rank-tier region' });
|
|
211
|
-
}
|
|
212
|
-
continue;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
// HARD FLOOR globs → ESCALATE regardless of rank.
|
|
216
|
-
if (matchesHardFloor(p)) { escalations.push({ path: p, reason: 'gate machinery / deploy switch (hard floor) — owner review required' }); continue; }
|
|
217
|
-
|
|
218
|
-
// Any other protected surface → NEEDS_TRUST (rank-trustable).
|
|
219
|
-
needsTrust.push({ path: p, reason: 'sensitive gate surface — auto-merges for Metic+ (rank trust)' });
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
const verdict = escalations.length ? 'escalate' : (needsTrust.length ? 'needs_trust' : 'pass');
|
|
223
|
-
return { verdict, escalations, needsTrust, autoPassed, surfaces };
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
// Resolve the merge action from the verdict + the two trust signals (both injected by
|
|
227
|
-
// the workflow, never read by the pure classifier). RANK trust (server-attested Metic+)
|
|
228
|
-
// clears NEEDS_TRUST only; the HARD FLOOR (escalate) is cleared ONLY by an explicit
|
|
229
|
-
// owner review — rank never bypasses it. An owner approval clears anything.
|
|
230
|
-
// pass → 'pass'
|
|
231
|
-
// needs_trust → 'pass' iff (rank-trusted OR owner-approved)
|
|
232
|
-
// escalate → 'pass' iff owner-approved
|
|
233
|
-
function resolveAction({ verdict, trusted, ownerApproved }) {
|
|
234
|
-
if (verdict === 'pass') return 'pass';
|
|
235
|
-
if (verdict === 'escalate') return ownerApproved ? 'pass' : 'block';
|
|
236
|
-
return trusted || ownerApproved ? 'pass' : 'block';
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
// ----------------------------------------------------------------------------------------
|
|
240
|
-
// CLI wrapper — gather the real diff and apply report-only / exit codes. Never returns 0
|
|
241
|
-
// on an analyzer error (fail-closed). The rank/owner-approval resolution for NEEDS_TRUST
|
|
242
|
-
// is provided by the workflow via GATE_AUTHOR_TRUSTED (set from the server-resolved rank /
|
|
243
|
-
// owner approval); the pure core never reads rank.
|
|
244
|
-
// ----------------------------------------------------------------------------------------
|
|
245
|
-
function git(args) { return execFileSync('git', args, { encoding: 'utf8', maxBuffer: 128 * 1024 * 1024 }); }
|
|
246
|
-
|
|
247
|
-
function gatherDiff(baseRef) {
|
|
248
|
-
const mb = git(['merge-base', baseRef, 'HEAD']).trim();
|
|
249
|
-
const raw = git(['diff', '--raw', `${mb}...HEAD`]).split('\n').filter(Boolean);
|
|
250
|
-
const files = [];
|
|
251
|
-
for (const line of raw) {
|
|
252
|
-
// ":100644 100644 sha sha M\tpath" (status D = delete; mode 120000 = symlink)
|
|
253
|
-
const m = /^:(\d{6}) (\d{6}) \S+ \S+ (\S+)\t(.+)$/.exec(line);
|
|
254
|
-
if (!m) { files.push({ path: '<unparseable>', mode: '000000', deleted: false, parseError: true }); continue; }
|
|
255
|
-
const [, , newMode, status, rawPath] = m;
|
|
256
|
-
files.push({ path: rawPath, mode: newMode, deleted: status.startsWith('D') });
|
|
257
|
-
}
|
|
258
|
-
const diffBytes = Buffer.byteLength(git(['diff', `${mb}...HEAD`]), 'utf8');
|
|
259
|
-
const blobAt = (which, p) => {
|
|
260
|
-
const ref = which === 'base' ? mb : 'HEAD';
|
|
261
|
-
try { return git(['show', `${ref}:${p}`]); } catch { return null; }
|
|
262
|
-
};
|
|
263
|
-
return { files, blobAt, diffBytes };
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
async function main() {
|
|
267
|
-
const baseRef = process.env.GATE_BASE_REF || 'origin/main';
|
|
268
|
-
const reportOnly = process.env.GATE_REPORT_ONLY === '1';
|
|
269
|
-
const trusted = process.env.GATE_AUTHOR_TRUSTED === '1'; // set by the workflow from server-resolved rank / owner approval
|
|
270
|
-
|
|
271
|
-
let result;
|
|
272
|
-
try {
|
|
273
|
-
if ((process.env.GATE_FILES_JSON || '').trim()) {
|
|
274
|
-
// test/dry-run injection: {files, blobs:{ 'base:path':..., 'head:path':... }, diffBytes}
|
|
275
|
-
const inj = JSON.parse(process.env.GATE_FILES_JSON);
|
|
276
|
-
const blobAt = (which, p) => (inj.blobs && Object.prototype.hasOwnProperty.call(inj.blobs, `${which}:${p}`)) ? inj.blobs[`${which}:${p}`] : null;
|
|
277
|
-
result = classifyChanges({ files: inj.files, blobAt, diffBytes: inj.diffBytes || 0 });
|
|
278
|
-
} else {
|
|
279
|
-
const { files, blobAt, diffBytes } = gatherDiff(baseRef);
|
|
280
|
-
if (files.some((f) => f.parseError)) throw new Error('unparseable --raw diff line (fail-closed)');
|
|
281
|
-
result = classifyChanges({ files, blobAt, diffBytes });
|
|
282
|
-
}
|
|
283
|
-
} catch (e) {
|
|
284
|
-
console.error(`gate-review: analyzer error (fail-closed): ${e && e.stack ? e.stack : e}`);
|
|
285
|
-
process.exit(reportOnly ? 0 : 1);
|
|
286
|
-
return;
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
// Resolve the final action.
|
|
290
|
-
const ownerApproved = process.env.GATE_OWNER_APPROVED === '1';
|
|
291
|
-
const action = resolveAction({ verdict: result.verdict, trusted, ownerApproved });
|
|
292
|
-
|
|
293
|
-
const lines = [`## gate-review: ${result.verdict.toUpperCase()}${reportOnly ? ' (report-only)' : ''} → would ${action === 'pass' ? 'PASS' : 'BLOCK + ping owner'}`, ''];
|
|
294
|
-
if (result.surfaces.length) lines.push(`Gate surfaces touched: ${result.surfaces.length}`);
|
|
295
|
-
for (const e of result.escalations) lines.push(`- ESCALATE \`${e.path}\` — ${e.reason}`);
|
|
296
|
-
for (const n of result.needsTrust) lines.push(`- NEEDS-TRUST \`${n.path}\` — ${n.reason} → ${action === 'pass' ? 'PASS' : 'BLOCK (needs Metic+ attestation or owner approval)'}`);
|
|
297
|
-
for (const a of result.autoPassed) lines.push(`- AUTO-PASS \`${a}\` — pure narrowing (strengthens the gate)`);
|
|
298
|
-
if (!result.surfaces.length) lines.push('No gate surface touched — auto-merge.');
|
|
299
|
-
|
|
300
|
-
const out = lines.join('\n');
|
|
301
|
-
console.log(out);
|
|
302
|
-
if (process.env.GITHUB_STEP_SUMMARY) { try { fs.appendFileSync(process.env.GITHUB_STEP_SUMMARY, out + '\n'); } catch (_) { /* best-effort */ } }
|
|
303
|
-
|
|
304
|
-
if (action === 'block' && !reportOnly) {
|
|
305
|
-
await pingOwner(result).catch(() => {});
|
|
306
|
-
process.exit(1);
|
|
307
|
-
}
|
|
308
|
-
process.exit(0);
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
async function pingOwner(result) {
|
|
312
|
-
const url = process.env.DISCORD_GATE_APPROVALS_WEBHOOK;
|
|
313
|
-
const pr = process.env.GATE_PR_NUMBER;
|
|
314
|
-
if (!url || !pr) return;
|
|
315
|
-
const surfaces = [...new Set([...result.escalations, ...result.needsTrust].map((x) => x.path))].slice(0, 10).join(', ');
|
|
316
|
-
const body = { content: `gate-pr #${pr} — gate-surface change needs your review: ${surfaces}\nReview + approve the PR to land it.`, allowed_mentions: { parse: [] } };
|
|
317
|
-
await fetch(url, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(body) });
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
module.exports = {
|
|
321
|
-
classifyChanges,
|
|
322
|
-
resolveAction,
|
|
323
|
-
certifyRunUnitTestsNarrowing,
|
|
324
|
-
certifyAllowlistNarrowing,
|
|
325
|
-
isCanonicalPath,
|
|
326
|
-
matchesHardFloor,
|
|
327
|
-
HARD_FLOOR_GLOBS,
|
|
328
|
-
};
|
|
329
|
-
|
|
330
|
-
if (require.main === module) main();
|
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// scripts/bongos/goal-names.js — give every open goal a NAME, and move its
|
|
3
|
-
// explanation into `subtitle` (task 1003225, goal 1000072, criterion
|
|
4
|
-
// legibility-goal-names-and-claude-text).
|
|
5
|
-
//
|
|
6
|
-
// Owner directive: a goal's subtitle should not sit on its title. 28 open goals
|
|
7
|
-
// carried theirs after a dash and 3 more after a colon — the punctuation is not
|
|
8
|
-
// the point, the jamming is. Task 1003224 gave `goals` a `subtitle` column and
|
|
9
|
-
// taught the hall to render name / subtitle / Claude's prose at three weights;
|
|
10
|
-
// this moves the existing text into that shape.
|
|
11
|
-
//
|
|
12
|
-
// WHY A MAPPING FILE AND NOT 31 AD-HOC PATCHES. A rename is a judgment call, and
|
|
13
|
-
// judgment that lives only in a shell history cannot be reviewed, re-run, or
|
|
14
|
-
// argued with. The table below IS the decision: it can be read in a diff, its
|
|
15
|
-
// invariants are unit-tested (no duplicate names, no name still carrying its own
|
|
16
|
-
// explanation, nothing dropped), and re-running is a no-op.
|
|
17
|
-
//
|
|
18
|
-
// IT IS NOT A MECHANICAL SPLIT, which is the whole reason this is its own task.
|
|
19
|
-
// Splitting on the separator works where the head already names the thing
|
|
20
|
-
// ("Findability — the grep path"). It FAILS where the head is not distinctive:
|
|
21
|
-
// goals 1000038/1000039/1000040 all head "Agents module" and differ ONLY in the
|
|
22
|
-
// tail, so a split would leave three identically-named goals — strictly worse
|
|
23
|
-
// than today. Those three, the version catch-all, and three wordy heads get
|
|
24
|
-
// written names; the rest keep theirs verbatim.
|
|
25
|
-
//
|
|
26
|
-
// SAFETY. Dry-run by DEFAULT (`--apply` to write). Every write goes through
|
|
27
|
-
// PATCH /goals/:id, which is authorized (owner|manager|Archon) and records
|
|
28
|
-
// prose-edit deltas — so the rename is audit-trailed, unlike the prod SQL UPDATE
|
|
29
|
-
// that would otherwise be the only channel. It REFUSES to run at all if the
|
|
30
|
-
// server does not yet return `subtitle`, because renaming titles against a
|
|
31
|
-
// server that cannot store the subtitle would DELETE the explanations.
|
|
32
|
-
|
|
33
|
-
'use strict';
|
|
34
|
-
|
|
35
|
-
// id → { title, subtitle, why? }. `why` is recorded only where the name is
|
|
36
|
-
// NEW rather than the existing head — i.e. where judgment was applied.
|
|
37
|
-
const GOAL_NAMES = Object.freeze({
|
|
38
|
-
// ── names written, because the head was not usable ───────────────────────
|
|
39
|
-
1000003: { title: 'Work with no goal yet',
|
|
40
|
-
subtitle: 'The version catch-all. Tasks land here when nothing else fits — and nothing here can satisfy a criterion (ADR 0183), so it is a holding pen, not a home.',
|
|
41
|
-
why: 'was "BONGOS-V1 — general": a version id and the word general name nothing. Named for what it actually is.' },
|
|
42
|
-
1000038: { title: 'Agent framework & registry',
|
|
43
|
-
subtitle: 'The foundation — the plugin surface an agent is written against, and the registry that holds the roster.',
|
|
44
|
-
why: 'one of THREE goals heading "Agents module"; a mechanical split would have left three identically-named goals. Written without a colon on purpose: "Agents: framework" is the same name-plus-qualifier shape this task exists to remove, and the test flagged it.' },
|
|
45
|
-
1000039: { title: 'Agent authoring & first roster',
|
|
46
|
-
subtitle: 'Owner and builder authoring, plus the first real roster — event-driven and on-demand agents.',
|
|
47
|
-
why: 'second of the three "Agents module" goals. Named for what it adds — authoring and the first roster — rather than by a tail no list ever showed.' },
|
|
48
|
-
1000040: { title: 'Agent scheduling & spend limits',
|
|
49
|
-
subtitle: 'Scheduled agents, and the guardrails on what they are allowed to spend.',
|
|
50
|
-
why: 'third of the three "Agents module" goals. Scheduling and spend are what separate it from the framework and authoring goals.' },
|
|
51
|
-
1000053: { title: 'Sessions start knowing',
|
|
52
|
-
subtitle: 'Front-load auth, environment and instance facts so a session does not spend its first minutes rediscovering them.',
|
|
53
|
-
why: 'was "Session efficiency: sessions start knowing" — the head restated the tail. Kept the half that is a name.' },
|
|
54
|
-
1000064: { title: 'The hall as the room',
|
|
55
|
-
subtitle: 'Agora, Monitor & Contribute, and chat that does not care which channel it came from.',
|
|
56
|
-
why: 'colon-separated rather than dashed; same jamming, so treated identically.' },
|
|
57
|
-
1000075: { title: 'Collaboration & the social hall',
|
|
58
|
-
subtitle: 'Ask for help by craft, and let the hall become a mode rather than a page.',
|
|
59
|
-
why: 'was "Collab, then the social hall" — an ordering, not a name.' },
|
|
60
|
-
1000077: { title: 'Guards, not notes',
|
|
61
|
-
subtitle: 'The four failure modes that prose already documented and nothing refused.',
|
|
62
|
-
why: 'colon-separated; the head is already a good name, the tail is the explanation.' },
|
|
63
|
-
1000078: { title: 'Project money out',
|
|
64
|
-
subtitle: 'A funded balance, real builder payouts, visible outflows, and work that cannot outrun the money.',
|
|
65
|
-
why: 'colon-separated rather than dashed. The head is already the name; the tail is a four-part explanation that belongs underneath it.' },
|
|
66
|
-
|
|
67
|
-
// ── heads kept verbatim; only the tail moves ─────────────────────────────
|
|
68
|
-
1000025: { title: 'Builder experience', subtitle: 'Hall, dev box, and skills.' },
|
|
69
|
-
1000027: { title: 'Findability', subtitle: 'The grep path (ADR 0095).' },
|
|
70
|
-
1000029: { title: 'Module upstreaming & catalog', subtitle: 'Instances contribute modules back into core.' },
|
|
71
|
-
1000032: { title: 'Instance hosting + domains', subtitle: 'The `provisioning` module.' },
|
|
72
|
-
1000044: { title: 'Living project sky', subtitle: 'A shared, persistent, data-driven orb gate.' },
|
|
73
|
-
1000045: { title: 'Public builder profiles', subtitle: 'Track record, apply-to-project, and owner recruiting.' },
|
|
74
|
-
1000046: { title: 'Project creation', subtitle: 'Visibility and customization.' },
|
|
75
|
-
1000048: { title: 'SEO & public discoverability', subtitle: 'Rankable per-project profiles.' },
|
|
76
|
-
1000049: { title: 'Orb Physics', subtitle: 'Orb motion and space-whale flight.' },
|
|
77
|
-
1000052: { title: 'First-run onboarding', subtitle: 'From a bare machine to a first ship.' },
|
|
78
|
-
1000054: { title: 'Public distribution', subtitle: 'CLI installer and Claude plugin.' },
|
|
79
|
-
1000057: { title: 'Rank blocker matrix', subtitle: 'Empirically verify intended vs unintended blockers across xenos, thetes and metic.' },
|
|
80
|
-
1000058: { title: 'Non-cloudbongos project lifecycle', subtitle: 'Creation, governance, planning, building — the whole flow works and feels right.' },
|
|
81
|
-
1000063: { title: 'The front door', subtitle: 'Enter the Future: hero-only landing, wormhole transit, About on the map.' },
|
|
82
|
-
1000065: { title: 'Community surface', subtitle: 'Builder discovery on the hub.' },
|
|
83
|
-
1000067: { title: 'Ideator compensation', subtitle: 'The idea credit stream, separate from cost-plus.' },
|
|
84
|
-
1000068: { title: 'Government, not governance', subtitle: 'Ranks are ranks, and "role" belongs to the crafts.' },
|
|
85
|
-
1000070: { title: 'Scan-before-install', subtitle: 'First-real-run hardening.' },
|
|
86
|
-
1000071: { title: 'Idea routing', subtitle: 'Work that has a home lands in it.' },
|
|
87
|
-
1000072: { title: 'Human legibility', subtitle: 'Seeing, sorting and steering an AI-run project.' },
|
|
88
|
-
1000073: { title: 'Finish the GDS→Bongos rename', subtitle: 'Retire a legacy Primea term without rewriting history.' },
|
|
89
|
-
1000074: { title: "The artist's loop", subtitle: 'Review authority, copy an artist can change, and a place to do it.' },
|
|
90
|
-
// 1000080 already carried a subtitle, so the tail is PREPENDED to it rather than
|
|
91
|
-
// replacing it — overwriting would delete text, the exact loss this task guards.
|
|
92
|
-
1000080: { title: 'Apex UI pass 2', subtitle: 'One top bar, two dropdowns, and a project card worth opening — the chrome and the card on cloudbongos.com and /projects.' },
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
// A name must not carry its own explanation — that is the whole point.
|
|
96
|
-
const SEPARATOR = / [—-] |: /;
|
|
97
|
-
|
|
98
|
-
// Goals deliberately untouched: their titles are already names. Listed so the
|
|
99
|
-
// coverage test can tell "already fine" from "forgotten".
|
|
100
|
-
const ALREADY_NAMES = Object.freeze([1000009, 1000024, 1000028, 1000037, 1000047, 1000050]);
|
|
101
|
-
|
|
102
|
-
module.exports = { GOAL_NAMES, ALREADY_NAMES, SEPARATOR };
|
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// scripts/bongos/rename-goals.js — apply the goal-names.js mapping (task 1003225).
|
|
3
|
-
//
|
|
4
|
-
// Usage:
|
|
5
|
-
// node scripts/bongos/rename-goals.js # DRY RUN — prints the diff, writes nothing
|
|
6
|
-
// node scripts/bongos/rename-goals.js --apply # writes, via PATCH /goals/:id
|
|
7
|
-
//
|
|
8
|
-
// Writes go through PATCH /goals/:id, never SQL: that route is authorized
|
|
9
|
-
// (owner|manager|Archon) and records prose-edit deltas, so a rename of somebody's
|
|
10
|
-
// goal is audit-trailed. A prod UPDATE would be invisible.
|
|
11
|
-
//
|
|
12
|
-
// THE GUARD THAT MATTERS: it refuses to run when the server does not return
|
|
13
|
-
// `subtitle`. Renaming a title against a server that cannot store the subtitle
|
|
14
|
-
// would DELETE the explanation — the exact data loss this task exists to avoid.
|
|
15
|
-
// core_224 ships the column; a live instance one release behind does not have it.
|
|
16
|
-
|
|
17
|
-
'use strict';
|
|
18
|
-
|
|
19
|
-
const { cliClient, requireSession, hasFlag } = require('./cli-lib');
|
|
20
|
-
const { GOAL_NAMES, SEPARATOR } = require('./goal-names.js');
|
|
21
|
-
|
|
22
|
-
// Nothing to do when the goal already reads the way the mapping wants.
|
|
23
|
-
function planFor(goal, want) {
|
|
24
|
-
const patch = {};
|
|
25
|
-
if ((goal.title || '') !== want.title) patch.title = want.title;
|
|
26
|
-
if ((goal.subtitle || '') !== want.subtitle) patch.subtitle = want.subtitle;
|
|
27
|
-
return patch;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
async function main() {
|
|
31
|
-
await requireSession();
|
|
32
|
-
const apply = hasFlag('--apply');
|
|
33
|
-
const api = await cliClient();
|
|
34
|
-
|
|
35
|
-
const res = await api.goals.getGoals({ query: { version: 'BONGOS-V1' } });
|
|
36
|
-
if (!res.ok) {
|
|
37
|
-
console.error(`could not list goals: ${res.status}`);
|
|
38
|
-
process.exit(1);
|
|
39
|
-
}
|
|
40
|
-
const goals = (res.data.goals || []).filter((g) => g.status === 'open');
|
|
41
|
-
if (!goals.length) {
|
|
42
|
-
console.error('no open goals returned — refusing to act on an empty read.');
|
|
43
|
-
process.exit(1);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// The refusal above all others.
|
|
47
|
-
if (!Object.prototype.hasOwnProperty.call(goals[0], 'subtitle')) {
|
|
48
|
-
console.error('REFUSING: this server does not return `subtitle`, so it cannot store one.');
|
|
49
|
-
console.error('Renaming titles now would drop every explanation on the floor.');
|
|
50
|
-
console.error('core_224 adds the column — wait for the release carrying it to DEPLOY, then re-run.');
|
|
51
|
-
process.exit(2);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
let changed = 0; let already = 0; let missing = 0; let failed = 0;
|
|
55
|
-
for (const g of goals.sort((a, b) => Number(a.id) - Number(b.id))) {
|
|
56
|
-
const want = GOAL_NAMES[Number(g.id)];
|
|
57
|
-
if (!want) {
|
|
58
|
-
// Not an error: goals whose titles were already names are deliberately absent.
|
|
59
|
-
if (SEPARATOR.test(g.title || '')) {
|
|
60
|
-
console.error(`! ${g.id} has a jammed title but no mapping entry: ${g.title}`);
|
|
61
|
-
missing += 1;
|
|
62
|
-
}
|
|
63
|
-
continue;
|
|
64
|
-
}
|
|
65
|
-
const patch = planFor(g, want);
|
|
66
|
-
if (!Object.keys(patch).length) { already += 1; continue; }
|
|
67
|
-
console.log(`${apply ? '→' : '·'} ${g.id}`);
|
|
68
|
-
if (patch.title) console.log(` title ${JSON.stringify(g.title)} -> ${JSON.stringify(patch.title)}`);
|
|
69
|
-
if (patch.subtitle !== undefined) console.log(` subtitle ${JSON.stringify(g.subtitle || '')} -> ${JSON.stringify(patch.subtitle)}`);
|
|
70
|
-
if (!apply) { changed += 1; continue; }
|
|
71
|
-
const r = await api.goals.patchGoalsId({ id: g.id, body: patch });
|
|
72
|
-
if (!r.ok) {
|
|
73
|
-
console.error(` FAILED ${r.status} ${JSON.stringify(r.data)}`);
|
|
74
|
-
failed += 1;
|
|
75
|
-
} else {
|
|
76
|
-
changed += 1;
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
console.log('');
|
|
81
|
-
console.log(`${apply ? 'applied' : 'would change'}: ${changed} · already correct: ${already}`
|
|
82
|
-
+ `${missing ? ` · UNMAPPED jammed titles: ${missing}` : ''}${failed ? ` · FAILED: ${failed}` : ''}`);
|
|
83
|
-
if (!apply) console.log('dry run — re-run with --apply to write.');
|
|
84
|
-
|
|
85
|
-
// THE POST-CONDITION, CHECKED AGAINST REAL DATA — not against the mapping.
|
|
86
|
-
// The task's deliverable is a STATE of the live goal set ("no open goal's title
|
|
87
|
-
// contains a dash-appended subtitle"), and unit tests over the mapping's internal
|
|
88
|
-
// consistency cannot witness that: they would pass just as happily if --apply had
|
|
89
|
-
// never been run. This re-reads what the server actually holds now and reports it,
|
|
90
|
-
// so the claim is falsifiable by anyone re-running the command.
|
|
91
|
-
//
|
|
92
|
-
// SCOPED TO THIS VERSION, deliberately. The read above is `version: BONGOS-V1`;
|
|
93
|
-
// CB-V1 carries jammed titles of its own (id 4, 1000043, 1000066, 1000079) which
|
|
94
|
-
// are ANOTHER version's work, and sweeping them in here would rename goals this
|
|
95
|
-
// task never reviewed.
|
|
96
|
-
const stillJammed = goals.filter((g) => SEPARATOR.test(g.title || ''));
|
|
97
|
-
console.log('');
|
|
98
|
-
console.log(`post-condition (BONGOS-V1 open goals): ${goals.length} checked · `
|
|
99
|
-
+ `${stillJammed.length} still carrying an explanation in the title`);
|
|
100
|
-
for (const g of stillJammed) console.log(` ! ${g.id} ${JSON.stringify(g.title)}`);
|
|
101
|
-
|
|
102
|
-
process.exit(failed || missing ? 1 : 0);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
if (require.main === module) {
|
|
106
|
-
main().catch((err) => { console.error('fatal:', err); process.exit(1); });
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
module.exports = { planFor };
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// seed-adopt-tasks.js
|
|
3
|
-
// Seeds the brownfield adopt-mode task breakdown for BONGOS-V1 goal 35
|
|
4
|
-
// ("Project creation flow"), criterion `adopt-existing-repo`, per ADR 0121
|
|
5
|
-
// (task 1986). Scope-only deliverable: these are the BUILD tasks for
|
|
6
|
-
// `bongos init --adopt`; they are seeded as `backlog` (NOT promoted) so the
|
|
7
|
-
// owner schedules the adopt build deliberately. See ADR 0121 §Consequences.
|
|
8
|
-
//
|
|
9
|
-
// Idempotent: task create on source_ref (`BONGOS-V1-adopt-A0N`), criterion
|
|
10
|
-
// links on (task_id, criterion_id), dependency edges on (task_id,
|
|
11
|
-
// depends_on_task_id). Safe to re-run. Run: node scripts/bongos/seed-adopt-tasks.js
|
|
12
|
-
const { apiCall } = require('./cli-lib.js');
|
|
13
|
-
|
|
14
|
-
const GOAL_ID = 35;
|
|
15
|
-
const CRIT = 'adopt-existing-repo';
|
|
16
|
-
const RUNBOOK_TASK = 1981; // goal-35 canonical runbook; A05 depends on it (external edge)
|
|
17
|
-
|
|
18
|
-
// ref → task spec. `deps` are local A0N refs; `extDeps` are literal task ids.
|
|
19
|
-
const TASKS = [
|
|
20
|
-
{ ref: 'A01', kind: 'feature', priority: 4, est: 180, crit: [CRIT], deps: [], extDeps: [],
|
|
21
|
-
title: 'bongos init --adopt: mode skeleton + additive config layering (no clobber)',
|
|
22
|
-
description: 'Add an `--adopt` mode to `scripts/bongos/init.js` (ADR 0121 §Decision 1-2). Instead of the greenfield "stand up a NEW instance" path (which refuses where config/branding.json exists and whose --force OVERWRITES), --adopt LAYERS onto the existing tree: add the pinned @bongos/core dependency + the lean config/ overrides + .claude/ ADDITIVELY. For any pre-existing file (package.json, an existing .claude/, a CI workflow) it MERGES or LEAVES-AND-REPORTS — it never overwrites, and it must NOT reuse the --force code path. Writes only the lean branding.json overrides (rest resolves from *.neutral.json). Serves criterion adopt-existing-repo. deps: none (the ADR-0108 configurable-root substrate is shipped).' },
|
|
23
|
-
{ ref: 'A02', kind: 'feature', priority: 4, est: 150, crit: [CRIT], deps: [], extDeps: [],
|
|
24
|
-
title: 'bongos init --adopt: detect the existing stack + layout',
|
|
25
|
-
description: 'Before writing anything, --adopt inspects the existing tree — language/runtime, package manager, test + CI setup, directory layout — so the enabled modules, the generated repo-map, and the core<->host fitness boundary (ADR 0062 §1) fit what is already there instead of assuming the reference layout (ADR 0121 §Decision 4). Output: a detected-stack summary consumed by A03 (real-state capture) and A04 (pre-flight). Serves criterion adopt-existing-repo. deps: none.' },
|
|
26
|
-
{ ref: 'A03', kind: 'feature', priority: 3, est: 180, crit: [CRIT], deps: ['A01'], extDeps: [],
|
|
27
|
-
title: 'bongos init --adopt: reflect REAL state — capture version/goal + import backlog',
|
|
28
|
-
description: 'Replace the synthetic "V1 — Prove the loop" seed with the repo\'s ACTUAL current version/milestone + open goal (by interview + detection), and IMPORT the existing backlog into GDS tasks — GitHub issues (via the auth the flow already establishes), a TODO/ROADMAP/BACKLOG.md, or an interview fallback — so the ledger starts truthful rather than empty (ADR 0121 §Decision 3). Serves criterion adopt-existing-repo. deps: A01.' },
|
|
29
|
-
{ ref: 'A04', kind: 'feature', priority: 3, est: 150, crit: [CRIT], deps: ['A01', 'A02'], extDeps: [],
|
|
30
|
-
title: 'bongos init --adopt: conflict pre-flight report (--dry-run default, accept-gated)',
|
|
31
|
-
description: 'Produce the pre-flight report the owner must ACCEPT before anything is written (ADR 0121 §Decision 5): migration-namespace collisions (ADR 0108 §5), core<->host fitness-boundary violations in existing imports (ADR 0062 §1), incoming files that land on protected paths (ADR 0043), and CI collisions between core\'s expected unit/gate check and the repo\'s own workflows. --dry-run is the default first pass; nothing is written until accepted. Serves criterion adopt-existing-repo. deps: A01, A02.' },
|
|
32
|
-
{ ref: 'A05', kind: 'feature', priority: 3, est: 120, crit: [CRIT], deps: ['A01', 'A03', 'A04'], extDeps: [RUNBOOK_TASK],
|
|
33
|
-
title: 'bongos init --adopt: wire greenfield-vs-adopt into the runbook + both forms',
|
|
34
|
-
description: 'Offer the greenfield-vs-adopt choice at the SCAFFOLD leg of goal 35\'s canonical step sequence, across all three forms: the /new-project skill (task 1981), the `bongos onboard` CLI (task 1982), and the hall "start a project" wizard (task 1983). Since --adopt is a branch of the same init, the forms inherit it (ADR 0121 §Decision 1). Serves criterion adopt-existing-repo. deps: A01, A03, A04, and the runbook task 1981.' },
|
|
35
|
-
];
|
|
36
|
-
|
|
37
|
-
(async () => {
|
|
38
|
-
const idByRef = {};
|
|
39
|
-
// Phase 1: create tasks (idempotent on source_ref) + link the criterion.
|
|
40
|
-
for (const t of TASKS) {
|
|
41
|
-
const create = await apiCall('POST', '/api/bongos/tasks', {
|
|
42
|
-
version_id: 'BONGOS-V1',
|
|
43
|
-
goal_id: GOAL_ID,
|
|
44
|
-
title: t.title,
|
|
45
|
-
description: t.description,
|
|
46
|
-
kind: t.kind,
|
|
47
|
-
discipline: 'engineer',
|
|
48
|
-
priority: t.priority,
|
|
49
|
-
est_minutes: t.est,
|
|
50
|
-
source: 'planning',
|
|
51
|
-
source_ref: `BONGOS-V1-adopt-${t.ref}`,
|
|
52
|
-
});
|
|
53
|
-
const task = create.data && (create.data.task || create.data);
|
|
54
|
-
const id = task && task.id;
|
|
55
|
-
if (!id) { console.log(` ${t.ref}: CREATE FAILED — ${create.status} ${JSON.stringify(create.data).slice(0, 160)}`); continue; }
|
|
56
|
-
idByRef[t.ref] = id;
|
|
57
|
-
const link = await apiCall('POST', `/api/bongos/tasks/${id}/criteria`, { criterion_ids: t.crit });
|
|
58
|
-
console.log(` ${t.ref} -> task ${id} (${t.kind}, P${t.priority}, goal ${task.goal_id}) · criterion ${link.status === 200 ? 'ok' : (JSON.stringify(link.data).slice(0, 120))}`);
|
|
59
|
-
}
|
|
60
|
-
// Phase 2: dependency edges (idempotent on (task_id, depends_on_task_id)).
|
|
61
|
-
console.log('--- dependencies ---');
|
|
62
|
-
for (const t of TASKS) {
|
|
63
|
-
const id = idByRef[t.ref];
|
|
64
|
-
if (!id) continue;
|
|
65
|
-
const localDepIds = t.deps.map((d) => idByRef[d]).filter(Boolean);
|
|
66
|
-
const allDeps = [...localDepIds, ...t.extDeps];
|
|
67
|
-
for (const depId of allDeps) {
|
|
68
|
-
const r = await apiCall('POST', `/api/bongos/tasks/${id}/dependencies`, { depends_on_task_id: depId });
|
|
69
|
-
console.log(` ${t.ref}(${id}) -> ${depId} ${r.status === 200 || r.status === 201 ? 'ok' : (JSON.stringify(r.data).slice(0, 120))}`);
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
// NOTE: intentionally NO promote step — these are backlog until the owner
|
|
73
|
-
// schedules the adopt build (ADR 0121; task 1986 is scope-only).
|
|
74
|
-
console.log('refs:', JSON.stringify(idByRef));
|
|
75
|
-
})().catch((e) => console.log('ERR ' + (e && e.message)));
|