@cotal-ai/auth 0.89.0 → 0.90.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -0
- package/dist/authority-client.d.ts +8 -1
- package/dist/authority-client.d.ts.map +1 -1
- package/dist/authority-client.js +11 -9
- package/dist/authority-client.js.map +1 -1
- package/dist/callout.d.ts +3 -0
- package/dist/callout.d.ts.map +1 -1
- package/dist/callout.js +24 -2
- package/dist/callout.js.map +1 -1
- package/dist/connect-reader.d.ts +7 -0
- package/dist/connect-reader.d.ts.map +1 -1
- package/dist/connect-reader.js +16 -1
- package/dist/connect-reader.js.map +1 -1
- package/dist/credential-ledger.d.ts.map +1 -1
- package/dist/credential-ledger.js +7 -1
- package/dist/credential-ledger.js.map +1 -1
- package/dist/idp.d.ts.map +1 -1
- package/dist/idp.js +2 -0
- package/dist/idp.js.map +1 -1
- package/dist/index.d.ts +5 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/issuer.d.ts +11 -0
- package/dist/issuer.d.ts.map +1 -1
- package/dist/issuer.js +29 -1
- package/dist/issuer.js.map +1 -1
- package/dist/lifecycle-registry.d.ts +6 -0
- package/dist/lifecycle-registry.d.ts.map +1 -1
- package/dist/lifecycle-registry.js +13 -1
- package/dist/lifecycle-registry.js.map +1 -1
- package/dist/native-issuance.d.ts +13 -1
- package/dist/native-issuance.d.ts.map +1 -1
- package/dist/native-issuance.js +76 -10
- package/dist/native-issuance.js.map +1 -1
- package/dist/native-service-callout.d.ts +38 -0
- package/dist/native-service-callout.d.ts.map +1 -0
- package/dist/native-service-callout.js +71 -0
- package/dist/native-service-callout.js.map +1 -0
- package/dist/native-service-gate.d.ts +72 -0
- package/dist/native-service-gate.d.ts.map +1 -0
- package/dist/native-service-gate.js +219 -0
- package/dist/native-service-gate.js.map +1 -0
- package/dist/native-service-name.d.ts +17 -0
- package/dist/native-service-name.d.ts.map +1 -0
- package/dist/native-service-name.js +77 -0
- package/dist/native-service-name.js.map +1 -0
- package/dist/permissions.js +2 -0
- package/dist/permissions.js.map +1 -1
- package/dist/plane-claim.d.ts +8 -0
- package/dist/plane-claim.d.ts.map +1 -1
- package/dist/plane-claim.js +11 -1
- package/dist/plane-claim.js.map +1 -1
- package/dist/provider.d.ts.map +1 -1
- package/dist/provider.js +48 -4
- package/dist/provider.js.map +1 -1
- package/dist/retirement-barrier.d.ts.map +1 -1
- package/dist/retirement-barrier.js +2 -1
- package/dist/retirement-barrier.js.map +1 -1
- package/dist/root-credential.d.ts.map +1 -1
- package/dist/root-credential.js +2 -1
- package/dist/root-credential.js.map +1 -1
- package/dist/service-authority-client.d.ts +19 -0
- package/dist/service-authority-client.d.ts.map +1 -0
- package/dist/service-authority-client.js +152 -0
- package/dist/service-authority-client.js.map +1 -0
- package/dist/service-caller-policy.d.ts +32 -0
- package/dist/service-caller-policy.d.ts.map +1 -0
- package/dist/service-caller-policy.js +218 -0
- package/dist/service-caller-policy.js.map +1 -0
- package/dist/service-custody-shapes.d.ts +109 -0
- package/dist/service-custody-shapes.d.ts.map +1 -0
- package/dist/service-custody-shapes.js +138 -0
- package/dist/service-custody-shapes.js.map +1 -0
- package/dist/service-custody.d.ts +96 -0
- package/dist/service-custody.d.ts.map +1 -0
- package/dist/service-custody.js +667 -0
- package/dist/service-custody.js.map +1 -0
- package/dist/service-host-contract.d.ts +312 -0
- package/dist/service-host-contract.d.ts.map +1 -0
- package/dist/service-host-contract.js +176 -0
- package/dist/service-host-contract.js.map +1 -0
- package/dist/service-renewal.d.ts +23 -0
- package/dist/service-renewal.d.ts.map +1 -0
- package/dist/service-renewal.js +51 -0
- package/dist/service-renewal.js.map +1 -0
- package/dist/service-root-reader.d.ts +16 -0
- package/dist/service-root-reader.d.ts.map +1 -0
- package/dist/service-root-reader.js +93 -0
- package/dist/service-root-reader.js.map +1 -0
- package/dist/session-ledger.d.ts.map +1 -1
- package/dist/session-ledger.js +8 -2
- package/dist/session-ledger.js.map +1 -1
- package/dist/store.d.ts +2 -0
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +2 -0
- package/dist/store.js.map +1 -1
- package/dist/token.d.ts +11 -1
- package/dist/token.d.ts.map +1 -1
- package/dist/token.js +31 -3
- package/dist/token.js.map +1 -1
- package/dist/tool-result-prerequisites.d.ts +55 -0
- package/dist/tool-result-prerequisites.d.ts.map +1 -0
- package/dist/tool-result-prerequisites.js +352 -0
- package/dist/tool-result-prerequisites.js.map +1 -0
- package/package.json +7 -4
package/dist/native-issuance.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { fromPublic } from "@nats-io/nkeys";
|
|
2
|
-
import { EP_AUTH_MARKER_TTL_MS, epAuthBucket, nativeIssuanceKey } from "@cotal-ai/core";
|
|
2
|
+
import { EP_AUTH_MARKER_TTL_MS, SERVICE_ACCESS_TTL_SECONDS, contractDigest, epAuthBucket, epcredRowKey, nativeIssuanceKey, parseLedgerRow, parseServiceConnectionBinding, parseServiceRegistrationWitness, } from "@cotal-ai/core";
|
|
3
3
|
import { CLOCK_TOLERANCE_SEC } from "./token.js";
|
|
4
4
|
import { registryStores } from "./lifecycle-registry.js";
|
|
5
5
|
const FIELDS = ["v", "space", "owner", "actor", "lifecycleUid", "credentialId", "view", "connId", "iat", "exp"];
|
|
@@ -13,6 +13,28 @@ function assertSeconds(v, what) {
|
|
|
13
13
|
throw new Error(`natkey: ${what} must be a positive integer of seconds`);
|
|
14
14
|
return v;
|
|
15
15
|
}
|
|
16
|
+
function parseServiceEvidence(raw) {
|
|
17
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw))
|
|
18
|
+
throw new Error("natkey: invalid service evidence");
|
|
19
|
+
const o = raw;
|
|
20
|
+
if (Object.keys(o).length !== 3 || Object.keys(o).some((k) => !["witness", "credentialId", "connection"].includes(k))
|
|
21
|
+
|| typeof o.credentialId !== "string")
|
|
22
|
+
throw new Error("natkey: invalid service evidence");
|
|
23
|
+
const witness = parseServiceRegistrationWitness(o.witness), connection = parseServiceConnectionBinding(o.connection);
|
|
24
|
+
epcredRowKey(witness.endpoint, witness.instanceId, o.credentialId);
|
|
25
|
+
return { witness, credentialId: o.credentialId, connection };
|
|
26
|
+
}
|
|
27
|
+
async function verifyServiceEvidence(kv, record) {
|
|
28
|
+
const service = record.service;
|
|
29
|
+
const key = epcredRowKey(service.witness.endpoint, service.witness.instanceId, service.credentialId), entry = await kv.get(key);
|
|
30
|
+
if (!entry || entry.operation !== "PUT")
|
|
31
|
+
throw new Error("natkey: service issuance evidence is unavailable");
|
|
32
|
+
const row = parseLedgerRow(entry.value, key);
|
|
33
|
+
if (row.state !== "active" || row.holderPrincipal !== `${record.owner}.${record.actor}` || row.exp !== record.exp
|
|
34
|
+
|| row.sourceChain.length !== 1 || row.sourceChain[0] !== "root" || !row.serviceConnection
|
|
35
|
+
|| contractDigest(row.serviceConnection) !== contractDigest(service.connection))
|
|
36
|
+
throw new Error("natkey: service issuance evidence does not match the connection");
|
|
37
|
+
}
|
|
16
38
|
/** Build the record from the validated token alone: never from a fresh alias lookup. */
|
|
17
39
|
export function nativeIssuanceRecord(space, i) {
|
|
18
40
|
const t = i.token;
|
|
@@ -24,14 +46,26 @@ export function nativeIssuanceRecord(space, i) {
|
|
|
24
46
|
const iat = assertSeconds(i.iat, "iat"), exp = assertSeconds(i.exp, "exp");
|
|
25
47
|
if (exp <= iat)
|
|
26
48
|
throw new Error("natkey: exp must follow iat");
|
|
27
|
-
|
|
49
|
+
const record = { v: 1, space, owner: t.owner, actor: t.act.actor, lifecycleUid, credentialId,
|
|
50
|
+
view: t.act.view ?? "agent", connId: i.connId, iat, exp };
|
|
51
|
+
if (t.act.view === "service-endpoint") {
|
|
52
|
+
if (!i.service || !t.act.service || exp > t.exp)
|
|
53
|
+
throw new Error("natkey: service issuance requires bounded native evidence");
|
|
54
|
+
record.service = parseServiceEvidence({ ...i.service, witness: t.act.service.witness });
|
|
55
|
+
if (record.service.connection.userNkey !== i.userNkey)
|
|
56
|
+
throw new Error("natkey: service evidence names a different user key");
|
|
57
|
+
}
|
|
58
|
+
else if (i.service !== undefined)
|
|
59
|
+
throw new Error("natkey: ordinary issuance cannot carry service evidence");
|
|
60
|
+
return record.service ? parseNativeIssuanceRecord(record) : record;
|
|
28
61
|
}
|
|
29
62
|
/** Parse a stored row; anything but the closed v1 shape refuses. */
|
|
30
63
|
export function parseNativeIssuanceRecord(raw) {
|
|
31
64
|
if (typeof raw !== "object" || raw === null || Array.isArray(raw))
|
|
32
65
|
throw new Error("natkey: record is not an object");
|
|
33
66
|
const keys = Object.keys(raw);
|
|
34
|
-
if (keys.length !== FIELDS.length || !FIELDS.every((f) => keys.includes(f))
|
|
67
|
+
if (keys.length !== FIELDS.length + (Object.hasOwn(raw, "service") ? 1 : 0) || !FIELDS.every((f) => keys.includes(f))
|
|
68
|
+
|| keys.some((key) => key !== "service" && !FIELDS.includes(key)))
|
|
35
69
|
throw new Error(`natkey: record fields ${JSON.stringify(keys.sort())} are not the closed v1 set`);
|
|
36
70
|
const r = raw;
|
|
37
71
|
if (r.v !== 1)
|
|
@@ -41,6 +75,16 @@ export function parseNativeIssuanceRecord(raw) {
|
|
|
41
75
|
throw new Error(`natkey: record ${f} is not a non-empty string`);
|
|
42
76
|
assertSeconds(r.iat, "iat");
|
|
43
77
|
assertSeconds(r.exp, "exp");
|
|
78
|
+
if ((r.view === "service-endpoint") !== Object.hasOwn(r, "service"))
|
|
79
|
+
throw new Error("natkey: service view and evidence must occur together");
|
|
80
|
+
if (r.service !== undefined) {
|
|
81
|
+
const service = parseServiceEvidence(r.service), witness = service.witness;
|
|
82
|
+
if (r.space !== witness.space || r.owner !== witness.principal.owner || r.actor !== witness.principal.actor
|
|
83
|
+
|| r.lifecycleUid !== witness.principal.lifecycleUid || r.credentialId !== witness.rootCredentialId
|
|
84
|
+
|| r.exp <= r.iat || r.exp > r.iat + SERVICE_ACCESS_TTL_SECONDS)
|
|
85
|
+
throw new Error("natkey: service evidence does not match its native root and lifetime");
|
|
86
|
+
return { ...r, service };
|
|
87
|
+
}
|
|
44
88
|
return r;
|
|
45
89
|
}
|
|
46
90
|
/**
|
|
@@ -54,8 +98,21 @@ export function makeNativeIssuanceRecorder(registry, fence) {
|
|
|
54
98
|
return async (i) => {
|
|
55
99
|
const key = assertUserNkey(i.userNkey);
|
|
56
100
|
const record = nativeIssuanceRecord(space, i);
|
|
57
|
-
|
|
58
|
-
|
|
101
|
+
const token = record.service ? structuredClone(i.token) : i.token;
|
|
102
|
+
const bytes = new TextEncoder().encode(JSON.stringify(record));
|
|
103
|
+
if (record.service) {
|
|
104
|
+
await verifyServiceEvidence(authKv, record);
|
|
105
|
+
await authKv.put(key, bytes, { previousSeq: 0 });
|
|
106
|
+
await verifyServiceEvidence(authKv, record);
|
|
107
|
+
const stored = await readNativeIssuance(authKv, i.userNkey);
|
|
108
|
+
if (!stored || contractDigest(stored) !== contractDigest(record))
|
|
109
|
+
throw new Error("natkey: service receipt changed before release");
|
|
110
|
+
}
|
|
111
|
+
else
|
|
112
|
+
await authKv.create(key, bytes);
|
|
113
|
+
await fence(token);
|
|
114
|
+
if (record.service && record.exp <= Math.floor(Date.now() / 1000))
|
|
115
|
+
throw new Error("natkey: service issuance expired before release");
|
|
59
116
|
};
|
|
60
117
|
}
|
|
61
118
|
/** Leader-read one key's evidence (the auth bucket is not Direct-capable). Absent is `undefined`. */
|
|
@@ -63,7 +120,10 @@ export async function readNativeIssuance(kv, userNkey) {
|
|
|
63
120
|
const entry = await kv.get(assertUserNkey(userNkey));
|
|
64
121
|
if (!entry || entry.operation !== "PUT")
|
|
65
122
|
return undefined;
|
|
66
|
-
|
|
123
|
+
const record = parseNativeIssuanceRecord(entry.json());
|
|
124
|
+
if (record.service && record.service.connection.userNkey !== userNkey)
|
|
125
|
+
throw new Error("natkey: service receipt key does not match its connection");
|
|
126
|
+
return record;
|
|
67
127
|
}
|
|
68
128
|
/**
|
|
69
129
|
* Remove the rows whose minted JWT expired more than the clock tolerance ago (a session-caller's
|
|
@@ -71,13 +131,14 @@ export async function readNativeIssuance(kv, userNkey) {
|
|
|
71
131
|
* `STREAM.MSG.GET` `next_by_subj` read over `natkey.` (no consumer: none may be created on the auth
|
|
72
132
|
* bucket), and each removal is a KV purge pinned to the exact sequence read. A row that does not
|
|
73
133
|
* parse throws: corrupt evidence is the caller's fault to surface, never a skipped row. The purge
|
|
74
|
-
* marker carries the bucket's own marker TTL, so it expires too.
|
|
134
|
+
* marker carries the bucket's own marker TTL, so it expires too. Expired service evidence is
|
|
135
|
+
* retained: elapsed JWT time alone is not topology-qualified absence or terminal retirement.
|
|
75
136
|
*/
|
|
76
137
|
export async function pruneNativeIssuance(registry, nowSec) {
|
|
77
138
|
const { authKv, jsm, space } = registryStores(registry);
|
|
78
139
|
const bucket = epAuthBucket(space);
|
|
79
140
|
const prefix = `$KV.${bucket}.`;
|
|
80
|
-
let seq = 1, live = 0, pruned = 0;
|
|
141
|
+
let seq = 1, live = 0, pruned = 0, retainedService = 0;
|
|
81
142
|
for (;;) {
|
|
82
143
|
const m = await jsm.streams.getMessage(`KV_${bucket}`, { seq, next_by_subj: `${prefix}natkey.>` });
|
|
83
144
|
if (!m)
|
|
@@ -90,13 +151,18 @@ export async function pruneNativeIssuance(registry, nowSec) {
|
|
|
90
151
|
if (assertUserNkey(key.slice("natkey.".length)) !== key)
|
|
91
152
|
throw new Error(`natkey: stored subject ${JSON.stringify(m.subject)} is not a natkey row`);
|
|
92
153
|
const record = parseNativeIssuanceRecord(m.json());
|
|
93
|
-
if (record.
|
|
154
|
+
if (record.service && nativeIssuanceKey(record.service.connection.userNkey) !== key)
|
|
155
|
+
throw new Error("natkey: service receipt key does not match its connection");
|
|
156
|
+
if (record.exp + CLOCK_TOLERANCE_SEC < nowSec && record.service) {
|
|
157
|
+
retainedService++;
|
|
158
|
+
}
|
|
159
|
+
else if (record.exp + CLOCK_TOLERANCE_SEC < nowSec) {
|
|
94
160
|
await authKv.purge(key, { previousSeq: m.seq, ttl: `${EP_AUTH_MARKER_TTL_MS / 1000}s` });
|
|
95
161
|
pruned++;
|
|
96
162
|
}
|
|
97
163
|
else
|
|
98
164
|
live++;
|
|
99
165
|
}
|
|
100
|
-
return { live, pruned };
|
|
166
|
+
return { live, pruned, retainedService };
|
|
101
167
|
}
|
|
102
168
|
//# sourceMappingURL=native-issuance.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"native-issuance.js","sourceRoot":"","sources":["../src/native-issuance.ts"],"names":[],"mappings":"AAYA,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAC5C,OAAO,
|
|
1
|
+
{"version":3,"file":"native-issuance.js","sourceRoot":"","sources":["../src/native-issuance.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { type ServiceConnectionBinding, type ServiceRegistrationWitness } from "@cotal-ai/core";
|
|
2
|
+
import type { ConnectReader } from "./connect-reader.js";
|
|
3
|
+
import { type LifecycleRegistry } from "./lifecycle-registry.js";
|
|
4
|
+
import { type ServiceAssignmentBinding } from "./native-service-gate.js";
|
|
5
|
+
import type { ValidatedUserToken } from "./token.js";
|
|
6
|
+
export interface NativeServiceCalloutAuthority {
|
|
7
|
+
readonly space: string;
|
|
8
|
+
readonly accountPublicKey: string;
|
|
9
|
+
}
|
|
10
|
+
interface ServiceCalloutContext {
|
|
11
|
+
connection: ServiceConnectionBinding;
|
|
12
|
+
inboxNonce: string;
|
|
13
|
+
admittedAt: number;
|
|
14
|
+
}
|
|
15
|
+
interface ServiceCalloutResult {
|
|
16
|
+
jwt: string;
|
|
17
|
+
principal: string;
|
|
18
|
+
exp: number;
|
|
19
|
+
}
|
|
20
|
+
/** Native host composition only. Assignment lookup and restore/mutation readiness belong to the
|
|
21
|
+
* host; neither callback is a public approval hook. Services receive no registry or signing key.
|
|
22
|
+
*/
|
|
23
|
+
export declare function createNativeServiceCalloutAuthority(opts: {
|
|
24
|
+
registry: LifecycleRegistry;
|
|
25
|
+
reader: () => ConnectReader;
|
|
26
|
+
dataAccount: {
|
|
27
|
+
pub: string;
|
|
28
|
+
signingSeed: string;
|
|
29
|
+
};
|
|
30
|
+
resolveAssignment: (witness: ServiceRegistrationWitness) => Promise<ServiceAssignmentBinding>;
|
|
31
|
+
readClusterArtifact: (digest: string) => unknown | Promise<unknown>;
|
|
32
|
+
assertOpen: () => void;
|
|
33
|
+
}): NativeServiceCalloutAuthority;
|
|
34
|
+
/** A structural object cannot opt into native service minting or select another tenant account. */
|
|
35
|
+
export declare function assertNativeServiceCalloutAuthority(authority: NativeServiceCalloutAuthority, space: string, accountPublicKey: string): void;
|
|
36
|
+
export declare function issueNativeServiceCallout(authority: NativeServiceCalloutAuthority, token: ValidatedUserToken, context: ServiceCalloutContext): Promise<ServiceCalloutResult>;
|
|
37
|
+
export {};
|
|
38
|
+
//# sourceMappingURL=native-service-callout.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"native-service-callout.d.ts","sourceRoot":"","sources":["../src/native-service-callout.ts"],"names":[],"mappings":"AAAA,OAAO,EAE+D,KAAK,wBAAwB,EAAE,KAAK,0BAA0B,EACnI,MAAM,gBAAgB,CAAC;AACxB,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,EAAkB,KAAK,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAEjF,OAAO,EAAkD,KAAK,wBAAwB,EAAE,MAAM,0BAA0B,CAAC;AAEzH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAErD,MAAM,WAAW,6BAA6B;IAAG,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAA;CAAE;AAC5G,UAAU,qBAAqB;IAAG,UAAU,EAAE,wBAAwB,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE;AAChH,UAAU,oBAAoB;IAAG,GAAG,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE;AAI9E;;GAEG;AACH,wBAAgB,mCAAmC,CAAC,IAAI,EAAE;IACxD,QAAQ,EAAE,iBAAiB,CAAC;IAAC,MAAM,EAAE,MAAM,aAAa,CAAC;IAAC,WAAW,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE,CAAC;IAC5G,iBAAiB,EAAE,CAAC,OAAO,EAAE,0BAA0B,KAAK,OAAO,CAAC,wBAAwB,CAAC,CAAC;IAC9F,mBAAmB,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAAC,UAAU,EAAE,MAAM,IAAI,CAAC;CAC7F,GAAG,6BAA6B,CA6ChC;AAED,mGAAmG;AACnG,wBAAgB,mCAAmC,CAAC,SAAS,EAAE,6BAA6B,EAAE,KAAK,EAAE,MAAM,EAAE,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAG3I;AAED,wBAAgB,yBAAyB,CAAC,SAAS,EAAE,6BAA6B,EAAE,KAAK,EAAE,kBAAkB,EAC3G,OAAO,EAAE,qBAAqB,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAI/D"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { SERVICE_ACCESS_TTL_SECONDS, authorizeServeGrant, contractDigest, mintServiceCalloutJwt, parseServiceConnectionBinding, parseServiceRegistrationWitness, principalKey, serveIssuanceGateKv, } from "@cotal-ai/core";
|
|
2
|
+
import { registryStores } from "./lifecycle-registry.js";
|
|
3
|
+
import { makeNativeIssuanceRecorder } from "./native-issuance.js";
|
|
4
|
+
import { readNativeServiceAccess, readNativeServiceGate } from "./native-service-gate.js";
|
|
5
|
+
import { nativeServiceNameAuthority } from "./native-service-name.js";
|
|
6
|
+
const authorities = new WeakMap();
|
|
7
|
+
/** Native host composition only. Assignment lookup and restore/mutation readiness belong to the
|
|
8
|
+
* host; neither callback is a public approval hook. Services receive no registry or signing key.
|
|
9
|
+
*/
|
|
10
|
+
export function createNativeServiceCalloutAuthority(opts) {
|
|
11
|
+
const { registry, reader, resolveAssignment, readClusterArtifact, assertOpen } = opts;
|
|
12
|
+
const dataAccount = { ...opts.dataAccount }, stores = registryStores(registry), space = stores.space;
|
|
13
|
+
const authority = Object.freeze({ space, accountPublicKey: dataAccount.pub });
|
|
14
|
+
const names = nativeServiceNameAuthority(registry, { space, accountPublicKey: dataAccount.pub });
|
|
15
|
+
authorities.set(authority, async (raw, supplied) => {
|
|
16
|
+
const token = structuredClone(raw), connection = parseServiceConnectionBinding(supplied.connection);
|
|
17
|
+
const { inboxNonce, admittedAt } = supplied, expiresAt = Math.min(token.exp, admittedAt + SERVICE_ACCESS_TTL_SECONDS);
|
|
18
|
+
if (!Number.isSafeInteger(admittedAt) || admittedAt <= 0 || admittedAt > Math.floor(Date.now() / 1000)
|
|
19
|
+
|| !Number.isSafeInteger(expiresAt) || expiresAt <= Math.floor(Date.now() / 1000))
|
|
20
|
+
throw new Error("service callout requires its original live admission deadline");
|
|
21
|
+
if (token.act.view !== "service-endpoint" || !token.act.service)
|
|
22
|
+
throw new Error("service callout requires a validated service bearer");
|
|
23
|
+
const witness = parseServiceRegistrationWitness(token.act.service.witness);
|
|
24
|
+
if (witness.accountPublicKey !== dataAccount.pub || witness.space !== space)
|
|
25
|
+
throw new Error("service callout bearer belongs to another native account or space");
|
|
26
|
+
assertOpen();
|
|
27
|
+
const binding = { ...await resolveAssignment(witness) }, bindingDigest = contractDigest(binding);
|
|
28
|
+
if (binding.accountPublicKey !== dataAccount.pub || binding.space !== space)
|
|
29
|
+
throw new Error("service callout assignment belongs to another native account or space");
|
|
30
|
+
await readNativeServiceAccess(registry, reader(), binding, token, expiresAt);
|
|
31
|
+
const serve = await authorizeServeGrant(stores.recordsKv, { space, endpoint: witness.endpoint, instanceId: witness.instanceId,
|
|
32
|
+
epoch: witness.processEpoch, holder: { owner: witness.principal.owner }, authority: names, readClusterArtifact,
|
|
33
|
+
readProcessEpoch: async () => {
|
|
34
|
+
const gate = await readNativeServiceGate(registry, binding);
|
|
35
|
+
if (!gate)
|
|
36
|
+
throw new Error("service callout has no native gate");
|
|
37
|
+
return gate.row.processEpoch;
|
|
38
|
+
} });
|
|
39
|
+
assertOpen();
|
|
40
|
+
const minted = await mintServiceCalloutJwt({ space, account: dataAccount }, { serve,
|
|
41
|
+
gate: serveIssuanceGateKv(stores.authKv, space, { endpoint: witness.endpoint, instanceId: witness.instanceId }),
|
|
42
|
+
principal: witness.principal, connection, inboxNonce, admittedAt, accessExpiresAt: token.exp, expiresAt });
|
|
43
|
+
const record = makeNativeIssuanceRecorder(registry, async (currentToken) => {
|
|
44
|
+
assertOpen();
|
|
45
|
+
const current = { ...await resolveAssignment(witness) };
|
|
46
|
+
if (contractDigest(current) !== bindingDigest)
|
|
47
|
+
throw new Error("service assignment changed before callout release");
|
|
48
|
+
await readNativeServiceAccess(registry, reader(), current, currentToken, expiresAt);
|
|
49
|
+
assertOpen();
|
|
50
|
+
});
|
|
51
|
+
await record({ userNkey: connection.userNkey, token, connId: inboxNonce, iat: minted.iat, exp: minted.exp,
|
|
52
|
+
service: { credentialId: minted.credentialId, connection } });
|
|
53
|
+
assertOpen();
|
|
54
|
+
if (expiresAt <= Math.floor(Date.now() / 1000))
|
|
55
|
+
throw new Error("service callout expired before response");
|
|
56
|
+
return { jwt: minted.jwt, principal: principalKey(witness.principal.owner, witness.principal.actor).key, exp: expiresAt };
|
|
57
|
+
});
|
|
58
|
+
return authority;
|
|
59
|
+
}
|
|
60
|
+
/** A structural object cannot opt into native service minting or select another tenant account. */
|
|
61
|
+
export function assertNativeServiceCalloutAuthority(authority, space, accountPublicKey) {
|
|
62
|
+
if (!authorities.has(authority) || authority.space !== space || authority.accountPublicKey !== accountPublicKey)
|
|
63
|
+
throw new Error("service callout authority is not native or belongs to another account/space");
|
|
64
|
+
}
|
|
65
|
+
export function issueNativeServiceCallout(authority, token, context) {
|
|
66
|
+
const issue = authorities.get(authority);
|
|
67
|
+
if (!issue)
|
|
68
|
+
throw new Error("service callout authority is not native");
|
|
69
|
+
return issue(token, context);
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=native-service-callout.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"native-service-callout.js","sourceRoot":"","sources":["../src/native-service-callout.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { type EndpointGateRow, type EpCapability, type ServiceGateAuthority, type ServiceRegistrationWitness } from "@cotal-ai/core";
|
|
2
|
+
import type { ConnectReader } from "./connect-reader.js";
|
|
3
|
+
import type { ValidatedUserToken } from "./token.js";
|
|
4
|
+
import { type LifecycleRegistry } from "./lifecycle-registry.js";
|
|
5
|
+
export interface ServiceAssignmentBinding {
|
|
6
|
+
accountPublicKey: string;
|
|
7
|
+
space: string;
|
|
8
|
+
owner: string;
|
|
9
|
+
endpoint: string;
|
|
10
|
+
instanceId: string;
|
|
11
|
+
lifecycleUid: string;
|
|
12
|
+
assignmentDigest: string;
|
|
13
|
+
}
|
|
14
|
+
type ServiceRow = EndpointGateRow & {
|
|
15
|
+
authorityFamily: "service";
|
|
16
|
+
serviceAuthority: ServiceGateAuthority;
|
|
17
|
+
};
|
|
18
|
+
interface ServiceGateRead {
|
|
19
|
+
row: ServiceRow;
|
|
20
|
+
revision: number;
|
|
21
|
+
}
|
|
22
|
+
/** Internal host storage seam. Consent and name authority are not established by a gate write. */
|
|
23
|
+
export declare function readNativeServiceGate(reg: LifecycleRegistry, binding: ServiceAssignmentBinding): Promise<ServiceGateRead | undefined>;
|
|
24
|
+
/** Package-internal pure endpoint-gate rows for a writer that issues, bounds and re-checks every
|
|
25
|
+
* request itself. No I/O: the same rows and checks the helpers below write and read. */
|
|
26
|
+
export interface ServiceGateRows {
|
|
27
|
+
readonly key: string;
|
|
28
|
+
/** Parse bytes read at `key`; `conflict` when they belong to another immutable assignment. */
|
|
29
|
+
parse(value: Uint8Array): ServiceRow;
|
|
30
|
+
/** Bytes of the pending-root row that a zero-history create writes. */
|
|
31
|
+
readonly pending: Uint8Array;
|
|
32
|
+
/** Bytes of the frozen revoked row that a zero-history create writes before the gate exists. */
|
|
33
|
+
uncreatedFence(operationId: string): Uint8Array;
|
|
34
|
+
/** Bytes of the deny-new successor of `row`, or `undefined` when it is already revoked. */
|
|
35
|
+
deny(row: ServiceRow, operationId: string): Uint8Array | undefined;
|
|
36
|
+
}
|
|
37
|
+
/** The pure endpoint-gate rows of one assignment in this registry's space. */
|
|
38
|
+
export declare function serviceGateRows(reg: LifecycleRegistry, binding: ServiceAssignmentBinding): ServiceGateRows;
|
|
39
|
+
/** Create the unmintable endpoint gate before native root activation. Never adopts a different assignment. */
|
|
40
|
+
export declare function createPendingServiceGate(reg: LifecycleRegistry, binding: ServiceAssignmentBinding): Promise<ServiceGateRead>;
|
|
41
|
+
/** Current native access prerequisites, not a serving grant or an issuance release fence.
|
|
42
|
+
* The host supplies an already signature-validated token and fixes expiresAt before its first
|
|
43
|
+
* authority read. Final issuance still needs a fresh check and the native serving gate CAS.
|
|
44
|
+
*/
|
|
45
|
+
export declare function readNativeServiceAccess(reg: LifecycleRegistry, reader: ConnectReader, expected: ServiceAssignmentBinding, raw: ValidatedUserToken, expiresAt: number): Promise<{
|
|
46
|
+
witness: ServiceRegistrationWitness;
|
|
47
|
+
gateRevision: number;
|
|
48
|
+
expiresAt: number;
|
|
49
|
+
}>;
|
|
50
|
+
/** Final approval CAS. The host must first verify the proposal, owner consent and operator target policy. */
|
|
51
|
+
export declare function approveNativeServiceGate(reg: LifecycleRegistry, binding: ServiceAssignmentBinding, args: {
|
|
52
|
+
expectedRevision: number;
|
|
53
|
+
rootCredentialId: string;
|
|
54
|
+
approvalDigest: string;
|
|
55
|
+
nameAuthorityRevision: number;
|
|
56
|
+
approvalExpiresAt: number | null;
|
|
57
|
+
callerCeiling: EpCapability[];
|
|
58
|
+
}): Promise<ServiceGateRead>;
|
|
59
|
+
/** The host withholds both access and renewal material until this monotonic activation CAS wins. */
|
|
60
|
+
export declare function markNativeServiceActivated(reg: LifecycleRegistry, binding: ServiceAssignmentBinding, args: {
|
|
61
|
+
expectedRevision: number;
|
|
62
|
+
witness: ServiceRegistrationWitness;
|
|
63
|
+
}): Promise<ServiceGateRead>;
|
|
64
|
+
/** Durable deny-new only. This is never a terminal retirement result or broker absence proof. */
|
|
65
|
+
export declare function denyNativeServiceGate(reg: LifecycleRegistry, binding: ServiceAssignmentBinding, args: {
|
|
66
|
+
expectedRevision: number;
|
|
67
|
+
operationId: string;
|
|
68
|
+
}): Promise<ServiceGateRead>;
|
|
69
|
+
/** Fence cancellation before the endpoint gate exists. Native UID cancellation remains a separate required barrier. */
|
|
70
|
+
export declare function fenceUncreatedServiceGate(reg: LifecycleRegistry, binding: ServiceAssignmentBinding, operationId: string): Promise<ServiceGateRead>;
|
|
71
|
+
export {};
|
|
72
|
+
//# sourceMappingURL=native-service-gate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"native-service-gate.d.ts","sourceRoot":"","sources":["../src/native-service-gate.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import { EpEnvelopeError, SERVICE_ACCESS_TTL_SECONDS, assertLifecycleToken, createRowByteIdempotent, epgateKey, isCasLoss, parseEndpointCapabilities, parseEndpointGate, parseServiceRegistrationWitness, principalKey, serviceEndpointActor, serviceGateApprovalLive, } from "@cotal-ai/core";
|
|
2
|
+
import { readNativeServiceName } from "./native-service-name.js";
|
|
3
|
+
import { registryStores } from "./lifecycle-registry.js";
|
|
4
|
+
import { readConnectServiceRoot, readRegistryServiceRoot } from "./service-root-reader.js";
|
|
5
|
+
const enc = new TextEncoder();
|
|
6
|
+
const bytes = (row) => enc.encode(JSON.stringify(row));
|
|
7
|
+
const conflict = (message) => { throw new EpEnvelopeError("conflict", message); };
|
|
8
|
+
function coordinates(reg, binding) {
|
|
9
|
+
const stores = registryStores(reg);
|
|
10
|
+
if (stores.space !== binding.space)
|
|
11
|
+
throw new EpEnvelopeError("permission-denied", "service assignment belongs to another space");
|
|
12
|
+
const actor = serviceEndpointActor(binding);
|
|
13
|
+
return { kv: stores.authKv, key: epgateKey(binding.endpoint, binding.instanceId), actor, principal: principalKey(binding.owner, actor).key };
|
|
14
|
+
}
|
|
15
|
+
function parseFor(binding, key, principal, value) {
|
|
16
|
+
const row = parseEndpointGate(value, key);
|
|
17
|
+
if (row.authorityFamily !== "service" || !row.serviceAuthority || row.principal !== principal
|
|
18
|
+
|| row.serviceAuthority.assignmentDigest !== binding.assignmentDigest || row.serviceAuthority.lifecycleUid !== binding.lifecycleUid)
|
|
19
|
+
conflict("service gate belongs to another immutable assignment");
|
|
20
|
+
return row;
|
|
21
|
+
}
|
|
22
|
+
/** Internal host storage seam. Consent and name authority are not established by a gate write. */
|
|
23
|
+
export async function readNativeServiceGate(reg, binding) {
|
|
24
|
+
const { kv, key, principal } = coordinates(reg, binding);
|
|
25
|
+
const entry = await kv.get(key);
|
|
26
|
+
if (!entry)
|
|
27
|
+
return undefined;
|
|
28
|
+
if (entry.operation !== "PUT")
|
|
29
|
+
throw new EpEnvelopeError("failed-precondition", "service gate is deleted; absence cannot be inferred from a tombstone");
|
|
30
|
+
return { row: parseFor(binding, key, principal, entry.value), revision: entry.revision };
|
|
31
|
+
}
|
|
32
|
+
function pendingRow(reg, binding) {
|
|
33
|
+
const { key, principal } = coordinates(reg, binding);
|
|
34
|
+
const row = {
|
|
35
|
+
state: "open", generation: 0, processEpoch: 0, registrationRevision: 0, nameAuthorityRevision: 0,
|
|
36
|
+
principal, authorityFamily: "service", serviceAuthority: {
|
|
37
|
+
v: 1, assignmentDigest: binding.assignmentDigest, lifecycleUid: binding.lifecycleUid,
|
|
38
|
+
state: "pending-root", rootCredentialId: null, rootExpiresAt: null, approvalDigest: null,
|
|
39
|
+
approvalExpiresAt: null, callerCeiling: [], activationIssued: false,
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
parseEndpointGate(bytes(row), key);
|
|
43
|
+
return row;
|
|
44
|
+
}
|
|
45
|
+
function deniedRow(row, operationId) {
|
|
46
|
+
assertLifecycleToken(operationId, "operationId");
|
|
47
|
+
if (row.serviceAuthority.state === "revoked")
|
|
48
|
+
return undefined;
|
|
49
|
+
const authority = { ...row.serviceAuthority, state: "revoked" };
|
|
50
|
+
return row.state === "open"
|
|
51
|
+
? { ...row, state: "frozen", op: { opId: operationId, kind: "retirement" }, serviceAuthority: authority }
|
|
52
|
+
: { ...row, serviceAuthority: authority };
|
|
53
|
+
}
|
|
54
|
+
function uncreatedFenceRow(reg, binding, operationId) {
|
|
55
|
+
assertLifecycleToken(operationId, "operationId");
|
|
56
|
+
const { key } = coordinates(reg, binding), pending = pendingRow(reg, binding);
|
|
57
|
+
const row = { ...pending, state: "frozen", op: { opId: operationId, kind: "retirement" },
|
|
58
|
+
serviceAuthority: { ...pending.serviceAuthority, state: "revoked" } };
|
|
59
|
+
parseEndpointGate(bytes(row), key);
|
|
60
|
+
return row;
|
|
61
|
+
}
|
|
62
|
+
/** The pure endpoint-gate rows of one assignment in this registry's space. */
|
|
63
|
+
export function serviceGateRows(reg, binding) {
|
|
64
|
+
const { key, principal } = coordinates(reg, binding), frozenBinding = { ...binding };
|
|
65
|
+
return Object.freeze({
|
|
66
|
+
key, pending: bytes(pendingRow(reg, frozenBinding)),
|
|
67
|
+
parse: (value) => parseFor(frozenBinding, key, principal, value),
|
|
68
|
+
uncreatedFence: (operationId) => bytes(uncreatedFenceRow(reg, frozenBinding, operationId)),
|
|
69
|
+
deny: (row, operationId) => {
|
|
70
|
+
const next = deniedRow(row, operationId);
|
|
71
|
+
if (next === undefined)
|
|
72
|
+
return undefined;
|
|
73
|
+
const encoded = bytes(next);
|
|
74
|
+
parseEndpointGate(encoded, key);
|
|
75
|
+
return encoded;
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
/** Create the unmintable endpoint gate before native root activation. Never adopts a different assignment. */
|
|
80
|
+
export async function createPendingServiceGate(reg, binding) {
|
|
81
|
+
const { kv, key } = coordinates(reg, binding);
|
|
82
|
+
await createRowByteIdempotent(kv, key, pendingRow(reg, binding));
|
|
83
|
+
const created = await readNativeServiceGate(reg, binding);
|
|
84
|
+
if (!created)
|
|
85
|
+
throw new EpEnvelopeError("unavailable", "created service gate is not readable; retain the operation identity");
|
|
86
|
+
return created;
|
|
87
|
+
}
|
|
88
|
+
async function current(reg, binding, expectedRevision) {
|
|
89
|
+
const found = await readNativeServiceGate(reg, binding);
|
|
90
|
+
if (!found || found.revision !== expectedRevision)
|
|
91
|
+
conflict("service gate revision changed");
|
|
92
|
+
return found;
|
|
93
|
+
}
|
|
94
|
+
async function replace(reg, binding, before, row) {
|
|
95
|
+
const { kv, key } = coordinates(reg, binding);
|
|
96
|
+
const encoded = bytes(row);
|
|
97
|
+
parseEndpointGate(encoded, key);
|
|
98
|
+
try {
|
|
99
|
+
return { row, revision: await kv.update(key, encoded, before.revision) };
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
if (isCasLoss(error))
|
|
103
|
+
conflict("service gate changed before its transition committed");
|
|
104
|
+
throw new EpEnvelopeError("unavailable", "service gate transition outcome is unknown; retain the operation identity");
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
async function rootExpiry(reg, binding, rootCredentialId, nowMs) {
|
|
108
|
+
return (await readRegistryServiceRoot(reg, binding, rootCredentialId, nowMs)).expiresAt;
|
|
109
|
+
}
|
|
110
|
+
/** Current native access prerequisites, not a serving grant or an issuance release fence.
|
|
111
|
+
* The host supplies an already signature-validated token and fixes expiresAt before its first
|
|
112
|
+
* authority read. Final issuance still needs a fresh check and the native serving gate CAS.
|
|
113
|
+
*/
|
|
114
|
+
export async function readNativeServiceAccess(reg, reader, expected, raw, expiresAt) {
|
|
115
|
+
const binding = { ...expected }, now = Math.floor(Date.now() / 1000);
|
|
116
|
+
if (raw.act.view !== "service-endpoint" || raw.act.service?.duty !== "serve")
|
|
117
|
+
throw new EpEnvelopeError("permission-denied", "native service access requires a validated service bearer");
|
|
118
|
+
const witness = parseServiceRegistrationWitness(raw.act.service.witness);
|
|
119
|
+
const token = { ...raw, scope: [...raw.scope], act: { ...raw.act,
|
|
120
|
+
scope: raw.act.scope === undefined ? undefined : [...raw.act.scope], service: { duty: "serve", witness } } };
|
|
121
|
+
const { actor } = coordinates(reg, binding);
|
|
122
|
+
if (reader.space !== binding.space || token.space !== binding.space || token.owner !== binding.owner
|
|
123
|
+
|| token.act.owner !== binding.owner || token.act.actor !== actor || token.act.lifecycleUid !== binding.lifecycleUid
|
|
124
|
+
|| token.act.credentialId !== witness.rootCredentialId || token.credentialId !== witness.rootCredentialId
|
|
125
|
+
|| token.scope.length || token.act.scope?.length || witness.accountPublicKey !== binding.accountPublicKey
|
|
126
|
+
|| witness.space !== binding.space || witness.endpoint !== binding.endpoint || witness.instanceId !== binding.instanceId
|
|
127
|
+
|| witness.principal.owner !== binding.owner || witness.principal.actor !== actor || witness.principal.lifecycleUid !== binding.lifecycleUid)
|
|
128
|
+
throw new EpEnvelopeError("permission-denied", "service bearer does not bind the native assignment");
|
|
129
|
+
if (!Number.isSafeInteger(expiresAt) || !Number.isSafeInteger(token.exp) || expiresAt <= now
|
|
130
|
+
|| expiresAt > token.exp || expiresAt > now + SERVICE_ACCESS_TTL_SECONDS)
|
|
131
|
+
throw new EpEnvelopeError("expired", "service access requires its original bounded expiry");
|
|
132
|
+
// The native cred root and the endpoint's epcred issuance family are distinct authorities.
|
|
133
|
+
const connectRoot = await readConnectServiceRoot(reader, binding, witness.rootCredentialId, Date.now());
|
|
134
|
+
const rootExpiresAt = await rootExpiry(reg, binding, witness.rootCredentialId, Date.now());
|
|
135
|
+
const found = await readNativeServiceGate(reg, binding);
|
|
136
|
+
if (!found)
|
|
137
|
+
throw new EpEnvelopeError("permission-denied", "service assignment has no native gate");
|
|
138
|
+
const { row } = found, authority = row.serviceAuthority, stamp = authority.registration;
|
|
139
|
+
if (row.state !== "open" || !authority.activationIssued || authority.pendingRegistration !== undefined
|
|
140
|
+
|| !serviceGateApprovalLive(authority, Math.floor(Date.now() / 1000)) || !stamp
|
|
141
|
+
|| authority.rootCredentialId !== witness.rootCredentialId || authority.approvalDigest !== witness.approvalDigest
|
|
142
|
+
|| row.processEpoch !== witness.processEpoch || row.registrationRevision !== witness.registrationRevision
|
|
143
|
+
|| row.nameAuthorityRevision !== witness.nameAuthorityRevision || stamp.processEpoch !== witness.processEpoch
|
|
144
|
+
|| stamp.registrationRevision !== witness.registrationRevision || stamp.nameAuthorityRevision !== witness.nameAuthorityRevision
|
|
145
|
+
|| stamp.requestId !== witness.registrationRequestId || stamp.registrationProof !== witness.registrationProof)
|
|
146
|
+
throw new EpEnvelopeError("permission-denied", "service access does not match the current activated registration");
|
|
147
|
+
if (expiresAt > connectRoot.expiresAt || expiresAt > rootExpiresAt || expiresAt > authority.rootExpiresAt
|
|
148
|
+
|| (authority.approvalExpiresAt !== null && expiresAt > authority.approvalExpiresAt))
|
|
149
|
+
throw new EpEnvelopeError("expired", "service access outlives its native root or approval");
|
|
150
|
+
const name = await readNativeServiceName(reg, binding, binding.endpoint);
|
|
151
|
+
if (!name || name.owner !== binding.owner || name.revision !== witness.nameAuthorityRevision)
|
|
152
|
+
throw new EpEnvelopeError("permission-denied", "service access does not match the native name binding");
|
|
153
|
+
const finalGate = await readNativeServiceGate(reg, binding);
|
|
154
|
+
if (!finalGate || finalGate.revision !== found.revision)
|
|
155
|
+
throw new EpEnvelopeError("conflict", "service authority changed during the access read");
|
|
156
|
+
const finalRoot = await readConnectServiceRoot(reader, binding, witness.rootCredentialId, Date.now());
|
|
157
|
+
if (finalRoot.uidRevision !== connectRoot.uidRevision || finalRoot.headRevision !== connectRoot.headRevision
|
|
158
|
+
|| finalRoot.gateRevision !== connectRoot.gateRevision || finalRoot.rootRevision !== connectRoot.rootRevision)
|
|
159
|
+
throw new EpEnvelopeError("conflict", "service native root authority changed during the access read");
|
|
160
|
+
if (expiresAt <= Math.floor(Date.now() / 1000))
|
|
161
|
+
throw new EpEnvelopeError("expired", "service access expired during native authorization");
|
|
162
|
+
return { witness, gateRevision: found.revision, expiresAt };
|
|
163
|
+
}
|
|
164
|
+
/** Final approval CAS. The host must first verify the proposal, owner consent and operator target policy. */
|
|
165
|
+
export async function approveNativeServiceGate(reg, binding, args) {
|
|
166
|
+
const found = await current(reg, binding, args.expectedRevision);
|
|
167
|
+
if (found.row.state !== "open" || found.row.serviceAuthority.state !== "pending-root")
|
|
168
|
+
conflict("only the pending root assignment can receive its first approval");
|
|
169
|
+
if (!Number.isSafeInteger(args.nameAuthorityRevision) || args.nameAuthorityRevision <= 0)
|
|
170
|
+
throw new EpEnvelopeError("failed-precondition", "service approval requires its native name binding revision");
|
|
171
|
+
const now = Date.now();
|
|
172
|
+
const authority = {
|
|
173
|
+
...found.row.serviceAuthority, state: "approved", rootCredentialId: args.rootCredentialId,
|
|
174
|
+
rootExpiresAt: await rootExpiry(reg, binding, args.rootCredentialId, now), approvalDigest: args.approvalDigest,
|
|
175
|
+
approvalExpiresAt: args.approvalExpiresAt, callerCeiling: parseEndpointCapabilities(args.callerCeiling),
|
|
176
|
+
};
|
|
177
|
+
if (!serviceGateApprovalLive(authority, Math.floor(now / 1000)))
|
|
178
|
+
throw new EpEnvelopeError("expired", "service approval has already expired");
|
|
179
|
+
return replace(reg, binding, found, { ...found.row, nameAuthorityRevision: args.nameAuthorityRevision, serviceAuthority: authority });
|
|
180
|
+
}
|
|
181
|
+
/** The host withholds both access and renewal material until this monotonic activation CAS wins. */
|
|
182
|
+
export async function markNativeServiceActivated(reg, binding, args) {
|
|
183
|
+
const witness = parseServiceRegistrationWitness(args.witness);
|
|
184
|
+
const found = await current(reg, binding, args.expectedRevision), row = found.row, authority = row.serviceAuthority;
|
|
185
|
+
const { actor } = coordinates(reg, binding);
|
|
186
|
+
if (witness.accountPublicKey !== binding.accountPublicKey || witness.space !== binding.space
|
|
187
|
+
|| witness.endpoint !== binding.endpoint || witness.instanceId !== binding.instanceId
|
|
188
|
+
|| witness.principal.owner !== binding.owner || witness.principal.actor !== actor
|
|
189
|
+
|| witness.principal.lifecycleUid !== binding.lifecycleUid || witness.rootCredentialId !== authority.rootCredentialId
|
|
190
|
+
|| witness.approvalDigest !== authority.approvalDigest || witness.processEpoch !== row.processEpoch
|
|
191
|
+
|| witness.registrationRevision !== row.registrationRevision || witness.nameAuthorityRevision !== row.nameAuthorityRevision
|
|
192
|
+
|| !authority.registration || witness.registrationRequestId !== authority.registration.requestId || witness.registrationProof !== authority.registration.registrationProof)
|
|
193
|
+
conflict("activation witness does not match the current service registration");
|
|
194
|
+
const now = Date.now();
|
|
195
|
+
if (row.state !== "open" || !serviceGateApprovalLive(authority, Math.floor(now / 1000)))
|
|
196
|
+
throw new EpEnvelopeError("expired", "service approval no longer permits activation");
|
|
197
|
+
await rootExpiry(reg, binding, witness.rootCredentialId, now);
|
|
198
|
+
if (authority.activationIssued)
|
|
199
|
+
return found;
|
|
200
|
+
return replace(reg, binding, found, { ...row, serviceAuthority: { ...authority, activationIssued: true } });
|
|
201
|
+
}
|
|
202
|
+
/** Durable deny-new only. This is never a terminal retirement result or broker absence proof. */
|
|
203
|
+
export async function denyNativeServiceGate(reg, binding, args) {
|
|
204
|
+
assertLifecycleToken(args.operationId, "operationId");
|
|
205
|
+
const found = await current(reg, binding, args.expectedRevision);
|
|
206
|
+
const next = deniedRow(found.row, args.operationId);
|
|
207
|
+
return next === undefined ? found : replace(reg, binding, found, next);
|
|
208
|
+
}
|
|
209
|
+
/** Fence cancellation before the endpoint gate exists. Native UID cancellation remains a separate required barrier. */
|
|
210
|
+
export async function fenceUncreatedServiceGate(reg, binding, operationId) {
|
|
211
|
+
assertLifecycleToken(operationId, "operationId");
|
|
212
|
+
const { kv, key } = coordinates(reg, binding), row = uncreatedFenceRow(reg, binding, operationId);
|
|
213
|
+
await createRowByteIdempotent(kv, key, row);
|
|
214
|
+
const created = await readNativeServiceGate(reg, binding);
|
|
215
|
+
if (!created)
|
|
216
|
+
throw new EpEnvelopeError("unavailable", "created service gate is not readable; retain the operation identity");
|
|
217
|
+
return created;
|
|
218
|
+
}
|
|
219
|
+
//# sourceMappingURL=native-service-gate.js.map
|