@sema-agent/core 7.53.0 → 7.54.1
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 +291 -1
- package/LICENSE +3 -1
- package/dist/agents/peer-session-drain.d.ts +0 -1
- package/dist/agents/peer-session-drain.js +0 -2
- package/dist/agents/send-message-tool.js +3 -4
- package/dist/agents/subagent.d.ts +3 -5
- package/dist/agents/subagent.js +18 -11
- package/dist/agents/tool-filter.d.ts +7 -4
- package/dist/agents/tool-filter.js +7 -3
- package/dist/core/a2a.js +4 -2
- package/dist/core/allow-entry.d.ts +1 -0
- package/dist/core/allow-entry.js +1 -0
- package/dist/core/approval-object.d.ts +87 -0
- package/dist/core/approval-object.js +85 -0
- package/dist/core/arg-summary.d.ts +3 -2
- package/dist/core/arg-summary.js +17 -11
- package/dist/core/ask-origin.d.ts +43 -27
- package/dist/core/ask-origin.js +20 -11
- package/dist/core/ask-question.d.ts +0 -1
- package/dist/core/ask-question.js +4 -67
- package/dist/core/ask-unresolvable-notice.d.ts +3 -2
- package/dist/core/auto-compaction.d.ts +49 -50
- package/dist/core/auto-compaction.js +41 -51
- package/dist/core/auto-mode-prompt.d.ts +4 -2
- package/dist/core/auto-mode-prompt.js +4 -3
- package/dist/core/canonical-json.d.ts +69 -0
- package/dist/core/canonical-json.js +130 -8
- package/dist/core/checkpoint-requirements.d.ts +13 -7
- package/dist/core/checkpoint-requirements.js +4 -1
- package/dist/core/checkpoint-store.d.ts +33 -18
- package/dist/core/checkpoint-store.js +5 -4
- package/dist/core/child-clamp-seats.d.ts +45 -6
- package/dist/core/child-clamp-seats.js +56 -4
- package/dist/core/consolidate-scope.d.ts +1 -1
- package/dist/core/cross-boundary-taint.d.ts +213 -0
- package/dist/core/cross-boundary-taint.js +196 -0
- package/dist/core/decision-receipt.d.ts +44 -1
- package/dist/core/decision-receipt.js +16 -0
- package/dist/core/effective-path-target.d.ts +3 -0
- package/dist/core/effective-path-target.js +4 -0
- package/dist/core/engine-notice.d.ts +34 -2
- package/dist/core/engine-notice.js +17 -1
- package/dist/core/engine-storage-backend.d.ts +73 -0
- package/dist/core/engine-storage-backend.js +1 -0
- package/dist/core/file-history-store.d.ts +6 -2
- package/dist/core/file-history-store.js +1 -0
- package/dist/core/gate-fold.js +10 -2
- package/dist/core/gate-lanes.js +30 -21
- package/dist/core/gate-outcome.d.ts +7 -3
- package/dist/core/governance-codes.d.ts +7 -2
- package/dist/core/governance-codes.js +6 -0
- package/dist/core/hooks.d.ts +14 -14
- package/dist/core/hooks.js +1 -2
- package/dist/core/locked-config.d.ts +28 -1
- package/dist/core/locked-config.js +30 -3
- package/dist/core/lsp.d.ts +1 -1
- package/dist/core/mcp-server-spec.d.ts +66 -9
- package/dist/core/mcp-server-spec.js +52 -19
- package/dist/core/mcp.js +5 -4
- package/dist/core/memory-engine/engine.js +2 -1
- package/dist/core/memory-engine/export-bundle.d.ts +1 -1
- package/dist/core/memory-engine/export-bundle.js +1 -1
- package/dist/core/memory-engine/file-backend.d.ts +2 -6
- package/dist/core/memory-engine/file-backend.js +1 -12
- package/dist/core/memory.d.ts +3 -3
- package/dist/core/mode-policy.d.ts +13 -8
- package/dist/core/mode-policy.js +3 -2
- package/dist/core/mods/kernel/receive.d.ts +2 -1
- package/dist/core/mods/kernel/receive.js +7 -2
- package/dist/core/parent-spec-seat.d.ts +10 -13
- package/dist/core/parent-spec-seat.js +14 -6
- package/dist/core/pause-registry.d.ts +35 -1
- package/dist/core/pause-registry.js +10 -0
- package/dist/core/permission-rule-consent.d.ts +18 -5
- package/dist/core/permission-rule-consent.js +13 -8
- package/dist/core/permission-rule-model.d.ts +29 -6
- package/dist/core/permission-rule-model.js +6 -1
- package/dist/core/permission-rule-org.d.ts +37 -0
- package/dist/core/permission-rule-org.js +34 -42
- package/dist/core/permission-rule-session.js +2 -2
- package/dist/core/permission-rule-store.d.ts +8 -0
- package/dist/core/permission-rule-store.js +1 -0
- package/dist/core/permission-rule-sync.js +3 -2
- package/dist/core/read-only-shell.d.ts +1 -1
- package/dist/core/read-only-shell.js +30 -9
- package/dist/core/remote-env.d.ts +41 -1
- package/dist/core/remote-env.js +18 -0
- package/dist/core/resume-startup-cause.d.ts +18 -0
- package/dist/core/resume-startup-cause.js +12 -0
- package/dist/core/runner/approval-feedback.d.ts +46 -0
- package/dist/core/runner/approval-feedback.js +41 -0
- package/dist/core/runner/compaction-call-options.d.ts +3 -3
- package/dist/core/runner/compaction-call-options.js +2 -2
- package/dist/core/runner/contracts.d.ts +33 -41
- package/dist/core/runner/cross-boundary-lane.d.ts +87 -0
- package/dist/core/runner/cross-boundary-lane.js +190 -0
- package/dist/core/runner/decide-continuation.d.ts +4 -4
- package/dist/core/runner/decide-continuation.js +3 -1
- package/dist/core/runner/gate-exit.d.ts +32 -12
- package/dist/core/runner/gate-exit.js +75 -33
- package/dist/core/runner/inherited-ask-grants.js +2 -2
- package/dist/core/runner/person-path-rows.d.ts +2 -2
- package/dist/core/runner/prepare-acquire-reconcile.d.ts +7 -9
- package/dist/core/runner/prepare-acquire-reconcile.js +8 -6
- package/dist/core/runner/prepare-ask-lane.js +3 -3
- package/dist/core/runner/prepare-caps-and-workflow.d.ts +6 -5
- package/dist/core/runner/prepare-caps-and-workflow.js +3 -3
- package/dist/core/runner/prepare-config-doors.d.ts +12 -15
- package/dist/core/runner/prepare-config-doors.js +8 -3
- package/dist/core/runner/prepare-context-lane.js +6 -3
- package/dist/core/runner/prepare-delegation-surface.d.ts +3 -2
- package/dist/core/runner/prepare-delegation-surface.js +2 -3
- package/dist/core/runner/prepare-execution-env.d.ts +1 -2
- package/dist/core/runner/prepare-execution-env.js +2 -1
- package/dist/core/runner/prepare-gate-stations.d.ts +2 -0
- package/dist/core/runner/prepare-gate-stations.js +4 -3
- package/dist/core/runner/prepare-hands-readface.d.ts +2 -2
- package/dist/core/runner/prepare-hands-readface.js +1 -1
- package/dist/core/runner/prepare-inherited-gate.d.ts +2 -0
- package/dist/core/runner/prepare-inherited-gate.js +1 -0
- package/dist/core/runner/prepare-park-ask.d.ts +1 -1
- package/dist/core/runner/prepare-park-ask.js +9 -3
- package/dist/core/runner/prepare-policy-chain.js +2 -7
- package/dist/core/runner/prepare-safety-scan.d.ts +7 -2
- package/dist/core/runner/prepare-safety-scan.js +34 -27
- package/dist/core/runner/prepare-suspend-saga.d.ts +2 -2
- package/dist/core/runner/prepare-suspend-saga.js +6 -2
- package/dist/core/runner/prepare-task.js +13 -10
- package/dist/core/runner/prepare-turn-wiring.d.ts +10 -5
- package/dist/core/runner/prepare-turn-wiring.js +14 -49
- package/dist/core/runner/prepare-wiring-manifest.d.ts +2 -2
- package/dist/core/runner/prepare-wiring-manifest.js +2 -2
- package/dist/core/runner/prepare-workspace-restore.js +2 -2
- package/dist/core/runner/resume-admission.js +9 -9
- package/dist/core/runner/resume-apply.js +4 -4
- package/dist/core/runner/resume-policy-outcome.d.ts +2 -2
- package/dist/core/runner/resume-policy-outcome.js +18 -15
- package/dist/core/runner/resume-preflight.js +2 -2
- package/dist/core/runner/resume-review-outcome.js +3 -3
- package/dist/core/runner/run-leg.js +57 -33
- package/dist/core/runner/run-settle-and-teardown.js +2 -0
- package/dist/core/runner/run-terminal-adoption.js +15 -4
- package/dist/core/runner/session-file-state-replay.d.ts +5 -2
- package/dist/core/runner/session-file-state-replay.js +3 -2
- package/dist/core/runner/stream-steer-verb.js +2 -2
- package/dist/core/runner/tool-face-overlay.d.ts +24 -6
- package/dist/core/runner/tool-face-overlay.js +24 -15
- package/dist/core/runner-deps.d.ts +9 -9
- package/dist/core/session-store.d.ts +35 -16
- package/dist/core/session-store.js +20 -12
- package/dist/core/{shell-lexer.d.ts → shell-run-candidates.d.ts} +36 -12
- package/dist/core/{shell-lexer.js → shell-run-candidates.js} +145 -225
- package/dist/core/shell-wrapper-table.d.ts +5 -3
- package/dist/core/shell-wrapper-table.js +1 -1
- package/dist/core/store-contracts/checkpoint-store-contract.js +2 -2
- package/dist/core/store-contracts/session-store-contract.d.ts +49 -0
- package/dist/core/store-contracts/session-store-contract.js +683 -0
- package/dist/core/store-contracts/session-store-incarnation-contract.d.ts +12 -0
- package/dist/core/store-contracts/session-store-incarnation-contract.js +5 -2
- package/dist/core/task-event.d.ts +6 -4
- package/dist/core/task-registry-agent.js +1 -0
- package/dist/core/task-registry-shared.d.ts +5 -0
- package/dist/core/task-registry.d.ts +7 -0
- package/dist/core/task-registry.js +17 -0
- package/dist/core/task-result.d.ts +1 -1
- package/dist/core/task-spec.d.ts +6 -6
- package/dist/core/tighten-task-spec.d.ts +6 -4
- package/dist/core/tighten-task-spec.js +2 -2
- package/dist/core/tool-catalog-entries.d.ts +1 -1
- package/dist/core/tool-catalog-entries.js +52 -52
- package/dist/core/tool-face-reader.d.ts +49 -0
- package/dist/core/tool-face-reader.js +94 -0
- package/dist/core/tool-face.d.ts +17 -8
- package/dist/core/tool-face.js +1 -0
- package/dist/core/tool-policy.d.ts +64 -23
- package/dist/core/tool-policy.js +62 -56
- package/dist/core/tool-roster.d.ts +46 -28
- package/dist/core/tool-roster.js +91 -14
- package/dist/core/tool-spec-members.js +2 -2
- package/dist/core/tool-spec.d.ts +20 -4
- package/dist/core/tools.js +2 -0
- package/dist/core/trace.d.ts +24 -2
- package/dist/core/types.d.ts +2 -2
- package/dist/core/types.js +1 -1
- package/dist/core/untrusted-text.d.ts +7 -0
- package/dist/core/untrusted-text.js +3 -8
- package/dist/core/user-attachment.d.ts +43 -0
- package/dist/core/user-attachment.js +92 -15
- package/dist/engine/compaction/compaction.d.ts +2 -2
- package/dist/engine/compaction/utils.d.ts +2 -2
- package/dist/engine/harness/types.d.ts +7 -4
- package/dist/engine/llm/types.d.ts +7 -2
- package/dist/engine/llm/types.js +2 -1
- package/dist/engine/session/parent-chain-guard.d.ts +10 -0
- package/dist/engine/session/parent-chain-guard.js +10 -0
- package/dist/engine/session/repo-utils.d.ts +5 -0
- package/dist/engine/session/repo-utils.js +9 -0
- package/dist/engine/session/storage-base.js +5 -2
- package/dist/index.d.ts +23 -9
- package/dist/index.js +19 -8
- package/dist/internal/harness.d.ts +1 -0
- package/dist/internal/harness.js +1 -0
- package/dist/internal/llm.d.ts +1 -1
- package/dist/internal/llm.js +1 -1
- package/dist/orchestration/run-workflow-tool.d.ts +3 -0
- package/dist/orchestration/run-workflow-tool.js +1 -0
- package/dist/orchestration/workflow-governance.d.ts +1 -1
- package/dist/orchestration/workflow.d.ts +3 -0
- package/dist/orchestration/workflow.js +2 -1
- package/dist/prompt-assembly/catalog/schema.d.ts +3 -0
- package/dist/prompt-assembly/catalog/schema.js +3 -0
- package/dist/prompt-assembly/overlay.js +2 -2
- package/dist/prompt-assembly/select.js +2 -2
- package/dist/prompts/default.d.ts +1 -1
- package/dist/shell/lex.d.ts +87 -8
- package/dist/shell/lex.js +283 -49
- package/dist/stores/file/index.d.ts +8 -6
- package/dist/tools/artifact/artifact-tool.js +10 -0
- package/dist/tools/fs/bash-program-position.d.ts +3 -15
- package/dist/tools/fs/bash-program-position.js +3 -10
- package/dist/tools/fs/bash-readonly-classifier.js +2 -0
- package/dist/tools/fs/fs-bash.d.ts +3 -3
- package/dist/tools/fs/fs-bash.js +72 -101
- package/dist/tools/fs/fs-read.d.ts +12 -0
- package/dist/tools/fs/fs-read.js +10 -9
- package/dist/tools/fs/fs-shared.d.ts +27 -31
- package/dist/tools/fs/fs-shared.js +12 -15
- package/dist/tools/fs/fs-write.js +1 -1
- package/dist/tools/fs/index.js +2 -2
- package/dist/tools/fs/read-deny-home.d.ts +1 -1
- package/dist/tools/fs/read-deny-home.js +5 -4
- package/dist/tools/fs/read-deny.d.ts +16 -3
- package/dist/tools/fs/read-deny.js +12 -9
- package/dist/tools/fs/safety.d.ts +7 -6
- package/dist/tools/fs/shell-read-bindings.d.ts +5 -3
- package/dist/tools/fs/shell-read-bindings.js +15 -16
- package/dist/tools/fs/shell-read-device.js +1 -1
- package/dist/tools/fs/shell-read-plan.js +36 -47
- package/dist/tools/fs/shell-read-word.d.ts +1 -6
- package/dist/tools/fs/shell-read-word.js +2 -20
- package/dist/tools/fs/shell-reentry-text.js +1 -1
- package/dist/tools/web.d.ts +2 -2
- package/package.json +4 -1
- package/test/export-surface.snapshot.json +5654 -5520
- package/wire-changes/core@7.53.0.json +30 -8
- package/wire-changes/core@7.54.0.json +2870 -0
- package/wire-changes/core@7.54.1.json +37 -0
- package/dist/core/shell-scan.d.ts +0 -60
- package/dist/core/shell-scan.js +0 -183
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
import { applyClampSeats, clampFieldSeatsOf } from "./child-clamp-seats.js";
|
|
2
|
+
import { assertNoRetiredKeys } from "./retired-keys.js";
|
|
3
|
+
const RETIRED_PARENT_SPEC_SEAT_KEYS = Object.freeze({
|
|
4
|
+
memoryOff: "the request-level memory-off fact is the clamp table's `memory` row now — carry `memory: { enabled: false }`",
|
|
5
|
+
});
|
|
6
|
+
export function assertParentSpecSeatCurrent(seat) {
|
|
7
|
+
if (seat !== undefined)
|
|
8
|
+
assertNoRetiredKeys(seat, RETIRED_PARENT_SPEC_SEAT_KEYS, "ParentSpecSeat");
|
|
9
|
+
}
|
|
1
10
|
export function parentSpecSeatOf(spec, frozenOnQuestion) {
|
|
2
11
|
return Object.freeze({
|
|
3
12
|
...(spec.principal !== undefined ? { principal: spec.principal } : {}),
|
|
@@ -15,14 +24,15 @@ export function parentSpecSeatOf(spec, frozenOnQuestion) {
|
|
|
15
24
|
...(spec.fileHistory !== undefined ? { fileHistory: spec.fileHistory } : {}),
|
|
16
25
|
...(spec.promptForm !== undefined ? { promptForm: spec.promptForm } : {}),
|
|
17
26
|
...(spec.promptBinding !== undefined ? { promptBinding: spec.promptBinding } : {}),
|
|
18
|
-
...(spec
|
|
27
|
+
...clampFieldSeatsOf(spec),
|
|
19
28
|
});
|
|
20
29
|
}
|
|
21
30
|
export function applyParentSpecSeat(child, seat) {
|
|
22
31
|
if (seat === undefined)
|
|
23
32
|
return child;
|
|
33
|
+
assertParentSpecSeatCurrent(seat);
|
|
24
34
|
const fill = (key, value) => child[key] === undefined && value !== undefined ? { [key]: value } : {};
|
|
25
|
-
|
|
35
|
+
const landed = {
|
|
26
36
|
...child,
|
|
27
37
|
...fill("principal", seat.principal),
|
|
28
38
|
...fill("clientContext", seat.clientContext),
|
|
@@ -39,15 +49,13 @@ export function applyParentSpecSeat(child, seat) {
|
|
|
39
49
|
...fill("promptBinding", seat.promptBinding),
|
|
40
50
|
...(seat.oneShot === true ? { oneShot: true } : {}),
|
|
41
51
|
...(seat.memoryPersistenceCapable === false ? { memoryPersistenceCapable: false } : fill("memoryPersistenceCapable", seat.memoryPersistenceCapable)),
|
|
42
|
-
...(seat.memoryOff === true ? { memory: memoryTurnedOff(child.memory) } : {}),
|
|
43
52
|
};
|
|
44
|
-
|
|
45
|
-
export function memoryTurnedOff(memory) {
|
|
46
|
-
return { ...(memory ?? {}), enabled: false };
|
|
53
|
+
return applyClampSeats(landed, clampFieldSeatsOf(seat));
|
|
47
54
|
}
|
|
48
55
|
const SEAT_FACTS_ON_CTX = ["principal", "oneShot", "onQuestion", "memoryPersistenceCapable"];
|
|
49
56
|
export function parentSpecSeatOfCtx(ctx) {
|
|
50
57
|
const seat = ctx.parentSpecSeat;
|
|
58
|
+
assertParentSpecSeatCurrent(seat);
|
|
51
59
|
const facts = SEAT_FACTS_ON_CTX.filter((k) => ctx[k] !== undefined);
|
|
52
60
|
if (seat === undefined) {
|
|
53
61
|
if (facts.length === 0)
|
|
@@ -3,10 +3,12 @@
|
|
|
3
3
|
* hanging / what brings it back), the three word types and the resume match DERIVED from it, and the
|
|
4
4
|
* registry-bound `gate` × `pendingAction` pair with its checked constructor. A vocabulary module on the
|
|
5
5
|
* floor: it types against `checkpoint-store.ts` (type-only, no runtime edge) and `checkpoint-store.ts`
|
|
6
|
-
* reaches down for the pair type
|
|
6
|
+
* reaches down for the pair type and the card reading; its one runtime edge is the card vocabulary
|
|
7
|
+
* (`tool-face.ts`, itself a leaf).
|
|
7
8
|
*/
|
|
8
9
|
import type { AssertAllKeysHandled } from "./ask-origin.js";
|
|
9
10
|
import type { Checkpoint, CheckpointGate, CheckpointRow, PendingAction, ResumeOutcome } from "./checkpoint-store.js";
|
|
11
|
+
import { type ToolApprovalCard } from "./tool-face.js";
|
|
10
12
|
/**
|
|
11
13
|
* THE registry of pauses — one row per way a run can stop and wait, and the ONE place the three faces
|
|
12
14
|
* of that pause are declared together: WHY it stopped (`CheckpointGate.kind`, the row key), WHAT it left
|
|
@@ -93,6 +95,38 @@ export declare function isQuestionGate(gate: {
|
|
|
93
95
|
readonly kind: GateKind;
|
|
94
96
|
readonly approvalCard?: unknown;
|
|
95
97
|
}): boolean;
|
|
98
|
+
/** What a parked row's gate says about its approval card — the four answers {@link parkedApprovalCardOf} gives. */
|
|
99
|
+
export type ParkedApprovalCardReading =
|
|
100
|
+
/** An approval gate carrying a word of the closed card set (`TOOL_APPROVAL_CARDS`). */
|
|
101
|
+
{
|
|
102
|
+
readonly kind: "card";
|
|
103
|
+
readonly card: ToolApprovalCard;
|
|
104
|
+
}
|
|
105
|
+
/** An approval gate carrying no card. */
|
|
106
|
+
| {
|
|
107
|
+
readonly kind: "no_card";
|
|
108
|
+
}
|
|
109
|
+
/** An approval gate carrying a value outside the closed set (a word a later engine minted, a hand-edited row,
|
|
110
|
+
* a non-string). `word` is the value as read, unscreened: a caller that renders it bounds and redacts it. */
|
|
111
|
+
| {
|
|
112
|
+
readonly kind: "unknown_word";
|
|
113
|
+
readonly word: unknown;
|
|
114
|
+
}
|
|
115
|
+
/** Not an approval gate: a registered gate whose `pending` column is not `tool_approval`, or a gate kind the
|
|
116
|
+
* registry does not hold. No card is read on it. */
|
|
117
|
+
| {
|
|
118
|
+
readonly kind: "not_approval";
|
|
119
|
+
};
|
|
120
|
+
/**
|
|
121
|
+
* The one reading of a parked row's approval card. A gate is an approval gate when its kind is a registry row whose
|
|
122
|
+
* `pending` column is `tool_approval`; only there is the card read, and only a member of the closed set is a card.
|
|
123
|
+
* Each member of `gate` is read once (the value screened is the value answered). Pure: the answer says nothing about
|
|
124
|
+
* how loud an `unknown_word` should be — that is the caller's to decide.
|
|
125
|
+
*/
|
|
126
|
+
export declare function parkedApprovalCardOf(gate: {
|
|
127
|
+
readonly kind: unknown;
|
|
128
|
+
readonly approvalCard?: unknown;
|
|
129
|
+
}): ParkedApprovalCardReading;
|
|
96
130
|
/** The one resume-match predicate: does this outcome gate word answer a checkpoint of this kind? `wake`
|
|
97
131
|
* answers any (it passed its own, stricter, non-gate validation upstream); every other word must be the
|
|
98
132
|
* row's `resume` column. Replaces a six-arm hand-written disjunction. */
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isToolApprovalCard } from "./tool-face.js";
|
|
1
2
|
export const PAUSE_REGISTRY = {
|
|
2
3
|
human: { pending: "tool_approval", resume: "policy_ask", facet: "A", taskStatus: "suspended", terminal: false },
|
|
3
4
|
irreversible_ask: { pending: "tool_approval", resume: "policy_ask", facet: "A", taskStatus: "suspended", terminal: false },
|
|
@@ -15,6 +16,15 @@ export function facetOf(gate) {
|
|
|
15
16
|
export function isQuestionGate(gate) {
|
|
16
17
|
return PAUSE_REGISTRY[gate.kind].pending === "tool_approval" && gate.approvalCard === "question";
|
|
17
18
|
}
|
|
19
|
+
export function parkedApprovalCardOf(gate) {
|
|
20
|
+
const kind = gate.kind;
|
|
21
|
+
if (!isGateKind(kind) || PAUSE_REGISTRY[kind].pending !== "tool_approval")
|
|
22
|
+
return { kind: "not_approval" };
|
|
23
|
+
const word = gate.approvalCard;
|
|
24
|
+
if (word === undefined)
|
|
25
|
+
return { kind: "no_card" };
|
|
26
|
+
return isToolApprovalCard(word) ? { kind: "card", card: word } : { kind: "unknown_word", word };
|
|
27
|
+
}
|
|
18
28
|
export function resumeGateMatches(gateKind, outcomeGate) {
|
|
19
29
|
return outcomeGate === "wake" || PAUSE_REGISTRY[gateKind].resume === outcomeGate;
|
|
20
30
|
}
|
|
@@ -38,8 +38,15 @@ export interface RuleCandidate {
|
|
|
38
38
|
rule: string;
|
|
39
39
|
scope: RuleScope;
|
|
40
40
|
}
|
|
41
|
+
/** The approval-record kinds, spelled once: {@link RuleApprovalKind} is derived from this tuple. */
|
|
42
|
+
export declare const RULE_APPROVAL_KINDS: readonly ["card", "import", "starter"];
|
|
41
43
|
/** What the approval record is for — it decides the provenance the minted adds carry. */
|
|
42
|
-
export type RuleApprovalKind =
|
|
44
|
+
export type RuleApprovalKind = (typeof RULE_APPROVAL_KINDS)[number];
|
|
45
|
+
/** The approval-record states, spelled once: {@link RuleApprovalState} is derived from this tuple. */
|
|
46
|
+
export declare const RULE_APPROVAL_STATES: readonly ["pending", "approved", "redeemed"];
|
|
47
|
+
/** Where one approval record stands: `pending` as minted, `approved` once a person confirms, `redeemed` once a
|
|
48
|
+
* redemption has recorded its dot (recorded before the rule is written, so a crash leaves a replayable intent). */
|
|
49
|
+
export type RuleApprovalState = (typeof RULE_APPROVAL_STATES)[number];
|
|
43
50
|
/**
|
|
44
51
|
* design/375 §4.1 — one "don't ask again" OPTION as the record stores it: references into the
|
|
45
52
|
* record's `candidates` by index, never rule text of its own. The display triple (rule, match,
|
|
@@ -78,6 +85,10 @@ export type RuleOffer2 = {
|
|
|
78
85
|
readonly uncoveredSegments?: number;
|
|
79
86
|
readonly uncoveredDetail?: readonly UncoveredSegmentDetail[];
|
|
80
87
|
};
|
|
88
|
+
/** The CURRENT record-form version stamp ({@link RuleApprovalRecord.schema}), spelled once: every mint writes it and
|
|
89
|
+
* every readback compares against it by equality (an older or absent stamp is a stale row, never a floor). A
|
|
90
|
+
* durable backend's stale gate reads this value rather than its own number. */
|
|
91
|
+
export declare const RULE_APPROVAL_RECORD_SCHEMA = 4;
|
|
81
92
|
/** A durable record of one pending/settled human decision about minting rules.
|
|
82
93
|
*
|
|
83
94
|
* Identity (design/182 §4.5, additive): a v1 record carries `principal` and nothing else — it stays
|
|
@@ -101,9 +112,9 @@ export interface RuleApprovalRecord {
|
|
|
101
112
|
* bargain, unchanged). `4`: every candidate carries its `behavior` (the settings import now carries
|
|
102
113
|
* the deny/ask lists too) — a `schema: 3` row is stale by the same equality gate.
|
|
103
114
|
*/
|
|
104
|
-
schema:
|
|
115
|
+
schema: typeof RULE_APPROVAL_RECORD_SCHEMA;
|
|
105
116
|
kind: RuleApprovalKind;
|
|
106
|
-
state:
|
|
117
|
+
state: RuleApprovalState;
|
|
107
118
|
candidates: RuleCandidate[];
|
|
108
119
|
/**
|
|
109
120
|
* The card's OPTION structure over `candidates` (design/375 §4.1). The flat candidate list stays
|
|
@@ -504,8 +515,10 @@ export declare function redeemRuleTicket(opts: {
|
|
|
504
515
|
owner?: RuleOwner;
|
|
505
516
|
deps: RuleConsentDeps;
|
|
506
517
|
}): Promise<RedeemResult>;
|
|
518
|
+
/** The settings layer names, spelled once: {@link ImportedSettingsLayer} is derived from this tuple. */
|
|
519
|
+
export declare const IMPORTED_SETTINGS_LAYERS: readonly ["userSettings", "projectSettings", "localSettings"];
|
|
507
520
|
/** The three user-editable settings layers this version reads. */
|
|
508
|
-
export type ImportedSettingsLayer =
|
|
521
|
+
export type ImportedSettingsLayer = (typeof IMPORTED_SETTINGS_LAYERS)[number];
|
|
509
522
|
/** One layer to read. `root` is the canonical directory a project-layer rule is scoped to — taken from
|
|
510
523
|
* where the FILE actually lives, since the local layer is not always at a repository root. */
|
|
511
524
|
export interface CcImportLayer {
|
|
@@ -528,7 +541,7 @@ export interface ImportPreview {
|
|
|
528
541
|
reason: string;
|
|
529
542
|
}>;
|
|
530
543
|
/**
|
|
531
|
-
* ALWAYS EMPTY in this version — a compatibility seat, kept on the wire shape so an existing reader
|
|
544
|
+
* ALWAYS EMPTY in this version — a compatibility seat (RL-1336-import-translated), kept on the wire shape so an existing reader
|
|
532
545
|
* keeps finding the key (removing a wire field would break readers for no gain).
|
|
533
546
|
*
|
|
534
547
|
* The seat once carried the entries whose MATCH FORM was rewritten on the way in (the space-star
|
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
import { randomBytes } from "node:crypto";
|
|
2
|
-
import { escapeForDisclosure, hasUnrenderableCharacters, parseRuleText, ruleAdmitsCommand, ruleBreadthWarningsOf, ruleToolGrammarOf, segmentCoverageOf, suggestRuleForName, suggestRulesForCommand, RULE_BEHAVIORS, RULE_BEHAVIOR_PRECEDENCE, isRuleBehavior, } from "./permission-rule-model.js";
|
|
2
|
+
import { escapeForDisclosure, hasUnrenderableCharacters, parseRuleText, ruleAdmitsCommand, ruleBreadthWarningsOf, ruleToolGrammarOf, segmentCoverageOf, suggestRuleForName, suggestRulesForCommand, EDITED_RULE_BREADTH_WARNING_CODES, UNCOVERED_SEGMENT_REASONS, RULE_BEHAVIORS, RULE_BEHAVIOR_PRECEDENCE, isRuleBehavior, } from "./permission-rule-model.js";
|
|
3
3
|
import { effectiveOrThrow } from "./permission-rule-provider.js";
|
|
4
4
|
import { errText, isValidConsentScope, landRuleAdd, sameRuleOwner, sameScope, writerOf } from "./permission-rule-store.js";
|
|
5
|
+
const isUncoveredSegmentReason = (v) => UNCOVERED_SEGMENT_REASONS.some((word) => v === word);
|
|
6
|
+
export const RULE_APPROVAL_KINDS = Object.freeze(["card", "import", "starter"]);
|
|
7
|
+
export const RULE_APPROVAL_STATES = Object.freeze(["pending", "approved", "redeemed"]);
|
|
8
|
+
export const RULE_APPROVAL_RECORD_SCHEMA = 4;
|
|
5
9
|
export class InMemoryRuleApprovalRecordStore {
|
|
6
10
|
rows = new Map();
|
|
7
11
|
async get(id) {
|
|
8
12
|
const r = this.rows.get(id);
|
|
9
13
|
if (r === undefined)
|
|
10
14
|
return undefined;
|
|
11
|
-
if (r.schema !==
|
|
15
|
+
if (r.schema !== RULE_APPROVAL_RECORD_SCHEMA) {
|
|
12
16
|
return {
|
|
13
17
|
staleSchema: true,
|
|
14
18
|
id: r.id,
|
|
@@ -156,7 +160,7 @@ function approvalRecordDamageOf(rec) {
|
|
|
156
160
|
if (typeof row !== "object" ||
|
|
157
161
|
row === null ||
|
|
158
162
|
typeof row.segment !== "string" ||
|
|
159
|
-
(
|
|
163
|
+
!UNCOVERED_SEGMENT_REASONS.some((word) => row.reason === word)) {
|
|
160
164
|
return `card batch offer ${at} reason row ${ri} is not a segment/reason row from the closed reason set`;
|
|
161
165
|
}
|
|
162
166
|
}
|
|
@@ -195,7 +199,7 @@ function approvalRecordDamageOf(rec) {
|
|
|
195
199
|
return "the edited seat is not an object";
|
|
196
200
|
const w = rec.edited.warnings;
|
|
197
201
|
if (w !== undefined) {
|
|
198
|
-
if (!Array.isArray(w) || w.length !== 1 || (
|
|
202
|
+
if (!Array.isArray(w) || w.length !== 1 || !EDITED_RULE_BREADTH_WARNING_CODES.some((code) => w[0] === code)) {
|
|
199
203
|
return "the edited seat's warnings are not a single-code array from the closed breadth-code set";
|
|
200
204
|
}
|
|
201
205
|
}
|
|
@@ -238,7 +242,7 @@ export function ruleOffersOfRecord(rec) {
|
|
|
238
242
|
return true;
|
|
239
243
|
const segment = row.segment;
|
|
240
244
|
const reason = row.reason;
|
|
241
|
-
if (typeof segment !== "string" || (reason
|
|
245
|
+
if (typeof segment !== "string" || !isUncoveredSegmentReason(reason))
|
|
242
246
|
return true;
|
|
243
247
|
detail.push({ segment, reason });
|
|
244
248
|
}
|
|
@@ -348,7 +352,7 @@ export async function prepareCardApproval(opts) {
|
|
|
348
352
|
const record = {
|
|
349
353
|
id: mintId(opts.deps, "rar"),
|
|
350
354
|
...recordIdentityOf(owner),
|
|
351
|
-
schema:
|
|
355
|
+
schema: RULE_APPROVAL_RECORD_SCHEMA,
|
|
352
356
|
kind: "card",
|
|
353
357
|
state: "pending",
|
|
354
358
|
rev: 0,
|
|
@@ -667,6 +671,7 @@ async function applyRedemption(args) {
|
|
|
667
671
|
function originOfRecordKind(kind) {
|
|
668
672
|
return kind === "import" ? "imported-cc" : kind === "starter" ? "starter" : "user";
|
|
669
673
|
}
|
|
674
|
+
export const IMPORTED_SETTINGS_LAYERS = Object.freeze(["userSettings", "projectSettings", "localSettings"]);
|
|
670
675
|
const IMPORT_UNCOVERED_LAYERS = { flagSettings: "not-imported-v1", policySettings: "not-imported-v1" };
|
|
671
676
|
export async function prepareCcImport(opts) {
|
|
672
677
|
const owner = resolveCallerOwner(opts.principal, opts.owner, "prepareCcImport");
|
|
@@ -760,7 +765,7 @@ export async function prepareCcImport(opts) {
|
|
|
760
765
|
const record = {
|
|
761
766
|
id: mintId(opts.deps, "rar"),
|
|
762
767
|
...recordIdentityOf(owner),
|
|
763
|
-
schema:
|
|
768
|
+
schema: RULE_APPROVAL_RECORD_SCHEMA,
|
|
764
769
|
kind: "import",
|
|
765
770
|
state: "pending",
|
|
766
771
|
rev: 0,
|
|
@@ -794,7 +799,7 @@ export async function prepareStarterBatch(opts) {
|
|
|
794
799
|
const record = {
|
|
795
800
|
id: mintId(opts.deps, "rar"),
|
|
796
801
|
...recordIdentityOf(owner),
|
|
797
|
-
schema:
|
|
802
|
+
schema: RULE_APPROVAL_RECORD_SCHEMA,
|
|
798
803
|
kind: "starter",
|
|
799
804
|
state: "pending",
|
|
800
805
|
rev: 0,
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* The two DIRECTIONS read a command differently, on purpose: an allow must cover the WHOLE call
|
|
16
16
|
* ({@link ruleAdmitsCommand} — one recorded yes clears exactly what it spells), while a deny/ask
|
|
17
17
|
* speaks about any PROGRAM RUN the call may perform ({@link programRunReachOf} — whole command or
|
|
18
|
-
* any segment of a chain, read
|
|
18
|
+
* any segment of a chain, read off the one shell lexer by `shell-run-candidates.ts`, and answering `unreadable`
|
|
19
19
|
* for what it cannot read rather than silence). Precedence across behaviors is the upstream one,
|
|
20
20
|
* deny > ask > allow ({@link RULE_BEHAVIOR_PRECEDENCE}, the one table every adjudication reads).
|
|
21
21
|
*
|
|
@@ -235,6 +235,10 @@ export declare function nameRuleSubjectOf(toolName: string, args: unknown): stri
|
|
|
235
235
|
* name, as a policy's rule does. Every lane that judges a call by a rule's word reads it here: the policy compiler, the
|
|
236
236
|
* person's stored rows, the org partition, the allow lists. Empty ⇒ no grammar speaks for the call. */
|
|
237
237
|
export declare function laneToolNamesOf(req: Pick<import("./tool-policy.js").ToolCallRequest, "toolName" | "face">): readonly string[];
|
|
238
|
+
/** The match words a persisted rule is stored with, spelled once: {@link PersistedRuleMatch} is derived from this
|
|
239
|
+
* tuple (each form is documented there). A store backend that checks a stored row's `match` reads this value
|
|
240
|
+
* rather than keeping its own list. */
|
|
241
|
+
export declare const PERSISTED_RULE_MATCHES: readonly ["exact", "prefix", "wildcard", "subpath", "path", "name", "name_prefix", "host", "subdomain"];
|
|
238
242
|
/**
|
|
239
243
|
* Match forms, one family per grammar (`prefix` and `wildcard` are one predicate):
|
|
240
244
|
* · `exact` — admits the one string the rule spells.
|
|
@@ -261,7 +265,7 @@ export declare function laneToolNamesOf(req: Pick<import("./tool-policy.js").Too
|
|
|
261
265
|
* · `host` / `subdomain` — the DOMAIN grammar ({@link parseDomainRuleContent}): one exact host, or every
|
|
262
266
|
* host below one domain.
|
|
263
267
|
*/
|
|
264
|
-
export type PersistedRuleMatch =
|
|
268
|
+
export type PersistedRuleMatch = (typeof PERSISTED_RULE_MATCHES)[number];
|
|
265
269
|
/**
|
|
266
270
|
* Where a rule applies — design/382 §4.1, the THREE consent dimensions.
|
|
267
271
|
*
|
|
@@ -306,9 +310,12 @@ export interface RuleDot {
|
|
|
306
310
|
actor: string;
|
|
307
311
|
counter: number;
|
|
308
312
|
}
|
|
313
|
+
/** The add provenance words, spelled once: {@link RuleAddOrigin} is derived from this tuple, and the sync wire's and
|
|
314
|
+
* the session overlay's add checks read it. */
|
|
315
|
+
export declare const RULE_ADD_ORIGINS: readonly ["user", "imported-cc", "starter"];
|
|
309
316
|
/** Where one add came from. Stored PER ADD — never folded across dots (a folded provenance cannot
|
|
310
317
|
* follow a dot that is later deleted on its own). */
|
|
311
|
-
export type RuleAddOrigin =
|
|
318
|
+
export type RuleAddOrigin = (typeof RULE_ADD_ORIGINS)[number];
|
|
312
319
|
/** One add of one logical rule, carrying its own dot and its own provenance. */
|
|
313
320
|
export interface RuleAdd {
|
|
314
321
|
dot: RuleDot;
|
|
@@ -462,7 +469,7 @@ export declare const BARE_INTERPRETER_NAMES: ReadonlySet<string>;
|
|
|
462
469
|
*
|
|
463
470
|
* Refusing the keyword puts the whole command outside the ALLOW lane, which is the honest answer: no rule
|
|
464
471
|
* can be minted for it and no allow matches it, so it asks. The DENY/ASK direction reads these shapes
|
|
465
|
-
* with its
|
|
472
|
+
* with its run-candidate view (`shell-run-candidates.ts`: a prefixing keyword is stripped so `do curl evil.example` runs
|
|
466
473
|
* `curl`; a `for`/`case` header, whose commands need a syntax tree, is `unreadable` and asks).
|
|
467
474
|
*
|
|
468
475
|
* `!` is here for the same reason with a sharper edge: it is a keyword that PREFIXES a real command, so
|
|
@@ -780,11 +787,14 @@ export declare function programRunReachOf(rule: Pick<PersistedRule, "match" | "c
|
|
|
780
787
|
/** The boolean face of {@link programRunReachOf}: `reached` only. The shadow view and the pins that
|
|
781
788
|
* assert a reach read this; a lane that must fail closed on `unreadable` reads the outcome. */
|
|
782
789
|
export declare function ruleReachesProgramRun(rule: Pick<PersistedRule, "match" | "command">, command: string): boolean;
|
|
790
|
+
/** The breadth-warning codes, spelled once: {@link EditedRuleBreadthWarning}'s `code` is derived from this tuple,
|
|
791
|
+
* and a consent record's `edited.warnings` stores exactly these words (its readback refuses any other). */
|
|
792
|
+
export declare const EDITED_RULE_BREADTH_WARNING_CODES: readonly ["broad_prefix", "compound_prefix"];
|
|
783
793
|
/** design/382 §3.3-5 — one BREADTH note about a rule a person is about to persist: which wide shape
|
|
784
794
|
* it has, said in words a surface can show beside the input line. A warning is never a refusal —
|
|
785
795
|
* the closed refusal set is untouched — and never adjudication input. */
|
|
786
796
|
export interface EditedRuleBreadthWarning {
|
|
787
|
-
readonly code:
|
|
797
|
+
readonly code: (typeof EDITED_RULE_BREADTH_WARNING_CODES)[number];
|
|
788
798
|
/** The baseline sentence (a surface may reword it, never invert it — the same contract as the
|
|
789
799
|
* §3.5 reason baselines). */
|
|
790
800
|
readonly message: string;
|
|
@@ -1187,6 +1197,11 @@ export interface SegmentRuleSuggestion {
|
|
|
1187
1197
|
* show a reader something other than what they authorize. */
|
|
1188
1198
|
readonly segment: string;
|
|
1189
1199
|
}
|
|
1200
|
+
/** The batch member discriminants as a value: the `kind` words of {@link RuleOfferBatchMember}, both directions
|
|
1201
|
+
* fenced at compile time ({@link RuleOfferBatchMemberKindsCoverEveryArm} / {@link RuleOfferBatchMemberArmsCoverEveryKind}).
|
|
1202
|
+
* A consumer applying the degrade arm below (a member whose `kind` is outside this set drops the whole batch offer)
|
|
1203
|
+
* reads this value rather than keeping its own list. */
|
|
1204
|
+
export declare const RULE_OFFER_BATCH_MEMBER_KINDS: readonly ["command", "directoryRead"];
|
|
1190
1205
|
/**
|
|
1191
1206
|
* design/382 §2.3 (B3, BREAKING) — one member of a batch offer, a DISCRIMINATED union keyed by
|
|
1192
1207
|
* `kind`:
|
|
@@ -1215,6 +1230,10 @@ export type RuleOfferBatchMember = (SegmentRuleSuggestion & {
|
|
|
1215
1230
|
/** The (folded) cd segment this authorization came from — rendering context, never adjudication input. */
|
|
1216
1231
|
readonly segment: string;
|
|
1217
1232
|
};
|
|
1233
|
+
/** Compile fence: an arm whose `kind` is not in {@link RULE_OFFER_BATCH_MEMBER_KINDS} turns the argument into that word. */
|
|
1234
|
+
export type RuleOfferBatchMemberKindsCoverEveryArm = AssertAllKeysHandled<Exclude<RuleOfferBatchMember["kind"], (typeof RULE_OFFER_BATCH_MEMBER_KINDS)[number]>>;
|
|
1235
|
+
/** Compile fence, the other direction: a word in {@link RULE_OFFER_BATCH_MEMBER_KINDS} that no arm carries. */
|
|
1236
|
+
export type RuleOfferBatchMemberArmsCoverEveryKind = AssertAllKeysHandled<Exclude<(typeof RULE_OFFER_BATCH_MEMBER_KINDS)[number], RuleOfferBatchMember["kind"]>>;
|
|
1218
1237
|
/**
|
|
1219
1238
|
* design/375 §3.1 — ONE "don't ask again" option on an approval card. A CLOSED discriminated union
|
|
1220
1239
|
* (`kind`); consumers parse it per element, and an element whose `kind` they do not know is dropped
|
|
@@ -1278,6 +1297,10 @@ export type RuleOffer = {
|
|
|
1278
1297
|
*/
|
|
1279
1298
|
readonly uncoveredDetail?: readonly UncoveredSegmentDetail[];
|
|
1280
1299
|
};
|
|
1300
|
+
/** The uncovered-segment reason words, spelled once: {@link UncoveredSegmentDetail}'s `reason` is derived from this
|
|
1301
|
+
* tuple, {@link UNCOVERED_SEGMENT_REASON_BASELINE} is keyed by it, and a consent record's reason rows are read
|
|
1302
|
+
* against it. */
|
|
1303
|
+
export declare const UNCOVERED_SEGMENT_REASONS: readonly ["redirection", "no_rule_form", "cap_overflow"];
|
|
1281
1304
|
/** design/382 §3.5 — the closed reason set for one uncovered segment of a batch offer.
|
|
1282
1305
|
*
|
|
1283
1306
|
* · `"redirection"` — the segment carries a shell redirection: refused by discipline (#490 修①,
|
|
@@ -1300,7 +1323,7 @@ export interface UncoveredSegmentDetail {
|
|
|
1300
1323
|
/** The folded, trimmed segment text — raw post-rewrite command bytes, same contract as
|
|
1301
1324
|
* {@link SegmentRuleSuggestion.segment}. */
|
|
1302
1325
|
readonly segment: string;
|
|
1303
|
-
readonly reason:
|
|
1326
|
+
readonly reason: (typeof UNCOVERED_SEGMENT_REASONS)[number];
|
|
1304
1327
|
}
|
|
1305
1328
|
/**
|
|
1306
1329
|
* design/382 §3.5-2 — the BASELINE "why + outlet" sentence per uncovered-segment reason: the
|
|
@@ -8,7 +8,7 @@ import { isShellFaceToolName, pathTargetOf, protectivePathTargetOf } from "./too
|
|
|
8
8
|
import { heldValues } from "./tool-name-readings.js";
|
|
9
9
|
import { carriesDotSegment, isAbsoluteForFamily, isIdentityDir, isPathIdentity, isShellRootedSpellingUnmapped, isUncIdentity, pathFamilyOf, pathIdentityOf } from "../tools/fs/safety.js";
|
|
10
10
|
import { bindPathRules, judgeRuleRows, rowsWhereTheyLead } from "./path-rule-binding.js";
|
|
11
|
-
import { readShellCommand } from "./shell-
|
|
11
|
+
import { readShellCommand } from "./shell-run-candidates.js";
|
|
12
12
|
import { lineDescriptorsOfText, readOnlyLineObjection, readOnlyShellVerdict } from "./read-only-shell.js";
|
|
13
13
|
import { ARTIFACT_ACTIONS } from "./artifact-host.js";
|
|
14
14
|
import { ARTIFACT_TOOL_NAME } from "./tool-catalog-entries.js";
|
|
@@ -111,9 +111,11 @@ function ruleWordOf(name) {
|
|
|
111
111
|
return COMMAND_RULE_TOOL;
|
|
112
112
|
return ruleToolGrammarOf(name) !== undefined ? name : undefined;
|
|
113
113
|
}
|
|
114
|
+
export const PERSISTED_RULE_MATCHES = Object.freeze(["exact", "prefix", "wildcard", "subpath", "path", "name", "name_prefix", "host", "subdomain"]);
|
|
114
115
|
function isCommandMatch(match) {
|
|
115
116
|
return match === "exact" || match === "prefix" || match === "wildcard";
|
|
116
117
|
}
|
|
118
|
+
export const RULE_ADD_ORIGINS = Object.freeze(["user", "imported-cc", "starter"]);
|
|
117
119
|
export const MAX_RULE_TEXT_CHARS = 512;
|
|
118
120
|
export const NOTICE_RULE_TEXT_MAX = 2 * MAX_RULE_TEXT_CHARS;
|
|
119
121
|
export function noticeRuleText(text) {
|
|
@@ -811,6 +813,7 @@ function admitsUnder(rule, command, reading) {
|
|
|
811
813
|
return false;
|
|
812
814
|
return folded === rule.command || folded.startsWith(rule.command + " ");
|
|
813
815
|
}
|
|
816
|
+
export const EDITED_RULE_BREADTH_WARNING_CODES = Object.freeze(["broad_prefix", "compound_prefix"]);
|
|
814
817
|
export function ruleBreadthWarningsOf(rule) {
|
|
815
818
|
if (rule.match !== "prefix" && rule.match !== "wildcard")
|
|
816
819
|
return [];
|
|
@@ -1159,6 +1162,8 @@ export function segmentCoverageOf(command, rules, call) {
|
|
|
1159
1162
|
};
|
|
1160
1163
|
});
|
|
1161
1164
|
}
|
|
1165
|
+
export const RULE_OFFER_BATCH_MEMBER_KINDS = Object.freeze(["command", "directoryRead"]);
|
|
1166
|
+
export const UNCOVERED_SEGMENT_REASONS = Object.freeze(["redirection", "no_rule_form", "cap_overflow"]);
|
|
1162
1167
|
export const UNCOVERED_SEGMENT_REASON_BASELINE = {
|
|
1163
1168
|
redirection: "This part carries a redirection, so it is approved per use — no rule can cover this spelling. If you trust its redirection-free form, a rule can be written for that form (the card's edit row, or settings).",
|
|
1164
1169
|
no_rule_form: "No rule form exists for this part; it can be approved per use.",
|
|
@@ -276,6 +276,27 @@ export declare function orgRuleListOf(rules: unknown): {
|
|
|
276
276
|
} | {
|
|
277
277
|
readonly refused: string;
|
|
278
278
|
};
|
|
279
|
+
/** A snapshot the partition holds whose rules the list judgment refused: the envelope it carried and the words it was refused in,
|
|
280
|
+
* judged ONCE, on the list as it stood at the receipt. It has no `rules` at all (so no reading can take it for a table of zero
|
|
281
|
+
* rows) and keeps none of the caller's value (a list past the cap is a live array, and the partition awaits its persistence
|
|
282
|
+
* between the receipt and the validator, so a source that shrank the list it handed over in between would otherwise be admitted,
|
|
283
|
+
* uncopied and mutable, on a judgment it no longer meets). It is never installed or served. */
|
|
284
|
+
export interface RefusedOrgSnapshot extends Omit<OrgRuleSnapshot, "rules"> {
|
|
285
|
+
readonly refused: string;
|
|
286
|
+
}
|
|
287
|
+
/** What the partition holds of one snapshot, fetched or read back: one that can be installed, or the refusal of one that cannot.
|
|
288
|
+
* Read it by its shape — `rules` is on the first arm only, and {@link validateOrgSnapshot} is the one way from a held snapshot to an
|
|
289
|
+
* installable one. */
|
|
290
|
+
export type HeldOrgSnapshot = OrgRuleSnapshot | RefusedOrgSnapshot;
|
|
291
|
+
/** A snapshot as the partition holds it — fetched from the source or read back from its persistence alike: its envelope's members
|
|
292
|
+
* ({@link ORG_SNAPSHOT_ENVELOPE_MEMBERS} through {@link scalarMembersOf}; anything else it carries is never read or kept), its
|
|
293
|
+
* rows as the one list reader reads them (each row its policy) — so a snapshot read back reads, and compares, exactly as the one
|
|
294
|
+
* fetched, and what is persisted is never larger than these. Rules the list judgment refuses (not a list, a Proxy, past the cap)
|
|
295
|
+
* are held as that refusal alone ({@link RefusedOrgSnapshot}; the list judgment runs no code of the value's); such a snapshot — and
|
|
296
|
+
* one that is not an object, whose envelope has no members — is never installed or served. A list that cannot be received, and a
|
|
297
|
+
* Proxy snapshot (its traps would answer for the envelope), throw: an envelope that cannot be copied. Exported for the pins; the
|
|
298
|
+
* package root does not carry it. */
|
|
299
|
+
export declare function orgSnapshotCopyOf(v: unknown): HeldOrgSnapshot;
|
|
279
300
|
/**
|
|
280
301
|
* The ONE reading of an org snapshot's rows: each row read once ({@link readOrgRow}), and the one judgment of a call
|
|
281
302
|
* against them. Every reader of org rows goes through it — the partition's degraded line, the lane's adjudication,
|
|
@@ -319,6 +340,22 @@ export declare function orgUnreadableClause(reason: string): string;
|
|
|
319
340
|
* exactly the ones that do not render.
|
|
320
341
|
*/
|
|
321
342
|
export declare function describeUnreadableOrgRows(unreadable: readonly UnreadableOrgRow[], revision?: unknown): string | undefined;
|
|
343
|
+
/**
|
|
344
|
+
* Envelope validation of one held snapshot — what the staleness bound and the anti-rollback fence need to judge it. Answers
|
|
345
|
+
* the snapshot as an installable one, or the words it is refused in: a finite revision, a finite observation time not in the
|
|
346
|
+
* future beyond the skew allowance, and rules that are a LIST ({@link listRefusalOf}, judged at the receipt — a held snapshot
|
|
347
|
+
* whose list was refused there is answered in those words). The rows inside are not judged here: a row this layer cannot
|
|
348
|
+
* read is read as such by {@link readOrgRules}, and never takes the rest of the snapshot down with it.
|
|
349
|
+
*
|
|
350
|
+
* Run on BOTH the fetch path (before install) and the serving path (before a persisted snapshot adjudicates), so the
|
|
351
|
+
* two cannot diverge on what an installable snapshot is — see backlog #177. The only way from a {@link HeldOrgSnapshot} to an
|
|
352
|
+
* installable {@link OrgRuleSnapshot}: install and serve read the `admitted` arm.
|
|
353
|
+
*/
|
|
354
|
+
export declare function validateOrgSnapshot(s: HeldOrgSnapshot, nowMs: number): {
|
|
355
|
+
readonly admitted: OrgRuleSnapshot;
|
|
356
|
+
} | {
|
|
357
|
+
readonly refused: string;
|
|
358
|
+
};
|
|
322
359
|
/**
|
|
323
360
|
* design/182 §7.2 — which org rule speaks for this call, if any: {@link readOrgRules}'s judgment, over a snapshot read for
|
|
324
361
|
* this call alone. Matching uses the SAME parser and the SAME tightening reach the personal deny/ask rows use
|
|
@@ -81,7 +81,7 @@ export function createOrgRulePartition(cfg) {
|
|
|
81
81
|
throw new Error(`the org snapshot source did not answer within ${ORG_ADJUDICATION_TIMEOUT_MS}ms`);
|
|
82
82
|
if ("e" in settled)
|
|
83
83
|
throw settled.e;
|
|
84
|
-
fetched = orgSnapshotCopyOf(settled.v);
|
|
84
|
+
fetched = settled.v === undefined ? undefined : orgSnapshotCopyOf(settled.v);
|
|
85
85
|
}
|
|
86
86
|
catch (err) {
|
|
87
87
|
fetchFailed = true;
|
|
@@ -102,29 +102,30 @@ export function createOrgRulePartition(cfg) {
|
|
|
102
102
|
const persisted = await guardedRead("persistence");
|
|
103
103
|
if (fetched === undefined)
|
|
104
104
|
return persisted;
|
|
105
|
-
const
|
|
106
|
-
if (
|
|
107
|
-
disclosures.push(`org snapshot refused: ${
|
|
105
|
+
const judged = validateOrgSnapshot(fetched, nowMs);
|
|
106
|
+
if ("refused" in judged) {
|
|
107
|
+
disclosures.push(`org snapshot refused: ${judged.refused}; the previously installed snapshot (if any) remains in force`);
|
|
108
108
|
return persisted;
|
|
109
109
|
}
|
|
110
|
+
const incoming = judged.admitted;
|
|
110
111
|
const persistedFloor = persisted === undefined ? undefined : antiRollbackFloor(persisted);
|
|
111
112
|
if (persisted !== undefined && !persistedMarkIsCoherent(persisted)) {
|
|
112
113
|
disclosures.push(`the durable org state carries a high-water mark (${escapeForDisclosure(persisted.revisionHighWater)}) that is not a finite number at or above its own snapshot revision (${escapeForDisclosure(persisted.snapshot?.revision)}) — the anti-rollback fence is being held at ${escapeForDisclosure(persistedFloor)} instead, and the durable state should be repaired`);
|
|
113
114
|
}
|
|
114
|
-
if (persistedFloor !== undefined &&
|
|
115
|
-
disclosures.push(`org snapshot revision ${
|
|
115
|
+
if (persistedFloor !== undefined && incoming.revision < persistedFloor) {
|
|
116
|
+
disclosures.push(`org snapshot revision ${incoming.revision} is below the installed high-water mark ${escapeForDisclosure(persistedFloor)} — refusing the rollback; the current deny set is unchanged`);
|
|
116
117
|
return persisted;
|
|
117
118
|
}
|
|
118
|
-
if (persisted !== undefined &&
|
|
119
|
+
if (persisted !== undefined && incoming.revision === persisted.snapshot.revision && sameOrgPolicyAsHeld(incoming, persisted.snapshot) && incoming.fetchedAtMs <= persisted.snapshot.fetchedAtMs) {
|
|
119
120
|
return persisted;
|
|
120
121
|
}
|
|
121
|
-
if (persisted !== undefined &&
|
|
122
|
-
disclosures.push(`org snapshot revision ${
|
|
122
|
+
if (persisted !== undefined && incoming.revision === persisted.snapshot.revision && !sameOrgPolicyAsHeld(incoming, persisted.snapshot)) {
|
|
123
|
+
disclosures.push(`org snapshot revision ${incoming.revision} equals the installed revision but carries DIFFERENT policy content — refusing the swap; the current deny set is unchanged`);
|
|
123
124
|
return persisted;
|
|
124
125
|
}
|
|
125
126
|
const state = {
|
|
126
|
-
revisionHighWater: Math.max(persistedFloor !== undefined && Number.isFinite(persistedFloor) ? persistedFloor : 0,
|
|
127
|
-
snapshot:
|
|
127
|
+
revisionHighWater: Math.max(persistedFloor !== undefined && Number.isFinite(persistedFloor) ? persistedFloor : 0, incoming.revision),
|
|
128
|
+
snapshot: incoming,
|
|
128
129
|
installedAtMs: nowMs,
|
|
129
130
|
};
|
|
130
131
|
try {
|
|
@@ -148,24 +149,25 @@ export function createOrgRulePartition(cfg) {
|
|
|
148
149
|
disclosures.push(`the installed org snapshot claims a FUTURE observation time (${escapeForDisclosure(installed.snapshot.fetchedAtMs)} vs now ${decisionMs}) — org adjudication is UNAVAILABLE; the consuming gate must tighten every terminal allow to a real-approval ask`);
|
|
149
150
|
return { source: "org", status: "unavailable", rules: [], disclosures };
|
|
150
151
|
}
|
|
151
|
-
const
|
|
152
|
-
if (
|
|
153
|
-
disclosures.push(`the installed org snapshot (revision ${escapeForDisclosure(installed.snapshot.revision)}) does not meet this version's validator: ${
|
|
152
|
+
const serving = validateOrgSnapshot(installed.snapshot, decisionMs);
|
|
153
|
+
if ("refused" in serving) {
|
|
154
|
+
disclosures.push(`the installed org snapshot (revision ${escapeForDisclosure(installed.snapshot.revision)}) does not meet this version's validator: ${serving.refused} — org adjudication is UNAVAILABLE; the consuming gate must tighten every terminal allow to a real-approval ask`);
|
|
154
155
|
return { source: "org", status: "unavailable", rules: [], disclosures };
|
|
155
156
|
}
|
|
156
|
-
|
|
157
|
+
const served = serving.admitted;
|
|
158
|
+
if (withinBound(served)) {
|
|
157
159
|
const freshlyInstalled = fetched !== undefined && installed.snapshot === fetched;
|
|
158
160
|
if (!freshlyInstalled && !fetchFailed && fetched === undefined) {
|
|
159
161
|
disclosures.push("org snapshot source returned no snapshot — continuing on the last-known-good");
|
|
160
162
|
}
|
|
161
|
-
const degraded = describeUnreadableOrgRows(readListedOrgRules(
|
|
163
|
+
const degraded = describeUnreadableOrgRows(readListedOrgRules(served.rules).unreadable, served.revision);
|
|
162
164
|
if (degraded !== undefined)
|
|
163
165
|
disclosures.push(degraded);
|
|
164
166
|
return {
|
|
165
167
|
source: "org",
|
|
166
168
|
status: freshlyInstalled ? "fresh" : "last-known-good",
|
|
167
|
-
rules: structuredClone(
|
|
168
|
-
revision:
|
|
169
|
+
rules: structuredClone(served.rules),
|
|
170
|
+
revision: served.revision,
|
|
169
171
|
disclosures,
|
|
170
172
|
};
|
|
171
173
|
}
|
|
@@ -203,6 +205,9 @@ export function sameOrgPolicyContent(a, b) {
|
|
|
203
205
|
const policy = (r) => canonicalize(orgRowPolicyOf(r));
|
|
204
206
|
return a.rules.every((r, i) => policy(b.rules[i]) === policy(r));
|
|
205
207
|
}
|
|
208
|
+
function sameOrgPolicyAsHeld(a, b) {
|
|
209
|
+
return !("refused" in b) && sameOrgPolicyContent(a, b);
|
|
210
|
+
}
|
|
206
211
|
const DISCLOSED_UNREADABLE_ROWS = 20;
|
|
207
212
|
function reachOfRowHead(text, behavior) {
|
|
208
213
|
const head = typeof text === "string" ? ruleSpellingOf(text)?.toolName : undefined;
|
|
@@ -221,13 +226,8 @@ function rowReaches(reach, words, face) {
|
|
|
221
226
|
return reach.words.some((w) => words.includes(w)) || (face !== undefined && reach.faces.includes(face));
|
|
222
227
|
}
|
|
223
228
|
function listRefusalOf(rules) {
|
|
224
|
-
if (typeof rules === "object" && rules !== null)
|
|
225
|
-
|
|
226
|
-
return "rules is a Proxy, not an array";
|
|
227
|
-
const held = HELD_LIST_REFUSALS.get(rules);
|
|
228
|
-
if (held !== undefined)
|
|
229
|
-
return held;
|
|
230
|
-
}
|
|
229
|
+
if (typeof rules === "object" && rules !== null && types.isProxy(rules))
|
|
230
|
+
return "rules is a Proxy, not an array";
|
|
231
231
|
if (!Array.isArray(rules))
|
|
232
232
|
return "rules is not an array";
|
|
233
233
|
const cap = DEFAULT_CAPS.maxRules;
|
|
@@ -262,22 +262,14 @@ export function orgRuleListOf(rules) {
|
|
|
262
262
|
const copy = received.value;
|
|
263
263
|
return { rows: Array.from({ length: copy.length }, (_, i) => orgRowPolicyOf(Object.hasOwn(copy, i) ? copy[i] : undefined)) };
|
|
264
264
|
}
|
|
265
|
-
|
|
266
|
-
function heldRefusalOf(words) {
|
|
267
|
-
const held = Object.freeze({});
|
|
268
|
-
HELD_LIST_REFUSALS.set(held, words);
|
|
269
|
-
return held;
|
|
270
|
-
}
|
|
271
|
-
function orgSnapshotCopyOf(v) {
|
|
272
|
-
if (v === undefined)
|
|
273
|
-
return undefined;
|
|
265
|
+
export function orgSnapshotCopyOf(v) {
|
|
274
266
|
if (typeof v === "object" && v !== null && types.isProxy(v))
|
|
275
267
|
throw new Error("the snapshot is a Proxy, not data");
|
|
276
268
|
const envelope = scalarMembersOf(v, ORG_SNAPSHOT_ENVELOPE_MEMBERS);
|
|
277
269
|
const rules = typeof v === "object" && v !== null ? v.rules : undefined;
|
|
278
|
-
const
|
|
279
|
-
if (
|
|
280
|
-
return { ...envelope,
|
|
270
|
+
const refused = listRefusalOf(rules);
|
|
271
|
+
if (refused !== undefined)
|
|
272
|
+
return { ...envelope, refused };
|
|
281
273
|
const list = orgRuleListOf(rules);
|
|
282
274
|
if ("refused" in list)
|
|
283
275
|
throw new Error(`the snapshot's ${list.refused}`);
|
|
@@ -379,15 +371,15 @@ export function describeUnreadableOrgRows(unreadable, revision) {
|
|
|
379
371
|
return (`the org snapshot${revision !== undefined ? ` (revision ${escapeForDisclosure(revision)})` : ""} is served DEGRADED: ${unreadable.length} row(s) this layer cannot read — ${named}${more > 0 ? ` and ${more} more` : ""}; ` +
|
|
380
372
|
`each stands at its strictest reading over the calls it may reach (a deny row, or one whose behavior cannot be read, refuses them; an ask row asks a person), and every readable row stands as published — republish the named rows in the shell tool's command grammar`);
|
|
381
373
|
}
|
|
382
|
-
function validateOrgSnapshot(s, nowMs) {
|
|
374
|
+
export function validateOrgSnapshot(s, nowMs) {
|
|
383
375
|
if (typeof s.revision !== "number" || !Number.isFinite(s.revision))
|
|
384
|
-
return `revision is not a finite number (${escapeForDisclosure(s.revision)})
|
|
376
|
+
return { refused: `revision is not a finite number (${escapeForDisclosure(s.revision)})` };
|
|
385
377
|
if (typeof s.fetchedAtMs !== "number" || !Number.isFinite(s.fetchedAtMs))
|
|
386
|
-
return `fetchedAtMs is not a finite number (${escapeForDisclosure(s.fetchedAtMs)})
|
|
378
|
+
return { refused: `fetchedAtMs is not a finite number (${escapeForDisclosure(s.fetchedAtMs)})` };
|
|
387
379
|
if (s.fetchedAtMs > nowMs + ORG_FETCHED_AT_SKEW_ALLOWANCE_MS) {
|
|
388
|
-
return `fetchedAtMs is ${s.fetchedAtMs - nowMs}ms in the future (allowance ${ORG_FETCHED_AT_SKEW_ALLOWANCE_MS}ms) — a future-dated observation would satisfy the staleness bound indefinitely
|
|
380
|
+
return { refused: `fetchedAtMs is ${s.fetchedAtMs - nowMs}ms in the future (allowance ${ORG_FETCHED_AT_SKEW_ALLOWANCE_MS}ms) — a future-dated observation would satisfy the staleness bound indefinitely` };
|
|
389
381
|
}
|
|
390
|
-
return
|
|
382
|
+
return "refused" in s ? { refused: s.refused } : { admitted: s };
|
|
391
383
|
}
|
|
392
384
|
export function orgRuleVerdictFor(rules, call) {
|
|
393
385
|
return readListedOrgRules(rules).verdictFor(call);
|