@sema-agent/core 5.57.0 → 5.59.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +96 -0
- package/dist/agents/cascade.d.ts +1 -1
- package/dist/agents/cumulative-stats.d.ts +1 -1
- package/dist/agents/observer.d.ts +2 -2
- package/dist/agents/peer-admission.d.ts +1 -1
- package/dist/agents/retain-ledger.d.ts +2 -2
- package/dist/agents/roster-store.d.ts +8 -8
- package/dist/agents/send-message-tool.d.ts +2 -2
- package/dist/agents/subagent-steps.d.ts +1 -1
- package/dist/agents/subagent.d.ts +13 -13
- package/dist/agents/team.d.ts +5 -5
- package/dist/agents/tool-filter.d.ts +2 -2
- package/dist/agents/verify.d.ts +1 -1
- package/dist/bench/metrics.d.ts +35 -35
- package/dist/brain/degrading.d.ts +1 -1
- package/dist/brain/errors.d.ts +3 -3
- package/dist/brain/reasoning.d.ts +2 -2
- package/dist/brain/repetition.d.ts +1 -1
- package/dist/brain/route-adjudicator.d.ts +8 -1
- package/dist/brain/route-adjudicator.js +8 -1
- package/dist/brain/status-sink.d.ts +2 -2
- package/dist/brain/stream-shared.d.ts +1 -1
- package/dist/config/catalog.d.ts +5 -5
- package/dist/core/arg-summary.d.ts +4 -4
- package/dist/core/ask-class.d.ts +2 -2
- package/dist/core/ask-question.d.ts +1 -1
- package/dist/core/auto-compaction.d.ts +15 -15
- package/dist/core/auto-mode.d.ts +5 -5
- package/dist/core/background-agent-store.d.ts +20 -20
- package/dist/core/background-shell.d.ts +4 -4
- package/dist/core/checkpoint-store.d.ts +35 -27
- package/dist/core/context-edit.d.ts +1 -1
- package/dist/core/context-guard.d.ts +1 -1
- package/dist/core/exec-output-tail.d.ts +6 -6
- package/dist/core/file-snapshot-store.d.ts +8 -8
- package/dist/core/git-worktree-env.d.ts +3 -3
- package/dist/core/governance-codes.d.ts +37 -10
- package/dist/core/governance-codes.js +54 -1
- package/dist/core/hooks.d.ts +73 -33
- package/dist/core/hooks.js +87 -25
- package/dist/core/image-downsample.d.ts +1 -1
- package/dist/core/locked-config.d.ts +1 -1
- package/dist/core/lsp.d.ts +1 -1
- package/dist/core/mailbox-store.d.ts +1 -1
- package/dist/core/mcp.d.ts +3 -3
- package/dist/core/memory-engine/consolidation-driver.d.ts +211 -0
- package/dist/core/memory-engine/consolidation-driver.js +427 -0
- package/dist/core/memory-engine/consolidation.d.ts +119 -3
- package/dist/core/memory-engine/consolidation.js +22 -1
- package/dist/core/memory-engine/data-plane.d.ts +1 -1
- package/dist/core/memory-engine/distiller.d.ts +550 -0
- package/dist/core/memory-engine/distiller.js +598 -0
- package/dist/core/memory-engine/dual-root.d.ts +1 -1
- package/dist/core/memory-engine/engine.d.ts +143 -10
- package/dist/core/memory-engine/engine.js +149 -23
- package/dist/core/memory-engine/file-backend.d.ts +14 -2
- package/dist/core/memory-engine/file-backend.js +3 -0
- package/dist/core/memory-engine/index.d.ts +6 -3
- package/dist/core/memory-engine/index.js +4 -2
- package/dist/core/memory-engine/layout.js +20 -6
- package/dist/core/memory-engine/origin-clearance.d.ts +1 -1
- package/dist/core/memory-engine/scope-contract.d.ts +4 -4
- package/dist/core/memory-engine/sync-client.d.ts +16 -16
- package/dist/core/memory-engine/sync.d.ts +4 -4
- package/dist/core/memory-engine/types.d.ts +17 -0
- package/dist/core/memory-recall.d.ts +1 -1
- package/dist/core/memory.d.ts +2 -2
- package/dist/core/permission-rule-consent.d.ts +185 -36
- package/dist/core/permission-rule-consent.js +219 -44
- package/dist/core/permission-rule-model.d.ts +235 -31
- package/dist/core/permission-rule-model.js +105 -35
- package/dist/core/permission-rules.d.ts +9 -9
- package/dist/core/remote-env.d.ts +8 -8
- package/dist/core/roles.d.ts +3 -3
- package/dist/core/roles.js +1 -0
- package/dist/core/runner/assemble-result.d.ts +2 -2
- package/dist/core/runner/compaction-call-options.d.ts +3 -3
- package/dist/core/runner/memory-consolidation-driver.d.ts +49 -0
- package/dist/core/runner/memory-consolidation-driver.js +60 -0
- package/dist/core/runner/memory-consolidation.d.ts +1 -1
- package/dist/core/runner/prepare-config-doors.d.ts +3 -3
- package/dist/core/runner/prepare-task.d.ts +21 -21
- package/dist/core/runner/prepare-task.js +24 -17
- package/dist/core/runner/prepare-workspace-restore.d.ts +2 -2
- package/dist/core/runner/runtask.d.ts +11 -11
- package/dist/core/runner/runtask.js +4 -4
- package/dist/core/runner/session-rule-policy.d.ts +1 -1
- package/dist/core/runner/teardown-bounded.d.ts +1 -1
- package/dist/core/runner/tool-disclosure.d.ts +2 -2
- package/dist/core/runner/turn-attachments.d.ts +11 -11
- package/dist/core/scheduler.d.ts +5 -5
- package/dist/core/secret-env.d.ts +1 -1
- package/dist/core/sensitive-path-policy.d.ts +1 -1
- package/dist/core/session-policy-store.d.ts +2 -2
- package/dist/core/session-reconcile.d.ts +2 -2
- package/dist/core/session-store.d.ts +3 -3
- package/dist/core/session.d.ts +1 -1
- package/dist/core/shutdown-debug.d.ts +2 -2
- package/dist/core/side-query.d.ts +2 -2
- package/dist/core/spec-contract.d.ts +1 -1
- package/dist/core/store-contracts/contract-harness.d.ts +2 -2
- package/dist/core/store-contracts/contract-kit-version.d.ts +2 -2
- package/dist/core/store-contracts/mailbox-store-contract.d.ts +1 -1
- package/dist/core/store-contracts/mailbox-store-contract.js +1 -1
- package/dist/core/task-notification.d.ts +5 -5
- package/dist/core/task-registry-agent.d.ts +12 -12
- package/dist/core/task-registry-monitor.d.ts +1 -1
- package/dist/core/task-registry-shared.d.ts +41 -41
- package/dist/core/task-registry.d.ts +12 -12
- package/dist/core/tool-detach.d.ts +2 -2
- package/dist/core/tool-errors.d.ts +3 -3
- package/dist/core/tool-policy.d.ts +113 -28
- package/dist/core/tool-policy.js +80 -1
- package/dist/core/tool-result-budget.d.ts +1 -1
- package/dist/core/tool-result-store.d.ts +2 -2
- package/dist/core/tools.d.ts +1 -1
- package/dist/core/trace.d.ts +26 -23
- package/dist/core/types.d.ts +164 -86
- package/dist/core/types.js +4 -3
- package/dist/core/untrusted-egress.d.ts +1 -1
- package/dist/core/untrusted-text.d.ts +7 -7
- package/dist/core/wiring-manifest.d.ts +5 -5
- package/dist/core/workflow-journal-store.d.ts +14 -14
- package/dist/core/workflow-run-store-contract.d.ts +1 -1
- package/dist/core/workflow-run-store-contract.js +1 -1
- package/dist/core/workflow-run-store.d.ts +4 -4
- package/dist/engine/compaction/compaction.d.ts +3 -3
- package/dist/engine/compaction/utils.d.ts +2 -2
- package/dist/engine/execution-env/kill-tree.d.ts +1 -1
- package/dist/engine/execution-env/node-execution-env.d.ts +8 -8
- package/dist/engine/harness/agent-harness.d.ts +6 -6
- package/dist/engine/harness/messages.d.ts +1 -1
- package/dist/engine/harness/types.d.ts +10 -10
- package/dist/engine/llm/types.d.ts +14 -14
- package/dist/engine/loop/agent-loop.d.ts +3 -3
- package/dist/engine/loop/types.d.ts +4 -4
- package/dist/engine/lsp/node-lsp-manager.d.ts +2 -2
- package/dist/engine/session/import-validate.d.ts +1 -1
- package/dist/engine/session/log-digest.d.ts +1 -1
- package/dist/engine/session/memory-repo.d.ts +2 -2
- package/dist/engine/session/session.d.ts +4 -4
- package/dist/fixtures/index.d.ts +4 -4
- package/dist/index.d.ts +6 -5
- package/dist/index.js +5 -4
- package/dist/orchestration/goal.d.ts +1 -1
- package/dist/orchestration/run-spec.d.ts +1 -1
- package/dist/orchestration/run-workflow-tool.d.ts +12 -12
- package/dist/orchestration/workflow-governance.d.ts +4 -4
- package/dist/orchestration/workflow-observe.d.ts +1 -1
- package/dist/orchestration/workflow-script-runner.d.ts +1 -1
- package/dist/orchestration/workflow-script-store.d.ts +9 -9
- package/dist/orchestration/workflow-size-guideline.d.ts +1 -1
- package/dist/orchestration/workflow-types.d.ts +5 -5
- package/dist/orchestration/workflow.d.ts +10 -10
- package/dist/prompt-assembly/artifact-store.d.ts +1 -1
- package/dist/prompt-assembly/artifact.d.ts +1 -1
- package/dist/prompt-assembly/assemble.d.ts +1 -1
- package/dist/prompt-assembly/composer.d.ts +2 -2
- package/dist/prompt-assembly/epoch.d.ts +2 -2
- package/dist/prompt-assembly/event-registry.d.ts +1 -1
- package/dist/prompt-assembly/explain.d.ts +3 -3
- package/dist/prompt-assembly/tool-catalog.d.ts +1 -1
- package/dist/prompt-assembly/turn-snapshot.d.ts +4 -4
- package/dist/prompt-assembly/types.d.ts +12 -12
- package/dist/prompts/coordinator.d.ts +1 -1
- package/dist/prompts/default.d.ts +8 -8
- package/dist/prompts/simple-sections.d.ts +3 -3
- package/dist/prompts/supervisor.d.ts +2 -2
- package/dist/scenarios/full-body.d.ts +3 -3
- package/dist/scenarios/scenario-registry.d.ts +1 -1
- package/dist/stores/cc/sidecar-transcript.d.ts +3 -3
- package/dist/stores/file/fs-atomic.d.ts +2 -2
- package/dist/stores/file/index.d.ts +1 -1
- package/dist/stores/file/session-store.d.ts +2 -2
- package/dist/stores/file/workflow-journal-store.d.ts +4 -4
- package/dist/tools/fs/bash-readonly-classifier.d.ts +1 -1
- package/dist/tools/fs/encoding.d.ts +4 -4
- package/dist/tools/fs/fs-bash.d.ts +3 -3
- package/dist/tools/fs/fs-pdf.d.ts +1 -1
- package/dist/tools/fs/fs-shared.d.ts +6 -6
- package/dist/tools/fs/index.d.ts +2 -2
- package/dist/tools/fs/notebook.d.ts +1 -1
- package/dist/tools/fs/pdf.d.ts +1 -1
- package/dist/tools/fs/read-deny.d.ts +1 -1
- package/dist/tools/fs/safety.d.ts +9 -9
- package/dist/tools/fs/search.d.ts +2 -2
- package/dist/tools/monitor.d.ts +3 -3
- package/dist/tools/task-list.d.ts +2 -2
- package/dist/tools/web.d.ts +4 -4
- package/dist/tools/worktree.d.ts +5 -5
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +1826 -3
|
@@ -303,21 +303,35 @@ export function dirCaseFolds(dir) {
|
|
|
303
303
|
}
|
|
304
304
|
function readScopesRecord(controlDir) {
|
|
305
305
|
const path = join(controlDir, SCOPES_FILE);
|
|
306
|
+
const journalPath = `${path}.journal`;
|
|
306
307
|
let raw;
|
|
308
|
+
let sourcePath = journalPath;
|
|
307
309
|
try {
|
|
308
|
-
raw = readFileSync(
|
|
310
|
+
raw = readFileSync(journalPath, "utf8");
|
|
309
311
|
}
|
|
310
312
|
catch (err) {
|
|
311
|
-
if (err.code
|
|
312
|
-
|
|
313
|
-
|
|
313
|
+
if (err.code !== "ENOENT") {
|
|
314
|
+
throw new ControlPlaneCorruptError(`scope registry journal unreadable (${err.code ?? "io error"}) — a committed next state may exist that cannot be proven: ${journalPath}`, { cause: err });
|
|
315
|
+
}
|
|
316
|
+
raw = undefined;
|
|
317
|
+
}
|
|
318
|
+
if (raw === undefined) {
|
|
319
|
+
sourcePath = path;
|
|
320
|
+
try {
|
|
321
|
+
raw = readFileSync(path, "utf8");
|
|
322
|
+
}
|
|
323
|
+
catch (err) {
|
|
324
|
+
if (err.code === "ENOENT")
|
|
325
|
+
return {};
|
|
326
|
+
throw new ControlPlaneCorruptError(`scope registry unreadable (${err.code ?? "io error"}): ${path}`, { cause: err });
|
|
327
|
+
}
|
|
314
328
|
}
|
|
315
329
|
let parsed;
|
|
316
330
|
try {
|
|
317
331
|
parsed = JSON.parse(raw);
|
|
318
332
|
}
|
|
319
333
|
catch {
|
|
320
|
-
throw new ControlPlaneCorruptError(`scope registry is unparseable: ${
|
|
334
|
+
throw new ControlPlaneCorruptError(`scope registry is unparseable: ${sourcePath}`);
|
|
321
335
|
}
|
|
322
336
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
323
337
|
throw new ControlPlaneCorruptError(`scope registry has the wrong shape: ${path}`);
|
|
@@ -351,7 +365,7 @@ function lockedScopesUpdate(controlDir, fn) {
|
|
|
351
365
|
const lock = `${file}.lock`;
|
|
352
366
|
const token = acquireSidecarLock(lock, { onDeadline: "throw" });
|
|
353
367
|
try {
|
|
354
|
-
|
|
368
|
+
rollForwardStrictSidecar(file, journal);
|
|
355
369
|
const rec = readScopesRecord(controlDir);
|
|
356
370
|
const { next, result } = fn(rec);
|
|
357
371
|
if (next !== undefined) {
|
|
@@ -63,7 +63,7 @@ export declare function markOriginClearanceTombstoned(controlDir: string, input:
|
|
|
63
63
|
/**
|
|
64
64
|
* Terminal event append + status flip. Unknown clearanceId is a corrupt-caller refusal.
|
|
65
65
|
*
|
|
66
|
-
* SINGLE-WINNER terminal
|
|
66
|
+
* SINGLE-WINNER terminal: a row already terminal ignores a late contradictory
|
|
67
67
|
* settlement — two concurrent resumers race the store legs (the store's own CAS decides those),
|
|
68
68
|
* and the loser's stale verdict must not overwrite the winner's audit state (a successfully
|
|
69
69
|
* cleared entry reading `failed` with contradictory events). The loser's own thrown refusal is
|
|
@@ -10,7 +10,7 @@ export declare const SCOPE_SEGMENT_MAX_ENCODED = 64;
|
|
|
10
10
|
*/
|
|
11
11
|
export declare function encodeScopeSegment(raw: string): string;
|
|
12
12
|
/**
|
|
13
|
-
* Inverse of {@link encodeScopeSegment}, CANONICAL-FORM ONLY
|
|
13
|
+
* Inverse of {@link encodeScopeSegment}, CANONICAL-FORM ONLY: the input must be
|
|
14
14
|
* exactly what the encoder produces — safe chars stay bare (so `%41` for `A` is refused: two
|
|
15
15
|
* spellings of one identity must not mint two keys), `%XX` sequences must decode as valid UTF-8
|
|
16
16
|
* (fatal decoder — no U+FFFD smoothing), and the 64-char cap holds here too (parse side enforces
|
|
@@ -52,7 +52,7 @@ export declare function parseScopeKey(key: string): ParsedScopeKey;
|
|
|
52
52
|
export declare function isPersonalScope(parsed: ParsedScopeKey): boolean;
|
|
53
53
|
/** True when the key's AUTHORITY is central (design/142 §2 表: user/userproj/org rows) — these keys
|
|
54
54
|
* must never project into an in-repo memory dir (they would leak to every collaborator AND enter
|
|
55
|
-
* commit history;
|
|
55
|
+
* commit history; extended the F9 gate from personal to org). `proj:`/legacy stay
|
|
56
56
|
* repo-planed. */
|
|
57
57
|
export declare function isCentralAuthorityScope(parsed: ParsedScopeKey): boolean;
|
|
58
58
|
/**
|
|
@@ -71,14 +71,14 @@ export declare function assertScopeContractPlacement(args: {
|
|
|
71
71
|
/** Repo-root marker file: minted once (by scaffold/deployment, NEVER by the engine), committed, and
|
|
72
72
|
* from then on the project's identity survives clone/copy/rename/machine/cloud-sandbox. */
|
|
73
73
|
export declare const PROJECT_MARKER_PATH = ".sema/project";
|
|
74
|
-
/** projectId
|
|
74
|
+
/** projectId 格式单源:generic UUID,version nibble 不收紧——宽读严写(读面认外部/
|
|
75
75
|
* 历史 v4;铸造面 formatProjectMarker / center mint 统一产 uuidv7)。center 侧自持镜像+dev-only
|
|
76
76
|
* drift 锁对着这条(registry-core coreTypes 先例)。 */
|
|
77
77
|
export declare const PROJECT_ID_REGEX: RegExp;
|
|
78
78
|
/** Serialize a marker file body (scaffold-side helper; the engine itself never writes one). */
|
|
79
79
|
export declare function formatProjectMarker(projectId: string): string;
|
|
80
80
|
/** Parse a marker body: TOML-subset `key = value` lines, `#` comments and blank lines ignored,
|
|
81
|
-
* optional quotes around the value. Unknown keys are ignored (forward-compatible).
|
|
81
|
+
* optional quotes around the value. Unknown keys are ignored (forward-compatible).
|
|
82
82
|
* ANY line that starts a `projectId` assignment but is not one canonical UUID assignment is a
|
|
83
83
|
* CORRUPT marker (fail-loud, `config.memory_project_marker`) — an empty value, an unclosed quote,
|
|
84
84
|
* a non-UUID, or a DUPLICATE projectId line must never degrade to "no marker" (that would be a
|
|
@@ -5,7 +5,7 @@ import { type MemoryBackend, type MemoryEntry, type PatchReport } from "./types.
|
|
|
5
5
|
* DECODED JSON body of a 2xx response; any non-2xx / network failure should THROW (the round then
|
|
6
6
|
* aborts with zero local side effects — the transport runs before any local write). */
|
|
7
7
|
export type MemorySyncTransport = (path: string, body: unknown) => Promise<unknown>;
|
|
8
|
-
/** The wire REQUEST of one round (the server's `parseMemorySyncRequest` twin
|
|
8
|
+
/** The wire REQUEST of one round (the server's `parseMemorySyncRequest` twin): the push
|
|
9
9
|
* half as computed against the client's own cursor. `deletes` ⊆ `Object.keys(baseRevs)` by
|
|
10
10
|
* construction (a delete is CAS'd on its baseline; an id never synced cannot be delete-propagated). */
|
|
11
11
|
export interface MemorySyncRequestBody {
|
|
@@ -14,20 +14,20 @@ export interface MemorySyncRequestBody {
|
|
|
14
14
|
baseRevs: Record<string, string>;
|
|
15
15
|
/** Full state of every entry new/changed here since the baseline. When
|
|
16
16
|
* {@link SyncMemoryScopeOptions.maxPushEntries} is set this is the id-ascending PREFIX of the
|
|
17
|
-
* full push set (
|
|
17
|
+
* full push set (batching — the remainder rides later rounds). */
|
|
18
18
|
entries: MemoryEntry[];
|
|
19
19
|
/** Ids deleted here since the baseline (tombstoned or gone from the local plane). Deletes are
|
|
20
|
-
* NEVER batched/split — they are id+baseRev pairs, weightless next to entry bodies
|
|
20
|
+
* NEVER batched/split — they are id+baseRev pairs, weightless next to entry bodies. */
|
|
21
21
|
deletes: string[];
|
|
22
|
-
/**
|
|
23
|
-
* server to cap this round's pull half at `limit` items. Absent ⇔ unbatched (the
|
|
22
|
+
/** pull-half batching (wire contract; server implements the response side): ask the
|
|
23
|
+
* server to cap this round's pull half at `limit` items. Absent ⇔ unbatched (the prior
|
|
24
24
|
* wire shape byte-for-byte — old servers never see the key). */
|
|
25
25
|
pull?: {
|
|
26
26
|
limit?: number;
|
|
27
27
|
};
|
|
28
28
|
}
|
|
29
|
-
/** The wire RESPONSE of one round (server `MemorySyncResponse` twin
|
|
30
|
-
* directions; `serverDeletes` is the delete-propagation leg [
|
|
29
|
+
/** The wire RESPONSE of one round (server `MemorySyncResponse` twin: one RTT, both
|
|
30
|
+
* directions; `serverDeletes` is the delete-propagation leg [without it a central
|
|
31
31
|
* removal never crosses = livelock]; `cursor` is the server-computed next baseline with the
|
|
32
32
|
* retention discipline ALREADY applied to the pull half). */
|
|
33
33
|
export interface MemorySyncResponseBody {
|
|
@@ -57,7 +57,7 @@ export interface MemorySyncResponseBody {
|
|
|
57
57
|
baseRevs: Record<string, string>;
|
|
58
58
|
updatedAtMs: number;
|
|
59
59
|
};
|
|
60
|
-
/**
|
|
60
|
+
/** pull-half batching (wire contract; server sets it): true ⇔ the server withheld part
|
|
61
61
|
* of the pull half under the requested `pull.limit` — the caller should run another round.
|
|
62
62
|
* The server MUST retain every withheld id at the OLD baseline inside `cursor` (the client
|
|
63
63
|
* refuses prefilled undelivered revs — see ⑤ defense — so violating this only costs the
|
|
@@ -87,22 +87,22 @@ export interface SyncMemoryScopeOptions {
|
|
|
87
87
|
peer?: string;
|
|
88
88
|
/** Caller-injected clock (engine precedent) — stamps the returned cursor's updatedAtMs. */
|
|
89
89
|
now?: () => number;
|
|
90
|
-
/**
|
|
90
|
+
/** push-half batching: cap the number of push-half ENTRIES sent this round. The full
|
|
91
91
|
* push set is put in STABLE order (id ascending — deterministic across rounds/replicas) and the
|
|
92
92
|
* first `maxPushEntries` ride the wire; a cut round reports `pushTruncated: true`. Deletes are
|
|
93
93
|
* never split (id+baseRev pairs are weightless next to bodies). CALLER CONTRACT: on
|
|
94
94
|
* `pushTruncated`/`pullTruncated`, persist the returned cursor and run ANOTHER round until both
|
|
95
95
|
* flags clear — core deliberately never loops internally (the runner loop is the deployment's,
|
|
96
|
-
* server-side semantics
|
|
96
|
+
* server-side semantics apply); bound your loop (e.g. a fixed max-rounds cap ≥
|
|
97
97
|
* ⌈expected entries / limit⌉ + slack) so a misbehaving peer cannot spin it forever. Must be a
|
|
98
98
|
* POSITIVE integer (≥ 1 — C5: a 0 cap would flag pushTruncated forever and livelock the caller
|
|
99
|
-
* loop); absent ⇔ unbatched (
|
|
99
|
+
* loop); absent ⇔ unbatched (prior behavior byte-for-byte). */
|
|
100
100
|
maxPushEntries?: number;
|
|
101
|
-
/**
|
|
101
|
+
/** pull-half batching: forwarded verbatim as the wire request's `pull.limit`; a server
|
|
102
102
|
* that withholds part of its pull half answers `pullTruncated`, passed through on the result.
|
|
103
103
|
* Same caller contract as {@link maxPushEntries}: continue rounds (bounded) until it clears.
|
|
104
104
|
* Must be a POSITIVE integer (≥ 1 — C5, same livelock gate); absent ⇔ the request carries NO
|
|
105
|
-
* `pull` key (old-server compatible, pre-
|
|
105
|
+
* `pull` key (old-server compatible, pre- wire bytes). */
|
|
106
106
|
maxPullEntries?: number;
|
|
107
107
|
/** design/178 §3 — per-entry byte bound for the PUSH gate, matched against the entry's serialized
|
|
108
108
|
* file form (the same domain the harvest gate measures). Defaults to {@link MAX_MEMORY_BYTES},
|
|
@@ -130,11 +130,11 @@ export interface MemorySyncClientResult {
|
|
|
130
130
|
/** The cursor the caller should now persist: advanced on full success, otherwise the INPUT
|
|
131
131
|
* cursor unchanged (undefined on a failed first round) — the retention discipline. */
|
|
132
132
|
cursor: MemorySyncCursor | undefined;
|
|
133
|
-
/**
|
|
133
|
+
/** present (true) ⇔ `maxPushEntries` cut this round's push set — run another round off
|
|
134
134
|
* the persisted cursor until it clears (see the {@link SyncMemoryScopeOptions.maxPushEntries}
|
|
135
135
|
* caller contract). Never present on an uncut round (additive: old callers see no new key). */
|
|
136
136
|
pushTruncated?: true;
|
|
137
|
-
/**
|
|
137
|
+
/** present (true) ⇔ the server reported its pull half was cut under `pull.limit` —
|
|
138
138
|
* same continue-until-clear caller contract. */
|
|
139
139
|
pullTruncated?: true;
|
|
140
140
|
/** Honest-disclosure channel for tolerated protocol violations (present only when non-empty).
|
|
@@ -146,7 +146,7 @@ export interface MemorySyncClientResult {
|
|
|
146
146
|
warnings?: string[];
|
|
147
147
|
}
|
|
148
148
|
/**
|
|
149
|
-
* One full CLIENT sync round for one (scope, peer) against the
|
|
149
|
+
* One full CLIENT sync round for one (scope, peer) against the central sync API — see the
|
|
150
150
|
* module header for the ①-⑤ flow and the cursor retention discipline. Pure orchestration: all I/O
|
|
151
151
|
* rides the injected backend/transport; the caller persists the returned cursor.
|
|
152
152
|
*/
|
|
@@ -3,7 +3,7 @@ import type { MemoryEntry, ScoredMemoryEntry } from "./types.js";
|
|
|
3
3
|
* design/142 §3 — the per-replica per-scope sync cursor: the COMMON BASELINE a reconciliation run
|
|
4
4
|
* judges divergence against (`id → rev` as of the last completed sync round). This is a NEW
|
|
5
5
|
* persistence face — deliberately NOT revs.json (that is the local disk-vs-committed ledger): File
|
|
6
|
-
* plane = a control-plane sidecar, DB plane = a service-owned table (PG+TiDB
|
|
6
|
+
* plane = a control-plane sidecar, DB plane = a service-owned table (PG+TiDB dual-dialect).
|
|
7
7
|
* Serializable by construction.
|
|
8
8
|
*/
|
|
9
9
|
export interface MemorySyncCursor {
|
|
@@ -31,7 +31,7 @@ export interface MemorySyncPlan {
|
|
|
31
31
|
/** Entries the LOCAL side should adopt from the peer (new there or changed-there-only). Same CAS
|
|
32
32
|
* discipline as {@link push}, mirrored: local conditional write with `baseRevs[id]`. */
|
|
33
33
|
pull: MemoryEntry[];
|
|
34
|
-
/**
|
|
34
|
+
/** DELETE propagation (without these the removal never crosses and every round
|
|
35
35
|
* re-plans the same state = livelock): the peer removed an entry the local side left UNCHANGED
|
|
36
36
|
* since baseline ⇒ delete locally (CAS on baseRev — a local edit racing in makes the delete a
|
|
37
37
|
* conflict on the next round instead of silent loss). */
|
|
@@ -61,7 +61,7 @@ export interface MemorySyncPlan {
|
|
|
61
61
|
* - changed on exactly ONE side → push/pull;
|
|
62
62
|
* - changed on BOTH sides, or delete-on-one × edit-on-the-other → conflict (caller's ladder);
|
|
63
63
|
* - deleted on one side × UNCHANGED on the other → the delete propagates (deleteLocal/deleteRemote,
|
|
64
|
-
* CAS on baseRev
|
|
64
|
+
* CAS on baseRev: without this leg removals never cross and the plan livelocks);
|
|
65
65
|
* - present only on one side with NO baseline → new entry → propagate;
|
|
66
66
|
* - absent on both but present in baseline → cleared (cursor cleanup).
|
|
67
67
|
*/
|
|
@@ -81,7 +81,7 @@ export declare function nextSyncBaseline(local: readonly MemoryEntry[], remote:
|
|
|
81
81
|
* because the seat is advisory: the authoritative body is re-read from the real backend by id/slug
|
|
82
82
|
* when a hit is followed. Mount point: a deployment composes its RecallSource(s) inside its recall
|
|
83
83
|
* select hook (the design/65 selective-recall trust seat) or pre-queries before prepare; core adds
|
|
84
|
-
* no RunnerDeps seat until a real integration signals the need (YAGNI
|
|
84
|
+
* no RunnerDeps seat until a real integration signals the need (YAGNI two-lane ruling).
|
|
85
85
|
*/
|
|
86
86
|
export interface RecallSource {
|
|
87
87
|
/** Stable adapter name (diagnostics/attribution: hits are labeled, never silently mixed). */
|
|
@@ -246,6 +246,23 @@ export interface NotePatch {
|
|
|
246
246
|
* Ignored on `update`/`delete`. */
|
|
247
247
|
guard?: "absent";
|
|
248
248
|
}
|
|
249
|
+
/**
|
|
250
|
+
* design/376 slice ③ (#437③) — a backend's scope-enumeration answer, DISCRIMINATED on purpose:
|
|
251
|
+
* "this backend cannot enumerate" and "this store hosts no scopes" must never share a spelling
|
|
252
|
+
* (an unsupported backend read as `[]` would make every audit sweep over it silently claim a
|
|
253
|
+
* clean store). The capability itself is an OPTIONAL backend face (`listScopes`, probed like the
|
|
254
|
+
* File class's audit faces): a backend without it is answered `supported: false` by
|
|
255
|
+
* `MemoryEngine.listMemoryScopes`, never an empty array. The File leg derives from the store
|
|
256
|
+
* root's durable scope registry; the SQL twins' leg is a store-contracts extension (separate
|
|
257
|
+
* ticket) — until it lands they simply do not implement the face.
|
|
258
|
+
*/
|
|
259
|
+
export type MemoryScopeEnumeration = {
|
|
260
|
+
supported: true;
|
|
261
|
+
scopes: string[];
|
|
262
|
+
} | {
|
|
263
|
+
supported: false;
|
|
264
|
+
reason: string;
|
|
265
|
+
};
|
|
249
266
|
/** Result of {@link MemoryBackend.applyPatches} — applied ops + per-id CAS conflicts (reported, not thrown). */
|
|
250
267
|
export interface PatchReport {
|
|
251
268
|
applied: Array<{
|
|
@@ -164,7 +164,7 @@ export declare function composeSelectiveBody(manifestText: string, selected: Mem
|
|
|
164
164
|
* both. Core owns the FORMAT so a caller can't drift the wire format (decision 7) — the encoding is OPAQUE
|
|
165
165
|
* and length-prefixes the scope so a scope/id containing the separator cannot self-declare a collision.
|
|
166
166
|
*
|
|
167
|
-
* ONE-WAY by design (
|
|
167
|
+
* ONE-WAY by design ((d)): the key is only ever PRODUCED here (`surfacedKeys`) and compared by
|
|
168
168
|
* STRING EQUALITY against the caller's persisted set ({@link LayeredRecallOptions.priorSurfacedKeys}, applied
|
|
169
169
|
* at memory-recall.ts via `priorSurfacedKeys.has(encodeSurfacedKey(h.scope, h.id))`). Nothing ever needs the
|
|
170
170
|
* `(scope, id)` BACK out of a key, so there is intentionally NO `decodeSurfacedKey` — adding a decode would
|
package/dist/core/memory.d.ts
CHANGED
|
@@ -282,7 +282,7 @@ export declare class MemoryGateError extends Error {
|
|
|
282
282
|
* prose that merely mentions tokens/keys/passwords passes through untouched (false-positives are the head
|
|
283
283
|
* risk — a wrongly-rejected normal note silently breaks legitimate memory).
|
|
284
284
|
*
|
|
285
|
-
* 🔴 RB-445
|
|
285
|
+
* 🔴 RB-445: this is a BEST-EFFORT tripwire for a handful of high-signal token
|
|
286
286
|
* formats, NOT a data-loss-prevention boundary — "the write gate passed" never means "the text contains
|
|
287
287
|
* no credential". Known-through shapes (deliberate, per the false-positive stance above): connection
|
|
288
288
|
* strings carrying passwords, `password=`/`Authorization:` header values, bare high-entropy bodies
|
|
@@ -530,7 +530,7 @@ export type MemorySpecInput = {
|
|
|
530
530
|
* - `writeScope` defaults to the LAST scope (the highest-priority layer); an EXPLICIT `null` means read-only
|
|
531
531
|
* (no `remember` tool, no consolidation); an explicit non-null string is taken as-is (it need NOT be one
|
|
532
532
|
* of `scopes` — a deployment may write a layer it does not inject, though normally it is the last scope).
|
|
533
|
-
* ⚠️ Layered READ seeding (
|
|
533
|
+
* ⚠️ Layered READ seeding (142-S4): when `scopes` comes from a registry's defaultScopes
|
|
534
534
|
* (shared read layers), ALWAYS pin `writeScope` explicitly — the last-scope default would land every
|
|
535
535
|
* harvest in whichever shared layer happens to be listed last (a cross-tenant write surface).
|
|
536
536
|
* - returns `undefined` when there are no usable scopes (the caller treats it like "no memory configured").
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
* this by editing a file backend's file. That is the settings-file trust model, stated rather than
|
|
28
28
|
* defended against: for a file backend, host = user, no more and no less.
|
|
29
29
|
*/
|
|
30
|
-
import { type RuleRejectCode, type RuleScope, type RuleDot } from "./permission-rule-model.js";
|
|
30
|
+
import { type RuleOffer, type RuleRejectCode, type RuleScope, type RuleDot } from "./permission-rule-model.js";
|
|
31
31
|
import type { PermissionRuleStoreProvider, RuleOwner } from "./permission-rule-store.js";
|
|
32
32
|
/** One candidate rule inside an approval record: the exact text and where it would apply. */
|
|
33
33
|
export interface RuleCandidate {
|
|
@@ -36,6 +36,37 @@ export interface RuleCandidate {
|
|
|
36
36
|
}
|
|
37
37
|
/** What the approval record is for — it decides the provenance the minted adds carry. */
|
|
38
38
|
export type RuleApprovalKind = "card" | "import" | "starter";
|
|
39
|
+
/**
|
|
40
|
+
* design/375 §4.1 — one "don't ask again" OPTION as the record stores it: references into the
|
|
41
|
+
* record's `candidates` by index, never rule text of its own. The display triple (rule, match,
|
|
42
|
+
* command) is deliberately NOT here — it is derived at projection time from the referenced
|
|
43
|
+
* candidate's text ({@link ruleOffersOfRecord}), so there is no stored copy to drift from the rule
|
|
44
|
+
* that would actually persist.
|
|
45
|
+
*
|
|
46
|
+
* · `kind: "single"` — one candidate covering the whole command.
|
|
47
|
+
* · `kind: "batch"` — a CONJUNCTION batch: confirming it is one yes to ALL of `candidates` at
|
|
48
|
+
* once, with no per-member sub-selection. On a CARD record the batch carries two MINT-TIME
|
|
49
|
+
* facts that cannot be re-derived from candidate texts and therefore must be stored:
|
|
50
|
+
* `segments` (each member's source segment, index-aligned with `candidates`) and
|
|
51
|
+
* `uncoveredSegments` (the honest surplus count the wire offer disclosed, judged on the
|
|
52
|
+
* prepare-time coverage snapshot). On an import/starter record there is no compound command
|
|
53
|
+
* and no coverage snapshot, so both fields must be ABSENT — honest absence, never an
|
|
54
|
+
* empty-string stand-in. The read-side integrity checks refuse either direction's violation.
|
|
55
|
+
*
|
|
56
|
+
* Cardinality: a batch references 1..N candidates where N is the record's own candidate count.
|
|
57
|
+
* The wire offer's 1..5 is the CARD-lane minting cap — a card record inherits it by being minted
|
|
58
|
+
* there — while an import batch of twenty is a legal record; the two contracts are deliberately
|
|
59
|
+
* separate and neither speaks in the other's name.
|
|
60
|
+
*/
|
|
61
|
+
export type RuleOffer2 = {
|
|
62
|
+
readonly kind: "single";
|
|
63
|
+
readonly candidate: number;
|
|
64
|
+
} | {
|
|
65
|
+
readonly kind: "batch";
|
|
66
|
+
readonly candidates: readonly number[];
|
|
67
|
+
readonly segments?: readonly string[];
|
|
68
|
+
readonly uncoveredSegments?: number;
|
|
69
|
+
};
|
|
39
70
|
/** A durable record of one pending/settled human decision about minting rules.
|
|
40
71
|
*
|
|
41
72
|
* Identity (design/182 §4.5, additive): a v1 record carries `principal` and nothing else — it stays
|
|
@@ -48,9 +79,26 @@ export interface RuleApprovalRecord {
|
|
|
48
79
|
principal?: string;
|
|
49
80
|
/** Structural owner (additive). Absent on every record a v1 tree minted. */
|
|
50
81
|
owner?: RuleOwner;
|
|
82
|
+
/**
|
|
83
|
+
* design/375 §4.5 — the record-form version stamp. A stored row WITHOUT it predates this form;
|
|
84
|
+
* such a row confirms and redeems nothing (refused loudly, `record_schema_stale`) and is never
|
|
85
|
+
* read through a compatibility shim: pending approvals are short-lived, and re-triggering the
|
|
86
|
+
* command draws a fresh card. Stores hand pre-form rows back as {@link StaleRuleApprovalRecord}.
|
|
87
|
+
*/
|
|
88
|
+
schema: 2;
|
|
51
89
|
kind: RuleApprovalKind;
|
|
52
90
|
state: "pending" | "approved" | "redeemed";
|
|
53
91
|
candidates: RuleCandidate[];
|
|
92
|
+
/**
|
|
93
|
+
* The card's OPTION structure over `candidates` (design/375 §4.1). The flat candidate list stays
|
|
94
|
+
* the redemption/replay anchor — tickets, `redeemedDots` and the store writes all key on
|
|
95
|
+
* candidate indices, unchanged — while confirmation keys on OFFER indices into this array.
|
|
96
|
+
* Import/starter records are the whole-list normalization: one batch offer over every candidate,
|
|
97
|
+
* so all three entries speak one protocol. Offers are pairwise DISJOINT in membership (an
|
|
98
|
+
* unchosen offer's members must stay unredeemable, which overlapping offers could not
|
|
99
|
+
* guarantee); the mint sites satisfy that by construction and the read side refuses violations.
|
|
100
|
+
*/
|
|
101
|
+
offers: RuleOffer2[];
|
|
54
102
|
createdAt: string;
|
|
55
103
|
/** The ask this record was drawn from, for reconciliation. Advisory metadata; never adjudication input.
|
|
56
104
|
* `boundInputHash` is ALSO the card-edit binding anchor: an edited-candidate confirmation must echo
|
|
@@ -69,23 +117,25 @@ export interface RuleApprovalRecord {
|
|
|
69
117
|
* comparing state alone cannot separate two different writes that both leave the state unchanged. */
|
|
70
118
|
rev: number;
|
|
71
119
|
/**
|
|
72
|
-
* Which
|
|
120
|
+
* Which OFFER the person actually chose, recorded by the confirmation transfer (design/375 §4.3;
|
|
121
|
+
* takes the seat the per-candidate selection held before offers existed).
|
|
73
122
|
*
|
|
74
|
-
* A card offers alternatives
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
123
|
+
* A card offers alternatives of different breadth and a human picks ONE. Without this, confirming
|
|
124
|
+
* the card would authorize every option on it: a host could redeem the narrow ticket, then later
|
|
125
|
+
* the wider one, and the second rule would exist without anyone ever having agreed to it.
|
|
126
|
+
* Choosing a BATCH offer is a conjunction — one yes to ALL of its member candidates at once —
|
|
127
|
+
* so every member becomes redeemable and every non-member stays unredeemable forever. Absent
|
|
128
|
+
* exactly while the record is pending.
|
|
79
129
|
*/
|
|
80
|
-
|
|
130
|
+
selectedOffer?: number;
|
|
81
131
|
/**
|
|
82
132
|
* The person-EDITED candidate this record carries, if any — full provenance for the one candidate
|
|
83
133
|
* whose text was authored at the card rather than derived by the engine. `index` names the appended
|
|
84
134
|
* row in `candidates` (whose `rule` holds the CANONICAL spelling); `text` keeps the raw input bytes
|
|
85
135
|
* exactly as submitted (the idempotency primary key — a client retrying a lost response resends the
|
|
86
|
-
* same bytes); `at` is when the edit landed. Present ⇒
|
|
87
|
-
*
|
|
88
|
-
* settled through an engine candidate.
|
|
136
|
+
* same bytes); `at` is when the edit landed. Present ⇒ the record's chosen offer is the single
|
|
137
|
+
* offer minted over this candidate (the append, the offer and the choice are one CAS write).
|
|
138
|
+
* Absent on every card settled through an engine candidate.
|
|
89
139
|
*/
|
|
90
140
|
edited?: {
|
|
91
141
|
index: number;
|
|
@@ -95,12 +145,32 @@ export interface RuleApprovalRecord {
|
|
|
95
145
|
/** Dots already minted for this record, keyed by candidate index — the replay anchor. */
|
|
96
146
|
redeemedDots?: Record<number, RuleDot>;
|
|
97
147
|
}
|
|
148
|
+
/**
|
|
149
|
+
* design/375 §4.5 — the minimal ENVELOPE a store hands back for a durable row that predates the
|
|
150
|
+
* `schema: 2` record form. A typed read cannot honestly return such a row as a
|
|
151
|
+
* {@link RuleApprovalRecord}, and dropping it at the store layer would misreport "stale" as
|
|
152
|
+
* corruption — so the store contract is this variant instead: `staleSchema: true` plus the
|
|
153
|
+
* IDENTITY fields, which are spelled the same way in every version and therefore safe to read.
|
|
154
|
+
* The identity fields exist so the owner gate can still run, and it runs FIRST: a non-owner
|
|
155
|
+
* asking about a stale row is answered exactly like a non-owner asking about any row (existence
|
|
156
|
+
* is never confirmed); the stale disclosure is for the record's own owner only. Nothing else
|
|
157
|
+
* about the row is surfaced.
|
|
158
|
+
*/
|
|
159
|
+
export interface StaleRuleApprovalRecord {
|
|
160
|
+
readonly staleSchema: true;
|
|
161
|
+
readonly id: string;
|
|
162
|
+
readonly principal?: string;
|
|
163
|
+
readonly owner?: RuleOwner;
|
|
164
|
+
}
|
|
98
165
|
/**
|
|
99
166
|
* The durable home of approval records. A separate record type on purpose: it is not a checkpoint row and
|
|
100
167
|
* must not evolve with one.
|
|
101
168
|
*/
|
|
102
169
|
export interface RuleApprovalRecordStore {
|
|
103
|
-
|
|
170
|
+
/** A row whose `schema` is missing or unknown MUST come back as {@link StaleRuleApprovalRecord}
|
|
171
|
+
* (identity fields only) — never thrown away as a parse failure, never widened into a full
|
|
172
|
+
* record the current form cannot vouch for. */
|
|
173
|
+
get(id: string): Promise<RuleApprovalRecord | StaleRuleApprovalRecord | undefined>;
|
|
104
174
|
/**
|
|
105
175
|
* Compare-and-set on the record's REVISION, not on its state.
|
|
106
176
|
*
|
|
@@ -130,15 +200,32 @@ export interface RuleConsentDeps {
|
|
|
130
200
|
*/
|
|
131
201
|
cardEdits?: boolean;
|
|
132
202
|
}
|
|
133
|
-
/** In-memory approval records — the test backend and the reference CAS semantics
|
|
203
|
+
/** In-memory approval records — the test backend and the reference CAS semantics, the stale-row
|
|
204
|
+
* envelope read included. */
|
|
134
205
|
export declare class InMemoryRuleApprovalRecordStore implements RuleApprovalRecordStore {
|
|
135
206
|
private readonly rows;
|
|
136
|
-
get(id: string): Promise<RuleApprovalRecord | undefined>;
|
|
207
|
+
get(id: string): Promise<RuleApprovalRecord | StaleRuleApprovalRecord | undefined>;
|
|
137
208
|
create(record: RuleApprovalRecord): Promise<void>;
|
|
138
209
|
cas(id: string, expectRev: number, next: RuleApprovalRecord): Promise<boolean>;
|
|
139
210
|
/** Test/inspection helper — how many records exist. The principal negative pins read this. */
|
|
140
211
|
size(): number;
|
|
141
212
|
}
|
|
213
|
+
/**
|
|
214
|
+
* design/375 §4.1 — project a record's stored offers into the wire {@link RuleOffer} shape: the
|
|
215
|
+
* AUTHORITATIVE card render source (record-is-what-you-see; the ask-time projection is advisory
|
|
216
|
+
* and may be coverage-narrower). The display triple (rule, match, command) is derived HERE, at
|
|
217
|
+
* projection time, by parsing each referenced candidate's stored text — never stored beside it,
|
|
218
|
+
* never accepted from a caller — so what a card shows is definitionally the rule that would
|
|
219
|
+
* persist; there is no second copy to corrupt. A batch offer's `segment`s and
|
|
220
|
+
* `uncoveredSegments` come from the mint-time facts the record carries.
|
|
221
|
+
*
|
|
222
|
+
* Card records only, in effect: an import/starter record's whole-list batch carries no segment
|
|
223
|
+
* metadata (its person-facing form is the import/starter PREVIEW, not an offer card), and asking
|
|
224
|
+
* for its card projection throws. A candidate text that no longer parses throws too — that is
|
|
225
|
+
* store damage on a row the validator once accepted, and rendering a wrong rule under a "don't
|
|
226
|
+
* ask again" label is the one failure a refusal always beats.
|
|
227
|
+
*/
|
|
228
|
+
export declare function ruleOffersOfRecord(rec: Pick<RuleApprovalRecord, "candidates" | "offers">): readonly RuleOffer[];
|
|
142
229
|
/** An opaque, single-purpose redemption ticket. Its only content is which record and which candidate. */
|
|
143
230
|
export type RuleTicket = string;
|
|
144
231
|
export declare function mintRuleTicket(recordId: string, candidateIndex: number): RuleTicket;
|
|
@@ -172,9 +259,19 @@ export declare function prepareCardApproval(opts: {
|
|
|
172
259
|
boundInputHash?: string;
|
|
173
260
|
/** Where a redeemed rule would apply. Defaults to global. */
|
|
174
261
|
scope?: RuleScope;
|
|
262
|
+
/**
|
|
263
|
+
* The working directory of the ADJUDICATED CALL — the same task root the gate's lane judged
|
|
264
|
+
* with — threaded by the caller from the original call context. Never inferred from `scope`
|
|
265
|
+
* (a rule's landing scope is not where the call ran; nested project roots make the two
|
|
266
|
+
* coverage answers diverge) and never from the process. Absent, a project-scope rule covers
|
|
267
|
+
* nothing (the shared predicate's fail-closed arm), so segments lean UNCOVERED and the batch
|
|
268
|
+
* leans wide — the over-offer direction, which redemption's per-rule validation keeps safe.
|
|
269
|
+
*/
|
|
270
|
+
cwd?: string;
|
|
175
271
|
deps: RuleConsentDeps;
|
|
176
272
|
}): Promise<{
|
|
177
273
|
approvalId: string;
|
|
274
|
+
offers: readonly RuleOffer[];
|
|
178
275
|
tickets: RuleTicket[];
|
|
179
276
|
candidates: RuleCandidate[];
|
|
180
277
|
} | undefined>;
|
|
@@ -191,12 +288,16 @@ export declare function confirmRuleApproval(opts: {
|
|
|
191
288
|
/** design/182 §4.5 (additive): the structural owner — only the local-owner path needs it. */
|
|
192
289
|
owner?: RuleOwner;
|
|
193
290
|
/**
|
|
194
|
-
*
|
|
195
|
-
*
|
|
196
|
-
*
|
|
197
|
-
*
|
|
291
|
+
* The index of the OFFER the person chose — into the record's `offers`, the same order
|
|
292
|
+
* `prepareCardApproval` returned them (design/375 §4.3). A card presents alternatives of
|
|
293
|
+
* different breadth, so "they said yes" is not an answer on its own — "they said yes to THIS
|
|
294
|
+
* one" is. A single offer authorizes its one candidate; a BATCH offer is a conjunction, one yes
|
|
295
|
+
* to ALL of its member candidates at once, with no per-member sub-selection. May be omitted
|
|
296
|
+
* exactly when the record carries ONE offer (a single-element choice space has no ambiguity —
|
|
297
|
+
* the import/starter whole-list batch lands here); a multi-offer record with no selection
|
|
298
|
+
* refuses `selection_missing`. Mutually exclusive with `editedCandidate`.
|
|
198
299
|
*/
|
|
199
|
-
|
|
300
|
+
selectedOffer?: number;
|
|
200
301
|
/**
|
|
201
302
|
* The person-EDITED rule text for this card, travelling on the SAME authenticated confirmation
|
|
202
303
|
* channel as a choice among the engine's candidates (never the un-authenticated prepare entry, which
|
|
@@ -205,7 +306,7 @@ export declare function confirmRuleApproval(opts: {
|
|
|
205
306
|
* command", an in-process mis-binding fence (a caller holding only a leaked approvalId cannot spell
|
|
206
307
|
* it), not a cryptographic one. The engine validates the text through the one shared validator,
|
|
207
308
|
* requires it to still ADMIT the adjudicated command, appends it as a new candidate and binds the
|
|
208
|
-
* selection to it, returning the minted ticket. Mutually exclusive with `
|
|
309
|
+
* selection to it, returning the minted ticket. Mutually exclusive with `selectedOffer`.
|
|
209
310
|
*/
|
|
210
311
|
editedCandidate?: {
|
|
211
312
|
text: string;
|
|
@@ -214,6 +315,13 @@ export declare function confirmRuleApproval(opts: {
|
|
|
214
315
|
deps: RuleConsentDeps;
|
|
215
316
|
}): Promise<ConfirmResult>;
|
|
216
317
|
/** Why a confirmation did not land. A closed set so a host can branch (re-present, re-fetch, give up).
|
|
318
|
+
* The two `record_*` members past `record_not_found` are the durable-row gates (design/375 §4.5/§4.7),
|
|
319
|
+
* disclosed to the record's own owner only — a non-owner gets `record_not_found` for every row:
|
|
320
|
+
* - `"record_schema_stale"` — the record predates the `schema: 2` form; the decision it carried
|
|
321
|
+
* is void, and the recovery path is re-triggering the command for a fresh card (no
|
|
322
|
+
* compatibility read, no migration — `detail.message` says so);
|
|
323
|
+
* - `"record_malformed"` — the record's offer structure failed the read-side integrity checks
|
|
324
|
+
* (store damage); `detail.message` names the breakage.
|
|
217
325
|
* The three `edit_*` members are the card-edit face's own refusals:
|
|
218
326
|
* - `"edit_disabled"` — the deployment has not opted into card edits (`RuleConsentDeps.cardEdits`);
|
|
219
327
|
* - `"edit_binding_mismatch"` — the confirmation does not echo the record's bound-input digest
|
|
@@ -221,7 +329,7 @@ export declare function confirmRuleApproval(opts: {
|
|
|
221
329
|
* settled replays included, and never returns a minted ticket;
|
|
222
330
|
* - `"edit_rejected"` — the edited text failed a gate (validator refusal, coverage, record shape);
|
|
223
331
|
* `detail` carries the specifics. */
|
|
224
|
-
export type ConfirmRefusalReason = "record_not_found" | "
|
|
332
|
+
export type ConfirmRefusalReason = "record_not_found" | "record_schema_stale" | "record_malformed" | "selection_missing" | "selection_invalid" | "selection_mismatch" | "not_pending" | "conflict" | "edit_disabled" | "edit_binding_mismatch" | "edit_rejected";
|
|
225
333
|
/** The confirmation outcome: landed, or refused with a named reason.
|
|
226
334
|
*
|
|
227
335
|
* `mintedCandidate` (additive) is present exactly when an EDITED candidate settled this confirmation —
|
|
@@ -370,20 +478,36 @@ export interface ImportPreview {
|
|
|
370
478
|
};
|
|
371
479
|
};
|
|
372
480
|
}
|
|
373
|
-
/**
|
|
374
|
-
*
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
481
|
+
/**
|
|
482
|
+
* One member row of a batch redemption's FULL table (design/375 §4.6) — what actually happened to
|
|
483
|
+
* each candidate of the chosen offer, keyed by its ORIGINAL candidate index. `candidateIndex` is
|
|
484
|
+
* the member's identity: the same rule text can be two candidates in two scopes with two different
|
|
485
|
+
* outcomes, so the bare text is not a key. A successful row always carries its `dot` and the
|
|
486
|
+
* independent `alreadyRedeemed` axis (the single-leg replay marker: a dot-recorded-but-unlanded
|
|
487
|
+
* retry is `alreadyRedeemed: true` AND `persisted`; a rule that pre-existed in the store is
|
|
488
|
+
* `false` AND `deduped` — the two axes answer different questions and neither implies the other).
|
|
489
|
+
* A refused row always carries its `reason`. `deduped` counts as LANDED: an equivalent rule
|
|
490
|
+
* already standing means the consent is already in effect.
|
|
491
|
+
*/
|
|
492
|
+
export type RedeemedBatchMember = {
|
|
493
|
+
readonly candidateIndex: number;
|
|
494
|
+
readonly rule: string;
|
|
495
|
+
readonly scope: RuleScope;
|
|
496
|
+
readonly status: "persisted" | "deduped";
|
|
497
|
+
readonly alreadyRedeemed: boolean;
|
|
498
|
+
readonly dot: RuleDot;
|
|
499
|
+
} | {
|
|
500
|
+
readonly candidateIndex: number;
|
|
501
|
+
readonly rule: string;
|
|
502
|
+
readonly scope: RuleScope;
|
|
503
|
+
readonly status: "refused";
|
|
504
|
+
readonly reason: string;
|
|
505
|
+
};
|
|
384
506
|
/**
|
|
385
507
|
* Read the allow buckets of the user-editable settings layers and produce a preview plus a PENDING
|
|
386
508
|
* approval record. Nothing is stored until someone confirms that record and the batch is redeemed.
|
|
509
|
+
* A preview with zero importable candidates returns NO `approvalId` — there is nothing to confirm,
|
|
510
|
+
* and the absence says so (design/375 §4.1).
|
|
387
511
|
*
|
|
388
512
|
* Only the allow bucket is IMPORTED. The deny/ask buckets are the tightening direction and have their own
|
|
389
513
|
* channel; importing them through a loosening lane would be the wrong door. They are still COUNTED and
|
|
@@ -398,7 +522,7 @@ export declare function prepareCcImport(opts: {
|
|
|
398
522
|
deps: RuleConsentDeps;
|
|
399
523
|
}): Promise<{
|
|
400
524
|
preview: ImportPreview;
|
|
401
|
-
approvalId
|
|
525
|
+
approvalId?: string;
|
|
402
526
|
}>;
|
|
403
527
|
/**
|
|
404
528
|
* The factory-reviewed starter set, offered when there is nothing to import.
|
|
@@ -420,15 +544,40 @@ export declare function prepareStarterBatch(opts: {
|
|
|
420
544
|
approvalId: string;
|
|
421
545
|
}>;
|
|
422
546
|
/**
|
|
423
|
-
* Redeem
|
|
424
|
-
*
|
|
547
|
+
* Redeem the CHOSEN offer of a confirmed record, member by member — reporting what actually landed
|
|
548
|
+
* rather than what was previewed (dedup, concurrency and redemption-time validation can all move a
|
|
549
|
+
* member between the two moments).
|
|
550
|
+
*
|
|
551
|
+
* design/375 §4.1/§4.6 — this is the one-yes walk for every record kind: an import/starter record's
|
|
552
|
+
* whole-list batch, a card's chosen batch (each per-segment rule lands as its own candidate), or a
|
|
553
|
+
* card's chosen single (a one-member walk). Members OUTSIDE the chosen offer are simply not walked
|
|
554
|
+
* — and stay individually unredeemable through the ticket entry, same guarantee, one predicate.
|
|
555
|
+
*
|
|
556
|
+
* The walk contract, spelled out because each clause is load-bearing:
|
|
557
|
+
* · every member goes through the SAME single-ticket redemption leg — dot CAS'd into the record
|
|
558
|
+
* BEFORE the store write (a crash in between leaves a replayable intent), concurrent-loser
|
|
559
|
+
* dot reuse included; the walk adds no concurrency primitive of its own;
|
|
560
|
+
* · a refused member does NOT fold the batch: "all or nothing" is the CONFIRMATION's semantics
|
|
561
|
+
* (one yes covered the whole offer), not the landing's transaction shape — the walk continues
|
|
562
|
+
* and the member's row says refused, with the reason;
|
|
563
|
+
* · resuming a partial walk IS re-walking: already-landed members answer `alreadyRedeemed: true`
|
|
564
|
+
* and reuse their recorded dots (two concurrent walkers converge on one dot set, zero double
|
|
565
|
+
* mints), unlanded members mint on;
|
|
566
|
+
* · the returned table is COMPLETE — exactly one row per member of the chosen offer, in the
|
|
567
|
+
* offer's member order, no extras — so a caller's aggregate is an honest projection: all rows
|
|
568
|
+
* landed (`persisted` or `deduped`, both count as the consent being in effect) is full
|
|
569
|
+
* success, and ANY refused row must project to a non-success aggregate, never to a silent
|
|
570
|
+
* partial one.
|
|
425
571
|
*/
|
|
426
572
|
export declare function redeemRuleBatch(opts: {
|
|
427
573
|
approvalId: string;
|
|
428
574
|
principal?: string;
|
|
429
575
|
owner?: RuleOwner;
|
|
430
576
|
deps: RuleConsentDeps;
|
|
431
|
-
}): Promise<
|
|
577
|
+
}): Promise<{
|
|
578
|
+
members: readonly RedeemedBatchMember[];
|
|
579
|
+
rev: number;
|
|
580
|
+
} | {
|
|
432
581
|
status: "refused";
|
|
433
582
|
reason: string;
|
|
434
583
|
}>;
|