@haiyangbg/buildbeat 3.2.0 → 3.2.1
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 +10 -0
- package/README.en.md +3 -3
- package/docs/README.md +1 -0
- package/docs/RELEASING.md +2 -2
- package/docs/v2/RFC-0001-product-definition.md +1 -1
- package/docs/v2/RFC-0002-domain-model.md +1 -1
- package/docs/v2/RFC-0003-workflow-policy.md +1 -1
- package/docs/v2/SPEC-0001-events-v1.md +1 -1
- package/docs/v2/guide/00-how-to-talk.en.md +61 -0
- package/docs/v2/guide/00-how-to-talk.md +2 -0
- package/docs/v2/guide/02-workflow-guide.en.md +125 -0
- package/docs/v2/guide/02-workflow-guide.md +3 -1
- package/docs/v2/guide/03-policy-guide.en.md +55 -0
- package/docs/v2/guide/03-policy-guide.md +2 -0
- package/docs/v2/guide/04-adapter-guide.en.md +66 -0
- package/docs/v2/guide/04-adapter-guide.md +2 -0
- package/docs/v2/guide/05-worker-contract.en.md +60 -0
- package/docs/v2/guide/05-worker-contract.md +2 -0
- package/docs/v2/guide/09-security-boundaries.en.md +41 -0
- package/docs/v2/guide/09-security-boundaries.md +3 -1
- package/docs/v2/guide/README.en.md +36 -0
- package/docs/v2/guide/README.md +8 -6
- package/package.json +3 -3
- package/src/v2/cli/run.js +28 -10
- package/src/v2/runtime/orchestrator.js +456 -401
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Security and permission boundaries
|
|
2
|
+
|
|
3
|
+
[简体中文](09-security-boundaries.md) | **English**
|
|
4
|
+
|
|
5
|
+
Authority: [`RFC-0001 §Protected actions`](../RFC-0001-product-definition.md) (Chinese), [`V2-PLAN.md`](../../history/V2-PLAN.md) §9 invariants (Chinese). Design philosophy: **a protected action = a removed capability** — not "please, agent, don't", but making it impossible.
|
|
6
|
+
|
|
7
|
+
## Local boundaries on the Runner side (LOCAL_ENFORCED, all tested)
|
|
8
|
+
|
|
9
|
+
Each row has two columns: **what the kernel actually does**, and **what cannot be concluded from it**. The former has regression tests; the latter depends on the host's sandbox / container / server, and the Runner does not pretend otherwise.
|
|
10
|
+
|
|
11
|
+
| Boundary | What the kernel actually does (detection or removal) | What cannot be concluded from it |
|
|
12
|
+
|---|---|---|
|
|
13
|
+
| Push blocked | Worktree-level `remote.pushurl=protected://push-blocked-by-buildbeat` — inside the workspace, a worker's `git push` to a configured remote has nowhere to go (tested against a real remote) | That a worker cannot `git remote add` another remote, or make any network request |
|
|
14
|
+
| Write scope | An out-of-scope write under `allowedPaths` → no candidate pinned, `workspace.scope` BLOCK recorded, the Run stops — an out-of-scope change **cannot** become a qualifying candidate | That the worker process cannot touch host directories outside the worktree |
|
|
15
|
+
| Read-only reviewer | A before/after snapshot comparison per step; any write to the worktree is recorded as a failure (invariant 9) — this is **detecting and blocking the result after the fact**, not an operating-system write ban | That the write is stopped the moment it happens |
|
|
16
|
+
| Credential isolation | The worker env allowlist is by default only `PATH HOME LANG LC_ALL TMPDIR TERM USER SHELL`; cloud-credential / token **environment variables** in the host shell do not reach the subprocess; opening it explicitly with `inheritEnv: true` is labelled ADVISORY by doctor; `env:` injects only the variables you name (really passed through on the CLI loading path since 2.0.1; in 2.0.0 and earlier doctor and start disagreed, see the [Adapter guide](04-adapter-guide.en.md)) | That a worker cannot read credential files under `$HOME`, the keychain, ssh keys or other host resources (`HOME` is on the allowlist) |
|
|
17
|
+
| One active Run | By default a repository-wide lock, so a repository drives one Run at a time; Works whose run configs set `parallel: true` may run in parallel with each other, and Runs of the same Work stay mutually exclusive (since 3.2.0) | That concurrency across repositories / machines is coordinated |
|
|
18
|
+
| Control files | Workflow / policy / run configs live in the main checkout, outside the worker worktree's write scope | That a worker cannot read them by other means |
|
|
19
|
+
| No external actions in the kernel | Merge, push, deploy and publish have **no call path** in the Runner (invariant 20, printed by doctor); at most the Runner puts "the candidate is fit to merge" in the inbox | That any external worker command you configure cannot do these things in every host environment |
|
|
20
|
+
|
|
21
|
+
In one sentence: **the kernel guarantees that an out-of-bounds result cannot enter the ledger, become a candidate or get stamped**; "the worker cannot do it in the first place" depends on the sandbox the host gives it (a tool allowlist, egress limits, no production credentials).
|
|
22
|
+
|
|
23
|
+
## Preconditions for running unattended (the stance enforced since the MVP)
|
|
24
|
+
|
|
25
|
+
Prompt injection is a first-class attack surface: an unattended worker consumes any file in the repository. An unattended run must satisfy all three layers at once; missing any layer downgrades it to attended (a human in the loop):
|
|
26
|
+
|
|
27
|
+
| Layer | Who guarantees it | What |
|
|
28
|
+
|---|---|---|
|
|
29
|
+
| Kernel | The Runner (the section above) | Push blocked, write scope, read-only reviewer, env allowlist, no call path for external actions |
|
|
30
|
+
| Host | Your worker sandbox / container / the tool's own permission mode (such as `codex exec -s read-only`) | A tool allowlist, egress limits, **no production credentials** — the kernel does not and cannot check this layer; doctor reports only the env posture |
|
|
31
|
+
| Server | Code hosting / CI / deployment platform | Branch protection, required CI, deployment approval (next section) |
|
|
32
|
+
|
|
33
|
+
observe's diagnose commands follow the same discipline: read-only, same env allowlist.
|
|
34
|
+
|
|
35
|
+
## SERVER_ENFORCED is an honest declaration
|
|
36
|
+
|
|
37
|
+
Branch protection, required CI and deployment approval are enforced by the server; marking a policy `SERVER_ENFORCED` says "this gate lives on the server", and the Runner records it without pretending it can guarantee it locally. Never mark as LOCAL what cannot be stopped locally.
|
|
38
|
+
|
|
39
|
+
## Credential red line (operations)
|
|
40
|
+
|
|
41
|
+
Credentials for publishing / deploying are read only at run time (for example from the macOS Keychain), never written to files, logs or Git; `doctor` checks the adapter's env posture. A configuration that breaks the red line should not pass review.
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# 安全与权限边界
|
|
2
2
|
|
|
3
|
+
**简体中文** | [English](09-security-boundaries.en.md)
|
|
4
|
+
|
|
3
5
|
权威:[`RFC-0001 §保护动作`](../RFC-0001-product-definition.md)、[`V2-PLAN.md`](../../history/V2-PLAN.md) §9 不变量。设计哲学:**保护动作 = 能力移除**——不是"请 Agent 别做",而是让它做不到。
|
|
4
6
|
|
|
5
7
|
## Runner 侧的本地边界(LOCAL_ENFORCED,均有测试)
|
|
@@ -12,7 +14,7 @@
|
|
|
12
14
|
| 写范围 | `allowedPaths` 越界写入 → 不固定 candidate、`workspace.scope` BLOCK 落账、Run 停——越界改动**不可能**成为合格候选 | Worker 进程无法触碰 worktree 之外的宿主目录 |
|
|
13
15
|
| Reviewer 只读 | 步级前后快照比对,任何工作树写入按失败落账(不变量 9)——这是**事后检测并阻断结果**,不是操作系统级禁写 | 写入在发生那一刻就被拦下 |
|
|
14
16
|
| 凭据隔离 | Worker env 默认白名单仅 `PATH HOME LANG LC_ALL TMPDIR TERM USER SHELL`;宿主 shell 里的云凭据 / token **环境变量**不进子进程;`inheritEnv: true` 显式打开会被 doctor 标为 ADVISORY;`env:` 只注入你点名的变量(CLI 加载路径 2.0.1 起真正透传,2.0.0 及更早 doctor 与 start 姿态不一致,见 [Adapter 指南](04-adapter-guide.md)) | Worker 读不到 `$HOME` 下的凭据文件、keychain、ssh key 等宿主资源(`HOME` 在白名单里) |
|
|
15
|
-
| 单活动 Run |
|
|
17
|
+
| 单活动 Run | 默认仓库级锁,一仓同时只驱动一个 Run;run 配置 `parallel: true` 的 Work 之间可并行,同一 Work 的 Run 仍互斥(自 3.2.0) | 多仓 / 多机并发有协调 |
|
|
16
18
|
| 控制文件 | workflow / policy / run 配置在主检出,不在 Worker 的 worktree 写范围内 | Worker 无法通过其他途径读到它们 |
|
|
17
19
|
| 内核无外部动作 | merge、push、部署、发布在 Runner **没有调用路径**(不变量 20,doctor 打印);Runner 至多把"候选具备合并条件"放进 inbox | 你配置的任意外部 Worker 命令在全部宿主环境下都做不了这些动作 |
|
|
18
20
|
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# BuildBeat v2 documentation
|
|
2
|
+
|
|
3
|
+
[简体中文](README.md) | **English**
|
|
4
|
+
|
|
5
|
+
> The normative authority is the RFCs/SPEC ([`RFC-0001`](../RFC-0001-product-definition.md) product definition / [`RFC-0002`](../RFC-0002-domain-model.md) domain model / [`RFC-0003`](../RFC-0003-workflow-policy.md) workflow and policy / [`SPEC-0001`](../SPEC-0001-events-v1.md) event schema; all in Chinese); this directory is the operator's view, and where it conflicts with the implementation, the RFCs/SPEC and the code win — please report it. Design history (V2-PLAN, iteration records) is not in this directory.
|
|
6
|
+
|
|
7
|
+
## First use
|
|
8
|
+
|
|
9
|
+
| Document | In one line |
|
|
10
|
+
|---|---|
|
|
11
|
+
| [How to talk to a session](00-how-to-talk.en.md) | **For users**: from not started to the next phase, what you say at each stage, what the session does, what you get |
|
|
12
|
+
| [Quickstart](01-quickstart.en.md) | The first Run: install `@latest` → work item and run config → accept → doctor → start → read the evidence and decide; failure branches included |
|
|
13
|
+
| [`templates/v2/`](../../../templates/v2/AGENTS.md) | Project entry points (AGENTS / CLAUDE / command board / BUILDBEAT marker), run config sample, envelope prompts and the worker wrapper |
|
|
14
|
+
|
|
15
|
+
People working in an AI session only need document 0; the session reads the driving manual in `SKILL.md` §0.5.
|
|
16
|
+
|
|
17
|
+
## Day to day
|
|
18
|
+
|
|
19
|
+
| Document | In one line |
|
|
20
|
+
|---|---|
|
|
21
|
+
| [Human approval guide](07-approval-guide.en.md) | inbox / approve / stale; what accept, approving a transition and the merge decision each mean; the triage gate; waits must reach a person; overview |
|
|
22
|
+
| [Evidence guide](06-evidence-guide.en.md) | Read-back evidence, status/grade, the UNVERIFIED culture, observe |
|
|
23
|
+
| [Session and team handoff](11-session-handoff.en.md) | Writing context down, closing the old chat, a new member taking over, cross-tool and cross-machine boundaries |
|
|
24
|
+
| [Recovery](10-recovery.en.md) | Corrupted ledger, interrupted Run, infra stops, locks, rebuilding after deleting the runtime, gc |
|
|
25
|
+
|
|
26
|
+
## Configuration reference
|
|
27
|
+
|
|
28
|
+
| Document | In one line |
|
|
29
|
+
|---|---|
|
|
30
|
+
| [Workflow authoring guide](02-workflow-guide.en.md) | Step order, explicit transitions, readonly, terminal, budgets, cache, requires, parallel runs |
|
|
31
|
+
| [Policy guide](03-policy-guide.en.md) | Four policy types, 8 operators, three-valued logic, enforcement levels |
|
|
32
|
+
| [Adapter guide](04-adapter-guide.en.md) | Shell/Mock, the env allowlist and `env:` injection, plugging in any CLI agent, live output |
|
|
33
|
+
| [Worker contract](05-worker-contract.en.md) | The input/output envelope (`severity` + `summary`), blocking semantics, per-role discipline, the wrapper script |
|
|
34
|
+
| [Security and permission boundaries](09-security-boundaries.en.md) | What the kernel actually does vs what cannot be concluded from it; the three preconditions for running unattended |
|
|
35
|
+
|
|
36
|
+
observe v0 (probe → tiered response → intent drafts → human triage) is covered in [Quickstart §9](01-quickstart.en.md) and the [Evidence guide](06-evidence-guide.en.md); its frozen schema is [`RFC-0003 §8`](../RFC-0003-workflow-policy.md).
|
package/docs/v2/guide/README.md
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
# BuildBeat v2 使用文档
|
|
2
2
|
|
|
3
|
+
**简体中文** | [English](README.en.md)
|
|
4
|
+
|
|
3
5
|
> 规范权威在 RFC/SPEC([`RFC-0001`](../RFC-0001-product-definition.md) 产品定位 / [`RFC-0002`](../RFC-0002-domain-model.md) 域模型 / [`RFC-0003`](../RFC-0003-workflow-policy.md) workflow 与 policy / [`SPEC-0001`](../SPEC-0001-events-v1.md) 事件 schema);本目录是操作视角,与实现冲突时以 RFC/SPEC 与代码为准并回报。设计历史(V2-PLAN、迭代记录)不在本目录。
|
|
4
6
|
|
|
5
7
|
## 第一次使用
|
|
6
8
|
|
|
7
9
|
| 文档 | 一句话 |
|
|
8
10
|
|---|---|
|
|
9
|
-
| [怎么和会话说话](00-how-to-talk.md) | **给用户看的**:项目从未开始到换期,每个阶段你说什么、会话做什么、你得到什么 |
|
|
11
|
+
| [怎么和会话说话](00-how-to-talk.md) · [English](00-how-to-talk.en.md) | **给用户看的**:项目从未开始到换期,每个阶段你说什么、会话做什么、你得到什么 |
|
|
10
12
|
| [快速开始](01-quickstart.md) · [English](01-quickstart.en.md) | 第一个 Run:装 `@latest` → 工作项与 run 配置 → accept → doctor → start → 看证据拍板;含失败分支 |
|
|
11
13
|
| [`templates/v2/`](../../../templates/v2/AGENTS.md) | 项目装载入口(AGENTS / CLAUDE / 指挥台 / BUILDBEAT 标记)、run 配置样板、信封 prompt 与 worker 包装 |
|
|
12
14
|
|
|
@@ -25,10 +27,10 @@
|
|
|
25
27
|
|
|
26
28
|
| 文档 | 一句话 |
|
|
27
29
|
|---|---|
|
|
28
|
-
| [Workflow 编写指南](02-workflow-guide.md) | 步序、显式转换、readonly、terminal、预算、cache、requires |
|
|
29
|
-
| [Policy 指南](03-policy-guide.md) | 四类 Policy、8 算子、三值逻辑、强制等级 |
|
|
30
|
-
| [Adapter 指南](04-adapter-guide.md) | Shell/Mock、env 白名单与 `env:` 注入、接任意 CLI Agent、实时输出 |
|
|
31
|
-
| [Worker 合同](05-worker-contract.md) | 输入输出信封(`severity` + `summary`)、阻断语义、各角色纪律、包装脚本 |
|
|
32
|
-
| [安全与权限边界](09-security-boundaries.md) | 内核实际做到的 vs 不能由此推出的;无人值守三层前置条件 |
|
|
30
|
+
| [Workflow 编写指南](02-workflow-guide.md) · [English](02-workflow-guide.en.md) | 步序、显式转换、readonly、terminal、预算、cache、requires、并行 Run |
|
|
31
|
+
| [Policy 指南](03-policy-guide.md) · [English](03-policy-guide.en.md) | 四类 Policy、8 算子、三值逻辑、强制等级 |
|
|
32
|
+
| [Adapter 指南](04-adapter-guide.md) · [English](04-adapter-guide.en.md) | Shell/Mock、env 白名单与 `env:` 注入、接任意 CLI Agent、实时输出 |
|
|
33
|
+
| [Worker 合同](05-worker-contract.md) · [English](05-worker-contract.en.md) | 输入输出信封(`severity` + `summary`)、阻断语义、各角色纪律、包装脚本 |
|
|
34
|
+
| [安全与权限边界](09-security-boundaries.md) · [English](09-security-boundaries.en.md) | 内核实际做到的 vs 不能由此推出的;无人值守三层前置条件 |
|
|
33
35
|
|
|
34
36
|
observe v0(探测→分层响应→Intent 草稿→人分诊)在 [快速开始 §9](01-quickstart.md) 与 [Evidence 指南](06-evidence-guide.md) 中覆盖;schema 冻结见 [`RFC-0003 §8`](../RFC-0003-workflow-policy.md)。
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@haiyangbg/buildbeat",
|
|
3
|
-
"version": "3.2.
|
|
3
|
+
"version": "3.2.1",
|
|
4
4
|
"description": "BuildBeat: Git-based AI delivery across models, tools, sessions, and people, with context in project files, build-test-review-fix loops, and human decisions.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -26,11 +26,11 @@
|
|
|
26
26
|
"scripts": {
|
|
27
27
|
"check:docs": "bash tests/check-docs.sh",
|
|
28
28
|
"test": "node --test tests/*.test.js",
|
|
29
|
-
"test:
|
|
29
|
+
"test:envelope": "bash tests/envelope-worker.test.sh",
|
|
30
30
|
"test:plugin": "bash tests/plugin-marketplace.test.sh",
|
|
31
31
|
"test:pack-firstrun": "bash tests/pack-firstrun.test.sh",
|
|
32
32
|
"pack:check": "npm pack --dry-run",
|
|
33
|
-
"prepublishOnly": "npm test && npm run test:
|
|
33
|
+
"prepublishOnly": "npm test && npm run test:envelope && npm run test:plugin && npm run test:pack-firstrun && npm run check:docs && npm run pack:check"
|
|
34
34
|
},
|
|
35
35
|
"engines": {
|
|
36
36
|
"node": ">=20"
|
package/src/v2/cli/run.js
CHANGED
|
@@ -64,17 +64,17 @@ Usage:
|
|
|
64
64
|
buildbeat resume --config <run-config.yaml> [--run <RUN-ID>] [--adopt <sha> --by <name>] # --adopt: hand fix committed in the worktree; skip fix, resume at verify
|
|
65
65
|
buildbeat status --repo <path> --run <RUN-ID> [--stall-after <minutes>]
|
|
66
66
|
buildbeat inbox --repo <path>
|
|
67
|
-
buildbeat overview --repo <path> [--work <WORK-ID>] [--json
|
|
67
|
+
buildbeat overview --repo <path> [--work <WORK-ID>] [--json]
|
|
68
68
|
buildbeat approve --repo <path> --run <RUN-ID> --transition <t> [--by <name>] [--config <run-config.yaml>]
|
|
69
69
|
buildbeat reject --repo <path> --run <RUN-ID> [--transition <t>] [--reason <text>] [--by <name>]
|
|
70
70
|
buildbeat accept --repo <path> --work <WORK-ID> --artifact <plan|intent|spec> [--by <name>]
|
|
71
71
|
buildbeat doctor --config <run-config.yaml>
|
|
72
72
|
buildbeat events --repo <path> --run <RUN-ID>
|
|
73
73
|
buildbeat replay --repo <path> --run <RUN-ID>
|
|
74
|
-
buildbeat metrics --repo <path> [--json
|
|
74
|
+
buildbeat metrics --repo <path> [--json]
|
|
75
75
|
buildbeat stop --repo <path> --run <RUN-ID> --reason <text>
|
|
76
|
-
buildbeat gc --repo <path> [--apply
|
|
77
|
-
buildbeat watch --repo <path> --run <RUN-ID> [--stall-after <minutes>] [--interval <seconds>] [--once
|
|
76
|
+
buildbeat gc --repo <path> [--apply] [--force]
|
|
77
|
+
buildbeat watch --repo <path> --run <RUN-ID> [--stall-after <minutes>] [--interval <seconds>] [--once]
|
|
78
78
|
buildbeat observe run --config <observe.yaml>
|
|
79
79
|
buildbeat observe status --repo <path>
|
|
80
80
|
buildbeat observe triage --repo <path> --intent <ref> --action <fix_now|schedule|dismiss> [--by <name>] [--note <text>]
|
|
@@ -83,15 +83,33 @@ Usage:
|
|
|
83
83
|
buildbeat findings adjudicate --repo <path> --work <WORK-ID> --fingerprint <fp> --action <accept|dismiss> [--by <name>] [--note <text>]
|
|
84
84
|
`;
|
|
85
85
|
|
|
86
|
+
// Switches may stand alone (`--json`) or take an explicit true/false
|
|
87
|
+
// (`--json true`, the older spelling); every other flag needs a value.
|
|
88
|
+
const SWITCHES = new Set(["json", "apply", "force", "once"]);
|
|
89
|
+
|
|
86
90
|
function parseFlags(argv) {
|
|
87
91
|
const flags = {};
|
|
88
|
-
for (let index = 0; index < argv.length; index +=
|
|
92
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
89
93
|
const key = argv[index];
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
94
|
+
if (!key.startsWith("--") || key === "--") {
|
|
95
|
+
throw new Error(`unexpected argument: ${key}`);
|
|
96
|
+
}
|
|
97
|
+
const name = key.slice(2);
|
|
98
|
+
const next = argv[index + 1];
|
|
99
|
+
if (SWITCHES.has(name)) {
|
|
100
|
+
if (next === "true" || next === "false") {
|
|
101
|
+
flags[name] = next;
|
|
102
|
+
index += 1;
|
|
103
|
+
} else {
|
|
104
|
+
flags[name] = "true";
|
|
105
|
+
}
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
if (next === undefined || next.startsWith("--")) {
|
|
109
|
+
throw new Error(`${key} needs a value`);
|
|
93
110
|
}
|
|
94
|
-
flags[
|
|
111
|
+
flags[name] = next;
|
|
112
|
+
index += 1;
|
|
95
113
|
}
|
|
96
114
|
return flags;
|
|
97
115
|
}
|
|
@@ -1084,7 +1102,7 @@ function commandGc(flags) {
|
|
|
1084
1102
|
if (flags.apply !== "true") {
|
|
1085
1103
|
console.log(
|
|
1086
1104
|
actionable > 0
|
|
1087
|
-
? `plan only: ${actionable} action(s); rerun with --apply
|
|
1105
|
+
? `plan only: ${actionable} action(s); rerun with --apply to execute (branches whose candidate lives only there are always kept)`
|
|
1088
1106
|
: "nothing to collect",
|
|
1089
1107
|
);
|
|
1090
1108
|
return;
|