@fungi.computer/approvals 0.0.0-stage → 0.0.2

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.
@@ -0,0 +1,203 @@
1
+ import * as Schema from "effect/Schema";
2
+ import type { WatchdogSqliteOwner } from "@fungi.computer/watchdog";
3
+ import { type ApprovalEvidence, type ApprovalRecord, type Approvals, type ApprovalRefusal, type ApprovalAnswerInput, type AutoApproveInput, type PendingApproval } from "./contract.js";
4
+ /** Canonical zero-effect or lifecycle refusal at a tool result boundary. */
5
+ export declare const ApprovalRefusalSchema: Schema.Struct<{
6
+ readonly kind: Schema.Literal<"refused">;
7
+ readonly reason: Schema.Literals<readonly ["not-found", "not-eligible", "already-answered", "stale-revision", "expired", "declined", "fenced", "not-granted", "read-only", "pending", "in-flight", "outcome-unknown", "identity-conflict"]>;
8
+ }>;
9
+ /** Native owner of this ledger; the Local runtime supplies its own SQLite owner. */
10
+ export type ApprovalHome = Readonly<{
11
+ kind: "team" | "oyster" | "local";
12
+ id: string;
13
+ }>;
14
+ /** A request is host-attested metadata and JSON bytes, never renderer-provided identity. */
15
+ export type ApprovalRequest = Readonly<{
16
+ requester: ApprovalRecord["requester"];
17
+ module: string;
18
+ operation: string;
19
+ access: ApprovalRecord["access"];
20
+ reversibility: ApprovalRecord["reversibility"];
21
+ authorityFence: string;
22
+ /** Persisted turn/tool identity and operation ordinal; never random on retry. */
23
+ idempotencyKey: string;
24
+ inputJson: string;
25
+ }>;
26
+ /** Host-only policy read for an admitted Agent/module/access selection, not execution authority. */
27
+ export type AutoApproveQuery = Readonly<{
28
+ agentId: ApprovalRequest["requester"]["agentId"];
29
+ module: ApprovalRequest["module"];
30
+ access: ApprovalRequest["access"];
31
+ }>;
32
+ /** Host-attested human identity and current authority over this exact operation. */
33
+ export type ApprovalAnswerer = Readonly<{
34
+ kind: "human";
35
+ memberId: string;
36
+ role: "owner" | "admin" | "member";
37
+ actingForAgent: boolean;
38
+ mayUseOperation: boolean;
39
+ }> | Readonly<{
40
+ kind: "agent" | "key";
41
+ }>;
42
+ /**
43
+ * Host-attested authority to write one auto-approve rule. A Team home
44
+ * resolves it against the exact rule: mayUseOperation for that family and
45
+ * mayManageAgent (agent.manage on that Agent; owners and admins hold it
46
+ * Team-wide). An answerer without mayManageAgent never manages an Agent.
47
+ * Oyster and Local homes are owner-only.
48
+ */
49
+ export type ApprovalRuleSetter = ApprovalAnswerer | (Extract<ApprovalAnswerer, {
50
+ kind: "human";
51
+ }> & Readonly<{
52
+ mayManageAgent: boolean;
53
+ }>);
54
+ /** Mycelium owns admission; the ledger compares its opaque fence, never grant grammar. */
55
+ export type ApprovalAdmission = Readonly<{
56
+ kind: "authorized";
57
+ authorityFence: string;
58
+ }> | Readonly<{
59
+ kind: "refused";
60
+ reason: "not-granted" | "read-only";
61
+ }>;
62
+ /** Host verdict outside App JSON: only an attested zero-effect refusal is denied. */
63
+ export type ApprovalDispatchCompletion = Readonly<{
64
+ kind: "completed";
65
+ output: typeof Schema.Json.Type;
66
+ }> | Readonly<{
67
+ kind: "refused";
68
+ reason: "not-granted" | "read-only";
69
+ }>;
70
+ /** Host-only adapter to the existing operation door; not an Agent continuation. */
71
+ export type ApprovalApplyHost = Readonly<{
72
+ /** Re-read requester grants and the operation fence immediately before dispatch. */
73
+ recheck: (record: ApprovalRecord) => Promise<ApprovalAdmission>;
74
+ /** Attempt the admitted operation once through the existing host operation door. */
75
+ dispatch: (request: Readonly<{
76
+ record: ApprovalRecord;
77
+ input: typeof Schema.Json.Type;
78
+ }>) => Promise<ApprovalDispatchCompletion>;
79
+ }>;
80
+ /** Only the submitting capability may attach evidence; never spread tool input here. */
81
+ export type ApprovalSubmitHost = ApprovalApplyHost & Readonly<{
82
+ evidence?: ApprovalEvidence;
83
+ }>;
84
+ /** Private result for inline tool output or ordinary deferred new-message delivery. */
85
+ export type ApprovalApplyResult = Readonly<{
86
+ kind: "applied";
87
+ record: ApprovalRecord;
88
+ output: typeof Schema.Json.Type;
89
+ }> | ApprovalRefusal;
90
+ /** Deferred result cargo; stored request input is not a field on this port. */
91
+ export type ApprovalResultNotice = Readonly<{
92
+ deliveryId: string;
93
+ record: ApprovalRecord;
94
+ result: ApprovalApplyResult;
95
+ }>;
96
+ /** Ordinary new-message receipt, explicit no-delivery result, or typed refusal. */
97
+ export type ApprovalHandoffResult = Readonly<{
98
+ kind: "delivered";
99
+ commandId: string;
100
+ }> | Readonly<{
101
+ kind: "not-required";
102
+ }>
103
+ /** The home's attempt limit was reached; the decided record is kept. */
104
+ | Readonly<{
105
+ kind: "undeliverable";
106
+ }> | ApprovalRefusal;
107
+ /** The initial owner-controlled wait duration; passing the deadline never approves. */
108
+ export declare const DEFAULT_APPROVAL_EXPIRY_MS: number;
109
+ /** SQLite lifecycle beside Mycelium. Native owner transactions serialize every transition. */
110
+ export declare class ApprovalLedger {
111
+ private readonly options;
112
+ private readonly homeKey;
113
+ constructor(options: Readonly<{
114
+ owner: WatchdogSqliteOwner;
115
+ home: ApprovalHome;
116
+ now: () => number;
117
+ /** Host-private dispatch target, committed in the same SQLite transaction as the record. */
118
+ persistIntent?: (record: ApprovalRecord) => undefined;
119
+ /**
120
+ * Present when this home's wake sends result messages: its wake then
121
+ * covers undelivered results, and a failed send waits retryDelay(attempt)
122
+ * from the home's existing delivery policy before the next try. The
123
+ * attemptLimit-th failure makes the result undeliverable: no more wakes.
124
+ */
125
+ resultDelivery?: Readonly<{
126
+ retryDelay: (attempt: number) => number;
127
+ attemptLimit: number;
128
+ }>;
129
+ }>);
130
+ /** Bound control surface; null identity means membership is gone, never an impersonated key. */
131
+ capability(host: Readonly<{
132
+ actor: (record: ApprovalRecord | null) => Promise<ApprovalAnswerer | null>;
133
+ decision: (recordId: string) => Promise<void>;
134
+ /** Team homes: the setter's authority over this exact rule. Without it a Team rule write is refused. */
135
+ setter?: (rule: AutoApproveInput) => Promise<ApprovalRuleSetter | null>;
136
+ }>): Approvals;
137
+ private eligible;
138
+ /** Normal tool admission returns pending immediately, or applies policy approval inline. */
139
+ submit(input: ApprovalRequest, host: ApprovalSubmitHost): Promise<PendingApproval | ApprovalApplyResult>;
140
+ /** A retried request's current outcome, without claiming any work. */
141
+ outcome(record: ApprovalRecord): PendingApproval | ApprovalApplyResult;
142
+ /**
143
+ * Stage one stable intended effect. Duplicate identities never create
144
+ * another action; created says whether this call staged the record.
145
+ */
146
+ request(input: ApprovalRequest, evidence?: ApprovalEvidence): Promise<Readonly<{
147
+ kind: "record";
148
+ record: ApprovalRecord;
149
+ created: boolean;
150
+ }> | ApprovalRefusal>;
151
+ /** Answering is revision-pinned and never changes the requester's underlying grants. */
152
+ answer(input: ApprovalAnswerInput, actor: ApprovalAnswerer | null): Readonly<{
153
+ kind: "answered";
154
+ record: ApprovalRecord;
155
+ }> | ApprovalRefusal;
156
+ /** Policy changes are attributed; an Agent can never enable its own writes. */
157
+ setAutoApprove(input: AutoApproveInput, setter: ApprovalRuleSetter | null): Readonly<{
158
+ kind: "updated";
159
+ rule: AutoApproveInput;
160
+ }> | ApprovalRefusal;
161
+ /**
162
+ * Team: a rule for one Agent and one module needs live use of that family
163
+ * and agent.manage on that Agent; any wider rule is the owner's alone.
164
+ * Oyster and Local rules are owner-only.
165
+ */
166
+ private canManagePolicy;
167
+ /** Pure policy selection: no record, claim, dispatch, grant widening or containment choice. */
168
+ selectAutoApprove(query: AutoApproveQuery): boolean;
169
+ /** Expire one bounded home-local quantum without a member read or an approval. */
170
+ expirePending(): void;
171
+ /** Earliest native wake for waiting or unfinished effects; no claim or dispatch. */
172
+ nextWakeAt(): number | null;
173
+ /** Bounded approved work for the native owner; selection never claims or dispatches. */
174
+ pendingApplications(): readonly string[];
175
+ /** Bounded, home-local terminal notices for the native owner's work loop. */
176
+ pendingHandoffs(): readonly string[];
177
+ /**
178
+ * Retry only the ordinary message, with the same recipient-side idempotency key.
179
+ * A send rejection leaves the intent pending; it never reopens effect execution.
180
+ * The host must return the real Session acceptance, not a success-shaped placeholder.
181
+ */
182
+ handoff(recordId: string, send: (notice: ApprovalResultNotice) => Promise<Readonly<{
183
+ commandId: string;
184
+ }>>): Promise<ApprovalHandoffResult>;
185
+ /**
186
+ * The home's work loop: only a durable approved record claims the existing
187
+ * host operation, and its result always reaches a waiting Agent as a message.
188
+ */
189
+ apply(recordId: string, host: ApprovalApplyHost): Promise<ApprovalApplyResult>;
190
+ /**
191
+ * Apply the caller's own just-staged policy approval inside its live tool
192
+ * call; that call's result is the outcome, so no later message is owed.
193
+ */
194
+ applyInline(recordId: string, host: ApprovalApplyHost): Promise<ApprovalApplyResult>;
195
+ private run;
196
+ private finishDenied;
197
+ /** Native host boot only: orphaned durable claims must never be redispatched. */
198
+ recoverClaims(): readonly ApprovalRecord[];
199
+ private payload;
200
+ private save;
201
+ /** Read a record from this home only; the maintained decoder owns stored JSON. */
202
+ get(recordId: string): ApprovalRecord | null;
203
+ }