agent-merge-broker 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +352 -0
  3. package/dist/broker.d.ts +110 -0
  4. package/dist/broker.d.ts.map +1 -0
  5. package/dist/broker.js +1023 -0
  6. package/dist/broker.js.map +1 -0
  7. package/dist/cli.d.ts +3 -0
  8. package/dist/cli.d.ts.map +1 -0
  9. package/dist/cli.js +558 -0
  10. package/dist/cli.js.map +1 -0
  11. package/dist/config.d.ts +19 -0
  12. package/dist/config.d.ts.map +1 -0
  13. package/dist/config.js +312 -0
  14. package/dist/config.js.map +1 -0
  15. package/dist/errors.d.ts +16 -0
  16. package/dist/errors.d.ts.map +1 -0
  17. package/dist/errors.js +36 -0
  18. package/dist/errors.js.map +1 -0
  19. package/dist/git.d.ts +46 -0
  20. package/dist/git.d.ts.map +1 -0
  21. package/dist/git.js +195 -0
  22. package/dist/git.js.map +1 -0
  23. package/dist/hooks.d.ts +22 -0
  24. package/dist/hooks.d.ts.map +1 -0
  25. package/dist/hooks.js +110 -0
  26. package/dist/hooks.js.map +1 -0
  27. package/dist/index.d.ts +11 -0
  28. package/dist/index.d.ts.map +1 -0
  29. package/dist/index.js +10 -0
  30. package/dist/index.js.map +1 -0
  31. package/dist/patterns.d.ts +9 -0
  32. package/dist/patterns.d.ts.map +1 -0
  33. package/dist/patterns.js +66 -0
  34. package/dist/patterns.js.map +1 -0
  35. package/dist/process.d.ts +30 -0
  36. package/dist/process.d.ts.map +1 -0
  37. package/dist/process.js +91 -0
  38. package/dist/process.js.map +1 -0
  39. package/dist/provenance.d.ts +10 -0
  40. package/dist/provenance.d.ts.map +1 -0
  41. package/dist/provenance.js +36 -0
  42. package/dist/provenance.js.map +1 -0
  43. package/dist/publisher.d.ts +37 -0
  44. package/dist/publisher.d.ts.map +1 -0
  45. package/dist/publisher.js +144 -0
  46. package/dist/publisher.js.map +1 -0
  47. package/dist/scheduler.d.ts +6 -0
  48. package/dist/scheduler.d.ts.map +1 -0
  49. package/dist/scheduler.js +96 -0
  50. package/dist/scheduler.js.map +1 -0
  51. package/dist/store.d.ts +73 -0
  52. package/dist/store.d.ts.map +1 -0
  53. package/dist/store.js +323 -0
  54. package/dist/store.js.map +1 -0
  55. package/dist/types.d.ts +219 -0
  56. package/dist/types.d.ts.map +1 -0
  57. package/dist/types.js +3 -0
  58. package/dist/types.js.map +1 -0
  59. package/dist/validation.d.ts +13 -0
  60. package/dist/validation.d.ts.map +1 -0
  61. package/dist/validation.js +75 -0
  62. package/dist/validation.js.map +1 -0
  63. package/dist/verify.d.ts +40 -0
  64. package/dist/verify.d.ts.map +1 -0
  65. package/dist/verify.js +177 -0
  66. package/dist/verify.js.map +1 -0
  67. package/docs/ARCHITECTURE.md +144 -0
  68. package/docs/PROTOCOL.md +127 -0
  69. package/docs/RELEASING.md +23 -0
  70. package/docs/SECURITY.md +57 -0
  71. package/package.json +68 -0
  72. package/schemas/config.schema.json +149 -0
  73. package/schemas/provenance.schema.json +56 -0
  74. package/schemas/receipt.schema.json +39 -0
  75. package/templates/AGENTS.snippet.md +5 -0
@@ -0,0 +1,57 @@
1
+ # Security model
2
+
3
+ ## Trust boundary
4
+
5
+ Agent Merge Broker is intended for trusted repositories and trusted integration hosts. The checked-in configuration is executable policy: validator commands run through the host shell inside an integration worktree. Reviewing a configuration change is as important as reviewing a CI workflow change.
6
+
7
+ Do not run the broker automatically on untrusted fork configuration. A safe forge integration should load policy from the protected base branch, not from an incoming change.
8
+
9
+ ## Credentials
10
+
11
+ - Lease tokens contain 192 bits of randomness and only SHA-256 digests are persisted in broker state.
12
+ - By default the broker also keeps the token itself in `<state>/tokens/`, mode 0600, so callers do
13
+ not have to invent a credential store. This grants nothing new: anyone who can read that directory
14
+ can already edit broker state directly. It exists because the alternative in practice is a token
15
+ written into the working tree, where `git add` and validator commands can reach it. Claim with
16
+ `--no-store-token` to opt out, or `--token-file` to place it elsewhere.
17
+ - Tokens may also be supplied through `MERGE_BROKER_TOKEN` or a process-level secret channel.
18
+ - `MERGE_BROKER_TOKEN` is removed from the environment passed to validator commands. Configuration
19
+ is trusted to execute commands, but a validator has no need for a credential that can submit or
20
+ cancel work on a worker's behalf.
21
+ - Do not place tokens in commits, task titles, validator commands, or logs.
22
+ - Git and GitHub credentials remain owned by the host's normal credential helpers.
23
+ - Validator stdout and stderr are retained locally and may contain accidental secrets. Output is capped, not redacted.
24
+
25
+ Runtime state is stored inside Git's common directory and inherits its filesystem permissions. Anyone who can modify the repository's Git directory can modify broker state or audit records. The audit stream is append-only by convention, not cryptographically tamper-evident.
26
+
27
+ ## Git safety
28
+
29
+ The broker does not force-push, reset the user's base worktree, merge into the base branch, or delete source branches. It creates uniquely named integration branches and temporary worktrees under its own state directory.
30
+
31
+ Failed integration worktrees are removed by default. Enabling `keepFailedWorktrees` can retain source content, generated artifacts, and secrets written by validators; operators must clean these worktrees deliberately after diagnosis.
32
+
33
+ Commit receipts are immutable IDs, but their objects can disappear after aggressive source-repository garbage collection if no ref retains them. Integrate or otherwise retain submitted commits before pruning unreachable branches.
34
+
35
+ ## Command construction
36
+
37
+ Configuration is trusted. `{files}` and `{taskId}` placeholders are shell-quoted, and structured metadata is also supplied as environment variables. Prefer environment variables when composing complex commands.
38
+
39
+ Validators run under a fixed interpreter — `/bin/sh` by default, or `validation.shell` — and never under a login shell. Sourcing an operator's personal profile would make an integration decision depend on whose machine assembled the batch, and would let a compromised dotfile influence what the broker reports as validated. Quoting follows the configured shell; POSIX shells are the supported and tested surface.
40
+
41
+ Task and batch metadata must still be treated as untrusted display text by external dashboards. The bundled GitHub publisher constructs command arguments without a shell and sends PR content through stdin.
42
+
43
+ ## Enforcement boundaries
44
+
45
+ `install-hooks` is a local convenience, not a security control: a pre-push hook is client-side and
46
+ any worker can bypass it. It exists to make the intended path the easy one and to catch mistakes
47
+ early.
48
+
49
+ `verify-provenance` is the enforceable boundary, and only when the forge requires it on a protected
50
+ branch. It reads verification policy from the configuration committed on the base branch, because
51
+ the checked-out tree belongs to the change being judged. It proves that a head is an unaltered
52
+ broker batch; it does not prove that the batch's contents are good, which is what the repository's
53
+ own validation is for.
54
+
55
+ ## Reporting vulnerabilities
56
+
57
+ Before a public security address is established, report vulnerabilities privately to the repository owner. Do not open a public issue containing exploitable details or credentials.
package/package.json ADDED
@@ -0,0 +1,68 @@
1
+ {
2
+ "name": "agent-merge-broker",
3
+ "version": "0.3.0",
4
+ "description": "A transaction coordinator for parallel code-producing agents and humans.",
5
+ "type": "module",
6
+ "bin": {
7
+ "merge-broker": "dist/cli.js",
8
+ "amb": "dist/cli.js"
9
+ },
10
+ "main": "./dist/index.js",
11
+ "types": "./dist/index.d.ts",
12
+ "exports": {
13
+ ".": {
14
+ "types": "./dist/index.d.ts",
15
+ "import": "./dist/index.js"
16
+ }
17
+ },
18
+ "files": [
19
+ "dist",
20
+ "!dist/**/*.test.*",
21
+ "schemas",
22
+ "templates",
23
+ "docs",
24
+ "README.md",
25
+ "LICENSE"
26
+ ],
27
+ "scripts": {
28
+ "build": "tsc -p tsconfig.json",
29
+ "dev": "tsx src/cli.ts",
30
+ "lint": "tsc -p tsconfig.json --noEmit",
31
+ "test": "node --test --test-reporter=spec dist/*.test.js",
32
+ "example": "sh examples/two-agents/run.sh",
33
+ "test:source": "tsx --test src/*.test.ts",
34
+ "verify": "npm run lint && npm run build && npm test",
35
+ "prepublishOnly": "npm run verify"
36
+ },
37
+ "engines": {
38
+ "node": ">=20.12"
39
+ },
40
+ "keywords": [
41
+ "ai-agents",
42
+ "git",
43
+ "merge-queue",
44
+ "worktree",
45
+ "ci",
46
+ "developer-tools"
47
+ ],
48
+ "author": "PowerHouse Exteriors",
49
+ "license": "Apache-2.0",
50
+ "repository": {
51
+ "type": "git",
52
+ "url": "https://github.com/WeSpitfire/agent-merge-broker.git"
53
+ },
54
+ "bugs": {
55
+ "url": "https://github.com/WeSpitfire/agent-merge-broker/issues"
56
+ },
57
+ "homepage": "https://github.com/WeSpitfire/agent-merge-broker#readme",
58
+ "dependencies": {
59
+ "commander": "^13.1.0",
60
+ "picomatch": "^4.0.2"
61
+ },
62
+ "devDependencies": {
63
+ "@types/node": "^22.15.0",
64
+ "@types/picomatch": "^4.0.2",
65
+ "tsx": "^4.19.3",
66
+ "typescript": "^5.8.3"
67
+ }
68
+ }
@@ -0,0 +1,149 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://github.com/WeSpitfire/agent-merge-broker/raw/main/schemas/config.schema.json",
4
+ "title": "Agent Merge Broker configuration",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "required": [
8
+ "version",
9
+ "baseBranch",
10
+ "baseRef",
11
+ "remote",
12
+ "stateDirectory",
13
+ "leases",
14
+ "policies",
15
+ "scheduling",
16
+ "integration",
17
+ "validation",
18
+ "publish"
19
+ ],
20
+ "properties": {
21
+ "version": { "const": 1 },
22
+ "baseBranch": { "type": "string", "minLength": 1 },
23
+ "baseRef": {
24
+ "type": "string",
25
+ "minLength": 1,
26
+ "description": "Git revision used for integration, such as origin/main; separate from the forge target branch."
27
+ },
28
+ "remote": { "type": "string", "minLength": 1 },
29
+ "stateDirectory": {
30
+ "type": "string",
31
+ "minLength": 1,
32
+ "description": "Safe relative directory beneath Git's common directory; Git-reserved top-level names are rejected."
33
+ },
34
+ "leases": {
35
+ "type": "object",
36
+ "additionalProperties": false,
37
+ "required": ["ttlSeconds", "lockTimeoutSeconds", "serializedPatterns"],
38
+ "properties": {
39
+ "ttlSeconds": { "type": "integer", "minimum": 1 },
40
+ "lockTimeoutSeconds": { "type": "integer", "minimum": 1 },
41
+ "serializedPatterns": { "type": "array", "items": { "type": "string", "minLength": 1 }, "uniqueItems": true }
42
+ }
43
+ },
44
+ "policies": {
45
+ "type": "object",
46
+ "additionalProperties": false,
47
+ "required": ["unexpectedPaths", "requireCleanWorktree", "requireDependencies"],
48
+ "properties": {
49
+ "unexpectedPaths": { "enum": ["error", "warn", "allow"] },
50
+ "requireCleanWorktree": { "type": "boolean" },
51
+ "requireDependencies": { "type": "boolean" }
52
+ }
53
+ },
54
+ "scheduling": {
55
+ "type": "object",
56
+ "additionalProperties": false,
57
+ "required": ["maxTasks", "maxCommits", "maxWaitSeconds", "allowPathOverlap"],
58
+ "properties": {
59
+ "maxTasks": { "type": "integer", "minimum": 1 },
60
+ "maxCommits": { "type": "integer", "minimum": 1 },
61
+ "maxWaitSeconds": { "type": "integer", "minimum": 1 },
62
+ "allowPathOverlap": { "type": "boolean" }
63
+ }
64
+ },
65
+ "integration": {
66
+ "type": "object",
67
+ "additionalProperties": false,
68
+ "required": ["branchPrefix", "history", "keepFailedWorktrees"],
69
+ "properties": {
70
+ "branchPrefix": { "type": "string", "minLength": 1 },
71
+ "history": { "enum": ["preserve", "squash"] },
72
+ "keepFailedWorktrees": { "type": "boolean" },
73
+ "refreshBase": {
74
+ "type": "boolean",
75
+ "description": "Fetch the remote base branch before cutting a batch so the branch is not born behind it.",
76
+ "default": true
77
+ },
78
+ "maxAttempts": {
79
+ "type": "integer",
80
+ "minimum": 1,
81
+ "description": "How many times a task may be automatically returned to the queue before it is left failed.",
82
+ "default": 3
83
+ },
84
+ "provenance": {
85
+ "type": "object",
86
+ "additionalProperties": false,
87
+ "required": ["enabled", "directory"],
88
+ "properties": {
89
+ "enabled": { "type": "boolean" },
90
+ "directory": { "type": "string", "minLength": 1 }
91
+ }
92
+ }
93
+ }
94
+ },
95
+ "validation": {
96
+ "type": "object",
97
+ "additionalProperties": false,
98
+ "required": ["focused", "authoritative"],
99
+ "properties": {
100
+ "shell": {
101
+ "type": "string",
102
+ "minLength": 1,
103
+ "description": "Interpreter used to run validator commands. Defaults to /bin/sh, or ComSpec on Windows. Never a login shell: validation must not depend on personal dotfiles."
104
+ },
105
+ "focused": { "type": "array", "items": { "$ref": "#/$defs/validator" } },
106
+ "authoritative": { "type": "array", "items": { "$ref": "#/$defs/validator" } }
107
+ }
108
+ },
109
+ "publish": {
110
+ "type": "object",
111
+ "additionalProperties": false,
112
+ "required": ["mode", "draft", "labels", "titleTemplate"],
113
+ "properties": {
114
+ "mode": { "enum": ["none", "branch", "pull-request"] },
115
+ "draft": { "type": "boolean" },
116
+ "autoMerge": {
117
+ "type": "boolean",
118
+ "description": "Ask GitHub to merge the pull request once required checks pass. Requires mode pull-request and draft false.",
119
+ "default": false
120
+ },
121
+ "mergeMethod": { "enum": ["squash", "merge", "rebase"], "default": "squash" },
122
+ "labels": { "type": "array", "items": { "type": "string", "minLength": 1 }, "uniqueItems": true },
123
+ "titleTemplate": { "type": "string", "minLength": 1 }
124
+ },
125
+ "allOf": [
126
+ {
127
+ "if": { "properties": { "autoMerge": { "const": true } }, "required": ["autoMerge"] },
128
+ "then": {
129
+ "properties": { "draft": { "const": false }, "mode": { "const": "pull-request" } }
130
+ }
131
+ }
132
+ ]
133
+ }
134
+ },
135
+ "$defs": {
136
+ "validator": {
137
+ "type": "object",
138
+ "additionalProperties": false,
139
+ "required": ["name", "command"],
140
+ "properties": {
141
+ "name": { "type": "string", "minLength": 1 },
142
+ "command": { "type": "string", "minLength": 1 },
143
+ "paths": { "type": "array", "items": { "type": "string", "minLength": 1 }, "uniqueItems": true },
144
+ "timeoutSeconds": { "type": "integer", "minimum": 1 },
145
+ "env": { "type": "object", "additionalProperties": { "type": "string" } }
146
+ }
147
+ }
148
+ }
149
+ }
@@ -0,0 +1,56 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://github.com/WeSpitfire/agent-merge-broker/raw/main/schemas/provenance.schema.json",
4
+ "title": "Agent Merge Broker batch provenance",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "required": ["version", "generator", "batchId", "baseBranch", "baseSha", "integratedHeadSha", "taskIds", "tasks", "validations", "createdAt"],
8
+ "properties": {
9
+ "version": { "const": 1 },
10
+ "generator": { "const": "agent-merge-broker" },
11
+ "batchId": { "type": "string", "minLength": 1 },
12
+ "baseBranch": { "type": "string", "minLength": 1 },
13
+ "history": {
14
+ "enum": ["preserve", "squash"],
15
+ "description": "How the batch was assembled. Squashing drops the cherry-pick trail, so a verifier cannot trace submitted commits in the integrated history. Absent on manifests written before this field existed, which always preserved history.",
16
+ "default": "preserve"
17
+ },
18
+ "baseSha": { "$ref": "#/$defs/sha" },
19
+ "integratedHeadSha": { "$ref": "#/$defs/sha" },
20
+ "taskIds": { "type": "array", "minItems": 1, "items": { "type": "string", "minLength": 1 }, "uniqueItems": true },
21
+ "tasks": {
22
+ "type": "array",
23
+ "minItems": 1,
24
+ "items": {
25
+ "type": "object",
26
+ "additionalProperties": false,
27
+ "required": ["id", "commits", "actualPaths", "dependsOn"],
28
+ "properties": {
29
+ "id": { "type": "string", "minLength": 1 },
30
+ "commits": { "type": "array", "minItems": 1, "items": { "$ref": "#/$defs/sha" } },
31
+ "actualPaths": { "type": "array", "items": { "type": "string", "minLength": 1 } },
32
+ "dependsOn": { "type": "array", "items": { "type": "string", "minLength": 1 }, "uniqueItems": true }
33
+ }
34
+ }
35
+ },
36
+ "validations": {
37
+ "type": "array",
38
+ "items": {
39
+ "type": "object",
40
+ "additionalProperties": false,
41
+ "required": ["name", "scope", "exitCode", "durationMs"],
42
+ "properties": {
43
+ "name": { "type": "string", "minLength": 1 },
44
+ "scope": { "enum": ["focused", "authoritative"] },
45
+ "taskId": { "type": "string", "minLength": 1 },
46
+ "exitCode": { "const": 0 },
47
+ "durationMs": { "type": "integer", "minimum": 0 }
48
+ }
49
+ }
50
+ },
51
+ "createdAt": { "type": "string", "format": "date-time" }
52
+ },
53
+ "$defs": {
54
+ "sha": { "type": "string", "pattern": "^[0-9a-f]{40,64}$" }
55
+ }
56
+ }
@@ -0,0 +1,39 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://github.com/WeSpitfire/agent-merge-broker/raw/main/schemas/receipt.schema.json",
4
+ "title": "Agent Merge Broker commit receipt",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "required": [
8
+ "version",
9
+ "taskId",
10
+ "baseSha",
11
+ "commits",
12
+ "expectedPaths",
13
+ "actualPaths",
14
+ "dependsOn",
15
+ "submittedAt",
16
+ "warnings"
17
+ ],
18
+ "properties": {
19
+ "version": { "const": 1 },
20
+ "taskId": {
21
+ "type": "string",
22
+ "minLength": 1,
23
+ "maxLength": 128,
24
+ "pattern": "^[a-zA-Z0-9][a-zA-Z0-9._:@/-]{0,127}$"
25
+ },
26
+ "agent": { "type": "string", "minLength": 1 },
27
+ "baseSha": { "$ref": "#/$defs/sha" },
28
+ "commits": { "type": "array", "minItems": 1, "items": { "$ref": "#/$defs/sha" } },
29
+ "expectedPaths": { "type": "array", "items": { "type": "string", "minLength": 1 } },
30
+ "actualPaths": { "type": "array", "items": { "type": "string", "minLength": 1 } },
31
+ "dependsOn": { "type": "array", "items": { "type": "string", "minLength": 1 }, "uniqueItems": true },
32
+ "worktree": { "type": "string", "minLength": 1 },
33
+ "submittedAt": { "type": "string", "format": "date-time" },
34
+ "warnings": { "type": "array", "items": { "type": "string" } }
35
+ },
36
+ "$defs": {
37
+ "sha": { "type": "string", "pattern": "^[0-9a-f]{40,64}$" }
38
+ }
39
+ }
@@ -0,0 +1,5 @@
1
+ ## Agent Merge Broker
2
+
3
+ Before editing, every worker must claim a task with the narrowest accurate path scope. Keep the lease alive during long work. Produce focused commits and submit their immutable SHAs to the broker. Workers must not merge, rebase, push, open pull requests, or modify integration branches.
4
+
5
+ Read `.merge-broker/agent-instructions.md` for the exact command protocol. The integration owner runs `merge-broker plan`, `merge-broker integrate`, and publication commands.