@astrosheep/keiyaku 2.9.3 → 2.9.5
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/build/.tsbuildinfo +1 -1
- package/build/agents/call-terms.js +14 -94
- package/build/agents/harness/activity-values.js +16 -0
- package/build/agents/harness/event-persistence.js +218 -0
- package/build/agents/harness/execution-handle.js +26 -222
- package/build/agents/harness/index.js +3 -1
- package/build/agents/harness/outcome.js +68 -5
- package/build/agents/harness/projection.js +179 -124
- package/build/agents/harness/provider-adapter.js +58 -0
- package/build/agents/harness/router.js +11 -187
- package/build/agents/harness/runtime.js +17 -4
- package/build/agents/harness/session.js +15 -0
- package/build/agents/launch-snapshot/model.js +15 -236
- package/build/agents/launch-snapshot/parse-helpers.js +114 -0
- package/build/agents/launch-snapshot/resolve.js +17 -43
- package/build/agents/opencode-sdk.js +45 -12
- package/build/agents/providers/claude-agent-sdk/adapter.js +6 -17
- package/build/agents/providers/claude-agent-sdk/registration.js +214 -0
- package/build/agents/providers/claude-agent-sdk/session-schema.js +2 -0
- package/build/agents/providers/codex-app-server/adapter.js +82 -53
- package/build/agents/providers/codex-app-server/events.js +90 -11
- package/build/agents/providers/codex-app-server/identity.js +1 -0
- package/build/agents/providers/codex-app-server/policy.js +29 -0
- package/build/agents/providers/codex-app-server/registration.js +248 -0
- package/build/agents/providers/codex-app-server/session-schema.js +6 -0
- package/build/agents/providers/codex-app-server/session.js +23 -17
- package/build/agents/providers/codex-app-server/terms.js +1 -0
- package/build/agents/providers/opencode-sdk/adapter.js +94 -50
- package/build/agents/providers/opencode-sdk/events.js +8 -4
- package/build/agents/providers/opencode-sdk/registration.js +135 -0
- package/build/agents/providers/opencode-sdk/session-schema.js +2 -0
- package/build/agents/providers/opencode-sdk/session.js +21 -14
- package/build/agents/providers/opencode-sdk/terms.js +1 -0
- package/build/agents/providers/pi/adapter.js +36 -30
- package/build/agents/providers/pi/events.js +4 -3
- package/build/agents/providers/pi/registration.js +145 -0
- package/build/agents/providers/pi/session-schema.js +6 -0
- package/build/agents/providers/pi/terms.js +1 -0
- package/build/agents/providers/registration.js +7 -0
- package/build/agents/providers/registry.js +76 -0
- package/build/agents/selector.js +2 -29
- package/build/agents/types.js +10 -0
- package/build/artifact-directories.js +22 -0
- package/build/cli/commands/akuma/akuma/handler.js +1 -1
- package/build/cli/commands/akuma/akuma/meta.js +1 -1
- package/build/cli/commands/akuma/list/meta-ls.js +1 -1
- package/build/cli/commands/akuma/view/handler.js +5 -4
- package/build/cli/commands/akuma/view/meta.js +1 -1
- package/build/cli/commands/akuma.js +6 -0
- package/build/cli/commands/contract/amend/handler.js +8 -2
- package/build/cli/commands/contract/amend/meta.js +4 -4
- package/build/cli/commands/contract/arc/handler.js +8 -2
- package/build/cli/commands/contract/arc/meta.js +4 -4
- package/build/cli/commands/contract/audit/handler.js +16 -0
- package/build/cli/commands/contract/audit/meta.js +16 -0
- package/build/cli/commands/contract/bind/handler.js +4 -3
- package/build/cli/commands/contract/bind/meta.js +44 -8
- package/build/cli/commands/contract/catalog.js +2 -0
- package/build/cli/commands/contract/forfeit/handler.js +4 -4
- package/build/cli/commands/contract/forfeit/meta.js +1 -1
- package/build/cli/commands/contract/log/handler.js +3 -3
- package/build/cli/commands/contract/log/meta.js +1 -1
- package/build/cli/commands/contract/petition/handler.js +5 -8
- package/build/cli/commands/contract/petition/meta.js +6 -6
- package/build/cli/commands/contract/renew/handler.js +20 -6
- package/build/cli/commands/contract/renew/meta.js +6 -5
- package/build/cli/commands/metadata.js +59 -0
- package/build/cli/commands/projection/call/handler.js +6 -5
- package/build/cli/commands/projection/call/meta.js +4 -1
- package/build/cli/commands/projection/kill/handler.js +16 -32
- package/build/cli/commands/projection/kill/meta.js +1 -1
- package/build/cli/commands/projection/revive/meta.js +4 -1
- package/build/cli/commands/projection/shared.js +28 -0
- package/build/cli/commands/projection/status/handler.js +1 -1
- package/build/cli/commands/projection/status/meta.js +1 -1
- package/build/cli/commands/projection/tell/handler.js +14 -30
- package/build/cli/commands/projection/tell/meta.js +4 -1
- package/build/cli/commands/projection/wait/handler.js +45 -22
- package/build/cli/commands/projection/wait/meta.js +8 -3
- package/build/cli/commands/registry.js +2 -54
- package/build/cli/commands/shared.js +10 -7
- package/build/cli/commands/skills/install/handler.js +2 -5
- package/build/cli/commands/skills/install/meta.js +1 -1
- package/build/cli/commands/skills/skills/handler.js +1 -1
- package/build/cli/commands/skills/skills/meta.js +1 -1
- package/build/cli/commands/system/completion/meta.js +1 -1
- package/build/cli/commands/system/dump-env/meta.js +1 -1
- package/build/cli/commands/system/guide/meta.js +1 -1
- package/build/cli/commands/task/add/handler.js +7 -15
- package/build/cli/commands/task/add/meta.js +4 -1
- package/build/cli/commands/task/catalog.js +2 -2
- package/build/cli/commands/task/doctor/meta.js +1 -1
- package/build/cli/commands/task/done/handler.js +4 -8
- package/build/cli/commands/task/done/meta.js +1 -1
- package/build/cli/commands/task/drop/handler.js +3 -6
- package/build/cli/commands/task/drop/meta.js +1 -1
- package/build/cli/commands/task/ls/handler.js +12 -8
- package/build/cli/commands/task/ls/meta.js +3 -3
- package/build/cli/commands/task/shared.js +14 -4
- package/build/cli/commands/task/{view → show}/handler.js +7 -8
- package/build/cli/commands/task/show/meta.js +8 -0
- package/build/cli/commands/task/start/handler.js +3 -6
- package/build/cli/commands/task/start/meta.js +1 -1
- package/build/cli/commands/task/stop/handler.js +4 -8
- package/build/cli/commands/task/stop/meta.js +1 -1
- package/build/cli/commands/task/task/handler.js +1 -1
- package/build/cli/commands/task/task/meta.js +1 -1
- package/build/cli/commands/task/update/handler.js +6 -11
- package/build/cli/commands/task/update/meta.js +4 -1
- package/build/cli/completion.js +1 -1
- package/build/cli/flags.js +37 -6
- package/build/cli/help.js +7 -14
- package/build/cli/index.js +112 -154
- package/build/cli/missing-subcommand.js +7 -0
- package/build/cli/output-stream-error-policy.js +19 -5
- package/build/cli/parse-flags.js +33 -7
- package/build/cli/parse-metadata.js +3 -9
- package/build/cli/parse-selectors.js +2 -1
- package/build/cli/parse.js +35 -5
- package/build/cli/projection-address.js +12 -12
- package/build/cli/render/address.js +41 -37
- package/build/cli/render/arc.js +24 -12
- package/build/cli/render/audit.js +121 -0
- package/build/cli/render/call.js +11 -5
- package/build/cli/render/errors.js +36 -1
- package/build/cli/render/format.js +4 -0
- package/build/cli/render/kanshi.js +6 -12
- package/build/cli/render/kill.js +9 -0
- package/build/cli/render/misc.js +58 -13
- package/build/cli/render/petition.js +20 -15
- package/build/cli/render/projection-activity.js +15 -6
- package/build/cli/render/shared.js +60 -21
- package/build/cli/render/skills.js +10 -0
- package/build/cli/render/status.js +56 -23
- package/build/cli/render/success-response.js +78 -0
- package/build/cli/render/task.js +66 -57
- package/build/cli/render/tell.js +5 -0
- package/build/cli/render/terminal-failure.js +63 -0
- package/build/cli/render/types.js +1 -0
- package/build/cli/render/wait.js +68 -38
- package/build/cli/types.js +1 -1
- package/build/config/akuma-loader.js +0 -5
- package/build/config/env-keys.js +3 -1
- package/build/config/env.js +3 -0
- package/build/config/provider-policy-ownership.js +3 -32
- package/build/config/settings/disease.js +8 -1
- package/build/config/settings/loader.js +11 -3
- package/build/config/settings/provider-instance-fields.js +51 -0
- package/build/config/settings/schema.js +6 -176
- package/build/core/addressing.js +66 -16
- package/build/core/amend.js +61 -18
- package/build/core/arc.js +33 -73
- package/build/core/audit.js +650 -0
- package/build/core/bind.js +82 -35
- package/build/core/call/call.js +15 -9
- package/build/core/call/context.js +6 -51
- package/build/core/call/execution.js +22 -22
- package/build/core/call/prompt.js +14 -1
- package/build/core/command-markdown.js +45 -0
- package/build/core/context.js +13 -7
- package/build/core/contract-view.js +22 -6
- package/build/core/draft.js +139 -19
- package/build/core/entry.js +3 -4
- package/build/core/forfeit.js +5 -2
- package/build/core/hints.js +1 -75
- package/build/core/identifier-slug.js +66 -0
- package/build/core/ids.js +25 -11
- package/build/core/ledger.js +20 -68
- package/build/core/markdown/lex.js +2 -1
- package/build/core/markdown/parser.js +0 -68
- package/build/core/markdown/render.js +68 -0
- package/build/core/markdown/sections.js +2 -1
- package/build/core/outcome-base.js +4 -0
- package/build/core/projection/generation/database.js +41 -52
- package/build/core/projection/generation/model.js +63 -2
- package/build/core/{projection-generation-continuation.js → projection/generation/projection-generation-continuation.js} +3 -3
- package/build/core/{projection-generation-execution.js → projection/generation/projection-generation-execution.js} +36 -53
- package/build/core/{projection-generation-identity.js → projection/generation/projection-generation-identity.js} +2 -2
- package/build/core/{projection-generation-launcher.js → projection/generation/projection-generation-launcher.js} +9 -7
- package/build/core/{projection-generation-process.js → projection/generation/projection-generation-process.js} +2 -1
- package/build/core/{projection-generation-runner.js → projection/generation/projection-generation-runner.js} +19 -3
- package/build/core/projection/generation/store.js +3 -3
- package/build/core/projection/generation/transitions.js +19 -3
- package/build/core/projection/index.js +27 -0
- package/build/core/{projection-activity.js → projection/projection-activity.js} +12 -0
- package/build/core/{projection-alias.js → projection/projection-alias.js} +13 -13
- package/build/core/projection/projection-coordinate.js +61 -0
- package/build/core/{projection-core.js → projection/projection-core.js} +9 -9
- package/build/core/{projection-execution-observer.js → projection/projection-execution-observer.js} +37 -78
- package/build/core/{projection-identity.js → projection/projection-identity.js} +1 -1
- package/build/core/{projection-kill.js → projection/projection-kill.js} +1 -1
- package/build/core/{projection-life-observer.js → projection/projection-life-observer.js} +16 -2
- package/build/core/{projection-mint.js → projection/projection-mint.js} +10 -10
- package/build/core/{projection-status.js → projection/projection-status.js} +163 -45
- package/build/core/projection/projection-terminal-failure.js +106 -0
- package/build/core/projection/projection-wait.js +275 -0
- package/build/core/projection/projection-wake.js +227 -0
- package/build/core/provider-loader-ports.js +4 -0
- package/build/core/ref-log.js +65 -40
- package/build/core/registry.js +63 -10
- package/build/core/renew-session.js +128 -0
- package/build/core/renew.js +448 -102
- package/build/core/scope.js +272 -68
- package/build/core/seal.js +165 -40
- package/build/core/{claim-delivery.js → settlement/claim-delivery.js} +8 -8
- package/build/core/{claim.js → settlement/claim.js} +32 -8
- package/build/core/settlement/index.js +19 -0
- package/build/core/{petition-claim-gates.js → settlement/petition-claim-gates.js} +24 -49
- package/build/core/{petition-forfeit.js → settlement/petition-forfeit.js} +2 -2
- package/build/core/{petition-head-guard.js → settlement/petition-head-guard.js} +2 -2
- package/build/core/settlement/petition-io.js +1 -0
- package/build/core/{petition-preview.js → settlement/petition-preview.js} +2 -2
- package/build/core/{petition.js → settlement/petition.js} +21 -12
- package/build/core/settlement/queue-read-model.js +143 -0
- package/build/core/settlement/queue-seat-allocation.js +156 -0
- package/build/core/settlement/queue.js +6 -0
- package/build/core/settlement/settlement.js +288 -0
- package/build/core/{verdict.js → settlement/verdict.js} +9 -37
- package/build/core/settlement/verification.js +59 -0
- package/build/core/status/board.js +62 -41
- package/build/core/status/ledger-batch.js +158 -0
- package/build/core/status/reconciliation.js +10 -4
- package/build/core/stored-agent-event.js +6 -2
- package/build/core/task/board.js +41 -4
- package/build/core/task/commands.js +45 -28
- package/build/core/task/document.js +5 -0
- package/build/core/task/index.js +12 -0
- package/build/core/task/model.js +8 -1
- package/build/core/task/query.js +172 -0
- package/build/core/task/settlement-git.js +265 -74
- package/build/core/task/settlement-policy.js +67 -29
- package/build/core/task/source-board.js +1 -0
- package/build/core/task/task-contract.js +444 -0
- package/build/core/{task-doctor.js → task/task-doctor.js} +1 -1
- package/build/core/{task-git-actor.js → task/task-git-actor.js} +5 -10
- package/build/core/{task-git-runtime.js → task/task-git-runtime.js} +3 -3
- package/build/core/{task-git-store.js → task/task-git-store.js} +90 -19
- package/build/core/task/task-repository.js +1 -0
- package/build/core/{task-store-repository.js → task/task-store-repository.js} +6 -4
- package/build/core/task/task-store.js +1 -0
- package/build/core/{task-worktree.js → task/task-worktree.js} +2 -1
- package/build/core/task/task.js +7 -0
- package/build/core/task/tree.js +37 -0
- package/build/core/task/validation.js +5 -2
- package/build/core/transcripts.js +15 -3
- package/build/core/worktree-path.js +43 -28
- package/build/flow-error.js +83 -39
- package/build/fs/path-coordinate.js +16 -0
- package/build/generated/version.js +3 -2
- package/build/git/branches.js +2 -1
- package/build/git/core.js +106 -2
- package/build/git/diff/parsers.js +23 -13
- package/build/git/diff/structured.js +127 -0
- package/build/git/path-coordinate.js +17 -0
- package/build/git/refs.js +57 -0
- package/build/git/streaming-batch.js +301 -0
- package/build/git/worktree.js +65 -0
- package/build/index.js +9 -9
- package/build/keiyaku.js +2 -36
- package/build/telemetry/logger.js +4 -2
- package/package.json +4 -10
- package/skills/keiyaku/SKILL.md +5 -4
- package/skills/keiyaku-akuma/SKILL.md +25 -11
- package/skills/keiyaku-task/SKILL.md +17 -8
- package/skills/keiyaku-workflow/SKILL.md +58 -9
- package/build/agents/providers/codex-sdk.js +0 -329
- package/build/cli/commands/task/view/meta.js +0 -8
- package/build/core/command-io.js +0 -174
- package/build/core/path-coordinate.js +0 -27
- package/build/core/petition-claim.js +0 -117
- package/build/core/petition-run.js +0 -83
- package/build/core/projection-coordinate.js +0 -63
- package/build/core/projection-wait.js +0 -165
- package/build/core/projection-wake.js +0 -136
- package/build/core/queue.js +0 -343
- package/build/core/task-contract.js +0 -237
- package/build/core/task.js +0 -4
- /package/build/{core/task-repository.js → agents/launch-snapshot/types.js} +0 -0
- /package/build/{core/task-store.js → agents/providers/claude-agent-sdk/terms.js} +0 -0
- /package/build/core/{projection-generation-runtime.js → projection/generation/projection-generation-runtime.js} +0 -0
- /package/build/core/{projection-life-protocol.js → projection/projection-life-protocol.js} +0 -0
- /package/build/core/{projection-runner-lock.js → projection/projection-runner-lock.js} +0 -0
package/build/core/scope.js
CHANGED
|
@@ -1,8 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import * as fs from "node:fs/promises";
|
|
3
|
-
import * as os from "node:os";
|
|
4
|
-
import * as path from "node:path";
|
|
5
|
-
import { createGit, wrapGitError } from "../git/core.js";
|
|
1
|
+
import { FlowError } from "../flow-error.js";
|
|
6
2
|
export class ScopePatternError extends Error {
|
|
7
3
|
code = "INVALID_SCOPE_PATTERN";
|
|
8
4
|
constructor(message) {
|
|
@@ -36,14 +32,140 @@ function stripUnescapedTrailingSpaces(value) {
|
|
|
36
32
|
function canonicalizeScopePatternInput(pattern) {
|
|
37
33
|
return stripUnescapedTrailingSpaces(pattern.replace(/^\s+/, ""));
|
|
38
34
|
}
|
|
39
|
-
/** True when a raw
|
|
35
|
+
/** True when a raw scope line has no pattern after gitignore blank rules. */
|
|
40
36
|
export function isBlankScopePatternLine(line) {
|
|
41
37
|
return canonicalizeScopePatternInput(line).length === 0;
|
|
42
38
|
}
|
|
39
|
+
/** Parse Keiyaku's source-line Scope opener, not a normalized Markdown node. */
|
|
40
|
+
export function parseScopeFenceOpening(line) {
|
|
41
|
+
const match = line.match(/^(`{3,})$/);
|
|
42
|
+
return match ? { length: match[1].length } : null;
|
|
43
|
+
}
|
|
44
|
+
/** Keiyaku closer: column-zero backticks, at least opener length, then whitespace. */
|
|
45
|
+
export function isScopeFenceClosing(line, fence) {
|
|
46
|
+
const match = line.match(/^(`+)[ \t]*$/);
|
|
47
|
+
return Boolean(match && match[1].length >= fence.length);
|
|
48
|
+
}
|
|
49
|
+
function fencedScopeError(heading, detail) {
|
|
50
|
+
return new FlowError("INVALID_KEIYAKU_DRAFT", `${heading} must contain exactly one fenced gitignore-subset block; ${detail}. ` +
|
|
51
|
+
`Paste-ready canonical example:\n\n${heading}\n\`\`\`\nfoo\nbar\n\`\`\``);
|
|
52
|
+
}
|
|
53
|
+
/** Validate and normalize the one fenced block that constitutes a Scope body. */
|
|
54
|
+
export function validateScopeFragmentLines(lines, options) {
|
|
55
|
+
const first = lines.findIndex((line) => line.trim().length > 0);
|
|
56
|
+
if (first === -1)
|
|
57
|
+
throw fencedScopeError(options.heading, "found no fenced block");
|
|
58
|
+
const fence = parseScopeFenceOpening(lines[first]);
|
|
59
|
+
if (!fence)
|
|
60
|
+
throw fencedScopeError(options.heading, `offending line '${lines[first]}'`);
|
|
61
|
+
let close = -1;
|
|
62
|
+
for (let index = first + 1; index < lines.length; index += 1) {
|
|
63
|
+
if (isScopeFenceClosing(lines[index], fence)) {
|
|
64
|
+
close = index;
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
if (close === -1)
|
|
69
|
+
throw fencedScopeError(options.heading, "the fenced block is not closed");
|
|
70
|
+
const outside = lines.slice(close + 1).find((line) => line.trim().length > 0);
|
|
71
|
+
if (outside !== undefined) {
|
|
72
|
+
throw fencedScopeError(options.heading, `offending line '${outside}' appears outside the fenced block`);
|
|
73
|
+
}
|
|
74
|
+
const patterns = [];
|
|
75
|
+
for (const line of lines.slice(first + 1, close)) {
|
|
76
|
+
if (isBlankScopePatternLine(line))
|
|
77
|
+
continue;
|
|
78
|
+
try {
|
|
79
|
+
patterns.push(normalizeScopePattern(line));
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
if (error instanceof ScopePatternError) {
|
|
83
|
+
throw new FlowError("INVALID_KEIYAKU_DRAFT", error.message);
|
|
84
|
+
}
|
|
85
|
+
throw error;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
if (patterns.length === 0)
|
|
89
|
+
throw new FlowError("INVALID_KEIYAKU_DRAFT", options.emptyMessage);
|
|
90
|
+
return patterns;
|
|
91
|
+
}
|
|
92
|
+
function sourceFenceOpening(line) {
|
|
93
|
+
const match = line.match(/^ {0,3}(`{3,}|~{3,})(.*)$/);
|
|
94
|
+
if (!match)
|
|
95
|
+
return null;
|
|
96
|
+
const marker = match[1];
|
|
97
|
+
if (marker[0] === "`" && match[2].includes("`"))
|
|
98
|
+
return null;
|
|
99
|
+
return { delimiter: marker[0], length: marker.length };
|
|
100
|
+
}
|
|
101
|
+
function sourceFenceClosing(line, fence) {
|
|
102
|
+
const match = line.match(/^ {0,3}(`+|~+)[ \t]*$/);
|
|
103
|
+
return Boolean(match && match[1][0] === fence.delimiter && match[1].length >= fence.length);
|
|
104
|
+
}
|
|
105
|
+
/** Locate structural H1/H2 source ranges while leaving fenced headings inert. */
|
|
106
|
+
export function markdownSourceSections(markdown) {
|
|
107
|
+
const lines = [];
|
|
108
|
+
let start = 0;
|
|
109
|
+
while (start < markdown.length) {
|
|
110
|
+
const newline = markdown.indexOf("\n", start);
|
|
111
|
+
const end = newline === -1 ? markdown.length : newline;
|
|
112
|
+
const next = newline === -1 ? markdown.length : newline + 1;
|
|
113
|
+
const raw = markdown.slice(start, end);
|
|
114
|
+
lines.push({ text: raw.endsWith("\r") ? raw.slice(0, -1) : raw, start, next });
|
|
115
|
+
start = next;
|
|
116
|
+
}
|
|
117
|
+
let frontMatterEnd = -1;
|
|
118
|
+
if (lines[0]?.text.trim() === "---") {
|
|
119
|
+
frontMatterEnd = lines.findIndex((line, index) => index > 0 && line.text.trim() === "---");
|
|
120
|
+
}
|
|
121
|
+
const headings = [];
|
|
122
|
+
let fence = null;
|
|
123
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
124
|
+
const line = lines[index];
|
|
125
|
+
if (index <= frontMatterEnd)
|
|
126
|
+
continue;
|
|
127
|
+
if (fence) {
|
|
128
|
+
if (sourceFenceClosing(line.text, fence))
|
|
129
|
+
fence = null;
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
const opening = sourceFenceOpening(line.text);
|
|
133
|
+
if (opening) {
|
|
134
|
+
fence = opening;
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
// Column-0 only: indented ATX-looking lines inside list items remain content.
|
|
138
|
+
const heading = line.text.match(/^(#{1,2})(?!#)[ \t]+(.+)$/);
|
|
139
|
+
if (!heading)
|
|
140
|
+
continue;
|
|
141
|
+
headings.push({
|
|
142
|
+
level: heading[1].length,
|
|
143
|
+
title: heading[2],
|
|
144
|
+
start: line.start,
|
|
145
|
+
contentStart: line.next,
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
return headings.map((heading, index) => {
|
|
149
|
+
const next = heading.level === 2
|
|
150
|
+
? headings.slice(index + 1).find((candidate) => candidate.level === 2)
|
|
151
|
+
: headings[index + 1];
|
|
152
|
+
return { ...heading, end: next?.start ?? markdown.length };
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
/** Render canonical Scope bytes with a fence that content cannot close. */
|
|
156
|
+
export function renderScopeFragment(patterns) {
|
|
157
|
+
let longestRun = 0;
|
|
158
|
+
for (const pattern of patterns) {
|
|
159
|
+
for (const run of pattern.match(/`+/g) ?? [])
|
|
160
|
+
longestRun = Math.max(longestRun, run.length);
|
|
161
|
+
}
|
|
162
|
+
const fence = "`".repeat(Math.max(3, longestRun + 1));
|
|
163
|
+
return `${fence}\n${patterns.join("\n")}\n${fence}`;
|
|
164
|
+
}
|
|
43
165
|
/**
|
|
44
|
-
* Canonicalize a scope
|
|
166
|
+
* Canonicalize a scope Gitignore-like pattern for matching and persistence.
|
|
45
167
|
* Strips only a cosmetic leading `./` (including after `!`); preserves meaningful
|
|
46
|
-
* leading `/` anchoring,
|
|
168
|
+
* leading `/` anchoring, supported backslash escapes, and escaped trailing spaces.
|
|
47
169
|
*/
|
|
48
170
|
export function normalizeScopePattern(pattern) {
|
|
49
171
|
if (pattern.includes("\0") || pattern.includes("\n") || pattern.includes("\r")) {
|
|
@@ -62,7 +184,7 @@ export function normalizeScopePattern(pattern) {
|
|
|
62
184
|
return negative ? `!${body}` : body;
|
|
63
185
|
}
|
|
64
186
|
/**
|
|
65
|
-
* Normalize a path to Git diff form for
|
|
187
|
+
* Normalize a path to Git diff form for matcher comparison:
|
|
66
188
|
* either slash convention, collapse separators, and strip cosmetic `./` or `/`.
|
|
67
189
|
* Leading and trailing spaces are significant path bytes and must remain unchanged.
|
|
68
190
|
*/
|
|
@@ -78,75 +200,157 @@ export function normalizeScopePath(filePath) {
|
|
|
78
200
|
throw new ScopePatternError("scope path cannot contain NUL");
|
|
79
201
|
return normalized;
|
|
80
202
|
}
|
|
81
|
-
function
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
203
|
+
function splitPatternComponents(pattern) {
|
|
204
|
+
const components = [];
|
|
205
|
+
let component = "";
|
|
206
|
+
let escaped = false;
|
|
207
|
+
for (const character of pattern) {
|
|
208
|
+
if (escaped) {
|
|
209
|
+
component += `\\${character}`;
|
|
210
|
+
escaped = false;
|
|
211
|
+
}
|
|
212
|
+
else if (character === "\\") {
|
|
213
|
+
escaped = true;
|
|
214
|
+
}
|
|
215
|
+
else if (character === "/") {
|
|
216
|
+
components.push(component);
|
|
217
|
+
component = "";
|
|
218
|
+
}
|
|
219
|
+
else {
|
|
220
|
+
component += character;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
if (escaped)
|
|
224
|
+
component += "\\\\";
|
|
225
|
+
components.push(component);
|
|
226
|
+
return components;
|
|
227
|
+
}
|
|
228
|
+
function parseScopePattern(pattern) {
|
|
229
|
+
const normalized = normalizeScopePattern(pattern);
|
|
230
|
+
const escapedLeading = normalized.startsWith("\\!") || normalized.startsWith("\\#");
|
|
231
|
+
const negative = normalized.startsWith("!");
|
|
232
|
+
const body = negative ? normalized.slice(1) : normalized;
|
|
233
|
+
const comment = !escapedLeading && body.startsWith("#");
|
|
234
|
+
const anchored = body.startsWith("/");
|
|
235
|
+
const unanchored = anchored ? body.slice(1) : body;
|
|
236
|
+
const directory = unanchored.endsWith("/");
|
|
237
|
+
const source = directory ? unanchored.slice(0, -1) : unanchored;
|
|
238
|
+
const segments = splitPatternComponents(source);
|
|
239
|
+
return { negative, comment, directory, segments, componentOnly: !anchored && !source.includes("/") };
|
|
240
|
+
}
|
|
241
|
+
function componentMatches(pattern, value) {
|
|
242
|
+
let expression = "^";
|
|
243
|
+
for (let index = 0; index < pattern.length; index += 1) {
|
|
244
|
+
const character = pattern[index];
|
|
245
|
+
if (character === "\\") {
|
|
246
|
+
index += 1;
|
|
247
|
+
expression += escapeRegex(pattern[index] ?? "\\");
|
|
248
|
+
}
|
|
249
|
+
else if (character === "*") {
|
|
250
|
+
if (pattern[index + 1] === "*") {
|
|
251
|
+
expression += ".*";
|
|
252
|
+
index += 1;
|
|
253
|
+
}
|
|
254
|
+
else
|
|
255
|
+
expression += "[^/]*";
|
|
256
|
+
}
|
|
257
|
+
else if (character === "?")
|
|
258
|
+
expression += "[^/]";
|
|
259
|
+
else if (character === "[") {
|
|
260
|
+
const close = pattern.indexOf("]", index + 1);
|
|
261
|
+
if (close === -1)
|
|
262
|
+
expression += "\\[";
|
|
263
|
+
else {
|
|
264
|
+
const rawClass = pattern.slice(index + 1, close);
|
|
265
|
+
const classBody = rawClass.startsWith("!") ? `^${rawClass.slice(1)}` : rawClass;
|
|
266
|
+
expression += `[${classBody.replace(/\\/g, "\\\\")}]`;
|
|
267
|
+
index = close;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
else
|
|
271
|
+
expression += escapeRegex(character);
|
|
272
|
+
}
|
|
273
|
+
return new RegExp(`${expression}$`).test(value);
|
|
274
|
+
}
|
|
275
|
+
function escapeRegex(value) {
|
|
276
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
277
|
+
}
|
|
278
|
+
function patternMatchesPath(parsed, filePath) {
|
|
279
|
+
if (parsed.comment)
|
|
280
|
+
return false;
|
|
281
|
+
const pathSegments = filePath.split("/");
|
|
282
|
+
if (parsed.componentOnly) {
|
|
283
|
+
const lastMatchIndex = parsed.directory ? pathSegments.length - 1 : pathSegments.length;
|
|
284
|
+
return pathSegments.slice(0, lastMatchIndex).some((segment) => componentMatches(parsed.segments[0], segment));
|
|
285
|
+
}
|
|
286
|
+
const memo = new Map();
|
|
287
|
+
const matchesFrom = (patternIndex, pathIndex) => {
|
|
288
|
+
const key = `${patternIndex}:${pathIndex}`;
|
|
289
|
+
const cached = memo.get(key);
|
|
290
|
+
if (cached !== undefined)
|
|
291
|
+
return cached;
|
|
292
|
+
let matched;
|
|
293
|
+
if (patternIndex === parsed.segments.length) {
|
|
294
|
+
matched = parsed.directory ? pathIndex < pathSegments.length : pathIndex === pathSegments.length;
|
|
295
|
+
memo.set(key, matched);
|
|
296
|
+
return matched;
|
|
297
|
+
}
|
|
298
|
+
const segment = parsed.segments[patternIndex];
|
|
299
|
+
if (segment === "**") {
|
|
300
|
+
matched = false;
|
|
301
|
+
for (let nextPathIndex = pathIndex; nextPathIndex <= pathSegments.length; nextPathIndex += 1) {
|
|
302
|
+
if (matchesFrom(patternIndex + 1, nextPathIndex)) {
|
|
303
|
+
matched = true;
|
|
304
|
+
break;
|
|
305
|
+
}
|
|
116
306
|
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
307
|
+
}
|
|
308
|
+
else {
|
|
309
|
+
matched = pathIndex < pathSegments.length
|
|
310
|
+
&& componentMatches(segment, pathSegments[pathIndex])
|
|
311
|
+
&& matchesFrom(patternIndex + 1, pathIndex + 1);
|
|
312
|
+
}
|
|
313
|
+
memo.set(key, matched);
|
|
314
|
+
return matched;
|
|
315
|
+
};
|
|
316
|
+
return matchesFrom(0, 0);
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* Evaluate the settled ordered Gitignore-like scope subset without invoking Git.
|
|
320
|
+
*/
|
|
321
|
+
export async function scopePathEvidence(_cwd, patterns, filePaths) {
|
|
322
|
+
const normalizedPatterns = patterns.map(normalizeScopePattern);
|
|
323
|
+
const normalizedPaths = filePaths.map(normalizeScopePath);
|
|
324
|
+
const parsedPatterns = normalizedPatterns.map(parseScopePattern);
|
|
325
|
+
return normalizedPaths.map((normalizedPath) => {
|
|
326
|
+
const pathDecisions = parsedPatterns.flatMap((parsed, index) => {
|
|
327
|
+
if (!patternMatchesPath(parsed, normalizedPath))
|
|
328
|
+
return [];
|
|
329
|
+
const effect = parsed.negative ? "exclude" : "include";
|
|
330
|
+
return [{ index, pattern: normalizedPatterns[index], effect, matchedAfter: effect === "include" }];
|
|
121
331
|
});
|
|
122
|
-
|
|
332
|
+
const decisivePattern = pathDecisions.at(-1);
|
|
333
|
+
const hasPositiveMatch = pathDecisions.some((decision) => decision.effect === "include");
|
|
334
|
+
const matched = decisivePattern?.matchedAfter ?? false;
|
|
335
|
+
return {
|
|
336
|
+
path: normalizedPath,
|
|
337
|
+
patterns: pathDecisions,
|
|
338
|
+
matched,
|
|
339
|
+
outcome: matched ? "matched" : hasPositiveMatch ? "excluded" : "no-positive-match",
|
|
340
|
+
...(decisivePattern ? { decisivePattern } : {}),
|
|
341
|
+
};
|
|
123
342
|
});
|
|
124
343
|
}
|
|
125
344
|
export async function pathsMatchingScope(cwd, patterns, filePaths) {
|
|
126
|
-
const
|
|
127
|
-
|
|
128
|
-
if (normalizedPatterns.length === 0 || normalizedPaths.length === 0)
|
|
129
|
-
return new Set();
|
|
130
|
-
const temporaryRoot = await fs.mkdtemp(path.join(os.tmpdir(), "keiyaku-scope-"));
|
|
131
|
-
const gitDir = path.join(temporaryRoot, "scope.git");
|
|
132
|
-
const workTree = path.join(temporaryRoot, "worktree");
|
|
133
|
-
try {
|
|
134
|
-
await Promise.all([fs.mkdir(gitDir), fs.mkdir(workTree)]);
|
|
135
|
-
await createGit(gitDir).init(true);
|
|
136
|
-
await fs.writeFile(path.join(gitDir, "info", "exclude"), `${normalizedPatterns.join("\n")}\n`, "utf8");
|
|
137
|
-
return await runCheckIgnore(gitDir, workTree, cwd, normalizedPaths);
|
|
138
|
-
}
|
|
139
|
-
finally {
|
|
140
|
-
await fs.rm(temporaryRoot, { recursive: true, force: true });
|
|
141
|
-
}
|
|
345
|
+
const evidence = await scopePathEvidence(cwd, patterns, filePaths);
|
|
346
|
+
return new Set(evidence.filter((item) => item.matched).map((item) => item.path));
|
|
142
347
|
}
|
|
143
348
|
export function isNegativeScopePattern(pattern) {
|
|
144
349
|
return normalizeScopePattern(pattern).startsWith("!");
|
|
145
350
|
}
|
|
146
351
|
/**
|
|
147
|
-
* Parse only the subset whose path language can be proved statically:
|
|
148
|
-
* segments, `*` segments, and `**` segments.
|
|
149
|
-
* syntax is left to Git and produces no advisory warning.
|
|
352
|
+
* Parse only the positive subset whose path language can be proved statically:
|
|
353
|
+
* literal segments, `*` segments, and `**` segments.
|
|
150
354
|
*/
|
|
151
355
|
function staticScopeSegments(pattern) {
|
|
152
356
|
const positive = normalizeScopePattern(pattern);
|
package/build/core/seal.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import * as fs from "node:fs/promises";
|
|
2
|
-
import * as path from "node:path";
|
|
3
1
|
import { FlowError } from "../flow-error.js";
|
|
4
|
-
import { createGit, wrapGitError } from "../git/core.js";
|
|
2
|
+
import { createGit, parseRevListOutput, wrapGitError } from "../git/core.js";
|
|
5
3
|
import { KEIYAKU_FILE } from "../keiyaku.js";
|
|
4
|
+
import { formatLedgerEntryCommitMessage } from "./entry.js";
|
|
6
5
|
import { activeLedgerRef, appendWithRetry, readLedger } from "./ledger.js";
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
6
|
+
import { refreshContractView } from "./contract-view.js";
|
|
7
|
+
import { normalizeScopePattern, scopePathEvidence, } from "./scope.js";
|
|
9
8
|
import { deriveContractState, isTerminalState } from "./status/lifecycle.js";
|
|
9
|
+
import { casUpdateRefLog, createRefLogCommit } from "./ref-log.js";
|
|
10
10
|
function findBind(entries) {
|
|
11
11
|
const bind = entries.find((entry) => entry.kind === "bind");
|
|
12
12
|
if (!bind)
|
|
@@ -64,10 +64,7 @@ async function resolveCommit(cwd, revision) {
|
|
|
64
64
|
async function revList(cwd, from, to) {
|
|
65
65
|
const git = createGit(cwd);
|
|
66
66
|
try {
|
|
67
|
-
return (await git.raw(["rev-list", "--reverse", `${from}..${to}`]))
|
|
68
|
-
.split(/\r?\n/)
|
|
69
|
-
.map((line) => line.trim())
|
|
70
|
-
.filter(Boolean);
|
|
67
|
+
return parseRevListOutput(await git.raw(["rev-list", "--reverse", `${from}..${to}`]));
|
|
71
68
|
}
|
|
72
69
|
catch (error) {
|
|
73
70
|
throw wrapGitError(`rev-list ${from}..${to}`, error, cwd);
|
|
@@ -104,8 +101,58 @@ async function diffstat(cwd, from, to) {
|
|
|
104
101
|
}
|
|
105
102
|
async function scopeViolations(cwd, paths, scope) {
|
|
106
103
|
const auditedPaths = paths.filter((changedPath) => changedPath !== KEIYAKU_FILE);
|
|
107
|
-
|
|
108
|
-
|
|
104
|
+
return (await scopePathEvidence(cwd, scope, auditedPaths)).filter((evidence) => !evidence.matched);
|
|
105
|
+
}
|
|
106
|
+
async function auditScope(cwd, entries, paths) {
|
|
107
|
+
const scope = effectiveScope(entries);
|
|
108
|
+
const scopeViolationEvidence = await scopeViolations(cwd, paths, scope);
|
|
109
|
+
if (scopeViolationEvidence.length > 0) {
|
|
110
|
+
throw new ScopeAuditError(findBind(entries).contract, scope, scopeViolationEvidence);
|
|
111
|
+
}
|
|
112
|
+
const violations = scopeViolationEvidence.map((violation) => violation.path);
|
|
113
|
+
return {
|
|
114
|
+
scopeAudit: { violations },
|
|
115
|
+
scopeViolationEvidence,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
const APPEND_SCOPE_AMENDMENT = "Scope extended by lifecycle command.";
|
|
119
|
+
export async function appendScopeTerms(input) {
|
|
120
|
+
if (!input.patterns || input.patterns.length === 0)
|
|
121
|
+
return null;
|
|
122
|
+
const patterns = input.patterns.map(normalizeScopePattern);
|
|
123
|
+
const at = new Date(input.nowMs).toISOString();
|
|
124
|
+
const result = await appendWithRetry(input.cwd, activeLedgerRef(input.contractId), (_head, entries) => {
|
|
125
|
+
assertOperable(input.contractId, entries);
|
|
126
|
+
return {
|
|
127
|
+
v: 1,
|
|
128
|
+
kind: "amend",
|
|
129
|
+
contract: input.contractId,
|
|
130
|
+
at,
|
|
131
|
+
actor: input.actor,
|
|
132
|
+
data: { amendment: APPEND_SCOPE_AMENDMENT, scopeDelta: { add: patterns } },
|
|
133
|
+
};
|
|
134
|
+
});
|
|
135
|
+
if (!result.ok) {
|
|
136
|
+
throw new FlowError("INTERNAL_STATE", `contract ${input.contractId} active ledger disappeared while appending scope`);
|
|
137
|
+
}
|
|
138
|
+
await refreshContractView({ ledgerCwd: input.cwd, renderCwd: input.renderCwd, contractId: input.contractId });
|
|
139
|
+
return result.head;
|
|
140
|
+
}
|
|
141
|
+
export class ScopeAuditError extends FlowError {
|
|
142
|
+
scopeViolationEvidence;
|
|
143
|
+
constructor(_contractId, scope, evidence) {
|
|
144
|
+
const count = evidence.length;
|
|
145
|
+
const fileLabel = count === 1 ? "file" : "files";
|
|
146
|
+
super("SCOPE_VIOLATION", `scope violation — ${count} ${fileLabel} outside declared scope`, {
|
|
147
|
+
facts: {
|
|
148
|
+
kind: "scope_violation",
|
|
149
|
+
scope: [...scope],
|
|
150
|
+
violations: evidence,
|
|
151
|
+
},
|
|
152
|
+
});
|
|
153
|
+
this.name = "ScopeAuditError";
|
|
154
|
+
this.scopeViolationEvidence = evidence;
|
|
155
|
+
}
|
|
109
156
|
}
|
|
110
157
|
function assertOperable(contractId, entries) {
|
|
111
158
|
const state = deriveContractState(entries);
|
|
@@ -127,14 +174,13 @@ export function currentSealArc(entries) {
|
|
|
127
174
|
}
|
|
128
175
|
/** Stable read-only coordinate naming the receipt source a petition would seal. */
|
|
129
176
|
export function petitionSealSource(entries) {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
: `a${arc} · fence ${fence}▸HEAD`;
|
|
177
|
+
return {
|
|
178
|
+
arc: currentSealArc(entries),
|
|
179
|
+
fence: getFence(entries),
|
|
180
|
+
};
|
|
135
181
|
}
|
|
136
182
|
/** Shared read-only owner for the seal capture and its compact changed-path evidence. */
|
|
137
|
-
export async function previewSeal(cwd, entries, head = "HEAD"
|
|
183
|
+
export async function previewSeal(cwd, entries, head = "HEAD") {
|
|
138
184
|
const arc = currentSealArc(entries);
|
|
139
185
|
if (arc === null)
|
|
140
186
|
return { capture: null, paths: [] };
|
|
@@ -145,13 +191,7 @@ export async function previewSeal(cwd, entries, head = "HEAD", waivePaths = [])
|
|
|
145
191
|
changedPaths(cwd, oldFence, newFence),
|
|
146
192
|
diffstat(cwd, oldFence, newFence),
|
|
147
193
|
]);
|
|
148
|
-
const
|
|
149
|
-
const violations = await scopeViolations(cwd, paths, scope);
|
|
150
|
-
const waivedPaths = new Set(waivePaths.map(normalizeScopePath));
|
|
151
|
-
const unwaived = violations.filter((violation) => !waivedPaths.has(violation));
|
|
152
|
-
if (unwaived.length > 0) {
|
|
153
|
-
throw new FlowError("INTERNAL_STATE", `scope audit failed for contract ${findBind(entries).contract}: paths ${unwaived.join(", ")} outside ${scope.join(", ")}`);
|
|
154
|
-
}
|
|
194
|
+
const scopeAudit = await auditScope(cwd, entries, paths);
|
|
155
195
|
return {
|
|
156
196
|
capture: {
|
|
157
197
|
arc,
|
|
@@ -159,20 +199,38 @@ export async function previewSeal(cwd, entries, head = "HEAD", waivePaths = [])
|
|
|
159
199
|
newFence,
|
|
160
200
|
receipts,
|
|
161
201
|
diffstat: stat,
|
|
162
|
-
scopeAudit
|
|
202
|
+
...scopeAudit,
|
|
163
203
|
},
|
|
164
204
|
paths,
|
|
165
205
|
};
|
|
166
206
|
}
|
|
167
|
-
export async function captureSeal(cwd, entries, head = "HEAD"
|
|
168
|
-
return (await previewSeal(cwd, entries, head
|
|
207
|
+
export async function captureSeal(cwd, entries, head = "HEAD") {
|
|
208
|
+
return (await previewSeal(cwd, entries, head)).capture;
|
|
169
209
|
}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
210
|
+
/** Preserve original receipt hashes while auditing the rebased delivery content. */
|
|
211
|
+
export async function captureRenewSeal(input) {
|
|
212
|
+
const arc = currentSealArc(input.entries);
|
|
213
|
+
if (arc === null)
|
|
214
|
+
return null;
|
|
215
|
+
const oldFence = getFence(input.entries);
|
|
216
|
+
const [receiptHead, contentBase, contentHead] = await Promise.all([
|
|
217
|
+
resolveCommit(input.cwd, input.receiptHead),
|
|
218
|
+
resolveCommit(input.cwd, input.contentBase),
|
|
219
|
+
resolveCommit(input.cwd, input.contentHead),
|
|
220
|
+
]);
|
|
221
|
+
const [receipts, paths, stat] = await Promise.all([
|
|
222
|
+
revList(input.cwd, oldFence, receiptHead),
|
|
223
|
+
changedPaths(input.cwd, contentBase, contentHead),
|
|
224
|
+
diffstat(input.cwd, contentBase, contentHead),
|
|
225
|
+
]);
|
|
226
|
+
return {
|
|
227
|
+
arc,
|
|
228
|
+
oldFence,
|
|
229
|
+
newFence: receiptHead,
|
|
230
|
+
receipts,
|
|
231
|
+
diffstat: stat,
|
|
232
|
+
...await auditScope(input.cwd, input.entries, paths),
|
|
233
|
+
};
|
|
176
234
|
}
|
|
177
235
|
export async function sealCurrentArc(input) {
|
|
178
236
|
const ledger = await readLedger(input.cwd, input.contractId);
|
|
@@ -180,7 +238,7 @@ export async function sealCurrentArc(input) {
|
|
|
180
238
|
throw new FlowError("INTERNAL_STATE", `contract ${input.contractId} not found or not active`);
|
|
181
239
|
}
|
|
182
240
|
assertOperable(input.contractId, ledger.entries);
|
|
183
|
-
const capture = input.capture ?? await captureSeal(input.cwd, ledger.entries, "HEAD"
|
|
241
|
+
const capture = input.capture ?? await captureSeal(input.cwd, ledger.entries, "HEAD");
|
|
184
242
|
if (!capture)
|
|
185
243
|
return null;
|
|
186
244
|
const at = new Date(input.nowMs).toISOString();
|
|
@@ -196,25 +254,92 @@ export async function sealCurrentArc(input) {
|
|
|
196
254
|
if (currentArc !== capture.arc || getFence(entries) !== capture.oldFence) {
|
|
197
255
|
throw new FlowError("INTERNAL_STATE", `contract ${input.contractId} changed while sealing arc a${capture.arc}`);
|
|
198
256
|
}
|
|
257
|
+
const { scopeViolationEvidence: _scopeViolationEvidence, ...durableCapture } = capture;
|
|
199
258
|
return {
|
|
200
|
-
v:
|
|
259
|
+
v: 2,
|
|
201
260
|
kind: "seal",
|
|
202
261
|
contract: input.contractId,
|
|
203
262
|
at,
|
|
204
263
|
actor: "engine",
|
|
205
|
-
data: { ...
|
|
264
|
+
data: { ...durableCapture, sealedBy: input.sealedBy },
|
|
206
265
|
};
|
|
207
266
|
});
|
|
208
267
|
if (!result.ok) {
|
|
209
268
|
throw new FlowError("INTERNAL_STATE", `failed to seal arc for ${input.contractId}: ${result.reason}`);
|
|
210
269
|
}
|
|
211
|
-
await
|
|
270
|
+
await refreshContractView({
|
|
271
|
+
ledgerCwd: input.cwd,
|
|
272
|
+
renderCwd: input.renderCwd,
|
|
273
|
+
contractId: input.contractId,
|
|
274
|
+
});
|
|
212
275
|
return {
|
|
213
276
|
...capture,
|
|
214
277
|
sealedBy: input.sealedBy,
|
|
215
278
|
ledgerHead: result.head,
|
|
216
|
-
incrementalDiff: capture.receipts.length === 0
|
|
217
|
-
? "nothing sealed"
|
|
218
|
-
: `${capture.receipts.length} receipt${capture.receipts.length === 1 ? "" : "s"} · +${capture.diffstat.insertions} −${capture.diffstat.deletions}`,
|
|
219
279
|
};
|
|
220
280
|
}
|
|
281
|
+
function splitLedgerMessage(entry) {
|
|
282
|
+
const message = formatLedgerEntryCommitMessage(entry);
|
|
283
|
+
const separator = message.indexOf("\n\n");
|
|
284
|
+
return { subject: message.slice(0, separator), body: message.slice(separator + 2).trimEnd() };
|
|
285
|
+
}
|
|
286
|
+
export async function appendRenewSettlement(input) {
|
|
287
|
+
const ledger = await readLedger(input.cwd, input.contractId);
|
|
288
|
+
if (!ledger || ledger.head !== input.expectedLedgerHead) {
|
|
289
|
+
throw new FlowError("CONCURRENT_MODIFICATION", `contract ${input.contractId} changed while renewing`);
|
|
290
|
+
}
|
|
291
|
+
assertOperable(input.contractId, ledger.entries);
|
|
292
|
+
if (input.capture && (currentSealArc(ledger.entries) !== input.capture.arc || getFence(ledger.entries) !== input.capture.oldFence)) {
|
|
293
|
+
throw new FlowError("CONCURRENT_MODIFICATION", `contract ${input.contractId} changed while renewing`);
|
|
294
|
+
}
|
|
295
|
+
const at = new Date(input.nowMs).toISOString();
|
|
296
|
+
let parentHead = ledger.head;
|
|
297
|
+
let sealHead;
|
|
298
|
+
if (input.capture) {
|
|
299
|
+
const { scopeViolationEvidence: _evidence, ...durableCapture } = input.capture;
|
|
300
|
+
const sealEntry = {
|
|
301
|
+
v: 2,
|
|
302
|
+
kind: "seal",
|
|
303
|
+
contract: input.contractId,
|
|
304
|
+
at,
|
|
305
|
+
actor: "engine",
|
|
306
|
+
data: { ...durableCapture, newFence: input.newHead, sealedBy: "renew" },
|
|
307
|
+
};
|
|
308
|
+
const message = splitLedgerMessage(sealEntry);
|
|
309
|
+
sealHead = await createRefLogCommit(input.cwd, {
|
|
310
|
+
...message,
|
|
311
|
+
parentHead,
|
|
312
|
+
errorLabel: `ledger seal ${input.contractId}`,
|
|
313
|
+
});
|
|
314
|
+
parentHead = sealHead;
|
|
315
|
+
}
|
|
316
|
+
const renewEntry = {
|
|
317
|
+
v: 1,
|
|
318
|
+
kind: "renew",
|
|
319
|
+
contract: input.contractId,
|
|
320
|
+
at,
|
|
321
|
+
actor: input.actor,
|
|
322
|
+
data: {
|
|
323
|
+
oldBase: input.oldBase,
|
|
324
|
+
newBase: input.newBase,
|
|
325
|
+
oldHead: input.oldHead,
|
|
326
|
+
newHead: input.newHead,
|
|
327
|
+
},
|
|
328
|
+
};
|
|
329
|
+
const message = splitLedgerMessage(renewEntry);
|
|
330
|
+
const renewHead = await createRefLogCommit(input.cwd, {
|
|
331
|
+
...message,
|
|
332
|
+
parentHead,
|
|
333
|
+
errorLabel: `ledger renew ${input.contractId}`,
|
|
334
|
+
});
|
|
335
|
+
if (!await casUpdateRefLog(input.cwd, activeLedgerRef(input.contractId), renewHead, ledger.head)) {
|
|
336
|
+
throw new FlowError("CONCURRENT_MODIFICATION", `contract ${input.contractId} changed while renewing`);
|
|
337
|
+
}
|
|
338
|
+
const sealed = input.capture ? {
|
|
339
|
+
...input.capture,
|
|
340
|
+
newFence: input.newHead,
|
|
341
|
+
sealedBy: "renew",
|
|
342
|
+
ledgerHead: sealHead,
|
|
343
|
+
} : null;
|
|
344
|
+
return { ledgerHead: renewHead, sealed };
|
|
345
|
+
}
|