forge-workflow 0.1.0-beta.4 → 0.1.0-beta.6

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 (196) hide show
  1. package/AGENTS.md +18 -7
  2. package/CHANGELOG.md +79 -1
  3. package/CLAUDE.md +0 -12
  4. package/CODING_STANDARDS.md +72 -0
  5. package/README.md +6 -2
  6. package/bin/forge-cmd.js +20 -0
  7. package/bin/forge.js +28 -375
  8. package/docs/INDEX.md +1 -1
  9. package/docs/guides/BEADS_GITHUB_SYNC.md +2 -31
  10. package/docs/guides/MIGRATION.md +4 -4
  11. package/docs/guides/SETUP.md +16 -16
  12. package/docs/reference/COMMANDS.md +8 -5
  13. package/docs/reference/FORGE_KERNEL_STORAGE_MODEL.md +4 -0
  14. package/docs/reference/INSIGHTS_RECAP.md +9 -20
  15. package/docs/reference/INSTALL.md +4 -0
  16. package/docs/reference/LEGACY_CLAIM_REPAIR.md +112 -0
  17. package/docs/reference/RELEASE.md +5 -3
  18. package/docs/reference/TOOLCHAIN.md +8 -0
  19. package/docs/reference/github-accounts.md +134 -0
  20. package/docs/reference/protected-state-surfaces.md +4 -4
  21. package/docs/reference/shepherd.md +114 -35
  22. package/lefthook.yml +12 -0
  23. package/lib/activation/ensure-forge-home.js +33 -15
  24. package/lib/adapters/pr-state-adapter.js +359 -144
  25. package/lib/audit-evidence.js +71 -110
  26. package/lib/base-remote.js +138 -0
  27. package/lib/beta5-compatibility-evidence.js +1093 -0
  28. package/lib/bun-lockfile-proof.js +413 -0
  29. package/lib/bun-workflow-pins.js +461 -0
  30. package/lib/capabilities/index.js +9 -0
  31. package/lib/capabilities/model.js +141 -0
  32. package/lib/capabilities/probes.js +347 -0
  33. package/lib/capped-jsonl-log.js +236 -0
  34. package/lib/codex-skills.js +2 -2
  35. package/lib/commands/_manifest.js +1 -0
  36. package/lib/commands/_registry.js +50 -20
  37. package/lib/commands/clean.js +252 -32
  38. package/lib/commands/dev.js +4 -33
  39. package/lib/commands/doctor.js +37 -6
  40. package/lib/commands/gate.js +197 -27
  41. package/lib/commands/github.js +215 -0
  42. package/lib/commands/hooks.js +276 -30
  43. package/lib/commands/insights.js +8 -3
  44. package/lib/commands/memory.js +66 -2
  45. package/lib/commands/merge.js +1265 -58
  46. package/lib/commands/plan.js +33 -2
  47. package/lib/commands/pr.js +3 -1
  48. package/lib/commands/preflight.js +21 -4
  49. package/lib/commands/prime.js +21 -8
  50. package/lib/commands/push.js +146 -54
  51. package/lib/commands/recall.js +127 -49
  52. package/lib/commands/recap.js +6 -1
  53. package/lib/commands/release.js +39 -3
  54. package/lib/commands/remember.js +28 -4
  55. package/lib/commands/serve.js +26 -9
  56. package/lib/commands/setup.js +323 -98
  57. package/lib/commands/shepherd.js +591 -73
  58. package/lib/commands/ship.js +36 -91
  59. package/lib/commands/skill.js +127 -11
  60. package/lib/commands/status.js +17 -1
  61. package/lib/commands/team.js +47 -8
  62. package/lib/commands/test.js +187 -38
  63. package/lib/commands/validate.js +65 -21
  64. package/lib/commands/worktree.js +359 -45
  65. package/lib/core/runtime-graph.js +1 -1
  66. package/lib/doc-assertions.js +297 -0
  67. package/lib/existing-tdd-gate.js +253 -0
  68. package/lib/fixtures/beta5-corpus/v1/README.md +9 -0
  69. package/lib/fixtures/beta5-corpus/v1/contract/command-contract.json +26 -0
  70. package/lib/fixtures/beta5-corpus/v1/contract/package-contract.json +13 -0
  71. package/lib/fixtures/beta5-corpus/v1/contract/workflow-stage-matrix.json +8 -0
  72. package/lib/fixtures/beta5-corpus/v1/manifest.json +25 -0
  73. package/lib/fixtures/beta5-corpus/v1/state/comments.jsonl +1 -0
  74. package/lib/fixtures/beta5-corpus/v1/state/config.yaml +6 -0
  75. package/lib/fixtures/beta5-corpus/v1/state/dependencies.jsonl +1 -0
  76. package/lib/fixtures/beta5-corpus/v1/state/issues.jsonl +2 -0
  77. package/lib/fixtures/beta5-corpus/v1/state/kernel.sql +20 -0
  78. package/lib/forge-context.js +1 -4
  79. package/lib/forge-issues.js +134 -32
  80. package/lib/gate-events.js +98 -10
  81. package/lib/git-defaults.js +56 -0
  82. package/lib/github-context.js +308 -0
  83. package/lib/global-flags.js +1 -0
  84. package/lib/harness-capability-matrix.js +3 -3
  85. package/lib/hook-renderer.js +122 -5
  86. package/lib/insights.js +96 -80
  87. package/lib/issue-render.js +19 -0
  88. package/lib/kernel/backing-issue.js +14 -2
  89. package/lib/kernel/broker.js +739 -31
  90. package/lib/kernel/claim-reconciler.js +238 -0
  91. package/lib/kernel/cli-broker-factory.js +12 -1
  92. package/lib/kernel/close-on-merge.js +154 -0
  93. package/lib/kernel/fs-class.js +42 -25
  94. package/lib/kernel/lease-enforcer.js +9 -4
  95. package/lib/kernel/legacy-claim-repair.js +442 -0
  96. package/lib/kernel/live-claim-projection.js +26 -0
  97. package/lib/kernel/migrations.js +118 -3
  98. package/lib/kernel/readiness-model.js +184 -12
  99. package/lib/kernel/schema.js +49 -1
  100. package/lib/kernel/sqlite-driver.js +3435 -172
  101. package/lib/kernel/taxonomy-validator.js +4 -1
  102. package/lib/kernel/windows-private-acl.js +239 -0
  103. package/lib/lefthook-wiring.js +21 -1
  104. package/lib/memory/hygiene.js +191 -0
  105. package/lib/memory/router.js +110 -28
  106. package/lib/memory/usage-evidence.js +4 -0
  107. package/lib/memory-digest.js +106 -15
  108. package/lib/memory-recall-events.js +145 -0
  109. package/lib/memory-recall.js +71 -10
  110. package/lib/merge-rules.js +143 -21
  111. package/lib/npm-publish-workflow.js +465 -0
  112. package/lib/orientation.js +68 -43
  113. package/lib/package-root.js +2 -0
  114. package/lib/plugin-catalog.js +14 -4
  115. package/lib/pr-bundle.js +5 -6
  116. package/lib/pr-monitor/auto-actions.js +169 -28
  117. package/lib/pr-monitor/differ.js +110 -4
  118. package/lib/pr-monitor/events.js +0 -0
  119. package/lib/pr-monitor/flow-monitor.js +1424 -0
  120. package/lib/pr-monitor/gather.js +251 -44
  121. package/lib/pr-monitor/journal.js +18 -39
  122. package/lib/pr-monitor/monitor.js +117 -10
  123. package/lib/pr-monitor/process-identity.js +117 -0
  124. package/lib/pr-monitor/reconcile-executor.js +1129 -470
  125. package/lib/pr-monitor/reconcile.js +0 -0
  126. package/lib/pr-monitor/render-summary.js +293 -0
  127. package/lib/pr-monitor/review-preflight.js +269 -0
  128. package/lib/pr-monitor/shepherd-lease.js +38 -20
  129. package/lib/pr-monitor/verdict.js +438 -0
  130. package/lib/pr-monitor/watch-lifecycle.js +145 -27
  131. package/lib/pr-monitor/watch-owner.js +1414 -0
  132. package/lib/pr-monitor/watch.js +129 -58
  133. package/lib/pr-pull.js +33 -14
  134. package/lib/pr-shepherd.js +51 -11
  135. package/lib/preflight/gates.js +65 -18
  136. package/lib/preflight/runner.js +5 -0
  137. package/lib/project-memory.js +178 -4
  138. package/lib/protected-state-authority.js +1100 -0
  139. package/lib/protected-state-surfaces.js +243 -45
  140. package/lib/release-readiness.js +53 -7
  141. package/lib/review-adapter.js +65 -0
  142. package/lib/shell-utils.js +1 -1
  143. package/lib/skills-sync.js +71 -35
  144. package/lib/smart-merge.js +28 -4
  145. package/lib/symlink-utils.js +74 -26
  146. package/lib/upgrade-safety.js +39 -0
  147. package/lib/using-forge.js +19 -6
  148. package/lib/validation/risk-manifest.js +339 -0
  149. package/lib/workflow/enforce-stage.js +44 -0
  150. package/lib/workflow/plan-authority.js +225 -0
  151. package/package.json +12 -9
  152. package/scripts/commitlint.js +13 -15
  153. package/scripts/doc-asserting-tests.js +158 -0
  154. package/scripts/generate-risk-manifest.js +91 -0
  155. package/scripts/github-context-bridge.sh +10 -0
  156. package/scripts/legacy-claim-repair.js +145 -0
  157. package/scripts/lib/behavioral-eval-runner.js +310 -0
  158. package/scripts/lib/behavioral-eval-runtime.js +457 -0
  159. package/scripts/lib/eval-evidence.js +328 -0
  160. package/scripts/lib/eval-runner.js +81 -41
  161. package/scripts/lib/immutable-eval-corpus.js +309 -0
  162. package/scripts/lib/promotion-evidence-loader.js +94 -0
  163. package/scripts/lib/promotion-scorecard.js +314 -0
  164. package/scripts/npm-release-receipt.js +134 -0
  165. package/scripts/process-tree.js +773 -0
  166. package/scripts/protected-state-check.js +479 -31
  167. package/scripts/run-command-eval.js +29 -1
  168. package/scripts/sync-agent-skills.js +333 -34
  169. package/scripts/sync-d20-audit.js +172 -0
  170. package/scripts/test-full-suite.js +935 -37
  171. package/scripts/test-profile.js +13 -3
  172. package/scripts/test.js +271 -57
  173. package/skills/coverage.json +1 -0
  174. package/skills/review/SKILL.md +6 -11
  175. package/skills/review/evals/scorecard.json +4 -4
  176. package/skills/rollback/SKILL.md +4 -11
  177. package/skills/rollback/evals/scorecard.json +3 -3
  178. package/skills/setup/SKILL.md +18 -0
  179. package/skills/setup/evals/scorecard.json +3 -3
  180. package/skills/shepherd/SKILL.md +39 -16
  181. package/skills/shepherd/evals/scorecard.json +4 -4
  182. package/skills/ship/SKILL.md +4 -12
  183. package/skills/ship/evals/scorecard.json +3 -3
  184. package/skills/validate/SKILL.md +3 -0
  185. package/skills/validate/evals/scorecard.json +1 -1
  186. package/skills/worktree/SKILL.md +6 -1
  187. package/skills/worktree/evals/scorecard.json +2 -2
  188. package/lib/beads-setup.js +0 -538
  189. package/lib/beads-sync-scaffold.js +0 -189
  190. package/lib/pat-setup.js +0 -207
  191. package/lib/pr-monitor/render-sticky.js +0 -206
  192. package/lib/pr-monitor/upsert-sticky.js +0 -169
  193. package/scripts/beads-context.sh +0 -577
  194. package/scripts/beads-migrate-to-dolt.sh +0 -7
  195. package/scripts/beads-upgrade-smoke.sh +0 -284
  196. package/scripts/lib/beads-migrate-to-dolt.mjs +0 -503
@@ -0,0 +1,1100 @@
1
+ 'use strict';
2
+
3
+ // The ignored JSONL audit log is visibility-only. Workflow write authority is
4
+ // an append-only Kernel capability scoped to one worktree and consumed once.
5
+
6
+ const { randomUUID } = require('node:crypto');
7
+ const fs = require('node:fs');
8
+ const path = require('node:path');
9
+ const { resolveOwnedKernel, closeIfOwned } = require('./kernel/owned-kernel');
10
+ const { hashProtectedContent, normalizeRepoPath } = require('./protected-state-surfaces');
11
+ const {
12
+ BUN_WORKFLOW_SPECS,
13
+ deriveBunWorkflowUpdate,
14
+ readIndexedPackageManifest,
15
+ readIndexedWorkflow,
16
+ readPinnedBunVersion,
17
+ readSourcePackageManifest,
18
+ readSourceWorkflow,
19
+ renderBunWorkflowPin,
20
+ resolveBunVersionTarget,
21
+ resolveCurrentHead,
22
+ } = require('./bun-workflow-pins');
23
+
24
+ const PROTECTED_STATE_ENTITY_TYPE = 'protected_state';
25
+ const PROTECTED_STATE_AUTHORIZATION_ISSUED = 'protected_state.authorization.issued';
26
+ const PROTECTED_STATE_WRITE_COMPLETED = 'protected_state.write.completed';
27
+ const PROTECTED_STATE_AUTHORIZATION_CONSUMED = 'protected_state.authorization.consumed';
28
+ const PROTECTED_STATE_AUTHORIZATION_VERSION = 1;
29
+ const PROTECTED_STATE_AUTHORIZATION_ORIGIN = 'cli';
30
+ const NPM_WORKFLOW_SOURCE_COMMAND = 'forge release generate-npm-workflow';
31
+ const BUN_WORKFLOW_SOURCE_COMMAND = 'forge release update-bun-pins';
32
+ const BUN_WORKFLOW_PATHS = new Set(BUN_WORKFLOW_SPECS.map(spec => spec.path));
33
+ const SKILL_MIRROR_SOURCE_COMMAND = 'scripts/sync-agent-skills.js';
34
+ const CLAUDE_SETUP_SOURCE_COMMAND = 'forge setup';
35
+
36
+ function isValidGitObjectId(value) {
37
+ return typeof value === 'string' && /^(?:[0-9a-f]{40}|[0-9a-f]{64})$/.test(value);
38
+ }
39
+
40
+ function owningWriter(pathname, surface) {
41
+ if (surface === 'workflows' && BUN_WORKFLOW_PATHS.has(pathname)) {
42
+ return {
43
+ sourceCommand: BUN_WORKFLOW_SOURCE_COMMAND,
44
+ issueOperation: 'update_bun_workflow_pin',
45
+ completionOperation: 'update_bun_workflow_pin_completed',
46
+ };
47
+ }
48
+ if (surface === 'workflows' && pathname === '.github/workflows/npm-publish.yml') {
49
+ return {
50
+ sourceCommand: NPM_WORKFLOW_SOURCE_COMMAND,
51
+ issueOperation: 'generate_npm_workflow',
52
+ completionOperation: 'generate_npm_workflow_completed',
53
+ };
54
+ }
55
+ if (surface === 'generated_harness' && /^\.agents\/skills\/[^/]+\/.+/.test(pathname)) {
56
+ return {
57
+ sourceCommand: SKILL_MIRROR_SOURCE_COMMAND,
58
+ issueOperation: 'sync_agent_skill',
59
+ completionOperation: 'sync_agent_skill_completed',
60
+ };
61
+ }
62
+ if (surface === 'generated_harness' && pathname === 'CLAUDE.md') {
63
+ return {
64
+ sourceCommand: CLAUDE_SETUP_SOURCE_COMMAND,
65
+ issueOperation: 'generate_claude_import',
66
+ completionOperation: 'generate_claude_import_completed',
67
+ };
68
+ }
69
+ return null;
70
+ }
71
+
72
+ function resolveWorktreeScope(projectRoot, deps = {}) {
73
+ const resolved = path.resolve(projectRoot);
74
+ const realpath = deps.realpathSync || fs.realpathSync.native;
75
+ let canonical;
76
+ try {
77
+ canonical = realpath(resolved);
78
+ } catch {
79
+ canonical = resolved;
80
+ }
81
+ if (process.platform === 'win32') canonical = canonical.toLowerCase();
82
+ return hashProtectedContent(canonical);
83
+ }
84
+
85
+ function authorizationEntityId(worktreeScope, filePath) {
86
+ return `${worktreeScope}:${normalizeRepoPath(filePath)}`;
87
+ }
88
+
89
+ function parsePayload(row) {
90
+ try {
91
+ return row?.payload_json ? JSON.parse(row.payload_json) : (row?.payload || {});
92
+ } catch {
93
+ return {};
94
+ }
95
+ }
96
+
97
+ function parseAuthorizationEvent(row) {
98
+ const payload = parsePayload(row);
99
+ return {
100
+ eventType: row?.event_type,
101
+ actor: row?.actor,
102
+ payloadActor: payload.actor,
103
+ origin: row?.origin,
104
+ entityId: row?.entity_id,
105
+ createdAt: row?.created_at,
106
+ version: payload.version,
107
+ capabilityId: payload.capabilityId,
108
+ path: normalizeRepoPath(payload.path),
109
+ surface: payload.surface,
110
+ contentHash: payload.contentHash,
111
+ operation: payload.operation,
112
+ viaForgeApi: payload.viaForgeApi === true,
113
+ sourceCommand: payload.sourceCommand,
114
+ sourceHead: payload.sourceHead,
115
+ worktreeScope: payload.worktreeScope,
116
+ writeIntent: payload.writeIntent,
117
+ targetBunVersion: payload.targetBunVersion,
118
+ };
119
+ }
120
+
121
+ function blockedDecision(request, reason) {
122
+ return {
123
+ allowed: false,
124
+ decision: 'blocked',
125
+ actor: request.actor,
126
+ path: normalizeRepoPath(request.path),
127
+ operation: request.operation || 'staged_edit',
128
+ requiredSurface: request.surface,
129
+ declaredSurface: request.surface,
130
+ contentHash: hashProtectedContent(request.content),
131
+ sourceHead: request.sourceHead,
132
+ reason,
133
+ repairHint: 'Regenerate the protected file through its owning Forge command, then stage that exact output.',
134
+ };
135
+ }
136
+
137
+ function evaluateAuthorization(request, rows = []) {
138
+ const expected = {
139
+ actor: request.actor,
140
+ path: normalizeRepoPath(request.path),
141
+ surface: request.surface,
142
+ contentHash: hashProtectedContent(request.content),
143
+ worktreeScope: request.worktreeScope,
144
+ sourceHead: request.sourceHead,
145
+ writeIntent: request.operation === 'staged_delete' ? 'delete' : 'update',
146
+ };
147
+ const issued = rows
148
+ .filter(row => row?.event_type === PROTECTED_STATE_AUTHORIZATION_ISSUED)
149
+ .map(parseAuthorizationEvent);
150
+ const consumedCapabilities = new Set(rows
151
+ .filter(row => row?.event_type === PROTECTED_STATE_AUTHORIZATION_CONSUMED)
152
+ .map(row => parseAuthorizationEvent(row).capabilityId)
153
+ .filter(Boolean));
154
+ const active = issued.filter(event => !consumedCapabilities.has(event.capabilityId));
155
+
156
+ if (issued.length === 0) {
157
+ return blockedDecision(request, 'No Forge-owned authorization exists for this protected path and content-bound write.');
158
+ }
159
+ if (active.length === 0) {
160
+ return blockedDecision(request, 'The latest Forge-owned authorization was already consumed; stale and same-content replays are denied.');
161
+ }
162
+ if (active.length !== 1) {
163
+ return blockedDecision(request, 'Protected-state authority is ambiguous because multiple unconsumed authorizations exist; failing closed.');
164
+ }
165
+ const latest = active[0];
166
+ const writer = owningWriter(latest.path, latest.surface);
167
+
168
+ const bunPinWriter = [BUN_WORKFLOW_SOURCE_COMMAND, NPM_WORKFLOW_SOURCE_COMMAND].includes(writer?.sourceCommand);
169
+ const structurallyValid =
170
+ writer !== null &&
171
+ latest.version === PROTECTED_STATE_AUTHORIZATION_VERSION &&
172
+ typeof latest.capabilityId === 'string' && latest.capabilityId.length > 0 &&
173
+ latest.origin === PROTECTED_STATE_AUTHORIZATION_ORIGIN &&
174
+ latest.actor === latest.payloadActor &&
175
+ latest.worktreeScope === expected.worktreeScope &&
176
+ latest.entityId === authorizationEntityId(expected.worktreeScope, latest.path) &&
177
+ latest.viaForgeApi === true &&
178
+ latest.sourceCommand === writer?.sourceCommand &&
179
+ latest.operation === writer?.issueOperation &&
180
+ latest.writeIntent === expected.writeIntent &&
181
+ isValidGitObjectId(latest.sourceHead) &&
182
+ (!bunPinWriter || /^\d+\.\d+\.\d+$/.test(latest.targetBunVersion));
183
+ if (!structurallyValid) {
184
+ return blockedDecision(request, 'The latest Forge-owned authorization is malformed or was not issued by the owning command.');
185
+ }
186
+
187
+ if (
188
+ latest.actor !== expected.actor ||
189
+ latest.path !== expected.path ||
190
+ latest.surface !== expected.surface ||
191
+ latest.contentHash !== expected.contentHash ||
192
+ latest.sourceHead !== expected.sourceHead ||
193
+ latest.writeIntent !== expected.writeIntent
194
+ ) {
195
+ return blockedDecision(request, 'The latest Forge-owned authorization does not match this actor, surface, path, content hash, and source HEAD.');
196
+ }
197
+
198
+ const matchingCompletions = rows
199
+ .filter(row => row?.event_type === PROTECTED_STATE_WRITE_COMPLETED)
200
+ .map(parseAuthorizationEvent)
201
+ .filter(event =>
202
+ event.version === PROTECTED_STATE_AUTHORIZATION_VERSION &&
203
+ event.capabilityId === latest.capabilityId &&
204
+ event.origin === PROTECTED_STATE_AUTHORIZATION_ORIGIN &&
205
+ event.actor === latest.actor &&
206
+ event.payloadActor === latest.payloadActor &&
207
+ event.entityId === latest.entityId &&
208
+ event.path === latest.path &&
209
+ event.surface === latest.surface &&
210
+ event.contentHash === latest.contentHash &&
211
+ event.sourceHead === latest.sourceHead &&
212
+ event.worktreeScope === latest.worktreeScope &&
213
+ event.writeIntent === latest.writeIntent &&
214
+ event.operation === writer.completionOperation &&
215
+ event.viaForgeApi === true &&
216
+ event.sourceCommand === writer.sourceCommand
217
+ && event.targetBunVersion === latest.targetBunVersion
218
+ );
219
+ if (matchingCompletions.length !== 1) {
220
+ return blockedDecision(request, 'The latest Forge-owned authorization is not completed by its owning writer; failing closed.');
221
+ }
222
+
223
+ return {
224
+ allowed: true,
225
+ decision: 'allowed',
226
+ actor: expected.actor,
227
+ path: expected.path,
228
+ operation: request.operation || 'staged_edit',
229
+ requiredSurface: expected.surface,
230
+ declaredSurface: expected.surface,
231
+ contentHash: expected.contentHash,
232
+ sourceHead: expected.sourceHead,
233
+ viaForgeApi: true,
234
+ capabilityId: latest.capabilityId,
235
+ worktreeScope: latest.worktreeScope,
236
+ targetBunVersion: latest.targetBunVersion,
237
+ reason: 'Staged content matches the latest unconsumed Forge-owned authorization.',
238
+ repairHint: null,
239
+ };
240
+ }
241
+
242
+ async function issueProtectedStateAuthorization(projectRoot, request = {}, options = {}) {
243
+ const actor = request.actor;
244
+ const normalizedPath = normalizeRepoPath(request.path);
245
+ if (!actor || !request.surface || !normalizedPath || request.content === undefined) {
246
+ throw new TypeError('Protected state authorization requires actor, surface, path, and content');
247
+ }
248
+
249
+ const capabilityId = options.capabilityId || randomUUID();
250
+ const createdAt = options.now || new Date().toISOString();
251
+ const worktreeScope = options.worktreeScope || resolveWorktreeScope(projectRoot, options.deps);
252
+ const entityId = authorizationEntityId(worktreeScope, normalizedPath);
253
+ const event = {
254
+ entity_type: PROTECTED_STATE_ENTITY_TYPE,
255
+ entity_id: entityId,
256
+ event_type: PROTECTED_STATE_AUTHORIZATION_ISSUED,
257
+ idempotency_key: `${PROTECTED_STATE_AUTHORIZATION_ISSUED}:${capabilityId}`,
258
+ expected_revision: 0,
259
+ actor,
260
+ origin: PROTECTED_STATE_AUTHORIZATION_ORIGIN,
261
+ payload: {
262
+ version: PROTECTED_STATE_AUTHORIZATION_VERSION,
263
+ capabilityId,
264
+ actor,
265
+ path: normalizedPath,
266
+ surface: request.surface,
267
+ contentHash: hashProtectedContent(request.content),
268
+ worktreeScope,
269
+ operation: request.operation || 'generate',
270
+ viaForgeApi: request.viaForgeApi === true,
271
+ sourceCommand: request.sourceCommand || NPM_WORKFLOW_SOURCE_COMMAND,
272
+ ...(request.sourceHead ? { sourceHead: request.sourceHead } : {}),
273
+ ...(request.targetBunVersion ? { targetBunVersion: request.targetBunVersion } : {}),
274
+ writeIntent: request.writeIntent || 'update',
275
+ },
276
+ created_at: createdAt,
277
+ };
278
+
279
+ const kernel = await resolveOwnedKernel(projectRoot, options.deps);
280
+ try {
281
+ const existingRows = await kernel.driver.listKernelEvents(
282
+ PROTECTED_STATE_ENTITY_TYPE,
283
+ entityId,
284
+ {},
285
+ kernel.config,
286
+ );
287
+ if (request.verifyBeforeInsert) {
288
+ const verification = await request.verifyBeforeInsert();
289
+ if (!verification?.success) return verification || { success: false, error: 'Protected-state source changed during authorization.' };
290
+ }
291
+ const consumedCapabilities = new Set((existingRows || [])
292
+ .filter(row => row?.event_type === PROTECTED_STATE_AUTHORIZATION_CONSUMED)
293
+ .map(row => parseAuthorizationEvent(row).capabilityId)
294
+ .filter(Boolean));
295
+ const activeCapabilities = (existingRows || [])
296
+ .filter(row => row?.event_type === PROTECTED_STATE_AUTHORIZATION_ISSUED)
297
+ .map(parseAuthorizationEvent)
298
+ .filter(existing => existing.capabilityId && !consumedCapabilities.has(existing.capabilityId));
299
+ for (const existing of activeCapabilities) {
300
+ await kernel.driver.insertKernelEvent({
301
+ entity_type: PROTECTED_STATE_ENTITY_TYPE,
302
+ entity_id: entityId,
303
+ event_type: PROTECTED_STATE_AUTHORIZATION_CONSUMED,
304
+ idempotency_key: `${PROTECTED_STATE_AUTHORIZATION_CONSUMED}:${existing.capabilityId}`,
305
+ expected_revision: 0,
306
+ actor,
307
+ origin: PROTECTED_STATE_AUTHORIZATION_ORIGIN,
308
+ payload: {
309
+ version: PROTECTED_STATE_AUTHORIZATION_VERSION,
310
+ capabilityId: existing.capabilityId,
311
+ actor,
312
+ path: normalizedPath,
313
+ surface: existing.surface,
314
+ contentHash: existing.contentHash,
315
+ sourceHead: existing.sourceHead,
316
+ worktreeScope,
317
+ operation: 'superseded',
318
+ viaForgeApi: existing.viaForgeApi === true,
319
+ sourceCommand: request.sourceCommand || NPM_WORKFLOW_SOURCE_COMMAND,
320
+ },
321
+ created_at: createdAt,
322
+ }, {}, kernel.config);
323
+ }
324
+ if (request.verifyBeforeInsert) {
325
+ const verification = await request.verifyBeforeInsert();
326
+ if (!verification?.success) return verification || { success: false, error: 'Protected-state source changed during authorization.' };
327
+ }
328
+ const inserted = await kernel.driver.insertKernelEvent(event, {}, kernel.config);
329
+ return { success: true, capabilityId, event: parseAuthorizationEvent(inserted) };
330
+ } finally {
331
+ closeIfOwned(kernel);
332
+ }
333
+ }
334
+
335
+ function resolveCurrentBunWorkflowTarget(projectRoot, params, requireWritten = false) {
336
+ let currentHead;
337
+ try {
338
+ currentHead = resolveCurrentHead(projectRoot);
339
+ } catch (error) {
340
+ return { success: false, error: `Could not resolve current HEAD: ${error.message}` };
341
+ }
342
+ if (currentHead !== params.sourceHead) {
343
+ return { success: false, error: `Protected Bun workflow source HEAD changed from ${params.sourceHead} to ${currentHead}.` };
344
+ }
345
+
346
+ let update;
347
+ try {
348
+ const target = resolveBunVersionTarget(projectRoot);
349
+ if (params.targetBunVersion !== target.version) {
350
+ return { success: false, error: 'Protected Bun workflow target does not match the staged package.json Bun pin.' };
351
+ }
352
+ update = deriveBunWorkflowUpdate(projectRoot, params.sourceHead, params.path, { version: target.version });
353
+ if (requireWritten) {
354
+ const fullPath = path.join(projectRoot, update.path);
355
+ const stat = fs.lstatSync(fullPath);
356
+ if (!stat.isFile() || stat.isSymbolicLink() || !fs.readFileSync(fullPath).equals(update.content)) {
357
+ return { success: false, error: 'Protected Bun workflow completion requires the exact generated workflow bytes.' };
358
+ }
359
+ }
360
+ } catch (error) {
361
+ return { success: false, error: error.message };
362
+ }
363
+ return { success: true, update };
364
+ }
365
+
366
+ function resolveBoundNpmBunTarget(projectRoot, params) {
367
+ if (!/^\d+\.\d+\.\d+$/.test(params.targetBunVersion || '')
368
+ || params.bunVersion !== params.targetBunVersion
369
+ || resolveBunVersionTarget(projectRoot).version !== params.targetBunVersion) {
370
+ return { success: false, error: 'Protected npm workflow target does not match the staged package.json Bun pin.' };
371
+ }
372
+ return { success: true, version: params.targetBunVersion };
373
+ }
374
+
375
+ async function issueNpmPublishWorkflowAuthorization(projectRoot, params = {}, options = {}) {
376
+ if (!isValidGitObjectId(params.sourceHead)) {
377
+ return {
378
+ success: false,
379
+ error: 'Protected npm workflow authorization requires a full 40- or 64-character lowercase source HEAD.',
380
+ };
381
+ }
382
+ const {
383
+ NPM_PUBLISH_WORKFLOW_PATH,
384
+ renderNpmPublishWorkflow,
385
+ } = require('./npm-publish-workflow');
386
+ let content;
387
+ try {
388
+ const target = resolveBoundNpmBunTarget(projectRoot, params);
389
+ if (!target.success) return target;
390
+ content = renderNpmPublishWorkflow(params.bunVersion);
391
+ } catch (error) {
392
+ return { success: false, error: error.message };
393
+ }
394
+ return issueProtectedStateAuthorization(projectRoot, {
395
+ actor: params.actor,
396
+ surface: 'workflows',
397
+ path: NPM_PUBLISH_WORKFLOW_PATH,
398
+ content,
399
+ operation: 'generate_npm_workflow',
400
+ viaForgeApi: true,
401
+ sourceCommand: NPM_WORKFLOW_SOURCE_COMMAND,
402
+ sourceHead: params.sourceHead,
403
+ targetBunVersion: params.targetBunVersion,
404
+ verifyBeforeInsert: () => {
405
+ try {
406
+ return resolveBoundNpmBunTarget(projectRoot, params).success
407
+ ? { success: true }
408
+ : { success: false, error: 'Protected npm workflow target changed during authorization.' };
409
+ } catch (error) {
410
+ return { success: false, error: error.message };
411
+ }
412
+ },
413
+ }, options);
414
+ }
415
+
416
+ async function issueBunWorkflowAuthorization(projectRoot, params = {}, options = {}) {
417
+ if (!isValidGitObjectId(params.sourceHead) || !BUN_WORKFLOW_PATHS.has(params.path)) {
418
+ return {
419
+ success: false,
420
+ error: 'Protected Bun workflow authorization requires an allowlisted path and a full 40- or 64-character lowercase source HEAD.',
421
+ };
422
+ }
423
+ const target = resolveCurrentBunWorkflowTarget(projectRoot, params);
424
+ if (!target.success) return target;
425
+ const { update } = target;
426
+ const expectedHash = hashProtectedContent(update.content);
427
+ return issueProtectedStateAuthorization(projectRoot, {
428
+ actor: params.actor,
429
+ surface: 'workflows',
430
+ path: update.path,
431
+ content: update.content,
432
+ operation: 'update_bun_workflow_pin',
433
+ viaForgeApi: true,
434
+ sourceCommand: BUN_WORKFLOW_SOURCE_COMMAND,
435
+ sourceHead: params.sourceHead,
436
+ verifyBeforeInsert: () => {
437
+ const fresh = resolveCurrentBunWorkflowTarget(projectRoot, params);
438
+ if (!fresh.success) return fresh;
439
+ return hashProtectedContent(fresh.update.content) === expectedHash
440
+ ? { success: true }
441
+ : { success: false, error: 'Protected Bun workflow target changed during authorization.' };
442
+ },
443
+ targetBunVersion: params.targetBunVersion,
444
+ }, options);
445
+ }
446
+
447
+ function findExactAuthorization(rows, params, expected) {
448
+ const issued = (rows || [])
449
+ .filter(row => row?.event_type === PROTECTED_STATE_AUTHORIZATION_ISSUED)
450
+ .map(parseAuthorizationEvent)
451
+ .find(event => event.capabilityId === params.capabilityId);
452
+ const consumed = (rows || [])
453
+ .filter(row => row?.event_type === PROTECTED_STATE_AUTHORIZATION_CONSUMED)
454
+ .map(parseAuthorizationEvent)
455
+ .some(event => event.capabilityId === params.capabilityId);
456
+ const exactIssued = issued?.version === PROTECTED_STATE_AUTHORIZATION_VERSION &&
457
+ issued.origin === PROTECTED_STATE_AUTHORIZATION_ORIGIN &&
458
+ issued.actor === expected.actor && issued.payloadActor === expected.actor &&
459
+ issued.entityId === expected.entityId && issued.path === expected.path &&
460
+ issued.surface === expected.surface && issued.contentHash === expected.contentHash &&
461
+ issued.sourceHead === params.sourceHead && issued.worktreeScope === expected.worktreeScope &&
462
+ issued.operation === expected.operation && issued.viaForgeApi === true &&
463
+ issued.writeIntent === expected.writeIntent && issued.sourceCommand === expected.sourceCommand;
464
+ const targetMatches = expected.targetBunVersion === undefined
465
+ || issued.targetBunVersion === expected.targetBunVersion;
466
+ return { exactIssued: exactIssued && targetMatches, consumed };
467
+ }
468
+
469
+ async function completeNpmPublishWorkflowAuthorization(projectRoot, params = {}, options = {}) {
470
+ if (!isValidGitObjectId(params.sourceHead)) {
471
+ return {
472
+ success: false,
473
+ error: 'Protected npm workflow completion requires a full 40- or 64-character lowercase source HEAD.',
474
+ };
475
+ }
476
+ if (typeof params.capabilityId !== 'string' || params.capabilityId.length === 0) {
477
+ return { success: false, error: 'Protected npm workflow completion requires a capability id.' };
478
+ }
479
+
480
+ const {
481
+ NPM_PUBLISH_WORKFLOW_PATH,
482
+ renderNpmPublishWorkflow,
483
+ } = require('./npm-publish-workflow');
484
+ const actor = params.actor;
485
+ const normalizedPath = normalizeRepoPath(NPM_PUBLISH_WORKFLOW_PATH);
486
+ let contentHash;
487
+ try {
488
+ const target = resolveBoundNpmBunTarget(projectRoot, params);
489
+ if (!target.success) return target;
490
+ contentHash = hashProtectedContent(renderNpmPublishWorkflow(params.bunVersion));
491
+ } catch (error) {
492
+ return { success: false, error: error.message };
493
+ }
494
+ const worktreeScope = options.worktreeScope || resolveWorktreeScope(projectRoot, options.deps);
495
+ const entityId = authorizationEntityId(worktreeScope, normalizedPath);
496
+ const createdAt = options.now || new Date().toISOString();
497
+ const kernel = await resolveOwnedKernel(projectRoot, options.deps);
498
+ try {
499
+ const rows = await kernel.driver.listKernelEvents(
500
+ PROTECTED_STATE_ENTITY_TYPE,
501
+ entityId,
502
+ {},
503
+ kernel.config,
504
+ );
505
+ const { exactIssued, consumed } = findExactAuthorization(rows, params, {
506
+ actor,
507
+ entityId,
508
+ path: normalizedPath,
509
+ surface: 'workflows',
510
+ contentHash,
511
+ worktreeScope,
512
+ operation: 'generate_npm_workflow',
513
+ writeIntent: 'update',
514
+ sourceCommand: NPM_WORKFLOW_SOURCE_COMMAND,
515
+ targetBunVersion: params.targetBunVersion,
516
+ });
517
+ if (!exactIssued || consumed) {
518
+ return {
519
+ success: false,
520
+ error: 'Protected npm workflow completion does not match one active exact authorization.',
521
+ };
522
+ }
523
+ try {
524
+ if (!resolveBoundNpmBunTarget(projectRoot, params).success) {
525
+ return { success: false, error: 'Protected npm workflow target changed during completion.' };
526
+ }
527
+ } catch (error) {
528
+ return { success: false, error: error.message };
529
+ }
530
+
531
+ const event = {
532
+ entity_type: PROTECTED_STATE_ENTITY_TYPE,
533
+ entity_id: entityId,
534
+ event_type: PROTECTED_STATE_WRITE_COMPLETED,
535
+ idempotency_key: `${PROTECTED_STATE_WRITE_COMPLETED}:${params.capabilityId}`,
536
+ expected_revision: 0,
537
+ actor,
538
+ origin: PROTECTED_STATE_AUTHORIZATION_ORIGIN,
539
+ payload: {
540
+ version: PROTECTED_STATE_AUTHORIZATION_VERSION,
541
+ capabilityId: params.capabilityId,
542
+ actor,
543
+ path: normalizedPath,
544
+ surface: 'workflows',
545
+ contentHash,
546
+ sourceHead: params.sourceHead,
547
+ worktreeScope,
548
+ writeIntent: 'update',
549
+ operation: 'generate_npm_workflow_completed',
550
+ viaForgeApi: true,
551
+ sourceCommand: NPM_WORKFLOW_SOURCE_COMMAND,
552
+ targetBunVersion: params.targetBunVersion,
553
+ },
554
+ created_at: createdAt,
555
+ };
556
+ const inserted = await kernel.driver.insertKernelEvent(event, {}, kernel.config);
557
+ return { success: true, capabilityId: params.capabilityId, event: parseAuthorizationEvent(inserted) };
558
+ } finally {
559
+ closeIfOwned(kernel);
560
+ }
561
+ }
562
+
563
+ async function completeBunWorkflowAuthorization(projectRoot, params = {}, options = {}) {
564
+ if (!isValidGitObjectId(params.sourceHead) || !BUN_WORKFLOW_PATHS.has(params.path)) {
565
+ return {
566
+ success: false,
567
+ error: 'Protected Bun workflow completion requires an allowlisted path and a full 40- or 64-character lowercase source HEAD.',
568
+ };
569
+ }
570
+ if (typeof params.capabilityId !== 'string' || params.capabilityId.length === 0) {
571
+ return { success: false, error: 'Protected Bun workflow completion requires a capability id.' };
572
+ }
573
+
574
+ const target = resolveCurrentBunWorkflowTarget(projectRoot, params, true);
575
+ if (!target.success) return target;
576
+ const { update } = target;
577
+
578
+ const actor = params.actor;
579
+ const normalizedPath = normalizeRepoPath(update.path);
580
+ const contentHash = hashProtectedContent(update.content);
581
+ const worktreeScope = options.worktreeScope || resolveWorktreeScope(projectRoot, options.deps);
582
+ const entityId = authorizationEntityId(worktreeScope, normalizedPath);
583
+ const createdAt = options.now || new Date().toISOString();
584
+ const kernel = await resolveOwnedKernel(projectRoot, options.deps);
585
+ try {
586
+ const rows = await kernel.driver.listKernelEvents(
587
+ PROTECTED_STATE_ENTITY_TYPE,
588
+ entityId,
589
+ {},
590
+ kernel.config,
591
+ );
592
+ const { exactIssued, consumed } = findExactAuthorization(rows, params, {
593
+ actor,
594
+ entityId,
595
+ path: normalizedPath,
596
+ surface: 'workflows',
597
+ contentHash,
598
+ worktreeScope,
599
+ operation: 'update_bun_workflow_pin',
600
+ writeIntent: 'update',
601
+ sourceCommand: BUN_WORKFLOW_SOURCE_COMMAND,
602
+ targetBunVersion: params.targetBunVersion,
603
+ });
604
+ if (!exactIssued || consumed) {
605
+ return {
606
+ success: false,
607
+ error: 'Protected Bun workflow completion does not match one active exact authorization.',
608
+ };
609
+ }
610
+ const fresh = resolveCurrentBunWorkflowTarget(projectRoot, params, true);
611
+ if (!fresh.success) return fresh;
612
+ if (hashProtectedContent(fresh.update.content) !== contentHash) {
613
+ return { success: false, error: 'Protected Bun workflow target changed during completion.' };
614
+ }
615
+
616
+ const event = {
617
+ entity_type: PROTECTED_STATE_ENTITY_TYPE,
618
+ entity_id: entityId,
619
+ event_type: PROTECTED_STATE_WRITE_COMPLETED,
620
+ idempotency_key: `${PROTECTED_STATE_WRITE_COMPLETED}:${params.capabilityId}`,
621
+ expected_revision: 0,
622
+ actor,
623
+ origin: PROTECTED_STATE_AUTHORIZATION_ORIGIN,
624
+ payload: {
625
+ version: PROTECTED_STATE_AUTHORIZATION_VERSION,
626
+ capabilityId: params.capabilityId,
627
+ actor,
628
+ path: normalizedPath,
629
+ surface: 'workflows',
630
+ contentHash,
631
+ sourceHead: params.sourceHead,
632
+ worktreeScope,
633
+ writeIntent: 'update',
634
+ operation: 'update_bun_workflow_pin_completed',
635
+ viaForgeApi: true,
636
+ sourceCommand: BUN_WORKFLOW_SOURCE_COMMAND,
637
+ targetBunVersion: params.targetBunVersion,
638
+ },
639
+ created_at: createdAt,
640
+ };
641
+ const inserted = await kernel.driver.insertKernelEvent(event, {}, kernel.config);
642
+ return { success: true, capabilityId: params.capabilityId, event: parseAuthorizationEvent(inserted) };
643
+ } finally {
644
+ closeIfOwned(kernel);
645
+ }
646
+ }
647
+
648
+ async function cancelBunWorkflowBatchAuthorizations(projectRoot, requests = [], options = {}) {
649
+ if (requests.length === 0) return { success: true, results: [] };
650
+ const worktreeScope = options.worktreeScope || resolveWorktreeScope(projectRoot, options.deps);
651
+ const kernel = await resolveOwnedKernel(projectRoot, options.deps);
652
+ const results = [];
653
+ try {
654
+ for (const request of requests) {
655
+ const normalizedPath = normalizeRepoPath(request.path);
656
+ const writer = owningWriter(normalizedPath, 'workflows');
657
+ if (!request.actor || !isValidGitObjectId(request.sourceHead) || !request.capabilityId ||
658
+ !writer || ![BUN_WORKFLOW_SOURCE_COMMAND, NPM_WORKFLOW_SOURCE_COMMAND].includes(writer.sourceCommand)) {
659
+ results.push({ success: false, path: normalizedPath, capabilityId: request.capabilityId, error: 'Invalid Bun workflow batch cancellation request.' });
660
+ continue;
661
+ }
662
+ try {
663
+ const entityId = authorizationEntityId(worktreeScope, normalizedPath);
664
+ const rows = await kernel.driver.listKernelEvents(PROTECTED_STATE_ENTITY_TYPE, entityId, {}, kernel.config);
665
+ const events = (rows || []).map(parseAuthorizationEvent);
666
+ const issued = events.find(event =>
667
+ event.eventType === PROTECTED_STATE_AUTHORIZATION_ISSUED &&
668
+ event.capabilityId === request.capabilityId
669
+ );
670
+ if (!issued) {
671
+ results.push({ success: true, path: normalizedPath, capabilityId: request.capabilityId, status: 'not-issued' });
672
+ continue;
673
+ }
674
+ const exact = issued.version === PROTECTED_STATE_AUTHORIZATION_VERSION &&
675
+ issued.origin === PROTECTED_STATE_AUTHORIZATION_ORIGIN &&
676
+ issued.actor === request.actor && issued.payloadActor === request.actor &&
677
+ issued.entityId === entityId && issued.path === normalizedPath &&
678
+ issued.surface === 'workflows' && issued.sourceHead === request.sourceHead &&
679
+ issued.worktreeScope === worktreeScope && issued.operation === writer.issueOperation &&
680
+ issued.viaForgeApi === true && issued.writeIntent === 'update' &&
681
+ issued.sourceCommand === writer.sourceCommand;
682
+ if (!exact) {
683
+ results.push({ success: false, path: normalizedPath, capabilityId: request.capabilityId, error: 'Capability does not match this Bun workflow batch.' });
684
+ continue;
685
+ }
686
+ if (events.some(event => event.eventType === PROTECTED_STATE_AUTHORIZATION_CONSUMED && event.capabilityId === request.capabilityId)) {
687
+ results.push({ success: true, path: normalizedPath, capabilityId: request.capabilityId, status: 'already-consumed' });
688
+ continue;
689
+ }
690
+ await kernel.driver.insertKernelEvent({
691
+ entity_type: PROTECTED_STATE_ENTITY_TYPE,
692
+ entity_id: entityId,
693
+ event_type: PROTECTED_STATE_AUTHORIZATION_CONSUMED,
694
+ idempotency_key: `${PROTECTED_STATE_AUTHORIZATION_CONSUMED}:${request.capabilityId}`,
695
+ expected_revision: 0,
696
+ actor: request.actor,
697
+ origin: PROTECTED_STATE_AUTHORIZATION_ORIGIN,
698
+ payload: {
699
+ version: PROTECTED_STATE_AUTHORIZATION_VERSION,
700
+ capabilityId: request.capabilityId,
701
+ actor: request.actor,
702
+ path: normalizedPath,
703
+ surface: 'workflows',
704
+ contentHash: issued.contentHash,
705
+ sourceHead: request.sourceHead,
706
+ worktreeScope,
707
+ writeIntent: 'update',
708
+ operation: 'batch_rollback',
709
+ viaForgeApi: true,
710
+ sourceCommand: BUN_WORKFLOW_SOURCE_COMMAND,
711
+ },
712
+ created_at: options.now || new Date().toISOString(),
713
+ }, {}, kernel.config);
714
+ results.push({ success: true, path: normalizedPath, capabilityId: request.capabilityId, status: 'consumed' });
715
+ } catch (error) {
716
+ results.push({ success: false, path: normalizedPath, capabilityId: request.capabilityId, error: error.message });
717
+ }
718
+ }
719
+ return { success: results.every(result => result.success), results };
720
+ } finally {
721
+ closeIfOwned(kernel);
722
+ }
723
+ }
724
+
725
+ const CLAUDE_POINTER_CONTENT = Buffer.from('@AGENTS.md\n');
726
+ const CLAUDE_POINTER_CRLF_CONTENT = Buffer.from('@AGENTS.md\r\n');
727
+
728
+ function isExactClaudePointer(content) {
729
+ const bytes = Buffer.from(content || '');
730
+ return bytes.equals(Buffer.from('@AGENTS.md')) || bytes.equals(CLAUDE_POINTER_CONTENT) || bytes.equals(CLAUDE_POINTER_CRLF_CONTENT);
731
+ }
732
+
733
+ async function issueGeneratedHarnessClaudeAuthorization(projectRoot, params = {}, options = {}) {
734
+ if (!isValidGitObjectId(params.sourceHead) || !isExactClaudePointer(params.content)) {
735
+ return {
736
+ success: false,
737
+ error: 'Root CLAUDE.md authorization requires exact @AGENTS.md pointer bytes and a full 40- or 64-character source HEAD.',
738
+ };
739
+ }
740
+ const content = Buffer.from(params.content || CLAUDE_POINTER_CONTENT);
741
+ return issueProtectedStateAuthorization(projectRoot, {
742
+ actor: params.actor,
743
+ surface: 'generated_harness',
744
+ path: 'CLAUDE.md',
745
+ content,
746
+ operation: 'generate_claude_import',
747
+ viaForgeApi: true,
748
+ sourceCommand: CLAUDE_SETUP_SOURCE_COMMAND,
749
+ sourceHead: params.sourceHead,
750
+ }, options);
751
+ }
752
+
753
+ async function completeGeneratedHarnessClaudeAuthorization(projectRoot, params = {}, options = {}) {
754
+ const content = Buffer.from(params.content || CLAUDE_POINTER_CONTENT);
755
+ if (!isValidGitObjectId(params.sourceHead) || !params.capabilityId || !isExactClaudePointer(content)) {
756
+ return { success: false, error: 'Root CLAUDE.md completion requires a capability id and full 40- or 64-character source HEAD.' };
757
+ }
758
+ const actor = params.actor;
759
+ const normalizedPath = 'CLAUDE.md';
760
+ const contentHash = hashProtectedContent(content);
761
+ const worktreeScope = options.worktreeScope || resolveWorktreeScope(projectRoot, options.deps);
762
+ const entityId = authorizationEntityId(worktreeScope, normalizedPath);
763
+ const kernel = await resolveOwnedKernel(projectRoot, options.deps);
764
+ try {
765
+ const rows = await kernel.driver.listKernelEvents(PROTECTED_STATE_ENTITY_TYPE, entityId, {}, kernel.config);
766
+ const { exactIssued, consumed } = findExactAuthorization(rows, params, {
767
+ actor,
768
+ entityId,
769
+ path: normalizedPath,
770
+ surface: 'generated_harness',
771
+ contentHash,
772
+ worktreeScope,
773
+ operation: 'generate_claude_import',
774
+ writeIntent: 'update',
775
+ sourceCommand: CLAUDE_SETUP_SOURCE_COMMAND,
776
+ });
777
+ if (!exactIssued || consumed) {
778
+ return { success: false, error: 'Root CLAUDE.md completion does not match one active exact authorization.' };
779
+ }
780
+ const inserted = await kernel.driver.insertKernelEvent({
781
+ entity_type: PROTECTED_STATE_ENTITY_TYPE,
782
+ entity_id: entityId,
783
+ event_type: PROTECTED_STATE_WRITE_COMPLETED,
784
+ idempotency_key: `${PROTECTED_STATE_WRITE_COMPLETED}:${params.capabilityId}`,
785
+ expected_revision: 0,
786
+ actor,
787
+ origin: PROTECTED_STATE_AUTHORIZATION_ORIGIN,
788
+ payload: {
789
+ version: PROTECTED_STATE_AUTHORIZATION_VERSION,
790
+ capabilityId: params.capabilityId,
791
+ actor,
792
+ path: normalizedPath,
793
+ surface: 'generated_harness',
794
+ contentHash,
795
+ sourceHead: params.sourceHead,
796
+ worktreeScope,
797
+ writeIntent: 'update',
798
+ operation: 'generate_claude_import_completed',
799
+ viaForgeApi: true,
800
+ sourceCommand: CLAUDE_SETUP_SOURCE_COMMAND,
801
+ },
802
+ created_at: options.now || new Date().toISOString(),
803
+ }, {}, kernel.config);
804
+ return { success: true, capabilityId: params.capabilityId, event: parseAuthorizationEvent(inserted) };
805
+ } finally {
806
+ closeIfOwned(kernel);
807
+ }
808
+ }
809
+
810
+ function resolveCanonicalSkillMirror(projectRoot, mirrorPath, options = {}) {
811
+ const normalizedPath = normalizeRepoPath(mirrorPath);
812
+ const match = /^\.agents\/skills\/([^/]+)\/(.+)$/.exec(normalizedPath);
813
+ if (!match || match[1] === '.' || match[1] === '..') return null;
814
+ const segments = match[2].split('/');
815
+ if (segments.some(segment => !segment || segment === '.' || segment === '..')) return null;
816
+ const skillRoot = path.resolve(projectRoot, 'skills', match[1]);
817
+ const canonicalPath = path.resolve(skillRoot, match[2]);
818
+ if (canonicalPath !== skillRoot && !canonicalPath.startsWith(`${skillRoot}${path.sep}`)) return null;
819
+ try {
820
+ const skillsRoot = path.resolve(projectRoot, 'skills');
821
+ const skillsStat = fs.lstatSync(skillsRoot);
822
+ if (!skillsStat.isDirectory() || skillsStat.isSymbolicLink()) return null;
823
+ const rootStat = fs.lstatSync(skillRoot);
824
+ if (!rootStat.isDirectory() || rootStat.isSymbolicLink()) return null;
825
+ let ancestor = skillRoot;
826
+ for (const segment of segments.slice(0, -1)) {
827
+ ancestor = path.join(ancestor, segment);
828
+ const ancestorStat = fs.lstatSync(ancestor);
829
+ if (!ancestorStat.isDirectory() || ancestorStat.isSymbolicLink()) return null;
830
+ }
831
+ const stat = fs.lstatSync(canonicalPath);
832
+ if (!stat.isFile() || stat.isSymbolicLink()) return null;
833
+ return { normalizedPath, content: fs.readFileSync(canonicalPath) };
834
+ } catch (error) {
835
+ if (options.allowMissing === true && error.code === 'ENOENT' && Buffer.isBuffer(options.priorContent)) {
836
+ return { normalizedPath, content: options.priorContent, missing: true };
837
+ }
838
+ return null;
839
+ }
840
+ }
841
+
842
+ async function issueGeneratedHarnessSkillAuthorization(projectRoot, params = {}, options = {}) {
843
+ if (!isValidGitObjectId(params.sourceHead)) {
844
+ return { success: false, error: 'Protected skill mirror authorization requires a full 40- or 64-character lowercase source HEAD.' };
845
+ }
846
+ const writeIntent = params.writeIntent === 'delete' ? 'delete' : 'update';
847
+ const canonical = resolveCanonicalSkillMirror(projectRoot, params.path, {
848
+ allowMissing: writeIntent === 'delete',
849
+ priorContent: params.priorContent,
850
+ });
851
+ if (!canonical) {
852
+ return { success: false, error: 'Protected skill mirror authorization requires a canonical .agents/skills/<name>/<file> path.' };
853
+ }
854
+ if (writeIntent === 'delete' && canonical.missing !== true) {
855
+ return { success: false, error: 'Protected skill mirror deletion authorization requires the canonical source to be absent.' };
856
+ }
857
+ return issueProtectedStateAuthorization(projectRoot, {
858
+ actor: params.actor,
859
+ surface: 'generated_harness',
860
+ path: canonical.normalizedPath,
861
+ content: canonical.content,
862
+ operation: 'sync_agent_skill',
863
+ viaForgeApi: true,
864
+ sourceCommand: SKILL_MIRROR_SOURCE_COMMAND,
865
+ sourceHead: params.sourceHead,
866
+ writeIntent,
867
+ }, options);
868
+ }
869
+
870
+ async function completeGeneratedHarnessSkillAuthorization(projectRoot, params = {}, options = {}) {
871
+ if (!isValidGitObjectId(params.sourceHead) || !params.capabilityId) {
872
+ return { success: false, error: 'Protected skill mirror completion requires a capability id and full 40- or 64-character source HEAD.' };
873
+ }
874
+ const writeIntent = params.writeIntent === 'delete' ? 'delete' : 'update';
875
+ const canonical = resolveCanonicalSkillMirror(projectRoot, params.path, {
876
+ allowMissing: writeIntent === 'delete',
877
+ priorContent: params.priorContent,
878
+ });
879
+ if (!canonical) {
880
+ return { success: false, error: 'Protected skill mirror completion requires a canonical .agents/skills/<name>/<file> path.' };
881
+ }
882
+ if (writeIntent === 'delete' && canonical.missing !== true) {
883
+ return { success: false, error: 'Protected skill mirror deletion completion requires the canonical source to be absent.' };
884
+ }
885
+ const actor = params.actor;
886
+ const contentHash = hashProtectedContent(canonical.content);
887
+ const mirrorTarget = path.resolve(projectRoot, canonical.normalizedPath);
888
+ if (writeIntent === 'delete' && fs.existsSync(mirrorTarget)) {
889
+ return { success: false, error: 'Protected skill mirror deletion completion requires the exact mirror path to be absent.' };
890
+ }
891
+ const worktreeScope = options.worktreeScope || resolveWorktreeScope(projectRoot, options.deps);
892
+ const entityId = authorizationEntityId(worktreeScope, canonical.normalizedPath);
893
+ const kernel = await resolveOwnedKernel(projectRoot, options.deps);
894
+ try {
895
+ const rows = await kernel.driver.listKernelEvents(PROTECTED_STATE_ENTITY_TYPE, entityId, {}, kernel.config);
896
+ const { exactIssued, consumed } = findExactAuthorization(rows, params, {
897
+ actor,
898
+ entityId,
899
+ path: canonical.normalizedPath,
900
+ surface: 'generated_harness',
901
+ contentHash,
902
+ worktreeScope,
903
+ operation: 'sync_agent_skill',
904
+ writeIntent,
905
+ sourceCommand: SKILL_MIRROR_SOURCE_COMMAND,
906
+ });
907
+ if (!exactIssued || consumed) {
908
+ return { success: false, error: 'Protected skill mirror completion does not match one active exact authorization.' };
909
+ }
910
+ const inserted = await kernel.driver.insertKernelEvent({
911
+ entity_type: PROTECTED_STATE_ENTITY_TYPE,
912
+ entity_id: entityId,
913
+ event_type: PROTECTED_STATE_WRITE_COMPLETED,
914
+ idempotency_key: `${PROTECTED_STATE_WRITE_COMPLETED}:${params.capabilityId}`,
915
+ expected_revision: 0,
916
+ actor,
917
+ origin: PROTECTED_STATE_AUTHORIZATION_ORIGIN,
918
+ payload: {
919
+ version: PROTECTED_STATE_AUTHORIZATION_VERSION,
920
+ capabilityId: params.capabilityId,
921
+ actor,
922
+ path: canonical.normalizedPath,
923
+ surface: 'generated_harness',
924
+ contentHash,
925
+ sourceHead: params.sourceHead,
926
+ worktreeScope,
927
+ writeIntent,
928
+ operation: 'sync_agent_skill_completed',
929
+ viaForgeApi: true,
930
+ sourceCommand: SKILL_MIRROR_SOURCE_COMMAND,
931
+ },
932
+ created_at: options.now || new Date().toISOString(),
933
+ }, {}, kernel.config);
934
+ return { success: true, capabilityId: params.capabilityId, event: parseAuthorizationEvent(inserted) };
935
+ } finally {
936
+ closeIfOwned(kernel);
937
+ }
938
+ }
939
+
940
+ function validateBunDecisionTargets(projectRoot, decisions) {
941
+ const bunDecisions = decisions.filter(decision => decision.allowed && decision.targetBunVersion);
942
+ if (bunDecisions.length === 0) return;
943
+ let stagedVersion;
944
+ try {
945
+ stagedVersion = resolveBunVersionTarget(projectRoot).version;
946
+ } catch (error) {
947
+ for (const decision of bunDecisions) Object.assign(decision, { allowed: false, decision: 'blocked', reason: error.message });
948
+ return;
949
+ }
950
+ for (const decision of bunDecisions) {
951
+ if (decision.targetBunVersion !== stagedVersion) {
952
+ Object.assign(decision, { allowed: false, decision: 'blocked', reason: 'The staged package.json Bun pin changed after workflow generation.' });
953
+ }
954
+ }
955
+ }
956
+
957
+ function validateCompleteBunPinBatch(projectRoot, options = {}) {
958
+ if (options.sourceHead === null) return { success: true };
959
+ let sourceHead;
960
+ let sourcePackage;
961
+ try {
962
+ sourceHead = options.sourceHead || resolveCurrentHead(projectRoot);
963
+ sourcePackage = Buffer.from((options.readSourcePackageManifest || readSourcePackageManifest)(projectRoot, sourceHead));
964
+ } catch (error) {
965
+ if (error.message.includes('package.json is not a regular file at')) return { success: true };
966
+ return { success: false, error: `Could not verify the complete staged Bun pin update: ${error.message}` };
967
+ }
968
+ try {
969
+ if (JSON.parse(sourcePackage.toString('utf8')).name !== 'forge-workflow') return { success: true };
970
+ const sourceVersion = readPinnedBunVersion(sourcePackage);
971
+ const indexedPackage = Buffer.from((options.readIndexedPackageManifest || readIndexedPackageManifest)(projectRoot));
972
+ const targetVersion = readPinnedBunVersion(indexedPackage);
973
+ if (targetVersion === sourceVersion) return { success: true };
974
+ if (resolveBunVersionTarget(projectRoot, options).version !== targetVersion) {
975
+ return { success: false, error: 'The working package.json Bun pin differs from the staged pin.' };
976
+ }
977
+
978
+ for (const spec of BUN_WORKFLOW_SPECS) {
979
+ const baseline = (options.readSourceWorkflow || readSourceWorkflow)(projectRoot, sourceHead, spec.path);
980
+ const expected = Buffer.from(renderBunWorkflowPin(spec.path, baseline, targetVersion));
981
+ const indexed = Buffer.from((options.readIndexedWorkflow || readIndexedWorkflow)(projectRoot, spec.path));
982
+ if (!indexed.equals(expected)) {
983
+ return { success: false, error: `A staged Bun pin change requires the complete generated workflow set; ${spec.path} does not match Bun ${targetVersion}.` };
984
+ }
985
+ }
986
+
987
+ const npmWorkflow = require('./npm-publish-workflow');
988
+ const indexedNpm = Buffer.from((options.readIndexedNpmPublishWorkflow || npmWorkflow.readIndexedNpmPublishWorkflow)(projectRoot));
989
+ if (!indexedNpm.equals(Buffer.from(npmWorkflow.renderNpmPublishWorkflow(targetVersion)))) {
990
+ return { success: false, error: `A staged Bun pin change requires the complete generated workflow set; ${npmWorkflow.NPM_PUBLISH_WORKFLOW_PATH} does not match Bun ${targetVersion}.` };
991
+ }
992
+ return { success: true, sourceVersion, targetVersion };
993
+ } catch (error) {
994
+ return { success: false, error: `Could not verify the complete staged Bun pin update: ${error.message}` };
995
+ }
996
+ }
997
+
998
+ function blockedBunBatchDecision(reason) {
999
+ return {
1000
+ allowed: false,
1001
+ decision: 'blocked',
1002
+ path: 'package.json',
1003
+ operation: 'staged_edit',
1004
+ requiredSurface: 'workflows',
1005
+ declaredSurface: 'workflows',
1006
+ reason,
1007
+ repairHint: 'Run forge release update-bun-pins from the staged package.json target, then stage the complete generated workflow set.',
1008
+ };
1009
+ }
1010
+
1011
+ async function authorizeAndConsumeProtectedStateWrites(projectRoot, requests = [], options = {}) {
1012
+ const bunBatch = (options.validateCompleteBunPinBatch || validateCompleteBunPinBatch)(projectRoot, options);
1013
+ if (!bunBatch.success) {
1014
+ return { success: false, decisions: [], batchDecision: blockedBunBatchDecision(bunBatch.error) };
1015
+ }
1016
+ if (requests.length === 0) return { success: true, decisions: [] };
1017
+ const worktreeScope = options.worktreeScope || resolveWorktreeScope(projectRoot, options.deps);
1018
+ const kernel = await resolveOwnedKernel(projectRoot, options.deps);
1019
+ try {
1020
+ const decisions = [];
1021
+ for (const request of requests) {
1022
+ const normalizedPath = normalizeRepoPath(request.path);
1023
+ const entityId = authorizationEntityId(worktreeScope, normalizedPath);
1024
+ const rows = await kernel.driver.listKernelEvents(
1025
+ PROTECTED_STATE_ENTITY_TYPE,
1026
+ entityId,
1027
+ {},
1028
+ kernel.config,
1029
+ );
1030
+ decisions.push(evaluateAuthorization({
1031
+ ...request,
1032
+ path: normalizedPath,
1033
+ worktreeScope,
1034
+ }, rows || []));
1035
+ }
1036
+ validateBunDecisionTargets(projectRoot, decisions);
1037
+
1038
+ if (decisions.some(decision => !decision.allowed)) {
1039
+ return { success: false, decisions };
1040
+ }
1041
+ const currentBunBatch = (options.validateCompleteBunPinBatch || validateCompleteBunPinBatch)(projectRoot, options);
1042
+ if (!currentBunBatch.success) {
1043
+ return { success: false, decisions, batchDecision: blockedBunBatchDecision(currentBunBatch.error) };
1044
+ }
1045
+
1046
+ for (const decision of decisions) {
1047
+ const createdAt = options.now || new Date().toISOString();
1048
+ await kernel.driver.insertKernelEvent({
1049
+ entity_type: PROTECTED_STATE_ENTITY_TYPE,
1050
+ entity_id: authorizationEntityId(decision.worktreeScope, decision.path),
1051
+ event_type: PROTECTED_STATE_AUTHORIZATION_CONSUMED,
1052
+ idempotency_key: `${PROTECTED_STATE_AUTHORIZATION_CONSUMED}:${decision.capabilityId}`,
1053
+ expected_revision: 0,
1054
+ actor: decision.actor,
1055
+ origin: PROTECTED_STATE_AUTHORIZATION_ORIGIN,
1056
+ payload: {
1057
+ version: PROTECTED_STATE_AUTHORIZATION_VERSION,
1058
+ capabilityId: decision.capabilityId,
1059
+ actor: decision.actor,
1060
+ path: decision.path,
1061
+ surface: decision.requiredSurface,
1062
+ contentHash: decision.contentHash,
1063
+ sourceHead: decision.sourceHead,
1064
+ worktreeScope: decision.worktreeScope,
1065
+ writeIntent: decision.operation === 'staged_delete' ? 'delete' : 'update',
1066
+ operation: 'staged_edit',
1067
+ viaForgeApi: decision.viaForgeApi === true,
1068
+ sourceCommand: 'scripts/protected-state-check.js',
1069
+ },
1070
+ created_at: createdAt,
1071
+ }, {}, kernel.config);
1072
+ }
1073
+
1074
+ return { success: true, decisions };
1075
+ } finally {
1076
+ closeIfOwned(kernel);
1077
+ }
1078
+ }
1079
+
1080
+ module.exports = {
1081
+ PROTECTED_STATE_ENTITY_TYPE,
1082
+ PROTECTED_STATE_AUTHORIZATION_ISSUED,
1083
+ PROTECTED_STATE_WRITE_COMPLETED,
1084
+ PROTECTED_STATE_AUTHORIZATION_CONSUMED,
1085
+ isValidGitObjectId,
1086
+ resolveWorktreeScope,
1087
+ authorizationEntityId,
1088
+ parseAuthorizationEvent,
1089
+ evaluateAuthorization,
1090
+ issueNpmPublishWorkflowAuthorization,
1091
+ completeNpmPublishWorkflowAuthorization,
1092
+ issueBunWorkflowAuthorization,
1093
+ completeBunWorkflowAuthorization,
1094
+ cancelBunWorkflowBatchAuthorizations,
1095
+ issueGeneratedHarnessClaudeAuthorization,
1096
+ completeGeneratedHarnessClaudeAuthorization,
1097
+ issueGeneratedHarnessSkillAuthorization,
1098
+ completeGeneratedHarnessSkillAuthorization,
1099
+ authorizeAndConsumeProtectedStateWrites,
1100
+ };