@immediately-run/preauth-core 0.1.6 → 0.1.8
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/dist/bootConsent.d.ts +12 -1
- package/dist/bootConsent.js +29 -2
- package/dist/capabilities.d.ts +8 -0
- package/dist/capabilities.js +12 -1
- package/dist/docLayout.d.ts +35 -1
- package/dist/docLayout.js +45 -1
- package/dist/m1PreAuth.d.ts +11 -2
- package/dist/m1PreAuth.js +15 -3
- package/dist/port.d.ts +25 -0
- package/package.json +1 -1
package/dist/bootConsent.d.ts
CHANGED
|
@@ -20,6 +20,11 @@ export interface MintResult {
|
|
|
20
20
|
* requested) — the post-boot caller lifts the frame cap on this alone, even
|
|
21
21
|
* if a mount selection failed (matching its historical behavior). */
|
|
22
22
|
netFetchOk: boolean;
|
|
23
|
+
/** Whether the plain app-scoped capability grant succeeded (vacuously true when
|
|
24
|
+
* none was requested), R3-233. False when caps were requested but the store has
|
|
25
|
+
* no `grantAppCapabilities` (fail-loud, never validate-then-drop) or the write
|
|
26
|
+
* threw — the caller lifts those frame caps only on `true`. */
|
|
27
|
+
capabilitiesOk: boolean;
|
|
23
28
|
/** Successfully minted per-selection space ids (for post-boot provisioning). */
|
|
24
29
|
minted: {
|
|
25
30
|
selection: ConsentSelection;
|
|
@@ -39,4 +44,10 @@ export type MintErrorSink = (ctx: string, err: unknown) => void;
|
|
|
39
44
|
* records which §11.4 declared mount it satisfies, so a later boot re-provisions
|
|
40
45
|
* it without re-consent.
|
|
41
46
|
*/
|
|
42
|
-
export declare function mintConsentedGrants(store: MintStore, uid: string, appKey: string, selections: readonly ConsentSelection[], netFetchHosts: readonly NetFetchHost[], mintPath?: MintPath, onError?: MintErrorSink
|
|
47
|
+
export declare function mintConsentedGrants(store: MintStore, uid: string, appKey: string, selections: readonly ConsentSelection[], netFetchHosts: readonly NetFetchHost[], mintPath?: MintPath, onError?: MintErrorSink,
|
|
48
|
+
/** PLAIN app-scoped on/off capabilities to grant (R3-233) — `task:invoke`,
|
|
49
|
+
* `llm:chat`, `contribute:self`, `diagnostics:read`. NOT `net:fetch` (host-
|
|
50
|
+
* parameterized — granted via `netFetchHosts` above); the caller (`applyPreAuth`)
|
|
51
|
+
* filters host-parameterized caps out. Defaults to none, so existing callers that
|
|
52
|
+
* only mint mounts + hosts are unaffected. */
|
|
53
|
+
capabilities?: readonly string[]): Promise<MintResult>;
|
package/dist/bootConsent.js
CHANGED
|
@@ -28,7 +28,13 @@ exports.mintConsentedGrants = mintConsentedGrants;
|
|
|
28
28
|
* records which §11.4 declared mount it satisfies, so a later boot re-provisions
|
|
29
29
|
* it without re-consent.
|
|
30
30
|
*/
|
|
31
|
-
async function mintConsentedGrants(store, uid, appKey, selections, netFetchHosts, mintPath = 'interactive', onError
|
|
31
|
+
async function mintConsentedGrants(store, uid, appKey, selections, netFetchHosts, mintPath = 'interactive', onError,
|
|
32
|
+
/** PLAIN app-scoped on/off capabilities to grant (R3-233) — `task:invoke`,
|
|
33
|
+
* `llm:chat`, `contribute:self`, `diagnostics:read`. NOT `net:fetch` (host-
|
|
34
|
+
* parameterized — granted via `netFetchHosts` above); the caller (`applyPreAuth`)
|
|
35
|
+
* filters host-parameterized caps out. Defaults to none, so existing callers that
|
|
36
|
+
* only mint mounts + hosts are unaffected. */
|
|
37
|
+
capabilities = []) {
|
|
32
38
|
let ok = true;
|
|
33
39
|
let netFetchOk = true;
|
|
34
40
|
if (netFetchHosts.length > 0) {
|
|
@@ -41,6 +47,27 @@ async function mintConsentedGrants(store, uid, appKey, selections, netFetchHosts
|
|
|
41
47
|
netFetchOk = false;
|
|
42
48
|
}
|
|
43
49
|
}
|
|
50
|
+
// Plain app-scoped capability grants (R3-233). Fail LOUD if asked to mint caps but
|
|
51
|
+
// the adapter has no `grantAppCapabilities` — a silent skip would resurrect the
|
|
52
|
+
// exact validate-then-drop bug this fixes.
|
|
53
|
+
let capabilitiesOk = true;
|
|
54
|
+
if (capabilities.length > 0) {
|
|
55
|
+
if (!store.grantAppCapabilities) {
|
|
56
|
+
onError?.('capability grant unsupported by this store', new Error('grantAppCapabilities not implemented'));
|
|
57
|
+
ok = false;
|
|
58
|
+
capabilitiesOk = false;
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
try {
|
|
62
|
+
await store.grantAppCapabilities({ uid, appKey, capabilities, mintPath });
|
|
63
|
+
}
|
|
64
|
+
catch (err) {
|
|
65
|
+
onError?.('capability grant failed', err);
|
|
66
|
+
ok = false;
|
|
67
|
+
capabilitiesOk = false;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
44
71
|
const minted = [];
|
|
45
72
|
for (const sel of selections) {
|
|
46
73
|
try {
|
|
@@ -63,5 +90,5 @@ async function mintConsentedGrants(store, uid, appKey, selections, netFetchHosts
|
|
|
63
90
|
ok = false;
|
|
64
91
|
}
|
|
65
92
|
}
|
|
66
|
-
return { ok, netFetchOk, minted };
|
|
93
|
+
return { ok, netFetchOk, capabilitiesOk, minted };
|
|
67
94
|
}
|
package/dist/capabilities.d.ts
CHANGED
|
@@ -49,6 +49,14 @@ export declare function isBaseline(cap: Capability): boolean;
|
|
|
49
49
|
* per decision #1.) */
|
|
50
50
|
export declare const APP_SCOPED_CAPABILITIES: readonly Capability[];
|
|
51
51
|
export declare function isAppScoped(cap: Capability): boolean;
|
|
52
|
+
/** App-scoped caps whose durable authority is a PARAMETER SET minted on its own
|
|
53
|
+
* path — today only `net:fetch` (its granted host set, §5.11). These are granted
|
|
54
|
+
* by that path, never as a bare on/off capability: a bare `net:fetch` grant would
|
|
55
|
+
* be UNBOUNDED (every origin), so the plain-capability mint (R3-233) MUST exclude
|
|
56
|
+
* them. `task:invoke` is `parameterized` too but its bound is the app's manifest
|
|
57
|
+
* `invokes` (§5.8), not a durable grant param, so it IS a plain on/off grant. */
|
|
58
|
+
export declare const HOST_PARAMETERIZED_CAPABILITIES: readonly Capability[];
|
|
59
|
+
export declare function isHostParameterized(cap: Capability): boolean;
|
|
52
60
|
/** Compare dotted numeric versions: <0 if a<b, 0 if equal, >0 if a>b. Missing
|
|
53
61
|
* segments are treated as 0 ("1.2" === "1.2.0"); non-numeric segments as 0. */
|
|
54
62
|
export declare function compareVersions(a: string, b: string): number;
|
package/dist/capabilities.js
CHANGED
|
@@ -9,11 +9,12 @@
|
|
|
9
9
|
// with a view() projection on a channel (§8.3); actions are gated before the
|
|
10
10
|
// handler (§8.4). Parameterized capabilities additionally bound an argument set.
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
-
exports.APP_SCOPED_CAPABILITIES = exports.BASELINE_CAPABILITIES = exports.REGISTRY_VERSION = exports.CAPABILITIES = void 0;
|
|
12
|
+
exports.HOST_PARAMETERIZED_CAPABILITIES = exports.APP_SCOPED_CAPABILITIES = exports.BASELINE_CAPABILITIES = exports.REGISTRY_VERSION = exports.CAPABILITIES = void 0;
|
|
13
13
|
exports.isKnownCapability = isKnownCapability;
|
|
14
14
|
exports.tierOf = tierOf;
|
|
15
15
|
exports.isBaseline = isBaseline;
|
|
16
16
|
exports.isAppScoped = isAppScoped;
|
|
17
|
+
exports.isHostParameterized = isHostParameterized;
|
|
17
18
|
exports.compareVersions = compareVersions;
|
|
18
19
|
exports.isSupportedCapability = isSupportedCapability;
|
|
19
20
|
exports.unsupportedCapabilities = unsupportedCapabilities;
|
|
@@ -234,6 +235,16 @@ exports.APP_SCOPED_CAPABILITIES = Object.keys(exports.CAPABILITIES).filter((c) =
|
|
|
234
235
|
function isAppScoped(cap) {
|
|
235
236
|
return exports.CAPABILITIES[cap].appScoped === true;
|
|
236
237
|
}
|
|
238
|
+
/** App-scoped caps whose durable authority is a PARAMETER SET minted on its own
|
|
239
|
+
* path — today only `net:fetch` (its granted host set, §5.11). These are granted
|
|
240
|
+
* by that path, never as a bare on/off capability: a bare `net:fetch` grant would
|
|
241
|
+
* be UNBOUNDED (every origin), so the plain-capability mint (R3-233) MUST exclude
|
|
242
|
+
* them. `task:invoke` is `parameterized` too but its bound is the app's manifest
|
|
243
|
+
* `invokes` (§5.8), not a durable grant param, so it IS a plain on/off grant. */
|
|
244
|
+
exports.HOST_PARAMETERIZED_CAPABILITIES = ['net:fetch'];
|
|
245
|
+
function isHostParameterized(cap) {
|
|
246
|
+
return exports.HOST_PARAMETERIZED_CAPABILITIES.includes(cap);
|
|
247
|
+
}
|
|
237
248
|
// ── §5.11 capability version gate (threat T26) ──────────────────────────────
|
|
238
249
|
//
|
|
239
250
|
// Each capability declares the lowest registry version that knows it (`since`).
|
package/dist/docLayout.d.ts
CHANGED
|
@@ -13,6 +13,30 @@ export interface MintSentinels {
|
|
|
13
13
|
* of a delegated grant's `parentGrantId`. `::` is delimiter-safe: `appKey` uses
|
|
14
14
|
* `__` separators and a Firestore `spaceId` is alphanumeric. */
|
|
15
15
|
export declare const grantKey: (appKey: string, spaceId: string) => string;
|
|
16
|
+
/** R3-98 S4 — the principal-aware grant key `(appKey, principal, spaceId)` (design
|
|
17
|
+
* 05a §3.1/§3.2). Additive: {@link grantKey} is retained for the legacy 2-field
|
|
18
|
+
* form. `::` stays delimiter-safe — `appKey` uses `__`, a `spaceId` is alphanumeric,
|
|
19
|
+
* and a named principal is lowercase-dotted/hyphenated (CA-3), none containing `::`. */
|
|
20
|
+
export declare const grantKeyWithPrincipal: (appKey: string, principal: string, spaceId: string) => string;
|
|
21
|
+
/** A parsed `parentGrantId` — the pieces the §8.15 revoke cascade reconstructs a
|
|
22
|
+
* grant doc path from. `principal` is present only for a 3-field (S4+) key. */
|
|
23
|
+
export interface ParsedGrantKey {
|
|
24
|
+
appKey: string;
|
|
25
|
+
spaceId: string;
|
|
26
|
+
/** The named principal for a 3-field {@link grantKeyWithPrincipal} key; undefined
|
|
27
|
+
* for a legacy 2-field {@link grantKey} (the caller defaults to its grandfather
|
|
28
|
+
* sentinel). */
|
|
29
|
+
principal?: string;
|
|
30
|
+
}
|
|
31
|
+
/** R3-98 S4 — ARITY-DETECTING parse of a grant key (design 05a §3.1 step 3 /
|
|
32
|
+
* MEDIUM-6). A 3-field key is `appKey::principal::spaceId`; a legacy 2-field key is
|
|
33
|
+
* `appKey::spaceId` (principal undefined). This lets the revoke cascade keep
|
|
34
|
+
* resolving BOTH legacy and keyed `parentGrantId`s after the re-key — a positional
|
|
35
|
+
* `split('::')` would mis-assign a legacy key's `spaceId` to `principal`. A
|
|
36
|
+
* malformed key (≠2/≠3 segments) degrades to best-effort `appKey::…::spaceId`
|
|
37
|
+
* (first + last), so the cascade fails safe (child self-revokes) rather than
|
|
38
|
+
* crashing. */
|
|
39
|
+
export declare const parseGrantKey: (key: string) => ParsedGrantKey;
|
|
16
40
|
/** Durable elevated/app-scoped grants expire after 90 days WITHOUT USE; first
|
|
17
41
|
* use after expiry re-prompts. Baseline needs no grant record, so this never
|
|
18
42
|
* touches it. */
|
|
@@ -50,7 +74,7 @@ export declare const appKeyTouchFields: (s: MintSentinels) => Record<string, unk
|
|
|
50
74
|
/** `user-app-spaces/{uid}/apps/{appKey}/spaces/{spaceId}` — the durable §8.7
|
|
51
75
|
* grant doc (merge). `mintPath` defaults to `interactive`; `grantedAt`/`lastUsedAt`
|
|
52
76
|
* drive the §8.15 90-day-unused expiry. */
|
|
53
|
-
export declare const appSpaceGrantFields: (params: Pick<GrantSpaceParams, "name" | "subtree" | "mode" | "rules" | "declaredUri" | "mintPath" | "parentGrantId">, s: MintSentinels) => Record<string, unknown>;
|
|
77
|
+
export declare const appSpaceGrantFields: (params: Pick<GrantSpaceParams, "name" | "subtree" | "mode" | "rules" | "declaredUri" | "mintPath" | "parentGrantId" | "principal">, s: MintSentinels) => Record<string, unknown>;
|
|
54
78
|
/** Union net:fetch host rules by origin (incoming wins) — the "consent
|
|
55
79
|
* accumulates" merge both adapters apply before writing the host set. */
|
|
56
80
|
export declare const mergeNetFetchHosts: (existing: readonly NetFetchHost[], incoming: readonly NetFetchHost[]) => NetFetchHost[];
|
|
@@ -59,3 +83,13 @@ export declare const mergeNetFetchHosts: (existing: readonly NetFetchHost[], inc
|
|
|
59
83
|
* grant time is stamped ONCE, on first mint, and `netFetchLastUsedAt` refreshes
|
|
60
84
|
* on every (re-)consent). */
|
|
61
85
|
export declare const netFetchGrantFields: (mergedHosts: readonly NetFetchHost[], hadGrantedAt: boolean, s: MintSentinels) => Record<string, unknown>;
|
|
86
|
+
/** Union granted PLAIN app-scoped capability names (set semantics; sorted for a
|
|
87
|
+
* stable, byte-faithful document) — the "consent accumulates" merge for the
|
|
88
|
+
* R3-233 capability grant, mirroring {@link mergeNetFetchHosts}. */
|
|
89
|
+
export declare const mergeCapabilities: (existing: readonly string[], incoming: readonly string[]) => string[];
|
|
90
|
+
/** `user-app-spaces/{uid}/apps/{appKey}` — the durable granted PLAIN app-scoped
|
|
91
|
+
* capability set (merge), R3-233. Lives on the SAME appKey doc as the net:fetch
|
|
92
|
+
* grant so one read (`getAppGrantDoc`) yields both. `capabilitiesGrantedAt` is
|
|
93
|
+
* stamped ONCE (first mint); `capabilitiesLastUsedAt` refreshes on every
|
|
94
|
+
* (re-)consent — the §8.15 90-day-unused expiry clock, identical to net:fetch. */
|
|
95
|
+
export declare const appCapabilitiesGrantFields: (mergedCaps: readonly string[], hadGrantedAt: boolean, s: MintSentinels) => Record<string, unknown>;
|
package/dist/docLayout.js
CHANGED
|
@@ -16,12 +16,35 @@
|
|
|
16
16
|
// `.set()`/`.update()` is the only thing each adapter does itself. Drift is then
|
|
17
17
|
// impossible without editing a helper both consume.
|
|
18
18
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
|
-
exports.netFetchGrantFields = exports.mergeNetFetchHosts = exports.appSpaceGrantFields = exports.appKeyTouchFields = exports.appCountFields = exports.userCountFields = exports.ownerUserSpaceFields = exports.ownerMemberFields = exports.spaceDocFields = exports.appCountPath = exports.userCountPath = exports.appSpacePath = exports.appKeyPath = exports.userSpacePath = exports.memberPath = exports.spacePath = exports.defined = exports.granteeId = exports.GRANT_EXPIRY_MS = exports.grantKey = void 0;
|
|
19
|
+
exports.appCapabilitiesGrantFields = exports.mergeCapabilities = exports.netFetchGrantFields = exports.mergeNetFetchHosts = exports.appSpaceGrantFields = exports.appKeyTouchFields = exports.appCountFields = exports.userCountFields = exports.ownerUserSpaceFields = exports.ownerMemberFields = exports.spaceDocFields = exports.appCountPath = exports.userCountPath = exports.appSpacePath = exports.appKeyPath = exports.userSpacePath = exports.memberPath = exports.spacePath = exports.defined = exports.granteeId = exports.GRANT_EXPIRY_MS = exports.parseGrantKey = exports.grantKeyWithPrincipal = exports.grantKey = void 0;
|
|
20
20
|
/** Stable per-user identifier for a grant `(appKey, spaceId)`, used as the value
|
|
21
21
|
* of a delegated grant's `parentGrantId`. `::` is delimiter-safe: `appKey` uses
|
|
22
22
|
* `__` separators and a Firestore `spaceId` is alphanumeric. */
|
|
23
23
|
const grantKey = (appKey, spaceId) => `${appKey}::${spaceId}`;
|
|
24
24
|
exports.grantKey = grantKey;
|
|
25
|
+
/** R3-98 S4 — the principal-aware grant key `(appKey, principal, spaceId)` (design
|
|
26
|
+
* 05a §3.1/§3.2). Additive: {@link grantKey} is retained for the legacy 2-field
|
|
27
|
+
* form. `::` stays delimiter-safe — `appKey` uses `__`, a `spaceId` is alphanumeric,
|
|
28
|
+
* and a named principal is lowercase-dotted/hyphenated (CA-3), none containing `::`. */
|
|
29
|
+
const grantKeyWithPrincipal = (appKey, principal, spaceId) => `${appKey}::${principal}::${spaceId}`;
|
|
30
|
+
exports.grantKeyWithPrincipal = grantKeyWithPrincipal;
|
|
31
|
+
/** R3-98 S4 — ARITY-DETECTING parse of a grant key (design 05a §3.1 step 3 /
|
|
32
|
+
* MEDIUM-6). A 3-field key is `appKey::principal::spaceId`; a legacy 2-field key is
|
|
33
|
+
* `appKey::spaceId` (principal undefined). This lets the revoke cascade keep
|
|
34
|
+
* resolving BOTH legacy and keyed `parentGrantId`s after the re-key — a positional
|
|
35
|
+
* `split('::')` would mis-assign a legacy key's `spaceId` to `principal`. A
|
|
36
|
+
* malformed key (≠2/≠3 segments) degrades to best-effort `appKey::…::spaceId`
|
|
37
|
+
* (first + last), so the cascade fails safe (child self-revokes) rather than
|
|
38
|
+
* crashing. */
|
|
39
|
+
const parseGrantKey = (key) => {
|
|
40
|
+
const parts = key.split('::');
|
|
41
|
+
if (parts.length === 3) {
|
|
42
|
+
return { appKey: parts[0], principal: parts[1], spaceId: parts[2] };
|
|
43
|
+
}
|
|
44
|
+
// Legacy 2-field, or malformed → first segment is the appKey, last the spaceId.
|
|
45
|
+
return { appKey: parts[0], spaceId: parts[parts.length - 1] };
|
|
46
|
+
};
|
|
47
|
+
exports.parseGrantKey = parseGrantKey;
|
|
25
48
|
/** Durable elevated/app-scoped grants expire after 90 days WITHOUT USE; first
|
|
26
49
|
* use after expiry re-prompts. Baseline needs no grant record, so this never
|
|
27
50
|
* touches it. */
|
|
@@ -123,6 +146,11 @@ const appSpaceGrantFields = (params, s) => (0, exports.defined)({
|
|
|
123
146
|
grantedAt: s.serverTimestamp(),
|
|
124
147
|
lastUsedAt: s.serverTimestamp(),
|
|
125
148
|
name: params.name,
|
|
149
|
+
// R3-98 S3/S4 — the named principal this grant was minted under (design 05a
|
|
150
|
+
// §3.1). `defined()` omits it when absent, so a legacy/unkeyed mint writes no
|
|
151
|
+
// `principal` field and is grandfathered at the gate (both adapters stamp it
|
|
152
|
+
// identically, keeping the byte-identical-doc guarantee).
|
|
153
|
+
principal: params.principal,
|
|
126
154
|
// UI_AS_APPS_SPEC §8.7: `rules` is authoritative; `subtree`/`mode` are kept as the
|
|
127
155
|
// deprecated `rules[0]` mirror for not-yet-migrated readers. When no rule-set
|
|
128
156
|
// is given, derive a single-rule set from the legacy scope so the backend
|
|
@@ -158,3 +186,19 @@ const netFetchGrantFields = (mergedHosts, hadGrantedAt, s) => (0, exports.define
|
|
|
158
186
|
netFetchLastUsedAt: s.serverTimestamp(),
|
|
159
187
|
});
|
|
160
188
|
exports.netFetchGrantFields = netFetchGrantFields;
|
|
189
|
+
/** Union granted PLAIN app-scoped capability names (set semantics; sorted for a
|
|
190
|
+
* stable, byte-faithful document) — the "consent accumulates" merge for the
|
|
191
|
+
* R3-233 capability grant, mirroring {@link mergeNetFetchHosts}. */
|
|
192
|
+
const mergeCapabilities = (existing, incoming) => [...new Set([...existing, ...incoming])].sort();
|
|
193
|
+
exports.mergeCapabilities = mergeCapabilities;
|
|
194
|
+
/** `user-app-spaces/{uid}/apps/{appKey}` — the durable granted PLAIN app-scoped
|
|
195
|
+
* capability set (merge), R3-233. Lives on the SAME appKey doc as the net:fetch
|
|
196
|
+
* grant so one read (`getAppGrantDoc`) yields both. `capabilitiesGrantedAt` is
|
|
197
|
+
* stamped ONCE (first mint); `capabilitiesLastUsedAt` refreshes on every
|
|
198
|
+
* (re-)consent — the §8.15 90-day-unused expiry clock, identical to net:fetch. */
|
|
199
|
+
const appCapabilitiesGrantFields = (mergedCaps, hadGrantedAt, s) => (0, exports.defined)({
|
|
200
|
+
grantedCapabilities: [...mergedCaps],
|
|
201
|
+
capabilitiesGrantedAt: hadGrantedAt ? undefined : s.serverTimestamp(),
|
|
202
|
+
capabilitiesLastUsedAt: s.serverTimestamp(),
|
|
203
|
+
});
|
|
204
|
+
exports.appCapabilitiesGrantFields = appCapabilitiesGrantFields;
|
package/dist/m1PreAuth.d.ts
CHANGED
|
@@ -46,8 +46,17 @@ export interface PreAuthResult {
|
|
|
46
46
|
}
|
|
47
47
|
/**
|
|
48
48
|
* The M1 write path: validate the requested capabilities against the §8.9 target
|
|
49
|
-
* check, then — only if clean — mint the mounts
|
|
50
|
-
*
|
|
49
|
+
* check, then — only if clean — mint the mounts, net:fetch hosts, AND the plain
|
|
50
|
+
* app-scoped on/off capabilities (`task:invoke`, `llm:chat`, `contribute:self`,
|
|
51
|
+
* `diagnostics:read`) as durable grants with `policy` provenance, through the same
|
|
52
|
+
* `mintConsentedGrants` M3 uses.
|
|
53
|
+
*
|
|
54
|
+
* R3-233: the `grantable` app-scoped caps used to be VALIDATED and then silently
|
|
55
|
+
* DROPPED (only mounts + hosts were minted), so pre-authorizing `task:invoke` /
|
|
56
|
+
* `llm:chat` reported success but granted nothing and the gate kept refusing. They
|
|
57
|
+
* are now actually minted. `net:fetch` is excluded from the plain-cap mint — it is
|
|
58
|
+
* host-parameterized and granted via `netFetchHosts` (a bare grant would be
|
|
59
|
+
* unbounded).
|
|
51
60
|
*
|
|
52
61
|
* Refusal is terminal and silent of side effects: when any requested capability
|
|
53
62
|
* is broad-elevated or unknown, the function mints NOTHING and returns the
|
package/dist/m1PreAuth.js
CHANGED
|
@@ -69,8 +69,17 @@ const isPreAuthClean = (plan) => plan.refused.length === 0;
|
|
|
69
69
|
exports.isPreAuthClean = isPreAuthClean;
|
|
70
70
|
/**
|
|
71
71
|
* The M1 write path: validate the requested capabilities against the §8.9 target
|
|
72
|
-
* check, then — only if clean — mint the mounts
|
|
73
|
-
*
|
|
72
|
+
* check, then — only if clean — mint the mounts, net:fetch hosts, AND the plain
|
|
73
|
+
* app-scoped on/off capabilities (`task:invoke`, `llm:chat`, `contribute:self`,
|
|
74
|
+
* `diagnostics:read`) as durable grants with `policy` provenance, through the same
|
|
75
|
+
* `mintConsentedGrants` M3 uses.
|
|
76
|
+
*
|
|
77
|
+
* R3-233: the `grantable` app-scoped caps used to be VALIDATED and then silently
|
|
78
|
+
* DROPPED (only mounts + hosts were minted), so pre-authorizing `task:invoke` /
|
|
79
|
+
* `llm:chat` reported success but granted nothing and the gate kept refusing. They
|
|
80
|
+
* are now actually minted. `net:fetch` is excluded from the plain-cap mint — it is
|
|
81
|
+
* host-parameterized and granted via `netFetchHosts` (a bare grant would be
|
|
82
|
+
* unbounded).
|
|
74
83
|
*
|
|
75
84
|
* Refusal is terminal and silent of side effects: when any requested capability
|
|
76
85
|
* is broad-elevated or unknown, the function mints NOTHING and returns the
|
|
@@ -81,6 +90,9 @@ async function applyPreAuth(store, uid, appKey, request, onError) {
|
|
|
81
90
|
if (!(0, exports.isPreAuthClean)(plan)) {
|
|
82
91
|
return { ok: false, refused: plan.refused };
|
|
83
92
|
}
|
|
84
|
-
|
|
93
|
+
// The plain on/off caps to mint: every grantable cap EXCEPT the host-parameterized
|
|
94
|
+
// ones (net:fetch), which are minted as their host set via `netFetchHosts`.
|
|
95
|
+
const plainCaps = plan.grantable.filter((c) => !(0, capabilities_1.isHostParameterized)(c));
|
|
96
|
+
const mint = await (0, bootConsent_1.mintConsentedGrants)(store, uid, appKey, request.mounts, request.netFetchHosts, 'policy', onError, plainCaps);
|
|
85
97
|
return { ok: mint.ok, refused: [], mint };
|
|
86
98
|
}
|
package/dist/port.d.ts
CHANGED
|
@@ -60,6 +60,11 @@ export interface GrantSpaceParams {
|
|
|
60
60
|
mintPath?: MintPath;
|
|
61
61
|
/** §8.15 — parent `grantKey` for an M2 `delegated` grant. */
|
|
62
62
|
parentGrantId?: string;
|
|
63
|
+
/** R3-98 S3/S4 — the **named principal** this grant is minted under (design 05a
|
|
64
|
+
* §3.1). Written as the `principal` field so the mount-admission gate re-checks
|
|
65
|
+
* it (a grant fires only under the principal it was minted with). Optional +
|
|
66
|
+
* additive: omitted ⇒ no field ⇒ a legacy/grandfathered grant. */
|
|
67
|
+
principal?: string;
|
|
63
68
|
}
|
|
64
69
|
/** Parameters for `MintStore.grantNetFetchHosts` — the per-(user, app) granted
|
|
65
70
|
* host set, the grant half of the `manifest ∩ grant` net:fetch allowlist. */
|
|
@@ -68,6 +73,19 @@ export interface GrantNetFetchParams {
|
|
|
68
73
|
appKey: string;
|
|
69
74
|
hosts: readonly NetFetchHost[];
|
|
70
75
|
}
|
|
76
|
+
/** Parameters for `MintStore.grantAppCapabilities` — the durable §8.7 grant of a
|
|
77
|
+
* set of PLAIN (on/off) app-scoped elevated capabilities for one (user, app):
|
|
78
|
+
* `task:invoke`, `llm:chat`, `contribute:self`, `diagnostics:read` (R3-233). NOT
|
|
79
|
+
* `net:fetch` — that is host-parameterized and granted via {@link GrantNetFetchParams};
|
|
80
|
+
* a bare `net:fetch` grant would be unbounded. The set unions into the app's
|
|
81
|
+
* granted-capability set (consent accumulates), mirroring net:fetch hosts. */
|
|
82
|
+
export interface GrantAppCapabilitiesParams {
|
|
83
|
+
uid: string;
|
|
84
|
+
appKey: string;
|
|
85
|
+
capabilities: readonly string[];
|
|
86
|
+
/** §8.15 provenance; defaults to `interactive` when omitted. */
|
|
87
|
+
mintPath?: MintPath;
|
|
88
|
+
}
|
|
71
89
|
/**
|
|
72
90
|
* The mint port: exactly the methods `mintConsentedGrants` calls. Every backend
|
|
73
91
|
* (browser Firestore, admin Firestore, the in-memory test double) implements
|
|
@@ -81,4 +99,11 @@ export interface MintStore {
|
|
|
81
99
|
grantSpaceToApp(params: GrantSpaceParams): Promise<void>;
|
|
82
100
|
/** Union the given net:fetch hosts into the app's consented host set. */
|
|
83
101
|
grantNetFetchHosts(params: GrantNetFetchParams): Promise<void>;
|
|
102
|
+
/** Union the given PLAIN app-scoped capabilities into the app's granted set
|
|
103
|
+
* (R3-233). **Optional** so existing {@link MintStore} implementers (the backend
|
|
104
|
+
* `AdminMintStore`) keep compiling; when a caller asks to mint plain caps and an
|
|
105
|
+
* adapter does not implement this, `mintConsentedGrants` fails LOUD (reports
|
|
106
|
+
* `capabilitiesOk:false`) rather than silently dropping them — the exact
|
|
107
|
+
* validate-then-drop bug R3-233 fixes. */
|
|
108
|
+
grantAppCapabilities?(params: GrantAppCapabilitiesParams): Promise<void>;
|
|
84
109
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@immediately-run/preauth-core",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.8",
|
|
4
4
|
"description": "The shared §8.9 pre-auth target check + the single grant-mint path (mintConsentedGrants) + the capability vocabulary + the byte-faithful grant/space/net-fetch document layout. Consumed by site-main (browser Firestore) and the backend (admin Firestore) so there is ONE gate, ONE mint path, ONE wire layout.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"repository": {
|