forge-workflow 0.1.0-beta.5 → 0.1.0-beta.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +4 -0
- package/CHANGELOG.md +48 -0
- package/CLAUDE.md +0 -12
- package/CODING_STANDARDS.md +72 -0
- package/bin/forge.js +12 -1
- package/docs/guides/MIGRATION.md +3 -3
- package/docs/reference/FORGE_KERNEL_STORAGE_MODEL.md +4 -0
- package/docs/reference/INSTALL.md +4 -0
- package/docs/reference/LEGACY_CLAIM_REPAIR.md +112 -0
- package/docs/reference/RELEASE.md +4 -4
- package/docs/reference/github-accounts.md +134 -0
- package/docs/reference/shepherd.md +63 -13
- package/lib/adapters/pr-state-adapter.js +15 -2
- package/lib/base-remote.js +138 -0
- package/lib/beta5-compatibility-evidence.js +1093 -0
- package/lib/bun-lockfile-proof.js +413 -0
- package/lib/bun-workflow-pins.js +461 -0
- package/lib/capabilities/index.js +9 -0
- package/lib/capabilities/model.js +141 -0
- package/lib/capabilities/probes.js +347 -0
- package/lib/codex-skills.js +2 -2
- package/lib/commands/_manifest.js +1 -0
- package/lib/commands/_registry.js +48 -18
- package/lib/commands/clean.js +57 -1
- package/lib/commands/doctor.js +37 -6
- package/lib/commands/gate.js +197 -27
- package/lib/commands/github.js +215 -0
- package/lib/commands/hooks.js +54 -6
- package/lib/commands/memory.js +66 -2
- package/lib/commands/merge.js +720 -73
- package/lib/commands/plan.js +33 -2
- package/lib/commands/pr.js +2 -0
- package/lib/commands/preflight.js +10 -2
- package/lib/commands/push.js +120 -9
- package/lib/commands/recall.js +95 -61
- package/lib/commands/release.js +23 -2
- package/lib/commands/remember.js +28 -4
- package/lib/commands/serve.js +26 -9
- package/lib/commands/setup.js +132 -4
- package/lib/commands/shepherd.js +578 -72
- package/lib/commands/ship.js +15 -69
- package/lib/commands/skill.js +8 -0
- package/lib/commands/team.js +47 -8
- package/lib/commands/test.js +163 -4
- package/lib/commands/validate.js +78 -22
- package/lib/commands/worktree.js +155 -19
- package/lib/fixtures/beta5-corpus/v1/README.md +9 -0
- package/lib/fixtures/beta5-corpus/v1/contract/command-contract.json +26 -0
- package/lib/fixtures/beta5-corpus/v1/contract/package-contract.json +13 -0
- package/lib/fixtures/beta5-corpus/v1/contract/workflow-stage-matrix.json +8 -0
- package/lib/fixtures/beta5-corpus/v1/manifest.json +25 -0
- package/lib/fixtures/beta5-corpus/v1/state/comments.jsonl +1 -0
- package/lib/fixtures/beta5-corpus/v1/state/config.yaml +6 -0
- package/lib/fixtures/beta5-corpus/v1/state/dependencies.jsonl +1 -0
- package/lib/fixtures/beta5-corpus/v1/state/issues.jsonl +2 -0
- package/lib/fixtures/beta5-corpus/v1/state/kernel.sql +20 -0
- package/lib/forge-issues.js +78 -0
- package/lib/gate-events.js +98 -10
- package/lib/github-context.js +308 -0
- package/lib/global-flags.js +1 -0
- package/lib/hook-renderer.js +29 -1
- package/lib/issue-render.js +19 -0
- package/lib/kernel/broker.js +723 -31
- package/lib/kernel/claim-reconciler.js +238 -0
- package/lib/kernel/lease-enforcer.js +9 -4
- package/lib/kernel/legacy-claim-repair.js +442 -0
- package/lib/kernel/live-claim-projection.js +26 -0
- package/lib/kernel/migrations.js +118 -3
- package/lib/kernel/readiness-model.js +184 -12
- package/lib/kernel/schema.js +49 -1
- package/lib/kernel/sqlite-driver.js +3322 -183
- package/lib/kernel/taxonomy-validator.js +4 -1
- package/lib/kernel/windows-private-acl.js +239 -0
- package/lib/memory/hygiene.js +191 -0
- package/lib/memory/router.js +94 -27
- package/lib/memory/usage-evidence.js +4 -0
- package/lib/memory-digest.js +59 -0
- package/lib/merge-rules.js +135 -17
- package/lib/npm-publish-workflow.js +233 -40
- package/lib/package-root.js +2 -0
- package/lib/pr-monitor/auto-actions.js +169 -28
- package/lib/pr-monitor/differ.js +110 -4
- package/lib/pr-monitor/events.js +0 -0
- package/lib/pr-monitor/flow-monitor.js +1424 -0
- package/lib/pr-monitor/gather.js +251 -44
- package/lib/pr-monitor/journal.js +0 -37
- package/lib/pr-monitor/monitor.js +117 -10
- package/lib/pr-monitor/process-identity.js +117 -0
- package/lib/pr-monitor/reconcile-executor.js +1101 -625
- package/lib/pr-monitor/reconcile.js +0 -0
- package/lib/pr-monitor/render-summary.js +121 -24
- package/lib/pr-monitor/review-preflight.js +269 -0
- package/lib/pr-monitor/shepherd-lease.js +28 -19
- package/lib/pr-monitor/verdict.js +438 -0
- package/lib/pr-monitor/watch-lifecycle.js +144 -38
- package/lib/pr-monitor/watch-owner.js +1414 -0
- package/lib/pr-monitor/watch.js +129 -58
- package/lib/pr-shepherd.js +17 -3
- package/lib/project-memory.js +145 -3
- package/lib/protected-state-authority.js +799 -4
- package/lib/protected-state-surfaces.js +181 -3
- package/lib/release-readiness.js +2 -3
- package/lib/review-adapter.js +65 -0
- package/lib/skills-sync.js +65 -32
- package/lib/validation/risk-manifest.js +339 -0
- package/lib/validation-receipt.js +190 -0
- package/lib/workflow/enforce-stage.js +44 -0
- package/lib/workflow/plan-authority.js +225 -0
- package/node_modules/@forge/contracts/compatibility-matrix.v1.json +1 -0
- package/node_modules/@forge/contracts/contract-baseline.v1.json +134 -0
- package/node_modules/@forge/contracts/fixtures/v1/canonical-hash.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/contract-inputs.v1.json +71 -0
- package/node_modules/@forge/contracts/fixtures/v1/feedback-secret-reject.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/identity-conflict.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/malformed-envelope-inputs.v1.json +16 -0
- package/node_modules/@forge/contracts/fixtures/v1/malformed-missing-required.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/monitor-bounds-reject.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/monitor-receipt-privacy-reject.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/not-executed-receipt.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/privacy-redaction.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/retry-identical.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/stale-authority-reject.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/structured-error-privacy-reject.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/structured-error-stripe-live-reject.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/structured-error-stripe-test-reject.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/unknown-advisory-roundtrip.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/unknown-consequential-reject.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/valid-full.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/valid-minimal.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/wrong-capability-digest.json +1 -0
- package/node_modules/@forge/contracts/index.js +32 -0
- package/node_modules/@forge/contracts/package.json +35 -0
- package/node_modules/@forge/contracts/schemas/v1/capability-manifest.schema.json +1 -0
- package/node_modules/@forge/contracts/schemas/v1/claim-request.schema.json +1 -0
- package/node_modules/@forge/contracts/schemas/v1/context-packet.schema.json +1 -0
- package/node_modules/@forge/contracts/schemas/v1/delivery-receipt.schema.json +1 -0
- package/node_modules/@forge/contracts/schemas/v1/feedback-report.schema.json +1 -0
- package/node_modules/@forge/contracts/schemas/v1/lease-receipt.schema.json +1 -0
- package/node_modules/@forge/contracts/schemas/v1/monitor-event.schema.json +1 -0
- package/node_modules/@forge/contracts/schemas/v1/monitor-receipt.schema.json +1 -0
- package/node_modules/@forge/contracts/schemas/v1/run-receipt.schema.json +1 -0
- package/node_modules/@forge/contracts/schemas/v1/structured-error.schema.json +1 -0
- package/node_modules/@forge/contracts/schemas/v1/work-packet.schema.json +1 -0
- package/node_modules/@forge/contracts/src/baseline.js +23 -0
- package/node_modules/@forge/contracts/src/canonical.js +151 -0
- package/node_modules/@forge/contracts/src/definitions.js +176 -0
- package/node_modules/@forge/contracts/src/identity.js +42 -0
- package/node_modules/@forge/contracts/src/schema.js +72 -0
- package/node_modules/@forge/contracts/src/validate.js +305 -0
- package/node_modules/@forge/flow/index.js +114 -0
- package/node_modules/@forge/flow/package.json +34 -0
- package/node_modules/@forge/flow/src/bounded-loop.js +415 -0
- package/node_modules/@forge/flow/src/efficiency-supervisor.js +89 -0
- package/node_modules/@forge/flow/src/executor.js +279 -0
- package/node_modules/@forge/flow/src/monitor-durability.js +419 -0
- package/node_modules/@forge/flow/src/monitor-runtime.js +460 -0
- package/node_modules/@forge/flow/src/process-lifecycle.js +469 -0
- package/node_modules/@forge/flow/src/skill-runtime.js +343 -0
- package/node_modules/@forge/memory/index.js +331 -0
- package/node_modules/@forge/memory/package.json +34 -0
- package/node_modules/@forge/memory/src/authority-provider.js +67 -0
- package/node_modules/@forge/memory/src/backend-registry.js +166 -0
- package/node_modules/@forge/memory/src/feedback-intake.js +243 -0
- package/node_modules/@forge/memory/src/pr-lifecycle-authority.js +946 -0
- package/node_modules/@forge/memory/src/usage-evidence.js +205 -0
- package/package.json +16 -4
- package/packages/flow/node_modules/@forge/contracts/compatibility-matrix.v1.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/contract-baseline.v1.json +134 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/canonical-hash.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/contract-inputs.v1.json +71 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/feedback-secret-reject.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/identity-conflict.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/malformed-envelope-inputs.v1.json +16 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/malformed-missing-required.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/monitor-bounds-reject.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/monitor-receipt-privacy-reject.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/not-executed-receipt.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/privacy-redaction.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/retry-identical.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/stale-authority-reject.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/structured-error-privacy-reject.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/structured-error-stripe-live-reject.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/structured-error-stripe-test-reject.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/unknown-advisory-roundtrip.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/unknown-consequential-reject.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/valid-full.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/valid-minimal.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/wrong-capability-digest.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/index.js +32 -0
- package/packages/flow/node_modules/@forge/contracts/package.json +35 -0
- package/packages/flow/node_modules/@forge/contracts/schemas/v1/capability-manifest.schema.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/schemas/v1/claim-request.schema.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/schemas/v1/context-packet.schema.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/schemas/v1/delivery-receipt.schema.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/schemas/v1/feedback-report.schema.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/schemas/v1/lease-receipt.schema.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/schemas/v1/monitor-event.schema.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/schemas/v1/monitor-receipt.schema.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/schemas/v1/run-receipt.schema.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/schemas/v1/structured-error.schema.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/schemas/v1/work-packet.schema.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/src/baseline.js +23 -0
- package/packages/flow/node_modules/@forge/contracts/src/canonical.js +151 -0
- package/packages/flow/node_modules/@forge/contracts/src/definitions.js +176 -0
- package/packages/flow/node_modules/@forge/contracts/src/identity.js +42 -0
- package/packages/flow/node_modules/@forge/contracts/src/schema.js +72 -0
- package/packages/flow/node_modules/@forge/contracts/src/validate.js +305 -0
- package/packages/memory/node_modules/@forge/contracts/compatibility-matrix.v1.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/contract-baseline.v1.json +134 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/canonical-hash.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/contract-inputs.v1.json +71 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/feedback-secret-reject.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/identity-conflict.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/malformed-envelope-inputs.v1.json +16 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/malformed-missing-required.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/monitor-bounds-reject.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/monitor-receipt-privacy-reject.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/not-executed-receipt.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/privacy-redaction.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/retry-identical.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/stale-authority-reject.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/structured-error-privacy-reject.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/structured-error-stripe-live-reject.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/structured-error-stripe-test-reject.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/unknown-advisory-roundtrip.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/unknown-consequential-reject.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/valid-full.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/valid-minimal.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/wrong-capability-digest.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/index.js +32 -0
- package/packages/memory/node_modules/@forge/contracts/package.json +35 -0
- package/packages/memory/node_modules/@forge/contracts/schemas/v1/capability-manifest.schema.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/schemas/v1/claim-request.schema.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/schemas/v1/context-packet.schema.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/schemas/v1/delivery-receipt.schema.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/schemas/v1/feedback-report.schema.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/schemas/v1/lease-receipt.schema.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/schemas/v1/monitor-event.schema.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/schemas/v1/monitor-receipt.schema.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/schemas/v1/run-receipt.schema.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/schemas/v1/structured-error.schema.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/schemas/v1/work-packet.schema.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/src/baseline.js +23 -0
- package/packages/memory/node_modules/@forge/contracts/src/canonical.js +151 -0
- package/packages/memory/node_modules/@forge/contracts/src/definitions.js +176 -0
- package/packages/memory/node_modules/@forge/contracts/src/identity.js +42 -0
- package/packages/memory/node_modules/@forge/contracts/src/schema.js +72 -0
- package/packages/memory/node_modules/@forge/contracts/src/validate.js +305 -0
- package/scripts/commitlint.js +13 -15
- package/scripts/generate-risk-manifest.js +91 -0
- package/scripts/github-context-bridge.sh +10 -0
- package/scripts/legacy-claim-repair.js +145 -0
- package/scripts/lib/behavioral-eval-runtime.js +3 -2
- package/scripts/process-tree.js +14 -2
- package/scripts/protected-state-check.js +440 -17
- package/scripts/sync-agent-skills.js +333 -34
- package/scripts/test-full-suite.js +704 -18
- package/scripts/test-profile.js +13 -3
- package/scripts/test.js +96 -15
- package/skills/coverage.json +1 -0
- package/skills/review/SKILL.md +2 -0
- package/skills/review/evals/scorecard.json +2 -2
- package/skills/setup/SKILL.md +18 -0
- package/skills/setup/evals/scorecard.json +3 -3
- package/skills/shepherd/SKILL.md +19 -2
- package/skills/shepherd/evals/scorecard.json +3 -3
- package/skills/validate/SKILL.md +3 -0
- package/skills/validate/evals/scorecard.json +1 -1
|
@@ -8,13 +8,66 @@ const fs = require('node:fs');
|
|
|
8
8
|
const path = require('node:path');
|
|
9
9
|
const { resolveOwnedKernel, closeIfOwned } = require('./kernel/owned-kernel');
|
|
10
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');
|
|
11
23
|
|
|
12
24
|
const PROTECTED_STATE_ENTITY_TYPE = 'protected_state';
|
|
13
25
|
const PROTECTED_STATE_AUTHORIZATION_ISSUED = 'protected_state.authorization.issued';
|
|
26
|
+
const PROTECTED_STATE_WRITE_COMPLETED = 'protected_state.write.completed';
|
|
14
27
|
const PROTECTED_STATE_AUTHORIZATION_CONSUMED = 'protected_state.authorization.consumed';
|
|
15
28
|
const PROTECTED_STATE_AUTHORIZATION_VERSION = 1;
|
|
16
29
|
const PROTECTED_STATE_AUTHORIZATION_ORIGIN = 'cli';
|
|
17
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
|
+
}
|
|
18
71
|
|
|
19
72
|
function resolveWorktreeScope(projectRoot, deps = {}) {
|
|
20
73
|
const resolved = path.resolve(projectRoot);
|
|
@@ -56,8 +109,12 @@ function parseAuthorizationEvent(row) {
|
|
|
56
109
|
surface: payload.surface,
|
|
57
110
|
contentHash: payload.contentHash,
|
|
58
111
|
operation: payload.operation,
|
|
112
|
+
viaForgeApi: payload.viaForgeApi === true,
|
|
59
113
|
sourceCommand: payload.sourceCommand,
|
|
114
|
+
sourceHead: payload.sourceHead,
|
|
60
115
|
worktreeScope: payload.worktreeScope,
|
|
116
|
+
writeIntent: payload.writeIntent,
|
|
117
|
+
targetBunVersion: payload.targetBunVersion,
|
|
61
118
|
};
|
|
62
119
|
}
|
|
63
120
|
|
|
@@ -71,6 +128,7 @@ function blockedDecision(request, reason) {
|
|
|
71
128
|
requiredSurface: request.surface,
|
|
72
129
|
declaredSurface: request.surface,
|
|
73
130
|
contentHash: hashProtectedContent(request.content),
|
|
131
|
+
sourceHead: request.sourceHead,
|
|
74
132
|
reason,
|
|
75
133
|
repairHint: 'Regenerate the protected file through its owning Forge command, then stage that exact output.',
|
|
76
134
|
};
|
|
@@ -83,6 +141,8 @@ function evaluateAuthorization(request, rows = []) {
|
|
|
83
141
|
surface: request.surface,
|
|
84
142
|
contentHash: hashProtectedContent(request.content),
|
|
85
143
|
worktreeScope: request.worktreeScope,
|
|
144
|
+
sourceHead: request.sourceHead,
|
|
145
|
+
writeIntent: request.operation === 'staged_delete' ? 'delete' : 'update',
|
|
86
146
|
};
|
|
87
147
|
const issued = rows
|
|
88
148
|
.filter(row => row?.event_type === PROTECTED_STATE_AUTHORIZATION_ISSUED)
|
|
@@ -103,15 +163,23 @@ function evaluateAuthorization(request, rows = []) {
|
|
|
103
163
|
return blockedDecision(request, 'Protected-state authority is ambiguous because multiple unconsumed authorizations exist; failing closed.');
|
|
104
164
|
}
|
|
105
165
|
const latest = active[0];
|
|
166
|
+
const writer = owningWriter(latest.path, latest.surface);
|
|
106
167
|
|
|
168
|
+
const bunPinWriter = [BUN_WORKFLOW_SOURCE_COMMAND, NPM_WORKFLOW_SOURCE_COMMAND].includes(writer?.sourceCommand);
|
|
107
169
|
const structurallyValid =
|
|
170
|
+
writer !== null &&
|
|
108
171
|
latest.version === PROTECTED_STATE_AUTHORIZATION_VERSION &&
|
|
109
172
|
typeof latest.capabilityId === 'string' && latest.capabilityId.length > 0 &&
|
|
110
173
|
latest.origin === PROTECTED_STATE_AUTHORIZATION_ORIGIN &&
|
|
111
174
|
latest.actor === latest.payloadActor &&
|
|
112
175
|
latest.worktreeScope === expected.worktreeScope &&
|
|
113
176
|
latest.entityId === authorizationEntityId(expected.worktreeScope, latest.path) &&
|
|
114
|
-
latest.
|
|
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));
|
|
115
183
|
if (!structurallyValid) {
|
|
116
184
|
return blockedDecision(request, 'The latest Forge-owned authorization is malformed or was not issued by the owning command.');
|
|
117
185
|
}
|
|
@@ -120,9 +188,36 @@ function evaluateAuthorization(request, rows = []) {
|
|
|
120
188
|
latest.actor !== expected.actor ||
|
|
121
189
|
latest.path !== expected.path ||
|
|
122
190
|
latest.surface !== expected.surface ||
|
|
123
|
-
latest.contentHash !== expected.contentHash
|
|
191
|
+
latest.contentHash !== expected.contentHash ||
|
|
192
|
+
latest.sourceHead !== expected.sourceHead ||
|
|
193
|
+
latest.writeIntent !== expected.writeIntent
|
|
124
194
|
) {
|
|
125
|
-
return blockedDecision(request, 'The latest Forge-owned
|
|
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.');
|
|
126
221
|
}
|
|
127
222
|
|
|
128
223
|
return {
|
|
@@ -134,8 +229,11 @@ function evaluateAuthorization(request, rows = []) {
|
|
|
134
229
|
requiredSurface: expected.surface,
|
|
135
230
|
declaredSurface: expected.surface,
|
|
136
231
|
contentHash: expected.contentHash,
|
|
232
|
+
sourceHead: expected.sourceHead,
|
|
233
|
+
viaForgeApi: true,
|
|
137
234
|
capabilityId: latest.capabilityId,
|
|
138
235
|
worktreeScope: latest.worktreeScope,
|
|
236
|
+
targetBunVersion: latest.targetBunVersion,
|
|
139
237
|
reason: 'Staged content matches the latest unconsumed Forge-owned authorization.',
|
|
140
238
|
repairHint: null,
|
|
141
239
|
};
|
|
@@ -169,7 +267,11 @@ async function issueProtectedStateAuthorization(projectRoot, request = {}, optio
|
|
|
169
267
|
contentHash: hashProtectedContent(request.content),
|
|
170
268
|
worktreeScope,
|
|
171
269
|
operation: request.operation || 'generate',
|
|
270
|
+
viaForgeApi: request.viaForgeApi === true,
|
|
172
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',
|
|
173
275
|
},
|
|
174
276
|
created_at: createdAt,
|
|
175
277
|
};
|
|
@@ -182,6 +284,10 @@ async function issueProtectedStateAuthorization(projectRoot, request = {}, optio
|
|
|
182
284
|
{},
|
|
183
285
|
kernel.config,
|
|
184
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
|
+
}
|
|
185
291
|
const consumedCapabilities = new Set((existingRows || [])
|
|
186
292
|
.filter(row => row?.event_type === PROTECTED_STATE_AUTHORIZATION_CONSUMED)
|
|
187
293
|
.map(row => parseAuthorizationEvent(row).capabilityId)
|
|
@@ -206,13 +312,19 @@ async function issueProtectedStateAuthorization(projectRoot, request = {}, optio
|
|
|
206
312
|
path: normalizedPath,
|
|
207
313
|
surface: existing.surface,
|
|
208
314
|
contentHash: existing.contentHash,
|
|
315
|
+
sourceHead: existing.sourceHead,
|
|
209
316
|
worktreeScope,
|
|
210
317
|
operation: 'superseded',
|
|
318
|
+
viaForgeApi: existing.viaForgeApi === true,
|
|
211
319
|
sourceCommand: request.sourceCommand || NPM_WORKFLOW_SOURCE_COMMAND,
|
|
212
320
|
},
|
|
213
321
|
created_at: createdAt,
|
|
214
322
|
}, {}, kernel.config);
|
|
215
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
|
+
}
|
|
216
328
|
const inserted = await kernel.driver.insertKernelEvent(event, {}, kernel.config);
|
|
217
329
|
return { success: true, capabilityId, event: parseAuthorizationEvent(inserted) };
|
|
218
330
|
} finally {
|
|
@@ -220,22 +332,687 @@ async function issueProtectedStateAuthorization(projectRoot, request = {}, optio
|
|
|
220
332
|
}
|
|
221
333
|
}
|
|
222
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
|
+
|
|
223
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
|
+
}
|
|
224
382
|
const {
|
|
225
383
|
NPM_PUBLISH_WORKFLOW_PATH,
|
|
226
384
|
renderNpmPublishWorkflow,
|
|
227
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
|
+
}
|
|
228
394
|
return issueProtectedStateAuthorization(projectRoot, {
|
|
229
395
|
actor: params.actor,
|
|
230
396
|
surface: 'workflows',
|
|
231
397
|
path: NPM_PUBLISH_WORKFLOW_PATH,
|
|
232
|
-
content
|
|
398
|
+
content,
|
|
233
399
|
operation: 'generate_npm_workflow',
|
|
400
|
+
viaForgeApi: true,
|
|
234
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
|
+
},
|
|
235
413
|
}, options);
|
|
236
414
|
}
|
|
237
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
|
+
|
|
238
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
|
+
}
|
|
239
1016
|
if (requests.length === 0) return { success: true, decisions: [] };
|
|
240
1017
|
const worktreeScope = options.worktreeScope || resolveWorktreeScope(projectRoot, options.deps);
|
|
241
1018
|
const kernel = await resolveOwnedKernel(projectRoot, options.deps);
|
|
@@ -256,10 +1033,15 @@ async function authorizeAndConsumeProtectedStateWrites(projectRoot, requests = [
|
|
|
256
1033
|
worktreeScope,
|
|
257
1034
|
}, rows || []));
|
|
258
1035
|
}
|
|
1036
|
+
validateBunDecisionTargets(projectRoot, decisions);
|
|
259
1037
|
|
|
260
1038
|
if (decisions.some(decision => !decision.allowed)) {
|
|
261
1039
|
return { success: false, decisions };
|
|
262
1040
|
}
|
|
1041
|
+
const currentBunBatch = (options.validateCompleteBunPinBatch || validateCompleteBunPinBatch)(projectRoot, options);
|
|
1042
|
+
if (!currentBunBatch.success) {
|
|
1043
|
+
return { success: false, decisions, batchDecision: blockedBunBatchDecision(currentBunBatch.error) };
|
|
1044
|
+
}
|
|
263
1045
|
|
|
264
1046
|
for (const decision of decisions) {
|
|
265
1047
|
const createdAt = options.now || new Date().toISOString();
|
|
@@ -278,8 +1060,11 @@ async function authorizeAndConsumeProtectedStateWrites(projectRoot, requests = [
|
|
|
278
1060
|
path: decision.path,
|
|
279
1061
|
surface: decision.requiredSurface,
|
|
280
1062
|
contentHash: decision.contentHash,
|
|
1063
|
+
sourceHead: decision.sourceHead,
|
|
281
1064
|
worktreeScope: decision.worktreeScope,
|
|
1065
|
+
writeIntent: decision.operation === 'staged_delete' ? 'delete' : 'update',
|
|
282
1066
|
operation: 'staged_edit',
|
|
1067
|
+
viaForgeApi: decision.viaForgeApi === true,
|
|
283
1068
|
sourceCommand: 'scripts/protected-state-check.js',
|
|
284
1069
|
},
|
|
285
1070
|
created_at: createdAt,
|
|
@@ -295,11 +1080,21 @@ async function authorizeAndConsumeProtectedStateWrites(projectRoot, requests = [
|
|
|
295
1080
|
module.exports = {
|
|
296
1081
|
PROTECTED_STATE_ENTITY_TYPE,
|
|
297
1082
|
PROTECTED_STATE_AUTHORIZATION_ISSUED,
|
|
1083
|
+
PROTECTED_STATE_WRITE_COMPLETED,
|
|
298
1084
|
PROTECTED_STATE_AUTHORIZATION_CONSUMED,
|
|
1085
|
+
isValidGitObjectId,
|
|
299
1086
|
resolveWorktreeScope,
|
|
300
1087
|
authorizationEntityId,
|
|
301
1088
|
parseAuthorizationEvent,
|
|
302
1089
|
evaluateAuthorization,
|
|
303
1090
|
issueNpmPublishWorkflowAuthorization,
|
|
1091
|
+
completeNpmPublishWorkflowAuthorization,
|
|
1092
|
+
issueBunWorkflowAuthorization,
|
|
1093
|
+
completeBunWorkflowAuthorization,
|
|
1094
|
+
cancelBunWorkflowBatchAuthorizations,
|
|
1095
|
+
issueGeneratedHarnessClaudeAuthorization,
|
|
1096
|
+
completeGeneratedHarnessClaudeAuthorization,
|
|
1097
|
+
issueGeneratedHarnessSkillAuthorization,
|
|
1098
|
+
completeGeneratedHarnessSkillAuthorization,
|
|
304
1099
|
authorizeAndConsumeProtectedStateWrites,
|
|
305
1100
|
};
|