@cotal-ai/auth 0.93.0 → 0.94.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.
Files changed (52) hide show
  1. package/dist/callout.d.ts.map +1 -1
  2. package/dist/callout.js +12 -3
  3. package/dist/callout.js.map +1 -1
  4. package/dist/index.d.ts +1 -0
  5. package/dist/index.d.ts.map +1 -1
  6. package/dist/index.js +1 -0
  7. package/dist/index.js.map +1 -1
  8. package/dist/lifecycle-registry.d.ts +5 -0
  9. package/dist/lifecycle-registry.d.ts.map +1 -1
  10. package/dist/lifecycle-registry.js +8 -1
  11. package/dist/lifecycle-registry.js.map +1 -1
  12. package/dist/permissions.d.ts +9 -0
  13. package/dist/permissions.d.ts.map +1 -1
  14. package/dist/permissions.js +11 -2
  15. package/dist/permissions.js.map +1 -1
  16. package/dist/service-caller-ancestry.d.ts +19 -0
  17. package/dist/service-caller-ancestry.d.ts.map +1 -0
  18. package/dist/service-caller-ancestry.js +56 -0
  19. package/dist/service-caller-ancestry.js.map +1 -0
  20. package/dist/service-caller-effective.d.ts +12 -0
  21. package/dist/service-caller-effective.d.ts.map +1 -0
  22. package/dist/service-caller-effective.js +94 -0
  23. package/dist/service-caller-effective.js.map +1 -0
  24. package/dist/service-caller-payload-bootstrap.d.ts +30 -0
  25. package/dist/service-caller-payload-bootstrap.d.ts.map +1 -0
  26. package/dist/service-caller-payload-bootstrap.js +154 -0
  27. package/dist/service-caller-payload-bootstrap.js.map +1 -0
  28. package/dist/service-caller-payload.d.ts +50 -0
  29. package/dist/service-caller-payload.d.ts.map +1 -0
  30. package/dist/service-caller-payload.js +77 -0
  31. package/dist/service-caller-payload.js.map +1 -0
  32. package/dist/service-caller-policy.d.ts +71 -0
  33. package/dist/service-caller-policy.d.ts.map +1 -1
  34. package/dist/service-caller-policy.js +83 -20
  35. package/dist/service-caller-policy.js.map +1 -1
  36. package/dist/service-host-contract.d.ts +8 -2
  37. package/dist/service-host-contract.d.ts.map +1 -1
  38. package/dist/service-host-contract.js +67 -4
  39. package/dist/service-host-contract.js.map +1 -1
  40. package/dist/service-host-ops.d.ts +85 -7
  41. package/dist/service-host-ops.d.ts.map +1 -1
  42. package/dist/service-host-ops.js +804 -18
  43. package/dist/service-host-ops.js.map +1 -1
  44. package/dist/service-write.d.ts +3 -0
  45. package/dist/service-write.d.ts.map +1 -1
  46. package/dist/service-write.js +1 -1
  47. package/dist/service-write.js.map +1 -1
  48. package/dist/service.d.ts +8 -3
  49. package/dist/service.d.ts.map +1 -1
  50. package/dist/service.js +44 -2
  51. package/dist/service.js.map +1 -1
  52. package/package.json +7 -4
@@ -0,0 +1,30 @@
1
+ /** Host-only payload observer and bootstrap. Uses the native broker VARZ API, never a caller's
2
+ * supplied topology or measurements. No generic SYS credential enters the data account or a client. */
3
+ import { type NatsConnection } from "@nats-io/transport-node";
4
+ import { type LifecycleRegistry } from "./lifecycle-registry.js";
5
+ import { type PlaneClaimHold } from "./plane-claim.js";
6
+ /** Broker-owner provisioning only. Needs the registered SYS signing seed, not operator/root seed.
7
+ * The resulting short-lived credential can read VARZ only and is never persisted by this helper. */
8
+ export declare function mintServiceCallerPayloadObserverCreds(sys: {
9
+ pub: string;
10
+ signingSeed: string;
11
+ }): Promise<string>;
12
+ export interface ServiceCallerPayloadBootstrap {
13
+ /** Host-private fresh short-lived VARZ-only credential, issued by the broker owner. */
14
+ observerCreds(): string | Promise<string>;
15
+ }
16
+ /** Startup assertion, and a fresh live verifier for each later positive issuance. The writer
17
+ * holds only this exact metadata key. The auth plane's existing claim must still be held. */
18
+ export declare function bootstrapServiceCallerPayload(opts: {
19
+ registry: LifecycleRegistry;
20
+ hold: PlaneClaimHold;
21
+ servers: string;
22
+ accountPublicKey: string;
23
+ dataAccount: {
24
+ pub: string;
25
+ signingSeed: string;
26
+ };
27
+ source: ServiceCallerPayloadBootstrap;
28
+ onConnection?: (nc: NatsConnection, label: string) => void;
29
+ }): Promise<() => Promise<void>>;
30
+ //# sourceMappingURL=service-caller-payload-bootstrap.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service-caller-payload-bootstrap.d.ts","sourceRoot":"","sources":["../src/service-caller-payload-bootstrap.ts"],"names":[],"mappings":"AAAA;uGACuG;AACvG,OAAO,EAAsB,KAAK,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAOlF,OAAO,EAA+E,KAAK,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAC9I,OAAO,EAAoC,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAIzF;oGACoG;AACpG,wBAAsB,qCAAqC,CAAC,GAAG,EAAE;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAOtH;AACD,MAAM,WAAW,6BAA6B;IAC5C,uFAAuF;IACvF,aAAa,IAAI,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CAC3C;AA4DD;6FAC6F;AAC7F,wBAAsB,6BAA6B,CAAC,IAAI,EAAE;IACxD,QAAQ,EAAE,iBAAiB,CAAC;IAAC,IAAI,EAAE,cAAc,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,gBAAgB,EAAE,MAAM,CAAC;IAC7F,WAAW,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE,CAAC;IAAC,MAAM,EAAE,6BAA6B,CAAC;IACzF,YAAY,CAAC,EAAE,CAAC,EAAE,EAAE,cAAc,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CAC5D,GAAG,OAAO,CAAC,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC,CA6C/B"}
@@ -0,0 +1,154 @@
1
+ /** Host-only payload observer and bootstrap. Uses the native broker VARZ API, never a caller's
2
+ * supplied topology or measurements. No generic SYS credential enters the data account or a client. */
3
+ import { credsAuthenticator } from "@nats-io/transport-node";
4
+ import { encodeUser, fmtCreds } from "@nats-io/jwt";
5
+ import { fromPublic, fromSeed } from "@nats-io/nkeys";
6
+ import { EpEnvelopeError, canonicalJson, contractDigest, credsClaims, dialerFor, epAuthBucket, isAccountNkey, newIdentity, recordsBucket, } from "@cotal-ai/core";
7
+ import { openAuthorityClient } from "./authority-client.js";
8
+ import { openLifecycleRegistry, registryMaxPayload, registryServerId, registryStores } from "./lifecycle-registry.js";
9
+ import { readPlaneClaim, genuinePlaneHold } from "./plane-claim.js";
10
+ import { parseServiceCallerPayloadRecord, SERVICE_CALLER_PAYLOAD_FLOOR, SERVICE_CALLER_PAYLOAD_KEY } from "./service-caller-payload.js";
11
+ const enc = new TextEncoder(), dec = new TextDecoder("utf-8", { fatal: true });
12
+ /** Broker-owner provisioning only. Needs the registered SYS signing seed, not operator/root seed.
13
+ * The resulting short-lived credential can read VARZ only and is never persisted by this helper. */
14
+ export async function mintServiceCallerPayloadObserverCreds(sys) {
15
+ if (!isAccountNkey(sys.pub))
16
+ throw new Error("payload observer requires the broker system account");
17
+ const id = newIdentity(), prefix = `_INBOX_${id.id}`, user = fromSeed(enc.encode(id.seed));
18
+ const jwt = await encodeUser("service-caller-payload-observer", fromPublic(id.id), fromPublic(sys.pub), { pub: { allow: ["$SYS.REQ.SERVER.*.VARZ"] }, sub: { allow: [`${prefix}.>`] } }, { signer: fromSeed(enc.encode(sys.signingSeed)), exp: Math.floor(Date.now() / 1000) + 60 });
19
+ return dec.decode(fmtCreds(jwt, user));
20
+ }
21
+ const fail = (detail) => { throw new EpEnvelopeError("failed-precondition", `service caller payload: ${detail}`); };
22
+ async function beforeDeadline(deadline, work, late) {
23
+ let timer, expired = false;
24
+ const ms = deadline - Date.now();
25
+ if (ms <= 0)
26
+ fail("observation deadline expired");
27
+ const flight = Promise.resolve().then(work).then(async (value) => {
28
+ if (expired) {
29
+ await late?.(value);
30
+ fail("observation deadline expired");
31
+ }
32
+ return value;
33
+ });
34
+ try {
35
+ return await Promise.race([flight, new Promise((_resolve, reject) => {
36
+ timer = setTimeout(() => { expired = true; reject(new EpEnvelopeError("failed-precondition", "service caller payload: observation deadline expired")); }, ms);
37
+ })]);
38
+ }
39
+ finally {
40
+ clearTimeout(timer);
41
+ }
42
+ }
43
+ const emptyObject = (o) => !!o && typeof o === "object" && !Array.isArray(o) && Object.keys(o).length === 0;
44
+ /** A real complete single-server observation. Cluster/gateway/leaf configurations refuse even
45
+ * when no peer is connected: reply counts and current route counts cannot prove topology. */
46
+ async function observe(registry, servers, source, deadline, onConnection) {
47
+ const serverId = registryServerId(registry), creds = await beforeDeadline(deadline, async () => source.observerCreds()), claims = credsClaims(creds);
48
+ const remaining = () => { const ms = deadline - Date.now(); if (ms <= 0)
49
+ fail("observation deadline expired"); return ms; };
50
+ if (!serverId || !/^N[A-Z2-7]{55}$/.test(serverId))
51
+ fail("the data connection has no broker incarnation");
52
+ const now = Math.floor(Date.now() / 1000);
53
+ if (!claims.sub || !Number.isSafeInteger(claims.exp) || !claims.exp || claims.exp <= now || claims.exp > now + 60)
54
+ fail("observer credential must expire within sixty seconds");
55
+ const allow = claims.nats?.pub?.allow, sub = claims.nats?.sub?.allow;
56
+ if (canonicalJson(allow) !== canonicalJson(["$SYS.REQ.SERVER.*.VARZ"]) || canonicalJson(sub) !== canonicalJson([`_INBOX_${claims.sub}.>`]))
57
+ fail("observer must have exactly VARZ-only read authority");
58
+ let nc;
59
+ try {
60
+ nc = await beforeDeadline(deadline, () => dialerFor(servers)({ servers, authenticator: credsAuthenticator(enc.encode(creds)), inboxPrefix: `_INBOX_${claims.sub}`,
61
+ reconnect: false, timeout: Math.min(3000, remaining()), name: "cotal:service-caller-payload-observer" }), late => late.close());
62
+ onConnection?.(nc, "cotal:service-caller-payload-observer");
63
+ if (nc.info?.server_id !== serverId)
64
+ fail("observer and data connection reached different brokers");
65
+ const reply = await nc.request(`$SYS.REQ.SERVER.${serverId}.VARZ`, enc.encode("{}"), { timeout: Math.min(3000, remaining()) });
66
+ const raw = JSON.parse(dec.decode(reply.data));
67
+ const data = raw?.data;
68
+ if (raw?.error !== undefined || raw?.server?.id !== serverId || data?.server_id !== serverId)
69
+ fail("VARZ response does not bind this broker");
70
+ if (Object.hasOwn(raw.server, "cluster") || !emptyObject(data.cluster) || !emptyObject(data.gateway) || !emptyObject(data.leaf)
71
+ || data.routes !== 0 || data.remotes !== 0 || data.leafnodes !== 0)
72
+ fail("only a declared standalone broker without gateway or leaf links is supported");
73
+ if (!Number.isSafeInteger(data.max_payload) || data.max_payload < SERVICE_CALLER_PAYLOAD_FLOOR)
74
+ fail(`broker max_payload ${String(data.max_payload)} is below required ${SERVICE_CALLER_PAYLOAD_FLOOR} bytes (2 MiB plus 1 KiB); configure the broker explicitly before enabling caller policy`);
75
+ if (!Number.isSafeInteger(data.max_control_line) || data.max_control_line < 4096)
76
+ fail("broker max_control_line does not admit native policy writes");
77
+ if (typeof data.config_digest !== "string" || !data.config_digest || typeof data.start !== "string" || typeof data.system_account !== "string"
78
+ || data.system_account !== (claims.nats?.issuer_account ?? claims.iss))
79
+ fail("VARZ config/system-account evidence is missing or foreign");
80
+ const stores = registryStores(registry), stream = await beforeDeadline(deadline, () => stores.jsm.streams.info(`KV_${epAuthBucket(stores.space)}`));
81
+ if (stream.config.max_msg_size !== -1)
82
+ fail("auth stream must have no per-message size cap");
83
+ const maxPayload = registryMaxPayload(registry);
84
+ if (registryServerId(registry) !== serverId || maxPayload === undefined || maxPayload < SERVICE_CALLER_PAYLOAD_FLOOR)
85
+ fail("data connection moved or has insufficient payload");
86
+ remaining();
87
+ return { serverId: serverId, configDigest: data.config_digest, floor: data.max_payload,
88
+ evidenceDigest: contractDigest({ serverId, systemAccount: data.system_account, start: data.start, configDigest: data.config_digest,
89
+ maxPayload: data.max_payload, maxControlLine: data.max_control_line, cluster: data.cluster, gateway: data.gateway, leaf: data.leaf,
90
+ stream: { name: stream.config.name, created: stream.created, maxMsgSize: stream.config.max_msg_size } }) };
91
+ }
92
+ finally {
93
+ await nc?.close();
94
+ }
95
+ }
96
+ /** Startup assertion, and a fresh live verifier for each later positive issuance. The writer
97
+ * holds only this exact metadata key. The auth plane's existing claim must still be held. */
98
+ export async function bootstrapServiceCallerPayload(opts) {
99
+ const { registry, hold, source, servers, accountPublicKey, dataAccount } = opts, space = registryStores(registry).space;
100
+ if (dataAccount.pub !== accountPublicKey)
101
+ fail("assertion account differs from the host assignment");
102
+ const proof = genuinePlaneHold(hold);
103
+ if (!proof || proof.space !== space)
104
+ fail("assertion requires this account's genuine plane claim");
105
+ const assertHeld = (_what, deadline) => beforeDeadline(deadline, () => proof.assertHeld("before"));
106
+ const deadline = Date.now() + 10_000;
107
+ await assertHeld("assert caller payload", deadline);
108
+ const evidence = await observe(registry, servers, source, deadline, opts.onConnection);
109
+ const broker = { serverId: evidence.serverId, configDigest: evidence.configDigest, claimId: hold.claimId, claimGeneration: hold.generation };
110
+ const row = { v: 1, kind: "service-caller-payload", state: "asserted", floor: evidence.floor,
111
+ assertedAtMs: Date.now(), evidenceDigest: evidence.evidenceDigest, broker };
112
+ const writer = await beforeDeadline(deadline, () => openAuthorityClient({ onConnection: opts.onConnection, server: servers, space, dataAccount, label: "cotal:service-caller-payload-bootstrap", noReconnect: true,
113
+ grants: id => ({ publish: ["$JS.API.INFO", `$JS.API.STREAM.INFO.KV_${epAuthBucket(space)}`, `$JS.API.STREAM.INFO.KV_${recordsBucket(space)}`,
114
+ `$JS.API.STREAM.MSG.GET.KV_${epAuthBucket(space)}`, `$JS.API.STREAM.MSG.GET.KV_${recordsBucket(space)}`, `$KV.${epAuthBucket(space)}.${SERVICE_CALLER_PAYLOAD_KEY}`], subscribe: [`_INBOX_${id}.>`] }), log: () => { } }), late => late.close());
115
+ const writerDeadline = setTimeout(() => { void writer.close(); }, Math.max(0, deadline - Date.now()));
116
+ let assertedRevision;
117
+ try {
118
+ const r = await beforeDeadline(deadline, () => openLifecycleRegistry(writer.nc, space));
119
+ if (registryServerId(r) !== evidence.serverId)
120
+ fail("assertion writer reached another broker");
121
+ const kv = registryStores(r).authKv, previous = await beforeDeadline(deadline, () => kv.get(SERVICE_CALLER_PAYLOAD_KEY));
122
+ if (previous && previous.operation !== "PUT")
123
+ fail("assertion key has deletion history");
124
+ if (previous)
125
+ parseServiceCallerPayloadRecord(JSON.parse(dec.decode(previous.value)));
126
+ await assertHeld("commit caller payload assertion", deadline);
127
+ const bytes = enc.encode(canonicalJson(row));
128
+ const revision = previous ? await kv.update(SERVICE_CALLER_PAYLOAD_KEY, bytes, previous.revision) : await kv.create(SERVICE_CALLER_PAYLOAD_KEY, bytes);
129
+ const after = await beforeDeadline(deadline, () => kv.get(SERVICE_CALLER_PAYLOAD_KEY));
130
+ if (!after || after.operation !== "PUT" || after.revision !== revision || dec.decode(after.value) !== dec.decode(bytes))
131
+ fail("assertion write outcome is unconfirmed");
132
+ assertedRevision = revision;
133
+ await assertHeld("release caller payload assertion", deadline);
134
+ }
135
+ finally {
136
+ clearTimeout(writerDeadline);
137
+ await writer.close();
138
+ }
139
+ return async () => {
140
+ const deadline = Date.now() + 10_000;
141
+ await assertHeld("verify caller payload", deadline);
142
+ const fresh = await observe(registry, servers, source, deadline, opts.onConnection);
143
+ if (fresh.serverId !== broker.serverId || fresh.configDigest !== broker.configDigest || fresh.evidenceDigest !== row.evidenceDigest)
144
+ fail("broker configuration changed since bootstrap; reopen and reassert before caller issuance");
145
+ const assertion = await beforeDeadline(deadline, () => registryStores(registry).authKv.get(SERVICE_CALLER_PAYLOAD_KEY));
146
+ if (!assertion || assertion.operation !== "PUT" || assertion.revision !== assertedRevision || dec.decode(assertion.value) !== canonicalJson(row))
147
+ fail("payload assertion changed or was withdrawn since bootstrap");
148
+ await beforeDeadline(deadline, () => proof.assertHeld("after"));
149
+ const claim = await beforeDeadline(deadline, () => readPlaneClaim(registryStores(registry).authKv, space));
150
+ if (!claim || claim.state !== "held" || claim.claimId !== broker.claimId || claim.generation !== broker.claimGeneration)
151
+ fail("payload assertion belongs to another plane claim");
152
+ };
153
+ }
154
+ //# sourceMappingURL=service-caller-payload-bootstrap.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service-caller-payload-bootstrap.js","sourceRoot":"","sources":["../src/service-caller-payload-bootstrap.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,50 @@
1
+ export declare const SERVICE_CALLER_PAYLOAD_KEY = "svchost.payload";
2
+ /** O: a bound on the header block of one caller-policy create or CAS, which `max_payload` counts
3
+ * with the body. The subject and publish line count against `max_control_line`, asserted
4
+ * separately, so this bound is conservative. */
5
+ export declare const SERVICE_CALLER_PAYLOAD_OVERHEAD = 1024;
6
+ /** The smallest `max_payload` under which every row the headroom rule admits fits in one write. */
7
+ export declare const SERVICE_CALLER_PAYLOAD_FLOOR: number;
8
+ export type ServiceCallerPayloadRecord =
9
+ /** `floor` is the smallest `max_payload` the operator's complete per-server assertion observed. */
10
+ {
11
+ v: 1;
12
+ kind: "service-caller-payload";
13
+ state: "asserted";
14
+ floor: number;
15
+ assertedAtMs: number;
16
+ evidenceDigest: string;
17
+ broker?: {
18
+ serverId: string;
19
+ configDigest: string;
20
+ claimId: string;
21
+ claimGeneration: number;
22
+ };
23
+ } | {
24
+ v: 1;
25
+ kind: "service-caller-payload";
26
+ state: "withdrawn";
27
+ reason: "live-check" | "payload-precondition";
28
+ operationId: string | null;
29
+ atMs: number;
30
+ };
31
+ export declare function parseServiceCallerPayloadRecord(raw: unknown): ServiceCallerPayloadRecord;
32
+ /** The live limits one connection observes: its server's `max_payload`, and the auth stream's
33
+ * `max_msg_size` (-1 when unlimited). Undefined means the value could not be read. */
34
+ export interface ServiceCallerPayloadLimits {
35
+ maxPayload: number | undefined;
36
+ authMaxMsgSize: number | undefined;
37
+ }
38
+ export type ServiceCallerPayloadCheck = {
39
+ holds: true;
40
+ } | {
41
+ holds: false;
42
+ why: "unasserted" | "withdrawn" | "server-limit" | "stream-limit";
43
+ };
44
+ /** Whether the precondition holds now: asserted at or above the floor, and this connection's server
45
+ * and the auth stream both admit the largest row. A finite stream limit fails it. */
46
+ export declare function serviceCallerPayloadCheck(record: ServiceCallerPayloadRecord | undefined, live: ServiceCallerPayloadLimits): ServiceCallerPayloadCheck;
47
+ /** Whether one row write of `bytes` fits the live limits: the smaller of the server's `max_payload`
48
+ * and a finite auth-stream `max_msg_size`, less O. An unreadable limit never fits. */
49
+ export declare function serviceCallerWriteFits(bytes: number, live: ServiceCallerPayloadLimits): boolean;
50
+ //# sourceMappingURL=service-caller-payload.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service-caller-payload.d.ts","sourceRoot":"","sources":["../src/service-caller-payload.ts"],"names":[],"mappings":"AAYA,eAAO,MAAM,0BAA0B,oBAAoB,CAAC;AAC5D;;iDAEiD;AACjD,eAAO,MAAM,+BAA+B,OAAO,CAAC;AACpD,mGAAmG;AACnG,eAAO,MAAM,4BAA4B,QAAgE,CAAC;AAE1G,MAAM,MAAM,0BAA0B;AACpC,mGAAmG;AACjG;IAAE,CAAC,EAAE,CAAC,CAAC;IAAC,IAAI,EAAE,wBAAwB,CAAC;IAAC,KAAK,EAAE,UAAU,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC;IAAC,cAAc,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,eAAe,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,GACvN;IAAE,CAAC,EAAE,CAAC,CAAC;IAAC,IAAI,EAAE,wBAAwB,CAAC;IAAC,KAAK,EAAE,WAAW,CAAC;IAAC,MAAM,EAAE,YAAY,GAAG,sBAAsB,CAAC;IAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAY1J,wBAAgB,+BAA+B,CAAC,GAAG,EAAE,OAAO,GAAG,0BAA0B,CAmBxF;AAED;uFACuF;AACvF,MAAM,WAAW,0BAA0B;IAAG,UAAU,EAAE,MAAM,GAAG,SAAS,CAAC;IAAC,cAAc,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE;AAElH,MAAM,MAAM,yBAAyB,GACjC;IAAE,KAAK,EAAE,IAAI,CAAA;CAAE,GACf;IAAE,KAAK,EAAE,KAAK,CAAC;IAAC,GAAG,EAAE,YAAY,GAAG,WAAW,GAAG,cAAc,GAAG,cAAc,CAAA;CAAE,CAAC;AAExF;sFACsF;AACtF,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,0BAA0B,GAAG,SAAS,EAAE,IAAI,EAAE,0BAA0B,GAAG,yBAAyB,CAMrJ;AAED;uFACuF;AACvF,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,0BAA0B,GAAG,OAAO,CAI/F"}
@@ -0,0 +1,77 @@
1
+ /** Package-internal payload precondition for caller policy. A policy row may grow to
2
+ * SERVICE_AUTHORITY_MAX_BYTES, and every row write is one message, so the revocation headroom the
3
+ * row reserves is publishable only where every server's `max_payload` admits the largest row plus
4
+ * the write's header block, and the auth stream sets no smaller per-message limit.
5
+ *
6
+ * The precondition has one durable record in the auth store, read by every caller-policy writer and
7
+ * by any later issuance path. Only a trusted complete broker observation may assert it. The stock
8
+ * standalone host bootstrap authenticates native VARZ and binds the row to its broker and plane
9
+ * claim. Any writer that sees a live check fail withdraws the record until explicit reassertion.
10
+ * One connection's INFO is not complete topology evidence and never replaces the observation. */
11
+ import { EpEnvelopeError, SERVICE_AUTHORITY_MAX_BYTES, isContractDigest } from "@cotal-ai/core";
12
+ export const SERVICE_CALLER_PAYLOAD_KEY = "svchost.payload";
13
+ /** O: a bound on the header block of one caller-policy create or CAS, which `max_payload` counts
14
+ * with the body. The subject and publish line count against `max_control_line`, asserted
15
+ * separately, so this bound is conservative. */
16
+ export const SERVICE_CALLER_PAYLOAD_OVERHEAD = 1024;
17
+ /** The smallest `max_payload` under which every row the headroom rule admits fits in one write. */
18
+ export const SERVICE_CALLER_PAYLOAD_FLOOR = SERVICE_AUTHORITY_MAX_BYTES + SERVICE_CALLER_PAYLOAD_OVERHEAD;
19
+ function bad() { throw new EpEnvelopeError("bad-request", "invalid service caller payload precondition"); }
20
+ function closed(raw, fields) {
21
+ if (!raw || typeof raw !== "object" || Array.isArray(raw))
22
+ bad();
23
+ const proto = Object.getPrototypeOf(raw);
24
+ if (proto !== Object.prototype && proto !== null)
25
+ bad();
26
+ if (fields.some((k) => !Object.hasOwn(raw, k)) || Reflect.ownKeys(raw).some((k) => typeof k !== "string" || !fields.includes(k)))
27
+ bad();
28
+ return raw;
29
+ }
30
+ const uint = (raw) => { if (typeof raw !== "number" || !Number.isSafeInteger(raw) || raw < 0)
31
+ bad(); return raw; };
32
+ export function parseServiceCallerPayloadRecord(raw) {
33
+ const o = raw;
34
+ if (o?.state === "asserted") {
35
+ const fields = ["v", "kind", "state", "floor", "assertedAtMs", "evidenceDigest"];
36
+ closed(raw, Object.hasOwn(o, "broker") ? [...fields, "broker"] : fields);
37
+ if (o.v !== 1 || o.kind !== "service-caller-payload" || typeof o.evidenceDigest !== "string" || !isContractDigest(o.evidenceDigest))
38
+ bad();
39
+ let broker;
40
+ if (o.broker !== undefined) {
41
+ const b = closed(o.broker, ["serverId", "configDigest", "claimId", "claimGeneration"]);
42
+ if (typeof b.serverId !== "string" || !/^N[A-Z2-7]{55}$/.test(b.serverId) || typeof b.configDigest !== "string" || !b.configDigest
43
+ || b.configDigest.length > 128 || typeof b.claimId !== "string" || !b.claimId || b.claimId.length > 128 || uint(b.claimGeneration) < 1)
44
+ bad();
45
+ broker = { serverId: b.serverId, configDigest: b.configDigest, claimId: b.claimId, claimGeneration: b.claimGeneration };
46
+ }
47
+ return { v: 1, kind: "service-caller-payload", state: "asserted", floor: uint(o.floor), assertedAtMs: uint(o.assertedAtMs), evidenceDigest: o.evidenceDigest, ...(broker ? { broker } : {}) };
48
+ }
49
+ closed(raw, ["v", "kind", "state", "reason", "operationId", "atMs"]);
50
+ if (o.v !== 1 || o.kind !== "service-caller-payload" || o.state !== "withdrawn" || (o.reason !== "live-check" && o.reason !== "payload-precondition"))
51
+ bad();
52
+ if (o.operationId !== null && (typeof o.operationId !== "string" || !/^[0-9a-f]{32}$/.test(o.operationId)))
53
+ bad();
54
+ return { v: 1, kind: "service-caller-payload", state: "withdrawn", reason: o.reason, operationId: o.operationId, atMs: uint(o.atMs) };
55
+ }
56
+ /** Whether the precondition holds now: asserted at or above the floor, and this connection's server
57
+ * and the auth stream both admit the largest row. A finite stream limit fails it. */
58
+ export function serviceCallerPayloadCheck(record, live) {
59
+ if (record === undefined || (record.state === "asserted" && record.floor < SERVICE_CALLER_PAYLOAD_FLOOR))
60
+ return { holds: false, why: "unasserted" };
61
+ if (record.state === "withdrawn")
62
+ return { holds: false, why: "withdrawn" };
63
+ if (live.maxPayload === undefined || live.maxPayload < SERVICE_CALLER_PAYLOAD_FLOOR)
64
+ return { holds: false, why: "server-limit" };
65
+ if (live.authMaxMsgSize !== -1)
66
+ return { holds: false, why: "stream-limit" };
67
+ return { holds: true };
68
+ }
69
+ /** Whether one row write of `bytes` fits the live limits: the smaller of the server's `max_payload`
70
+ * and a finite auth-stream `max_msg_size`, less O. An unreadable limit never fits. */
71
+ export function serviceCallerWriteFits(bytes, live) {
72
+ if (live.maxPayload === undefined || live.authMaxMsgSize === undefined)
73
+ return false;
74
+ const stream = live.authMaxMsgSize === -1 ? Number.POSITIVE_INFINITY : live.authMaxMsgSize;
75
+ return bytes <= Math.min(live.maxPayload, stream) - SERVICE_CALLER_PAYLOAD_OVERHEAD;
76
+ }
77
+ //# sourceMappingURL=service-caller-payload.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service-caller-payload.js","sourceRoot":"","sources":["../src/service-caller-payload.ts"],"names":[],"mappings":"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"}
@@ -14,10 +14,69 @@ export interface NativeServiceCallerPolicyRead {
14
14
  row: NativeServiceCallerPolicy;
15
15
  revision: number;
16
16
  }
17
+ /** A grant's immutable fields as canonical JSON: everything but `status`, the one field that advances. */
18
+ export declare function immutableGrantView(g: ServiceCallerGrant): string;
19
+ /** One stored grant's closed syntax, as a row holds it. */
20
+ export declare function parseServiceCallerGrant(raw: unknown): ServiceCallerGrant;
21
+ /** The auth-store key of one grantee's policy row. */
22
+ export declare function servicePolicyKey(granteeUid: string): string;
17
23
  /** Stored syntax only. Eligibility, ancestry and native capability containment are host checks. */
18
24
  export declare function parseNativeServiceCallerPolicy(raw: unknown): NativeServiceCallerPolicy;
19
25
  /** Internal leader-served read. Deleted or corrupt records are not an absent/empty policy. */
20
26
  export declare function readNativeServiceCallerPolicy(reg: LifecycleRegistry, binding: ServiceCallerPolicyBinding): Promise<NativeServiceCallerPolicyRead | undefined>;
27
+ /** Whether every grant of `row` could still take its largest status within the byte bound. */
28
+ export declare function callerPolicyHasRevocationHeadroom(row: NativeServiceCallerPolicy): boolean;
29
+ /** Appending `candidate` (status active) at `expectedRevision`. An existing grant with the same id
30
+ * answers present whatever its status, because the comparison leaves out status, which only
31
+ * advances: a replay after deny-new adopts the row and never reactivates the grant. */
32
+ export type CallerAppendStep = {
33
+ kind: "present";
34
+ grant: ServiceCallerGrant;
35
+ } | {
36
+ kind: "mismatch";
37
+ } | {
38
+ kind: "moved";
39
+ } | {
40
+ kind: "headroom";
41
+ } | {
42
+ kind: "write";
43
+ row: NativeServiceCallerPolicy;
44
+ };
45
+ export declare function callerAppendStep(found: NativeServiceCallerPolicyRead | undefined, empty: NativeServiceCallerPolicy, expectedRevision: number, candidate: ServiceCallerGrant): CallerAppendStep;
46
+ /** Deny-new: an active grant moves to `revoking` under `operationId` in phase `cutoff-required`. A
47
+ * grant already revoking or revoked names the operation that holds its intent. */
48
+ export type CallerBeginStep = {
49
+ kind: "absent";
50
+ } | {
51
+ kind: "held";
52
+ grant: ServiceCallerGrant;
53
+ operationId: string;
54
+ } | {
55
+ kind: "write";
56
+ row: NativeServiceCallerPolicy;
57
+ grant: ServiceCallerGrant;
58
+ };
59
+ export declare function callerBeginStep(row: NativeServiceCallerPolicy | undefined, grantId: string, operationId: string): CallerBeginStep;
60
+ /** The cutoff marker and its fixed deadline, on a grant whose deny-new intent `operationId` holds.
61
+ * The same marker again is a no-op; anything else is a conflict, so a recorded deadline is never
62
+ * reset. `notBefore` is in epoch seconds. */
63
+ export type CallerCutoffStep = {
64
+ kind: "absent";
65
+ } | {
66
+ kind: "same";
67
+ } | {
68
+ kind: "conflict";
69
+ detail: string;
70
+ } | {
71
+ kind: "write";
72
+ row: NativeServiceCallerPolicy;
73
+ };
74
+ export declare function callerCutoffStep(row: NativeServiceCallerPolicy | undefined, grantId: string, marker: {
75
+ operationId: string;
76
+ markerKey: string;
77
+ markerRevision: number;
78
+ notBefore: number;
79
+ }): CallerCutoffStep;
21
80
  /** Storage commit only, after trusted host eligibility checks. Never replaces an existing grant. */
22
81
  export declare function appendNativeServiceCallerGrant(reg: LifecycleRegistry, binding: ServiceCallerPolicyBinding, args: {
23
82
  expectedRevision: number;
@@ -29,4 +88,16 @@ export declare function beginNativeServiceCallerRevocation(reg: LifecycleRegistr
29
88
  grantId: string;
30
89
  operationId: string;
31
90
  }): Promise<NativeServiceCallerPolicyRead>;
91
+ /** The cutoff step after deny-new: records the op-bound marker and its fixed deadline on a grant
92
+ * whose intent this operation already holds. The same values again are a no-op. Other values,
93
+ * another operation's intent in either revoking form, an active grant or a revoked one are a
94
+ * conflict, so a recorded deadline is never reset. `notBefore` is in epoch seconds. */
95
+ export declare function recordNativeServiceCallerCutoff(reg: LifecycleRegistry, binding: ServiceCallerPolicyBinding, args: {
96
+ expectedRevision: number;
97
+ grantId: string;
98
+ operationId: string;
99
+ markerKey: string;
100
+ markerRevision: number;
101
+ notBefore: number;
102
+ }): Promise<NativeServiceCallerPolicyRead>;
32
103
  //# sourceMappingURL=service-caller-policy.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"service-caller-policy.d.ts","sourceRoot":"","sources":["../src/service-caller-policy.ts"],"names":[],"mappings":"AAAA,OAAO,EAGmC,KAAK,gBAAgB,EAC9D,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAkB,KAAK,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AACjF,OAAO,KAAK,EAAE,kBAAkB,EAA4E,MAAM,4BAA4B,CAAC;AAE/I,MAAM,WAAW,0BAA0B;IACzC,gBAAgB,EAAE,MAAM,CAAC;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,gBAAgB,CAAC;CAC3B;AACD,MAAM,WAAW,yBAA0B,SAAQ,0BAA0B;IAC3E,CAAC,EAAE,CAAC,CAAC;IACL,MAAM,EAAE,kBAAkB,EAAE,CAAC;CAC9B;AACD,MAAM,WAAW,6BAA6B;IAAG,GAAG,EAAE,yBAAyB,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE;AAuFnG,mGAAmG;AACnG,wBAAgB,8BAA8B,CAAC,GAAG,EAAE,OAAO,GAAG,yBAAyB,CAYtF;AAQD,8FAA8F;AAC9F,wBAAsB,6BAA6B,CAAC,GAAG,EAAE,iBAAiB,EAAE,OAAO,EAAE,0BAA0B,GAAG,OAAO,CAAC,6BAA6B,GAAG,SAAS,CAAC,CAQnK;AA0BD,oGAAoG;AACpG,wBAAsB,8BAA8B,CAAC,GAAG,EAAE,iBAAiB,EAAE,OAAO,EAAE,0BAA0B,EAAE,IAAI,EAAE;IACtH,gBAAgB,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,kBAAkB,CAAC;CACrD,GAAG,OAAO,CAAC,6BAA6B,CAAC,CAgBzC;AAED,4GAA4G;AAC5G,wBAAsB,kCAAkC,CAAC,GAAG,EAAE,iBAAiB,EAAE,OAAO,EAAE,0BAA0B,EAAE,IAAI,EAAE;IAC1H,gBAAgB,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;CAChE,GAAG,OAAO,CAAC,6BAA6B,CAAC,CAezC"}
1
+ {"version":3,"file":"service-caller-policy.d.ts","sourceRoot":"","sources":["../src/service-caller-policy.ts"],"names":[],"mappings":"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"}
@@ -109,6 +109,15 @@ function grant(raw) {
109
109
  return { grantId: id(o.grantId), endpoint: o.endpoint, instanceId: lifecycle(o.instanceId), serviceApprovalDigest: approvalDigest,
110
110
  capabilities, source: provenance, parentRefs: parents, status: state(o.status) };
111
111
  }
112
+ /** A grant's immutable fields as canonical JSON: everything but `status`, the one field that advances. */
113
+ export function immutableGrantView(g) {
114
+ return canonicalJson({ grantId: g.grantId, endpoint: g.endpoint, instanceId: g.instanceId, serviceApprovalDigest: g.serviceApprovalDigest,
115
+ capabilities: g.capabilities, source: g.source, parentRefs: g.parentRefs });
116
+ }
117
+ /** One stored grant's closed syntax, as a row holds it. */
118
+ export function parseServiceCallerGrant(raw) { return grant(raw); }
119
+ /** The auth-store key of one grantee's policy row. */
120
+ export function servicePolicyKey(granteeUid) { return `servicepolicy.${lifecycle(granteeUid)}`; }
112
121
  /** Stored syntax only. Eligibility, ancestry and native capability containment are host checks. */
113
122
  export function parseNativeServiceCallerPolicy(raw) {
114
123
  const o = object(raw, ["v", "accountPublicKey", "space", "grantee", "grants"]);
@@ -133,7 +142,7 @@ function context(reg, binding) {
133
142
  const expected = parseNativeServiceCallerPolicy({ v: 1, ...binding, grants: [] });
134
143
  if (stores.space !== expected.space)
135
144
  throw new EpEnvelopeError("permission-denied", "caller policy belongs to another registry space");
136
- return { kv: stores.authKv, key: `servicepolicy.${expected.grantee.lifecycleUid}`, expected };
145
+ return { kv: stores.authKv, key: servicePolicyKey(expected.grantee.lifecycleUid), expected };
137
146
  }
138
147
  /** Internal leader-served read. Deleted or corrupt records are not an absent/empty policy. */
139
148
  export async function readNativeServiceCallerPolicy(reg, binding) {
@@ -168,11 +177,44 @@ const maxStatusBytes = Math.max(...[
168
177
  markerRevision: Number.MAX_SAFE_INTEGER, notBefore: Number.MAX_SAFE_INTEGER },
169
178
  { state: "revoked", operationId: "f".repeat(32), verifiedAt: Number.MAX_SAFE_INTEGER },
170
179
  ].map(byteSize));
171
- function assertRevocationHeadroom(row) {
180
+ /** Whether every grant of `row` could still take its largest status within the byte bound. */
181
+ export function callerPolicyHasRevocationHeadroom(row) {
172
182
  const worstBytes = byteSize(row) + row.grants.reduce((sum, g) => sum + Math.max(0, maxStatusBytes - byteSize(g.status)), 0);
173
- if (worstBytes > SERVICE_AUTHORITY_MAX_BYTES)
174
- throw new EpEnvelopeError("resource-exhausted", "native service caller policy lacks all-grant revocation status headroom");
183
+ return worstBytes <= SERVICE_AUTHORITY_MAX_BYTES;
184
+ }
185
+ export function callerAppendStep(found, empty, expectedRevision, candidate) {
186
+ const existing = found?.row.grants.find((g) => g.grantId === candidate.grantId);
187
+ if (existing)
188
+ return immutableGrantView(existing) === immutableGrantView(candidate) ? { kind: "present", grant: existing } : { kind: "mismatch" };
189
+ if ((found?.revision ?? 0) !== expectedRevision)
190
+ return { kind: "moved" };
191
+ const row = found?.row ?? empty;
192
+ const next = { ...row, grants: [...row.grants, candidate] };
193
+ return callerPolicyHasRevocationHeadroom(next) ? { kind: "write", row: next } : { kind: "headroom" };
194
+ }
195
+ export function callerBeginStep(row, grantId, operationId) {
196
+ const existing = row?.grants.find((g) => g.grantId === grantId);
197
+ if (!row || !existing)
198
+ return { kind: "absent" };
199
+ if (existing.status.state !== "active")
200
+ return { kind: "held", grant: existing, operationId: existing.status.operationId };
201
+ const status = { state: "revoking", operationId, phase: "cutoff-required" };
202
+ return { kind: "write", row: { ...row, grants: row.grants.map((g) => (g.grantId === grantId ? { ...g, status } : g)) }, grant: { ...existing, status } };
203
+ }
204
+ export function callerCutoffStep(row, grantId, marker) {
205
+ const existing = row?.grants.find((g) => g.grantId === grantId);
206
+ if (!row || !existing)
207
+ return { kind: "absent" };
208
+ const next = { state: "revoking", operationId: id(marker.operationId), markerKey: marker.markerKey,
209
+ markerRevision: uint(marker.markerRevision, true), notBefore: uint(marker.notBefore, true) };
210
+ const s = existing.status;
211
+ if (s.state !== "revoking" || s.operationId !== next.operationId)
212
+ return { kind: "conflict", detail: "caller grant is not under this revocation operation's deny-new intent" };
213
+ if (!("phase" in s))
214
+ return canonicalJson(s) === canonicalJson(next) ? { kind: "same" } : { kind: "conflict", detail: "caller grant already records another cutoff marker" };
215
+ return { kind: "write", row: { ...row, grants: row.grants.map((g) => (g.grantId === grantId ? { ...g, status: next } : g)) } };
175
216
  }
217
+ // ---- storage helpers ----------------------------------------------------------------------------
176
218
  /** Storage commit only, after trusted host eligibility checks. Never replaces an existing grant. */
177
219
  export async function appendNativeServiceCallerGrant(reg, binding, args) {
178
220
  const revision = uint(args.expectedRevision);
@@ -181,18 +223,17 @@ export async function appendNativeServiceCallerGrant(reg, binding, args) {
181
223
  const candidate = parseNativeServiceCallerPolicy({ v: 1, ...binding, grants: [args.grant] }).grants[0];
182
224
  if (candidate.status.state !== "active")
183
225
  bad();
184
- const found = await readNativeServiceCallerPolicy(reg, binding), existing = found?.row.grants.find((g) => g.grantId === candidate.grantId);
185
- if (existing) {
186
- if (canonicalJson(existing) !== canonicalJson(candidate))
187
- conflict("grant id is immutable and cannot be rebound or reactivated");
226
+ const found = await readNativeServiceCallerPolicy(reg, binding);
227
+ const step = callerAppendStep(found, context(reg, binding).expected, revision, candidate);
228
+ if (step.kind === "present")
188
229
  return found;
189
- }
190
- if ((found?.revision ?? 0) !== revision)
230
+ if (step.kind === "mismatch")
231
+ conflict("grant id is immutable and cannot be rebound");
232
+ if (step.kind === "moved")
191
233
  conflict("native caller policy revision changed");
192
- const row = found?.row ?? context(reg, binding).expected;
193
- const next = { ...row, grants: [...row.grants, candidate] };
194
- assertRevocationHeadroom(next);
195
- return write(reg, binding, next, revision);
234
+ if (step.kind === "headroom")
235
+ throw new EpEnvelopeError("resource-exhausted", "native service caller policy lacks all-grant revocation status headroom");
236
+ return write(reg, binding, step.row, revision);
196
237
  }
197
238
  /** Monotonic deny-new intent only. Cutoff, descendant invalidation and live retirement remain host work. */
198
239
  export async function beginNativeServiceCallerRevocation(reg, binding, args) {
@@ -202,17 +243,39 @@ export async function beginNativeServiceCallerRevocation(reg, binding, args) {
202
243
  const found = await readNativeServiceCallerPolicy(reg, binding);
203
244
  if (!found)
204
245
  throw new EpEnvelopeError("not-found", "native caller policy is absent");
205
- const existing = found.row.grants.find((g) => g.grantId === grantId);
206
- if (!existing)
246
+ const step = callerBeginStep(found.row, grantId, operationId);
247
+ if (step.kind === "absent")
207
248
  throw new EpEnvelopeError("not-found", "native caller grant is absent");
208
- if (existing.status.state !== "active") {
209
- if (existing.status.operationId !== operationId)
249
+ if (step.kind === "held") {
250
+ if (step.operationId !== operationId)
210
251
  conflict("caller grant is already bound to another revocation operation");
211
252
  return found;
212
253
  }
213
254
  if (found.revision !== revision)
214
255
  conflict("native caller policy revision changed");
215
- return write(reg, binding, { ...found.row, grants: found.row.grants.map((g) => g.grantId === grantId
216
- ? { ...g, status: { state: "revoking", operationId, phase: "cutoff-required" } } : g) }, found.revision);
256
+ return write(reg, binding, step.row, found.revision);
257
+ }
258
+ /** The cutoff step after deny-new: records the op-bound marker and its fixed deadline on a grant
259
+ * whose intent this operation already holds. The same values again are a no-op. Other values,
260
+ * another operation's intent in either revoking form, an active grant or a revoked one are a
261
+ * conflict, so a recorded deadline is never reset. `notBefore` is in epoch seconds. */
262
+ export async function recordNativeServiceCallerCutoff(reg, binding, args) {
263
+ const revision = uint(args.expectedRevision), grantId = id(args.grantId), operationId = id(args.operationId);
264
+ const marker = { operationId, markerKey: args.markerKey, markerRevision: uint(args.markerRevision, true), notBefore: uint(args.notBefore, true) };
265
+ const { accountPublicKey, space, grantee } = context(reg, binding).expected;
266
+ binding = { accountPublicKey, space, grantee };
267
+ const found = await readNativeServiceCallerPolicy(reg, binding);
268
+ if (!found)
269
+ throw new EpEnvelopeError("not-found", "native caller policy is absent");
270
+ const step = callerCutoffStep(found.row, grantId, marker);
271
+ if (step.kind === "absent")
272
+ throw new EpEnvelopeError("not-found", "native caller grant is absent");
273
+ if (step.kind === "conflict")
274
+ conflict(step.detail);
275
+ if (step.kind === "same")
276
+ return found;
277
+ if (found.revision !== revision)
278
+ conflict("native caller policy revision changed");
279
+ return write(reg, binding, step.row, found.revision);
217
280
  }
218
281
  //# sourceMappingURL=service-caller-policy.js.map