@astrosheep/keiyaku 2.9.8 → 2.9.10
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/README.md +50 -182
- package/build/.tsbuildinfo +1 -1
- package/build/agents/harness/control-types.js +18 -1
- package/build/agents/harness/index.js +2 -0
- package/build/agents/harness/outcome-error.js +81 -0
- package/build/agents/harness/projection.js +10 -7
- package/build/agents/harness/router.js +5 -1
- package/build/agents/providers/claude-agent-sdk/adapter.js +18 -4
- package/build/agents/providers/claude-agent-sdk/registration.js +11 -1
- package/build/agents/providers/claude-agent-sdk/session.js +19 -5
- package/build/agents/providers/codex-app-server/adapter.js +11 -6
- package/build/agents/providers/codex-app-server/events.js +67 -6
- package/build/agents/providers/codex-app-server/registration.js +11 -1
- package/build/agents/providers/opencode-sdk/adapter.js +8 -1
- package/build/agents/providers/opencode-sdk/events.js +24 -2
- package/build/agents/providers/opencode-sdk/registration.js +11 -1
- package/build/agents/providers/pi/checkpoint.js +9 -1
- package/build/agents/providers/pi/events.js +2 -0
- package/build/agents/providers/pi/registration.js +14 -1
- package/build/cli/commands/akuma/akuma/handler.js +1 -1
- package/build/cli/commands/akuma/akuma/meta.js +3 -3
- package/build/cli/commands/akuma/catalog.js +2 -2
- package/build/cli/commands/akuma/{view → show}/handler.js +5 -5
- package/build/cli/commands/akuma/{view → show}/meta.js +3 -3
- package/build/cli/commands/akuma.js +1 -1
- package/build/cli/commands/contract/amend/handler.js +5 -4
- package/build/cli/commands/contract/arc/handler.js +5 -4
- package/build/cli/commands/contract/audit/handler.js +2 -2
- package/build/cli/commands/contract/bind/handler.js +12 -3
- package/build/cli/commands/contract/bind/meta.js +45 -15
- package/build/cli/commands/contract/forfeit/handler.js +9 -5
- package/build/cli/commands/contract/log/handler.js +2 -2
- package/build/cli/commands/contract/petition/handler.js +34 -7
- package/build/cli/commands/contract/renew/handler.js +7 -7
- package/build/cli/commands/contract/renew/meta.js +3 -3
- package/build/cli/commands/metadata.js +10 -6
- package/build/cli/commands/projection/call/meta.js +1 -1
- package/build/cli/commands/projection/catalog.js +2 -0
- package/build/cli/commands/projection/history/handler.js +21 -0
- package/build/cli/commands/projection/history/meta.js +9 -0
- package/build/cli/commands/projection/shared.js +3 -4
- package/build/cli/commands/projection/status/handler.js +3 -3
- package/build/cli/commands/projection/wait/handler.js +20 -11
- package/build/cli/commands/task/add/handler.js +14 -5
- package/build/cli/commands/task/add/meta.js +2 -2
- package/build/cli/commands/task/catalog.js +4 -0
- package/build/cli/commands/task/done/handler.js +1 -4
- package/build/cli/commands/task/done/meta.js +1 -1
- package/build/cli/commands/task/drop/meta.js +1 -1
- package/build/cli/commands/task/hold/handler.js +3 -0
- package/build/cli/commands/task/hold/meta.js +8 -0
- package/build/cli/commands/task/log/handler.js +1 -1
- package/build/cli/commands/task/log/meta.js +2 -2
- package/build/cli/commands/task/resume/handler.js +3 -0
- package/build/cli/commands/task/resume/meta.js +8 -0
- package/build/cli/commands/task/shared.js +39 -14
- package/build/cli/commands/task/show/handler.js +1 -1
- package/build/cli/commands/task/show/meta.js +1 -1
- package/build/cli/commands/task/start/meta.js +1 -1
- package/build/cli/commands/task/stop/handler.js +1 -4
- package/build/cli/commands/task/stop/meta.js +1 -1
- package/build/cli/commands/task/update/handler.js +11 -3
- package/build/cli/commands/task/update/meta.js +3 -3
- package/build/cli/completion.js +3 -2
- package/build/cli/flags.js +51 -14
- package/build/cli/help.js +4 -1
- package/build/cli/index.js +40 -25
- package/build/cli/parse-flags.js +40 -7
- package/build/cli/parse-metadata.js +5 -2
- package/build/cli/parse-selectors.js +2 -3
- package/build/cli/parse.js +40 -6
- package/build/cli/projection-address.js +12 -10
- package/build/cli/render/amendment-warning.js +8 -0
- package/build/cli/render/arc.js +6 -2
- package/build/cli/render/audit.js +400 -81
- package/build/cli/render/kanshi.js +242 -79
- package/build/cli/render/lifecycle.js +56 -0
- package/build/cli/render/misc.js +24 -28
- package/build/cli/render/petition.js +5 -0
- package/build/cli/render/projection-activity.js +65 -29
- package/build/cli/render/projection-history.js +64 -0
- package/build/cli/render/shared.js +23 -9
- package/build/cli/render/status.js +13 -35
- package/build/cli/render/task.js +51 -26
- package/build/cli/render/terminal-failure.js +3 -1
- package/build/cli/render/wait.js +45 -69
- package/build/cli/subagent-guard.js +1 -1
- package/build/cli/types.js +1 -1
- package/build/config/env-keys.js +3 -1
- package/build/config/env.js +24 -1
- package/build/config/settings/schema.js +3 -1
- package/build/core/address-carrier.js +89 -0
- package/build/core/addressing.js +255 -196
- package/build/core/amend.js +115 -38
- package/build/core/amendment-criteria.js +17 -0
- package/build/core/amendment-warning.js +10 -0
- package/build/core/arc.js +184 -83
- package/build/core/audit/candidate.js +110 -9
- package/build/core/audit/coordinates.js +120 -65
- package/build/core/audit/evidence.js +19 -23
- package/build/core/audit/facade.js +34 -11
- package/build/core/audit/report.js +5 -5
- package/build/core/bind-reconciliation.js +390 -0
- package/build/core/bind-workspace.js +12 -20
- package/build/core/bind.js +242 -273
- package/build/core/call/call.js +1 -0
- package/build/core/call/context.js +46 -71
- package/build/core/call/prompt.js +6 -3
- package/build/core/context.js +49 -35
- package/build/core/contract-carrier-runtime.js +408 -0
- package/build/core/contract-carrier.js +270 -0
- package/build/core/contract-view.js +33 -27
- package/build/core/contract.js +5 -1
- package/build/core/derived-replay.js +207 -0
- package/build/core/draft.js +125 -57
- package/build/core/forfeit.js +113 -56
- package/build/core/identifier-slug.js +63 -13
- package/build/core/ids.js +4 -4
- package/build/core/lifecycle-history.js +35 -0
- package/build/core/lifecycle-recovery.js +117 -0
- package/build/core/lifecycle-runner.js +377 -0
- package/build/core/log.js +37 -11
- package/build/core/outcome-base.js +181 -0
- package/build/core/process-group.js +139 -0
- package/build/core/projection/generation/database.js +82 -7
- package/build/core/projection/generation/model.js +9 -36
- package/build/core/projection/generation/projection-generation-continuation.js +10 -22
- package/build/core/projection/generation/projection-generation-execution.js +13 -6
- package/build/core/projection/index.js +5 -4
- package/build/core/projection/projection-activity.js +29 -8
- package/build/core/projection/projection-alias.js +23 -10
- package/build/core/projection/projection-core.js +21 -7
- package/build/core/projection/projection-execution-observer.js +91 -39
- package/build/core/projection/projection-history.js +474 -0
- package/build/core/projection/projection-runner-lock.js +12 -0
- package/build/core/projection/projection-status-observation.js +365 -0
- package/build/core/projection/projection-status.js +35 -337
- package/build/core/projection/projection-terminal-failure.js +13 -8
- package/build/core/projection/projection-wait.js +7 -2
- package/build/core/projection/tell/launch-store.js +244 -0
- package/build/core/projection/tell/model.js +3 -3
- package/build/core/projection/tell/store.js +70 -118
- package/build/core/registry.js +22 -87
- package/build/core/renew-build.js +424 -0
- package/build/core/renew-plan.js +239 -0
- package/build/core/renew-rewrite.js +77 -0
- package/build/core/renew.js +295 -520
- package/build/core/repository-ledger/accepted-fold-read.js +19 -0
- package/build/core/repository-ledger/accepted-tail.js +116 -0
- package/build/core/repository-ledger/admission.js +38 -0
- package/build/core/repository-ledger/authoritative-transaction.js +1 -0
- package/build/core/repository-ledger/claim-evidence.js +147 -0
- package/build/core/repository-ledger/codec.js +297 -0
- package/build/core/repository-ledger/current-state-store.js +853 -0
- package/build/core/repository-ledger/fold-repository.js +375 -0
- package/build/core/repository-ledger/fold.js +248 -0
- package/build/core/repository-ledger/git-object-store.js +243 -0
- package/build/core/repository-ledger/history-read.js +15 -0
- package/build/core/repository-ledger/incremental-read.js +153 -0
- package/build/core/repository-ledger/index.js +15 -0
- package/build/core/repository-ledger/inventory-plan.js +75 -0
- package/build/core/repository-ledger/inventory.js +310 -0
- package/build/core/repository-ledger/publication-admission.js +125 -0
- package/build/core/repository-ledger/publication.js +353 -0
- package/build/core/repository-ledger/read-model.js +469 -0
- package/build/core/repository-ledger/transaction-membership.js +78 -0
- package/build/core/repository-ledger/traversal.js +404 -0
- package/build/core/repository-ledger/write-transaction.js +339 -0
- package/build/core/seal.js +168 -161
- package/build/core/settlement/claim-delivery.js +42 -215
- package/build/core/settlement/claim.js +235 -153
- package/build/core/settlement/index.js +4 -4
- package/build/core/settlement/petition-claim-gates.js +52 -33
- package/build/core/settlement/petition-forfeit.js +12 -5
- package/build/core/settlement/petition-head-guard.js +8 -5
- package/build/core/settlement/petition-preview.js +68 -56
- package/build/core/settlement/petition-window.js +50 -0
- package/build/core/settlement/petition.js +252 -131
- package/build/core/settlement/queue-read-model.js +72 -95
- package/build/core/settlement/queue-seat-allocation.js +35 -126
- package/build/core/settlement/queue.js +1 -1
- package/build/core/settlement/settlement.js +172 -80
- package/build/core/settlement/verdict.js +112 -66
- package/build/core/settlement/verification-coordination.js +305 -0
- package/build/core/settlement/verification-supervisor.js +275 -0
- package/build/core/settlement/verification.js +398 -31
- package/build/core/status/board.js +125 -127
- package/build/core/status/lifecycle.js +7 -80
- package/build/core/structured-query.js +23 -0
- package/build/core/target-ref.js +1 -1
- package/build/core/task/board.js +59 -24
- package/build/core/task/commands.js +181 -33
- package/build/core/task/coordinate.js +56 -0
- package/build/core/task/document.js +25 -9
- package/build/core/task/index.js +7 -4
- package/build/core/task/query.js +17 -27
- package/build/core/task/settlement-git.js +164 -179
- package/build/core/task/settlement-policy.js +15 -11
- package/build/core/task/source-board.js +31 -13
- package/build/core/task/task-bind-preparation.js +193 -0
- package/build/core/task/task-contract.js +102 -193
- package/build/core/task/task-file-transaction.js +171 -0
- package/build/core/task/task-git-runtime.js +76 -14
- package/build/core/task/task-git-store.js +68 -169
- package/build/core/task/task-store-repository.js +13 -6
- package/build/core/task/task-worktree.js +6 -33
- package/build/core/task/task.js +1 -1
- package/build/core/task/tree.js +3 -3
- package/build/core/task/validation.js +4 -3
- package/build/core/transcripts.js +93 -1
- package/build/core/worktree-bootstrap.js +44 -36
- package/build/core/worktree-path.js +210 -77
- package/build/flow-error.js +7 -7
- package/build/generated/version.js +2 -2
- package/build/git/core.js +76 -13
- package/build/git/refs.js +168 -46
- package/build/git/streaming-batch.js +73 -13
- package/package.json +1 -1
- package/skills/keiyaku/SKILL.md +1 -1
- package/skills/keiyaku-akuma/SKILL.md +3 -3
- package/skills/keiyaku-task/SKILL.md +4 -2
- package/skills/keiyaku-workflow/SKILL.md +88 -203
- package/build/core/entry.js +0 -305
- package/build/core/ledger-batch.js +0 -194
- package/build/core/ledger.js +0 -84
- package/build/core/projection/leash.js +0 -87
- package/build/core/ref-log.js +0 -119
- package/build/core/renew-session.js +0 -128
- package/build/core/status/ledger-batch.js +0 -1
- package/build/core/status/reconciliation.js +0 -137
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import { ProjectionStateError } from "../../atomic-publish.js";
|
|
2
|
+
import { withRead, withWrite } from "./database.js";
|
|
3
|
+
import { parseFence, requireExecutionId, submittedFromRow } from "./model.js";
|
|
4
|
+
function selectTell(database, tellId) {
|
|
5
|
+
return database.prepare(`
|
|
6
|
+
SELECT tell_id, text, created_at, effort, state, execution_id, fence_json, replayed_from_json
|
|
7
|
+
FROM projection_tells WHERE tell_id = ?
|
|
8
|
+
`).get(tellId);
|
|
9
|
+
}
|
|
10
|
+
export function claimFencedTells(projectionDirectory, fence, executionId) {
|
|
11
|
+
const owner = requireExecutionId(executionId);
|
|
12
|
+
return withWrite(projectionDirectory, (database) => {
|
|
13
|
+
const claimed = [];
|
|
14
|
+
const seen = new Set();
|
|
15
|
+
for (const tellId of fence) {
|
|
16
|
+
if (seen.has(tellId))
|
|
17
|
+
throw new ProjectionStateError(`duplicate tell in generation fence: ${tellId}`);
|
|
18
|
+
seen.add(tellId);
|
|
19
|
+
const row = selectTell(database, tellId);
|
|
20
|
+
if (!row)
|
|
21
|
+
throw new ProjectionStateError(`generation tell fence member is missing: ${tellId}`);
|
|
22
|
+
if (row.state === "inbox") {
|
|
23
|
+
database.prepare(`
|
|
24
|
+
UPDATE projection_tells
|
|
25
|
+
SET state = 'inflight'
|
|
26
|
+
WHERE tell_id = ? AND state = 'inbox'
|
|
27
|
+
`).run(tellId);
|
|
28
|
+
claimed.push(tellId);
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
if (row.state === "inflight") {
|
|
32
|
+
claimed.push(tellId);
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
if (row.state === "submitted" || row.state === "seen") {
|
|
36
|
+
const submitted = submittedFromRow(row);
|
|
37
|
+
if (submitted.executionId === owner) {
|
|
38
|
+
throw new ProjectionStateError(`submitted tell belongs to current execution: ${tellId}`);
|
|
39
|
+
}
|
|
40
|
+
const replayedFrom = [
|
|
41
|
+
...(submitted.replayedFrom ?? []),
|
|
42
|
+
{ executionId: submitted.executionId, fence: submitted.fence },
|
|
43
|
+
];
|
|
44
|
+
const result = database.prepare(`
|
|
45
|
+
UPDATE projection_tells
|
|
46
|
+
SET
|
|
47
|
+
state = 'inflight',
|
|
48
|
+
execution_id = NULL,
|
|
49
|
+
fence_json = NULL,
|
|
50
|
+
replayed_from_json = ?
|
|
51
|
+
WHERE tell_id = ? AND state = ?
|
|
52
|
+
`).run(JSON.stringify(replayedFrom), tellId, row.state);
|
|
53
|
+
if (Number(result.changes) !== 1) {
|
|
54
|
+
throw new ProjectionStateError(`generation tell fence member changed while claiming: ${tellId}`);
|
|
55
|
+
}
|
|
56
|
+
claimed.push(tellId);
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
if (row.state === "consumed" && row.execution_id === owner)
|
|
60
|
+
continue;
|
|
61
|
+
throw new ProjectionStateError(`generation tell fence member is not claimable: ${tellId}`);
|
|
62
|
+
}
|
|
63
|
+
return claimed;
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* The generation launch reducer: one heart transaction validates the frozen
|
|
68
|
+
* fence, claims exactly its non-consumed members, and establishes the matching
|
|
69
|
+
* immutable reservation before any provider can start.
|
|
70
|
+
*/
|
|
71
|
+
export function claimAndReserveFencedTells(projectionDirectory, fence, executionId) {
|
|
72
|
+
const owner = requireExecutionId(executionId);
|
|
73
|
+
return withWrite(projectionDirectory, (database) => {
|
|
74
|
+
const seen = new Set();
|
|
75
|
+
for (const tellId of fence) {
|
|
76
|
+
if (seen.has(tellId))
|
|
77
|
+
throw new ProjectionStateError(`duplicate tell in generation fence: ${tellId}`);
|
|
78
|
+
seen.add(tellId);
|
|
79
|
+
}
|
|
80
|
+
const existing = database.prepare(`
|
|
81
|
+
SELECT tell_id FROM projection_tell_launch_batches
|
|
82
|
+
WHERE execution_id = ? ORDER BY tell_id
|
|
83
|
+
`).all(owner);
|
|
84
|
+
const reservationExists = database.prepare(`
|
|
85
|
+
SELECT 1 FROM projection_tell_launch_admissions WHERE execution_id = ?
|
|
86
|
+
`).get(owner) !== undefined;
|
|
87
|
+
if (reservationExists) {
|
|
88
|
+
const requested = [...seen].sort();
|
|
89
|
+
const reserved = existing.map((row) => row.tell_id);
|
|
90
|
+
if (reserved.length !== requested.length || reserved.some((tellId, index) => tellId !== requested[index])) {
|
|
91
|
+
throw new ProjectionStateError(`launch batch conflict for execution '${owner}'`);
|
|
92
|
+
}
|
|
93
|
+
// A retry is an insert-or-verify of immutable membership. Receipt stages
|
|
94
|
+
// may already have advanced, so it must not re-run claim validation.
|
|
95
|
+
return reserved;
|
|
96
|
+
}
|
|
97
|
+
const claimed = [];
|
|
98
|
+
for (const tellId of fence) {
|
|
99
|
+
const row = selectTell(database, tellId);
|
|
100
|
+
if (!row)
|
|
101
|
+
throw new ProjectionStateError(`generation tell fence member is missing: ${tellId}`);
|
|
102
|
+
if (row.state === "inbox") {
|
|
103
|
+
database.prepare(`UPDATE projection_tells SET state = 'inflight' WHERE tell_id = ? AND state = 'inbox'`).run(tellId);
|
|
104
|
+
claimed.push(tellId);
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
if (row.state === "inflight") {
|
|
108
|
+
claimed.push(tellId);
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
if (row.state === "submitted" || row.state === "seen") {
|
|
112
|
+
const submitted = submittedFromRow(row);
|
|
113
|
+
if (submitted.executionId === owner)
|
|
114
|
+
throw new ProjectionStateError(`submitted tell belongs to current execution: ${tellId}`);
|
|
115
|
+
const replayedFrom = [...(submitted.replayedFrom ?? []), { executionId: submitted.executionId, fence: submitted.fence }];
|
|
116
|
+
const result = database.prepare(`
|
|
117
|
+
UPDATE projection_tells
|
|
118
|
+
SET state = 'inflight', execution_id = NULL, fence_json = NULL, replayed_from_json = ?
|
|
119
|
+
WHERE tell_id = ? AND state = ?
|
|
120
|
+
`).run(JSON.stringify(replayedFrom), tellId, row.state);
|
|
121
|
+
if (Number(result.changes) !== 1)
|
|
122
|
+
throw new ProjectionStateError(`generation tell fence member changed while claiming: ${tellId}`);
|
|
123
|
+
claimed.push(tellId);
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
if (row.state !== "consumed" || row.execution_id !== owner) {
|
|
127
|
+
throw new ProjectionStateError(`generation tell fence member is not claimable: ${tellId}`);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
database.prepare(`INSERT INTO projection_tell_launch_admissions (execution_id) VALUES (?)`).run(owner);
|
|
131
|
+
for (const tellId of claimed) {
|
|
132
|
+
database.prepare(`INSERT INTO projection_tell_launch_batches (execution_id, tell_id) VALUES (?, ?)`).run(owner, tellId);
|
|
133
|
+
}
|
|
134
|
+
return claimed;
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Atomically bind an adapter-owned native coordinate to a pre-existing launch
|
|
139
|
+
* reservation. Heart never derives a coordinate from later activity.
|
|
140
|
+
*/
|
|
141
|
+
export function bindTellLaunchReservation(projectionDirectory, admission) {
|
|
142
|
+
const executionId = requireExecutionId(admission.executionId);
|
|
143
|
+
const fence = parseFence(admission.fence);
|
|
144
|
+
withWrite(projectionDirectory, (database) => {
|
|
145
|
+
const unique = new Set();
|
|
146
|
+
for (const tellId of admission.tellIds) {
|
|
147
|
+
if (unique.has(tellId))
|
|
148
|
+
throw new ProjectionStateError(`duplicate tell in launch batch: ${tellId}`);
|
|
149
|
+
unique.add(tellId);
|
|
150
|
+
}
|
|
151
|
+
const established = database.prepare(`
|
|
152
|
+
SELECT 1 FROM projection_tell_launch_admissions WHERE execution_id = ?
|
|
153
|
+
`).get(executionId) !== undefined;
|
|
154
|
+
const existing = database.prepare(`
|
|
155
|
+
SELECT tell_id FROM projection_tell_launch_batches
|
|
156
|
+
WHERE execution_id = ? ORDER BY tell_id
|
|
157
|
+
`).all(executionId);
|
|
158
|
+
const requested = [...unique].sort();
|
|
159
|
+
if (!established) {
|
|
160
|
+
throw new ProjectionStateError(`launch admission has no reservation for execution '${executionId}'`);
|
|
161
|
+
}
|
|
162
|
+
const admitted = existing.map((row) => row.tell_id);
|
|
163
|
+
if (admitted.length !== requested.length || admitted.some((tellId, index) => tellId !== requested[index])) {
|
|
164
|
+
throw new ProjectionStateError(`launch batch conflict for execution '${executionId}'`);
|
|
165
|
+
}
|
|
166
|
+
database.prepare(`
|
|
167
|
+
INSERT INTO projection_tell_launch_coordinates (execution_id, fence_json)
|
|
168
|
+
VALUES (?, ?)
|
|
169
|
+
ON CONFLICT(execution_id, fence_json) DO NOTHING
|
|
170
|
+
`).run(executionId, JSON.stringify(fence));
|
|
171
|
+
for (const tellId of unique) {
|
|
172
|
+
const row = selectTell(database, tellId);
|
|
173
|
+
if (!row)
|
|
174
|
+
throw new ProjectionStateError(`tell '${tellId}' does not exist`);
|
|
175
|
+
if (row.state === "inflight") {
|
|
176
|
+
database.prepare(`
|
|
177
|
+
UPDATE projection_tells SET state = 'submitted', execution_id = ?, fence_json = ?
|
|
178
|
+
WHERE tell_id = ? AND state = 'inflight'
|
|
179
|
+
`).run(executionId, JSON.stringify(fence), tellId);
|
|
180
|
+
}
|
|
181
|
+
else if (row.execution_id !== executionId || !["submitted", "seen", "consumed"].includes(row.state)) {
|
|
182
|
+
throw new ProjectionStateError(`launch batch tell changed while admitting: ${tellId}`);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
export function listTellLaunchBatch(projectionDirectory, executionId) {
|
|
188
|
+
const owner = requireExecutionId(executionId);
|
|
189
|
+
return withRead(projectionDirectory, (database) => database.prepare(`
|
|
190
|
+
SELECT tell_id FROM projection_tell_launch_batches
|
|
191
|
+
WHERE execution_id = ?
|
|
192
|
+
ORDER BY tell_id
|
|
193
|
+
`).all(owner).map((row) => row.tell_id));
|
|
194
|
+
}
|
|
195
|
+
export function listTellLaunchAdmissions(projectionDirectory, executionId) {
|
|
196
|
+
const owner = requireExecutionId(executionId);
|
|
197
|
+
return withRead(projectionDirectory, (database) => {
|
|
198
|
+
const tells = database.prepare(`
|
|
199
|
+
SELECT tell_id FROM projection_tell_launch_batches
|
|
200
|
+
WHERE execution_id = ? ORDER BY tell_id
|
|
201
|
+
`).all(owner);
|
|
202
|
+
return database.prepare(`
|
|
203
|
+
SELECT fence_json FROM projection_tell_launch_coordinates
|
|
204
|
+
WHERE execution_id = ? ORDER BY fence_json
|
|
205
|
+
`).all(owner).map((row) => ({
|
|
206
|
+
fence: parseFence(JSON.parse(row.fence_json)),
|
|
207
|
+
tellIds: tells.map((tell) => tell.tell_id),
|
|
208
|
+
}));
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
export function isTellLaunchAdmissionMember(projectionDirectory, executionId, tellId) {
|
|
212
|
+
const owner = requireExecutionId(executionId);
|
|
213
|
+
return withRead(projectionDirectory, (database) => database.prepare(`
|
|
214
|
+
SELECT 1 FROM projection_tell_launch_batches
|
|
215
|
+
WHERE execution_id = ? AND tell_id = ?
|
|
216
|
+
`).get(owner, tellId) !== undefined);
|
|
217
|
+
}
|
|
218
|
+
export function markTellSubmitted(projectionDirectory, tellId, fields) {
|
|
219
|
+
const executionId = requireExecutionId(fields.executionId);
|
|
220
|
+
const fence = parseFence(fields.fence);
|
|
221
|
+
const replayedFrom = (fields.replayedFrom ?? []).map((entry) => ({
|
|
222
|
+
executionId: requireExecutionId(entry.executionId),
|
|
223
|
+
fence: parseFence(entry.fence),
|
|
224
|
+
}));
|
|
225
|
+
withWrite(projectionDirectory, (database) => {
|
|
226
|
+
const row = selectTell(database, tellId);
|
|
227
|
+
if (!row)
|
|
228
|
+
throw new ProjectionStateError(`tell '${tellId}' does not exist`);
|
|
229
|
+
if (row.state === "submitted")
|
|
230
|
+
return;
|
|
231
|
+
if (row.state !== "inflight") {
|
|
232
|
+
throw new ProjectionStateError(`tell '${tellId}' is not inflight`);
|
|
233
|
+
}
|
|
234
|
+
database.prepare(`
|
|
235
|
+
UPDATE projection_tells
|
|
236
|
+
SET
|
|
237
|
+
state = 'submitted',
|
|
238
|
+
execution_id = ?,
|
|
239
|
+
fence_json = ?,
|
|
240
|
+
replayed_from_json = COALESCE(?, replayed_from_json)
|
|
241
|
+
WHERE tell_id = ? AND state = 'inflight'
|
|
242
|
+
`).run(executionId, JSON.stringify(fence), replayedFrom.length > 0 ? JSON.stringify(replayedFrom) : null, tellId);
|
|
243
|
+
});
|
|
244
|
+
}
|
|
@@ -3,10 +3,10 @@ export const PROJECTION_TELL_STATES = [
|
|
|
3
3
|
"inbox",
|
|
4
4
|
"inflight",
|
|
5
5
|
"submitted",
|
|
6
|
+
"seen",
|
|
6
7
|
"consumed",
|
|
7
|
-
"delivered",
|
|
8
8
|
];
|
|
9
|
-
export const PENDING_PROJECTION_TELL_STATES = ["inbox", "inflight", "submitted"];
|
|
9
|
+
export const PENDING_PROJECTION_TELL_STATES = ["inbox", "inflight", "submitted", "seen"];
|
|
10
10
|
export function requireExecutionId(value) {
|
|
11
11
|
if (typeof value !== "string" || value.trim() === "") {
|
|
12
12
|
throw new ProjectionStateError("submitted tell executionId must be a non-empty string");
|
|
@@ -63,7 +63,7 @@ export function originalFromRow(row) {
|
|
|
63
63
|
};
|
|
64
64
|
}
|
|
65
65
|
export function submittedFromRow(row) {
|
|
66
|
-
if (row.state !== "submitted" && row.state !== "
|
|
66
|
+
if (row.state !== "submitted" && row.state !== "seen" && row.state !== "consumed") {
|
|
67
67
|
throw new ProjectionStateError(`tell '${row.tell_id}' is not submitted`);
|
|
68
68
|
}
|
|
69
69
|
if (row.execution_id === null || row.fence_json === null) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ProjectionStateError, randomHex8 } from "../../atomic-publish.js";
|
|
2
2
|
import { withRead, withReadSnapshot, withWrite } from "./database.js";
|
|
3
3
|
import { assertTellIdRandomHex, originalFromRow, PENDING_PROJECTION_TELL_STATES, parseFence, requireExecutionId, submittedFromRow, } from "./model.js";
|
|
4
|
+
export { bindTellLaunchReservation, claimAndReserveFencedTells, claimFencedTells, isTellLaunchAdmissionMember, listTellLaunchAdmissions, listTellLaunchBatch, markTellSubmitted, } from "./launch-store.js";
|
|
4
5
|
function selectTell(database, tellId) {
|
|
5
6
|
return database.prepare(`
|
|
6
7
|
SELECT
|
|
@@ -11,8 +12,7 @@ function selectTell(database, tellId) {
|
|
|
11
12
|
state,
|
|
12
13
|
execution_id,
|
|
13
14
|
fence_json,
|
|
14
|
-
replayed_from_json
|
|
15
|
-
delivered_at
|
|
15
|
+
replayed_from_json
|
|
16
16
|
FROM projection_tells
|
|
17
17
|
WHERE tell_id = ?
|
|
18
18
|
`).get(tellId);
|
|
@@ -62,40 +62,13 @@ export function readPendingTellWindowFromDatabase(database, limit) {
|
|
|
62
62
|
LIMIT ?
|
|
63
63
|
`).all(...PENDING_PROJECTION_TELL_STATES, limit);
|
|
64
64
|
return {
|
|
65
|
-
totals: {
|
|
65
|
+
totals: { pending: Number(total.count) },
|
|
66
66
|
window: rows.map((row) => ({ tellId: row.tell_id, text: row.text, createdAt: row.created_at, state: row.state })),
|
|
67
67
|
};
|
|
68
68
|
}
|
|
69
69
|
export function readPendingTellWindow(projectionDirectory, limit) {
|
|
70
70
|
return withReadSnapshot(projectionDirectory, (database) => readPendingTellWindowFromDatabase(database, limit));
|
|
71
71
|
}
|
|
72
|
-
/** Whether a failed successor fence still retains delivered mail for replay. */
|
|
73
|
-
export function hasRetainedDeliveredTellFromDatabase(database, retainedFence) {
|
|
74
|
-
if (retainedFence.length === 0)
|
|
75
|
-
return false;
|
|
76
|
-
const statement = database.prepare(`
|
|
77
|
-
SELECT 1
|
|
78
|
-
FROM projection_tells
|
|
79
|
-
WHERE tell_id = ? AND state = 'delivered'
|
|
80
|
-
LIMIT 1
|
|
81
|
-
`);
|
|
82
|
-
return retainedFence.some((tellId) => statement.get(tellId) !== undefined);
|
|
83
|
-
}
|
|
84
|
-
export function hasRetainedDeliveredTell(projectionDirectory, retainedFence) {
|
|
85
|
-
return withRead(projectionDirectory, (database) => hasRetainedDeliveredTellFromDatabase(database, retainedFence));
|
|
86
|
-
}
|
|
87
|
-
/** Whether interrupted replay has any delivered tell left to return to inbox. */
|
|
88
|
-
export function hasDeliveredTellFromDatabase(database, executionId) {
|
|
89
|
-
return database.prepare(`
|
|
90
|
-
SELECT 1
|
|
91
|
-
FROM projection_tells
|
|
92
|
-
WHERE state = 'delivered' AND execution_id = ?
|
|
93
|
-
LIMIT 1
|
|
94
|
-
`).get(executionId) !== undefined;
|
|
95
|
-
}
|
|
96
|
-
export function hasDeliveredTell(projectionDirectory, executionId) {
|
|
97
|
-
return withRead(projectionDirectory, (database) => hasDeliveredTellFromDatabase(database, executionId));
|
|
98
|
-
}
|
|
99
72
|
export function readTellOriginal(projectionDirectory, state, tellId) {
|
|
100
73
|
return withRead(projectionDirectory, (database) => {
|
|
101
74
|
const row = selectTell(database, tellId);
|
|
@@ -160,100 +133,79 @@ export function claimAllInbox(projectionDirectory) {
|
|
|
160
133
|
return rows.map((row) => row.tell_id);
|
|
161
134
|
});
|
|
162
135
|
}
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
return withWrite(projectionDirectory, (database) => {
|
|
166
|
-
const claimed = [];
|
|
167
|
-
const seen = new Set();
|
|
168
|
-
for (const tellId of fence) {
|
|
169
|
-
if (seen.has(tellId))
|
|
170
|
-
throw new ProjectionStateError(`duplicate tell in generation fence: ${tellId}`);
|
|
171
|
-
seen.add(tellId);
|
|
172
|
-
const row = selectTell(database, tellId);
|
|
173
|
-
if (!row)
|
|
174
|
-
throw new ProjectionStateError(`generation tell fence member is missing: ${tellId}`);
|
|
175
|
-
if (row.state === "inbox") {
|
|
176
|
-
database.prepare(`
|
|
177
|
-
UPDATE projection_tells
|
|
178
|
-
SET state = 'inflight'
|
|
179
|
-
WHERE tell_id = ? AND state = 'inbox'
|
|
180
|
-
`).run(tellId);
|
|
181
|
-
claimed.push(tellId);
|
|
182
|
-
continue;
|
|
183
|
-
}
|
|
184
|
-
if (row.state === "inflight") {
|
|
185
|
-
claimed.push(tellId);
|
|
186
|
-
continue;
|
|
187
|
-
}
|
|
188
|
-
if (row.state === "submitted" || row.state === "delivered") {
|
|
189
|
-
const submitted = submittedFromRow(row);
|
|
190
|
-
if (submitted.executionId === owner) {
|
|
191
|
-
throw new ProjectionStateError(`submitted tell belongs to current execution: ${tellId}`);
|
|
192
|
-
}
|
|
193
|
-
const replayedFrom = [
|
|
194
|
-
...(submitted.replayedFrom ?? []),
|
|
195
|
-
{ executionId: submitted.executionId, fence: submitted.fence },
|
|
196
|
-
];
|
|
197
|
-
const result = database.prepare(`
|
|
198
|
-
UPDATE projection_tells
|
|
199
|
-
SET
|
|
200
|
-
state = 'inflight',
|
|
201
|
-
execution_id = NULL,
|
|
202
|
-
fence_json = NULL,
|
|
203
|
-
replayed_from_json = ?,
|
|
204
|
-
delivered_at = NULL
|
|
205
|
-
WHERE tell_id = ? AND state = ?
|
|
206
|
-
`).run(JSON.stringify(replayedFrom), tellId, row.state);
|
|
207
|
-
if (Number(result.changes) !== 1) {
|
|
208
|
-
throw new ProjectionStateError(`generation tell fence member changed while claiming: ${tellId}`);
|
|
209
|
-
}
|
|
210
|
-
claimed.push(tellId);
|
|
211
|
-
continue;
|
|
212
|
-
}
|
|
213
|
-
if (row.state === "consumed" && row.execution_id === owner)
|
|
214
|
-
continue;
|
|
215
|
-
throw new ProjectionStateError(`generation tell fence member is not claimable: ${tellId}`);
|
|
216
|
-
}
|
|
217
|
-
return claimed;
|
|
218
|
-
});
|
|
136
|
+
function receiptTimestamp() {
|
|
137
|
+
return new Date().toISOString();
|
|
219
138
|
}
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
const
|
|
223
|
-
const
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
139
|
+
/** Reduce persisted native evidence through the one monotonic heart-stage algebra. */
|
|
140
|
+
function recordTellReceiptFromDatabase(database, receipt) {
|
|
141
|
+
const executionId = requireExecutionId(receipt.executionId);
|
|
142
|
+
const fence = receipt.fence ? parseFence(receipt.fence) : undefined;
|
|
143
|
+
const receivedAt = receipt.receivedAt ?? receiptTimestamp();
|
|
144
|
+
const row = selectTell(database, receipt.tellId);
|
|
145
|
+
if (!row || row.execution_id !== executionId)
|
|
146
|
+
return false;
|
|
147
|
+
const inserted = database.prepare(`
|
|
148
|
+
INSERT INTO projection_tell_receipts (
|
|
149
|
+
tell_id, execution_id, stage, correlation_kind, fence_json, received_at
|
|
150
|
+
) VALUES (?, ?, ?, ?, ?, ?)
|
|
151
|
+
ON CONFLICT(tell_id, execution_id, stage) DO NOTHING
|
|
152
|
+
`).run(receipt.tellId, executionId, receipt.stage, receipt.correlation, fence ? JSON.stringify(fence) : null, receivedAt);
|
|
153
|
+
const receipts = database.prepare(`
|
|
154
|
+
SELECT stage FROM projection_tell_receipts
|
|
155
|
+
WHERE tell_id = ? AND execution_id = ?
|
|
156
|
+
`).all(receipt.tellId, executionId);
|
|
157
|
+
const stages = new Set(receipts.map((entry) => entry.stage));
|
|
158
|
+
let state = row.state;
|
|
159
|
+
let advanced = false;
|
|
160
|
+
if (state === "submitted" && stages.has("seen")) {
|
|
161
|
+
database.prepare(`UPDATE projection_tells SET state = 'seen' WHERE tell_id = ? AND state = 'submitted'`)
|
|
162
|
+
.run(receipt.tellId);
|
|
163
|
+
state = "seen";
|
|
164
|
+
advanced = true;
|
|
165
|
+
}
|
|
166
|
+
if (state === "seen" && stages.has("consumed")) {
|
|
167
|
+
database.prepare(`UPDATE projection_tells SET state = 'consumed' WHERE tell_id = ? AND state = 'seen'`)
|
|
168
|
+
.run(receipt.tellId);
|
|
169
|
+
advanced = true;
|
|
170
|
+
}
|
|
171
|
+
return Number(inserted.changes) === 1 || advanced;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Persist a provider-native receipt and advance the mailbox only when the
|
|
175
|
+
* receipt proves the next monotonic heart stage. Event-ledger persistence is
|
|
176
|
+
* deliberately not part of this transaction.
|
|
177
|
+
*/
|
|
178
|
+
export function recordTellReceipt(projectionDirectory, receipt) {
|
|
179
|
+
return withWrite(projectionDirectory, (database) => recordTellReceiptFromDatabase(database, receipt));
|
|
180
|
+
}
|
|
181
|
+
/** Persist the execution's native receipt strength independently of AgentEvent storage. */
|
|
182
|
+
export function recordTellReceiptCapabilities(projectionDirectory, executionId, capabilities) {
|
|
183
|
+
const owner = requireExecutionId(executionId);
|
|
227
184
|
withWrite(projectionDirectory, (database) => {
|
|
228
|
-
const
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
185
|
+
const existing = database.prepare(`
|
|
186
|
+
SELECT seen, consumed FROM projection_tell_receipt_capabilities
|
|
187
|
+
WHERE execution_id = ?
|
|
188
|
+
`).get(owner);
|
|
189
|
+
if (existing) {
|
|
190
|
+
if (existing.seen !== capabilities.seen || existing.consumed !== capabilities.consumed) {
|
|
191
|
+
throw new ProjectionStateError(`tell receipt capabilities conflict for execution '${owner}'`);
|
|
192
|
+
}
|
|
232
193
|
return;
|
|
233
|
-
if (row.state !== "inflight") {
|
|
234
|
-
throw new ProjectionStateError(`tell '${tellId}' is not inflight`);
|
|
235
194
|
}
|
|
236
195
|
database.prepare(`
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
execution_id = ?,
|
|
241
|
-
fence_json = ?,
|
|
242
|
-
replayed_from_json = COALESCE(?, replayed_from_json)
|
|
243
|
-
WHERE tell_id = ? AND state = 'inflight'
|
|
244
|
-
`).run(executionId, JSON.stringify(fence), replayedFrom.length > 0 ? JSON.stringify(replayedFrom) : null, tellId);
|
|
196
|
+
INSERT INTO projection_tell_receipt_capabilities (execution_id, seen, consumed)
|
|
197
|
+
VALUES (?, ?, ?)
|
|
198
|
+
`).run(owner, capabilities.seen, capabilities.consumed);
|
|
245
199
|
});
|
|
246
200
|
}
|
|
247
|
-
export function
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
WHERE
|
|
253
|
-
`).
|
|
254
|
-
|
|
255
|
-
throw new ProjectionStateError(`tell '${tellId}' is not submitted or delivered`);
|
|
256
|
-
}
|
|
201
|
+
export function readTellReceiptCapabilities(projectionDirectory, executionId) {
|
|
202
|
+
const owner = requireExecutionId(executionId);
|
|
203
|
+
return withRead(projectionDirectory, (database) => {
|
|
204
|
+
const row = database.prepare(`
|
|
205
|
+
SELECT seen, consumed FROM projection_tell_receipt_capabilities
|
|
206
|
+
WHERE execution_id = ?
|
|
207
|
+
`).get(owner);
|
|
208
|
+
return row && { seen: row.seen, consumed: row.consumed };
|
|
257
209
|
});
|
|
258
210
|
}
|
|
259
211
|
export function readProjectionTellCounts(projectionDirectory) {
|
|
@@ -262,8 +214,8 @@ export function readProjectionTellCounts(projectionDirectory) {
|
|
|
262
214
|
inbox: 0,
|
|
263
215
|
inflight: 0,
|
|
264
216
|
submitted: 0,
|
|
217
|
+
seen: 0,
|
|
265
218
|
consumed: 0,
|
|
266
|
-
delivered: 0,
|
|
267
219
|
};
|
|
268
220
|
const rows = database.prepare(`
|
|
269
221
|
SELECT state, COUNT(*) AS count
|
package/build/core/registry.js
CHANGED
|
@@ -1,71 +1,30 @@
|
|
|
1
1
|
import { FlowError } from "../flow-error.js";
|
|
2
2
|
import { createGit, wrapGitError } from "../git/core.js";
|
|
3
|
-
import { readLedger } from "./ledger.js";
|
|
4
|
-
import { appendRefLogWithRetry, readRefLog } from "./ref-log.js";
|
|
5
3
|
import { patternsOverlap, scopePathEvidence } from "./scope.js";
|
|
6
|
-
import {
|
|
7
|
-
export const REGISTRY_REF = "refs/keiyaku/registry";
|
|
4
|
+
import { readRepositoryLifecycle } from "./repository-ledger/read-model.js";
|
|
8
5
|
export const SCOPE_OVERLAP_PATH_PREVIEW_LIMIT = 10;
|
|
9
|
-
function
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
throw new FlowError("INTERNAL_STATE", `invalid registry entry for ${REGISTRY_REF}`);
|
|
14
|
-
}
|
|
15
|
-
return parsed;
|
|
16
|
-
}
|
|
17
|
-
function registrySubject(entry) {
|
|
18
|
-
return `keiyaku registry: ${entry.kind} ${entry.contract}`;
|
|
19
|
-
}
|
|
20
|
-
async function isActiveNonTerminal(cwd, contractId) {
|
|
21
|
-
const ledger = await readLedger(cwd, contractId);
|
|
22
|
-
if (!ledger)
|
|
23
|
-
return false;
|
|
24
|
-
const state = deriveContractState(ledger.entries);
|
|
25
|
-
return state.state !== "corrupt" && !isTerminalState(state);
|
|
26
|
-
}
|
|
27
|
-
async function activeRegistrations(cwd, entries) {
|
|
28
|
-
const released = new Set(entries.filter((entry) => entry.kind === "release").map((entry) => entry.contract));
|
|
29
|
-
const registrations = new Map();
|
|
30
|
-
for (const entry of entries) {
|
|
31
|
-
if (entry.kind === "register" && !released.has(entry.contract)) {
|
|
32
|
-
registrations.set(entry.contract, { scope: entry.scope, tree: entry.tree });
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
const active = [];
|
|
36
|
-
for (const [contract, registration] of registrations) {
|
|
37
|
-
if (await isActiveNonTerminal(cwd, contract))
|
|
38
|
-
active.push({ contract, scope: registration.scope, tree: registration.tree });
|
|
39
|
-
}
|
|
40
|
-
return active;
|
|
6
|
+
function activeRegistrations(read) {
|
|
7
|
+
return read.contracts
|
|
8
|
+
.filter((model) => !["claimed", "forfeited"].includes(model.phase))
|
|
9
|
+
.map((model) => ({ contract: model.contract, scope: [...model.effectiveScope] }));
|
|
41
10
|
}
|
|
42
|
-
async function trackedRepositoryPaths(cwd) {
|
|
11
|
+
async function trackedRepositoryPaths(cwd, tree) {
|
|
43
12
|
try {
|
|
44
|
-
return (await createGit(cwd).raw(["ls-
|
|
13
|
+
return (await createGit(cwd).raw(["ls-tree", "-r", "-z", "--name-only", tree]))
|
|
45
14
|
.split("\0")
|
|
46
15
|
.filter(Boolean)
|
|
47
16
|
.sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
|
|
48
17
|
}
|
|
49
18
|
catch (error) {
|
|
50
|
-
throw wrapGitError(
|
|
19
|
+
throw wrapGitError(`ls-tree ${tree}`, error, cwd);
|
|
51
20
|
}
|
|
52
21
|
}
|
|
53
|
-
async function scopeRegistrationAdmission(cwd, input,
|
|
22
|
+
async function scopeRegistrationAdmission(cwd, input, read, trackedPaths) {
|
|
54
23
|
const nextOverlaps = [];
|
|
55
|
-
const active =
|
|
56
|
-
if (input.exclusive && active.length > 0) {
|
|
57
|
-
const blocking = active.find((existing) => existing.contract !== input.contractId) ?? active[0];
|
|
58
|
-
throw new FlowError("ACTIVE_KEIYAKU_EXISTS", `exclusive bind rejected: active contract ${blocking.contract} already exists`);
|
|
59
|
-
}
|
|
24
|
+
const active = activeRegistrations(read);
|
|
60
25
|
for (const existing of active) {
|
|
61
26
|
if (existing.contract === input.contractId)
|
|
62
27
|
continue;
|
|
63
|
-
if (existing.tree === input.tree) {
|
|
64
|
-
throw new FlowError("ACTIVE_KEIYAKU_EXISTS", [
|
|
65
|
-
`tree is already occupied by active contract ${existing.contract}`,
|
|
66
|
-
`Run \`keiyaku forfeit @${existing.contract}\` to free this tree.`,
|
|
67
|
-
].join(" "));
|
|
68
|
-
}
|
|
69
28
|
const [nextEvidence, existingEvidence] = await Promise.all([
|
|
70
29
|
scopePathEvidence(cwd, input.scope, trackedPaths),
|
|
71
30
|
scopePathEvidence(cwd, existing.scope, trackedPaths),
|
|
@@ -121,44 +80,20 @@ async function scopeRegistrationAdmission(cwd, input, entries, trackedPaths) {
|
|
|
121
80
|
}
|
|
122
81
|
return nextOverlaps;
|
|
123
82
|
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
readRefLog(cwd, REGISTRY_REF, parseRegistryEntry),
|
|
128
|
-
trackedRepositoryPaths(cwd),
|
|
129
|
-
]);
|
|
130
|
-
return await scopeRegistrationAdmission(cwd, input, entries, trackedPaths);
|
|
83
|
+
function refuseExclusiveOverlap(contractId, overlaps) {
|
|
84
|
+
const occupants = [...new Set(overlaps.map((overlap) => overlap.contracts[1]))].sort();
|
|
85
|
+
throw new FlowError("INVALID_TARGET", `exclusive bind ${contractId} overlaps active contract${occupants.length === 1 ? "" : "s"}: ${occupants.join(", ")}`);
|
|
131
86
|
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
v: 1,
|
|
139
|
-
kind: "register",
|
|
140
|
-
contract: input.contractId,
|
|
141
|
-
at: input.at,
|
|
142
|
-
actor: input.actor,
|
|
143
|
-
scope: input.scope,
|
|
144
|
-
tree: input.tree,
|
|
145
|
-
};
|
|
146
|
-
});
|
|
147
|
-
if (!result.ok)
|
|
148
|
-
throw new FlowError("INTERNAL_STATE", `failed to register scope for ${input.contractId}: ${result.reason}`);
|
|
149
|
-
return { head: result.head, overlaps };
|
|
87
|
+
/** Bind admission against one immutable repository fold and candidate Git tree. */
|
|
88
|
+
export async function admitScopeRegistration(cwd, input, read) {
|
|
89
|
+
const overlaps = await scopeRegistrationAdmission(cwd, input, read, await trackedRepositoryPaths(cwd, input.tree));
|
|
90
|
+
if (input.exclusive && overlaps.length > 0)
|
|
91
|
+
refuseExclusiveOverlap(input.contractId, overlaps);
|
|
92
|
+
return overlaps;
|
|
150
93
|
}
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
const alreadyReleased = entries.some((entry) => entry.kind === "release" && entry.contract === input.contractId);
|
|
155
|
-
if (!hasActiveRegistration || alreadyReleased)
|
|
156
|
-
return null;
|
|
157
|
-
return { v: 1, kind: "release", contract: input.contractId, at: input.at, actor: input.actor };
|
|
158
|
-
});
|
|
159
|
-
if (!result.ok && result.reason !== "aborted") {
|
|
160
|
-
throw new FlowError("INTERNAL_STATE", `failed to release scope for ${input.contractId}: ${result.reason}`);
|
|
161
|
-
}
|
|
94
|
+
/** Read-only preview observes the repository once and shares mutation admission. */
|
|
95
|
+
export async function previewScopeRegistration(cwd, input) {
|
|
96
|
+
return await admitScopeRegistration(cwd, input, await readRepositoryLifecycle(cwd));
|
|
162
97
|
}
|
|
163
98
|
export function bindScope(entry) {
|
|
164
99
|
return entry.data.scope;
|