instar 1.3.441 → 1.3.443
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/commands/server.d.ts.map +1 -1
- package/dist/commands/server.js +58 -2
- package/dist/commands/server.js.map +1 -1
- package/dist/coordination/MandateStore.d.ts +30 -2
- package/dist/coordination/MandateStore.d.ts.map +1 -1
- package/dist/coordination/MandateStore.js +89 -3
- package/dist/coordination/MandateStore.js.map +1 -1
- package/dist/coordination/types.d.ts +35 -0
- package/dist/coordination/types.d.ts.map +1 -1
- package/dist/permissions/LlmIntentClassifier.d.ts +77 -0
- package/dist/permissions/LlmIntentClassifier.d.ts.map +1 -0
- package/dist/permissions/LlmIntentClassifier.js +225 -0
- package/dist/permissions/LlmIntentClassifier.js.map +1 -0
- package/dist/permissions/MandateBackedGrantStore.d.ts +40 -0
- package/dist/permissions/MandateBackedGrantStore.d.ts.map +1 -0
- package/dist/permissions/MandateBackedGrantStore.js +72 -0
- package/dist/permissions/MandateBackedGrantStore.js.map +1 -0
- package/dist/permissions/index.d.ts +2 -0
- package/dist/permissions/index.d.ts.map +1 -1
- package/dist/permissions/index.js +2 -0
- package/dist/permissions/index.js.map +1 -1
- package/dist/server/CapabilityIndex.d.ts.map +1 -1
- package/dist/server/CapabilityIndex.js +1 -0
- package/dist/server/CapabilityIndex.js.map +1 -1
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/server/routes.js +57 -0
- package/dist/server/routes.js.map +1 -1
- package/package.json +1 -1
- package/src/data/builtin-manifest.json +46 -46
- package/upgrades/1.3.442.md +28 -0
- package/upgrades/1.3.443.md +25 -0
- package/upgrades/side-effects/mandate-user-grants.md +78 -0
- package/upgrades/side-effects/slack-llm-intent-classifier.md +67 -0
|
@@ -16,8 +16,18 @@
|
|
|
16
16
|
* (threat-model T12, out of scope) — the proof stops a forged/edited AUTHORED mandate;
|
|
17
17
|
* local-file tamper of server-managed state is the baseline trust boundary.
|
|
18
18
|
*/
|
|
19
|
-
import type { Authority, CoordinationMandate } from './types.js';
|
|
20
|
-
/**
|
|
19
|
+
import type { Authority, CoordinationMandate, UserAuthorityGrant } from './types.js';
|
|
20
|
+
/**
|
|
21
|
+
* Canonical bytes the authProof covers — the AUTHORED fields only (proof + revoked excluded).
|
|
22
|
+
*
|
|
23
|
+
* BACKWARD-COMPAT (non-negotiable): `grants` is appended to the byte sequence ONLY
|
|
24
|
+
* when it is present AND non-empty. A mandate with no grants (the pre-extension shape,
|
|
25
|
+
* `grants` undefined OR `[]`) therefore canonicalizes to the EXACT same bytes as before
|
|
26
|
+
* this extension existed — so every previously-signed mandate's stored `authProof`
|
|
27
|
+
* keeps verifying. When grants ARE present, they extend the canonical bytes (each grant
|
|
28
|
+
* as a field-ordered tuple), so the proof covers them and a grant cannot be forged or
|
|
29
|
+
* added without re-signing through the PIN-gated path.
|
|
30
|
+
*/
|
|
21
31
|
export declare function canonicalMandate(m: Omit<CoordinationMandate, 'authProof' | 'revoked'>): string;
|
|
22
32
|
export interface MandateStoreDeps {
|
|
23
33
|
/** Absolute path to the mandates JSON file. */
|
|
@@ -38,6 +48,8 @@ export interface IssueMandateInput {
|
|
|
38
48
|
expiresAt: string;
|
|
39
49
|
id?: string;
|
|
40
50
|
createdAt?: string;
|
|
51
|
+
/** Optional user→agent authority grants signed into the mandate at issuance. */
|
|
52
|
+
grants?: UserAuthorityGrant[];
|
|
41
53
|
}
|
|
42
54
|
export declare class MandateStore {
|
|
43
55
|
private readonly d;
|
|
@@ -50,6 +62,22 @@ export declare class MandateStore {
|
|
|
50
62
|
* authored bytes, persists, returns the mandate.
|
|
51
63
|
*/
|
|
52
64
|
issue(input: IssueMandateInput): CoordinationMandate;
|
|
65
|
+
/**
|
|
66
|
+
* Add user→agent authority grant(s) to an existing mandate and RE-SIGN it so the
|
|
67
|
+
* `authProof` covers the new grants (the only path that can sign a grant in — the
|
|
68
|
+
* PIN-gated route is the sole caller). Grants are validated and clamped against the
|
|
69
|
+
* mandate's own expiry: a grant's `expiresAt` may never exceed the mandate's.
|
|
70
|
+
*
|
|
71
|
+
* Returns `{ ok: false }` with a reason for a missing/revoked mandate or an invalid
|
|
72
|
+
* grant; `{ ok: true, mandate }` on success. Never partially applies.
|
|
73
|
+
*/
|
|
74
|
+
addGrants(id: string, grants: UserAuthorityGrant[]): {
|
|
75
|
+
ok: true;
|
|
76
|
+
mandate: CoordinationMandate;
|
|
77
|
+
} | {
|
|
78
|
+
ok: false;
|
|
79
|
+
reason: string;
|
|
80
|
+
};
|
|
53
81
|
/** Verify a mandate's authorship proof (T1/T2). */
|
|
54
82
|
verifyAuthorship(m: CoordinationMandate): boolean;
|
|
55
83
|
get(id: string): CoordinationMandate | undefined;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MandateStore.d.ts","sourceRoot":"","sources":["../../src/coordination/MandateStore.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAIH,OAAO,KAAK,EAAE,SAAS,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"MandateStore.d.ts","sourceRoot":"","sources":["../../src/coordination/MandateStore.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAIH,OAAO,KAAK,EAAE,SAAS,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAYrF;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,IAAI,CAAC,mBAAmB,EAAE,WAAW,GAAG,SAAS,CAAC,GAAG,MAAM,CAW9F;AAED,MAAM,WAAW,gBAAgB;IAC/B,+CAA+C;IAC/C,QAAQ,EAAE,MAAM,CAAC;IACjB,sFAAsF;IACtF,IAAI,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,MAAM,CAAC;IACpC,0DAA0D;IAC1D,SAAS,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC;IACzD,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,gFAAgF;IAChF,KAAK,CAAC,EAAE,MAAM,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACzB,WAAW,EAAE,SAAS,EAAE,CAAC;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gFAAgF;IAChF,MAAM,CAAC,EAAE,kBAAkB,EAAE,CAAC;CAC/B;AAED,qBAAa,YAAY;IACvB,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAmB;gBACzB,IAAI,EAAE,gBAAgB;IAIlC,OAAO,CAAC,MAAM;IAId,OAAO,CAAC,OAAO;IAOf,OAAO,CAAC,QAAQ;IAKhB;;;OAGG;IACH,KAAK,CAAC,KAAK,EAAE,iBAAiB,GAAG,mBAAmB;IAyCpD;;;;;;;;OAQG;IACH,SAAS,CACP,EAAE,EAAE,MAAM,EACV,MAAM,EAAE,kBAAkB,EAAE,GAC3B;QAAE,EAAE,EAAE,IAAI,CAAC;QAAC,OAAO,EAAE,mBAAmB,CAAA;KAAE,GAAG;QAAE,EAAE,EAAE,KAAK,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE;IAuC7E,mDAAmD;IACnD,gBAAgB,CAAC,CAAC,EAAE,mBAAmB,GAAG,OAAO;IAKjD,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,mBAAmB,GAAG,SAAS;IAIhD,IAAI,IAAI,mBAAmB,EAAE;IAI7B,0FAA0F;IAC1F,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,mBAAmB,GAAG,SAAS;CAUpE"}
|
|
@@ -29,13 +29,28 @@ function stableStringify(value) {
|
|
|
29
29
|
const keys = Object.keys(obj).sort();
|
|
30
30
|
return '{' + keys.map((k) => JSON.stringify(k) + ':' + stableStringify(obj[k])).join(',') + '}';
|
|
31
31
|
}
|
|
32
|
-
/**
|
|
32
|
+
/**
|
|
33
|
+
* Canonical bytes the authProof covers — the AUTHORED fields only (proof + revoked excluded).
|
|
34
|
+
*
|
|
35
|
+
* BACKWARD-COMPAT (non-negotiable): `grants` is appended to the byte sequence ONLY
|
|
36
|
+
* when it is present AND non-empty. A mandate with no grants (the pre-extension shape,
|
|
37
|
+
* `grants` undefined OR `[]`) therefore canonicalizes to the EXACT same bytes as before
|
|
38
|
+
* this extension existed — so every previously-signed mandate's stored `authProof`
|
|
39
|
+
* keeps verifying. When grants ARE present, they extend the canonical bytes (each grant
|
|
40
|
+
* as a field-ordered tuple), so the proof covers them and a grant cannot be forged or
|
|
41
|
+
* added without re-signing through the PIN-gated path.
|
|
42
|
+
*/
|
|
33
43
|
export function canonicalMandate(m) {
|
|
34
|
-
|
|
44
|
+
const base = [
|
|
35
45
|
m.id, m.scope, m.agents,
|
|
36
46
|
m.authorities.map((a) => [a.action, a.bounds, a.requiresCondition ?? '']),
|
|
37
47
|
m.author, m.createdAt, m.expiresAt,
|
|
38
|
-
]
|
|
48
|
+
];
|
|
49
|
+
// Append-only-when-non-empty: no grants → identical bytes to a pre-extension mandate.
|
|
50
|
+
if (Array.isArray(m.grants) && m.grants.length > 0) {
|
|
51
|
+
base.push(m.grants.map((g) => [g.floorAction, g.grantedTo, g.authorizedBy, g.expiresAt, g.bounds ?? {}]));
|
|
52
|
+
}
|
|
53
|
+
return stableStringify(base);
|
|
39
54
|
}
|
|
40
55
|
export class MandateStore {
|
|
41
56
|
d;
|
|
@@ -65,9 +80,32 @@ export class MandateStore {
|
|
|
65
80
|
issue(input) {
|
|
66
81
|
const id = input.id ?? (this.d.genId ? this.d.genId() : `mandate-${Math.random().toString(36).slice(2, 10)}`);
|
|
67
82
|
const createdAt = input.createdAt ?? this.nowIso();
|
|
83
|
+
// Defense-in-depth (second-pass hardening): grants issued WITH the mandate are
|
|
84
|
+
// validated + expiry-clamped the SAME way addGrants() validates them, so a grant
|
|
85
|
+
// can never outlive the mandate that carries it regardless of caller. The HTTP
|
|
86
|
+
// issue route drops grants today; this keeps the library contract uniformly safe
|
|
87
|
+
// for any future caller, not relying solely on the query-side clamp.
|
|
88
|
+
if (input.grants && input.grants.length > 0) {
|
|
89
|
+
const mandateExpiryMs = Date.parse(input.expiresAt);
|
|
90
|
+
for (const g of input.grants) {
|
|
91
|
+
if (!g || typeof g.floorAction !== 'string' || !g.floorAction
|
|
92
|
+
|| typeof g.grantedTo !== 'string' || !g.grantedTo
|
|
93
|
+
|| typeof g.authorizedBy !== 'string' || !g.authorizedBy
|
|
94
|
+
|| typeof g.expiresAt !== 'string' || isNaN(Date.parse(g.expiresAt))) {
|
|
95
|
+
throw new Error('each grant needs a non-empty floorAction, grantedTo, authorizedBy, and a valid ISO expiresAt');
|
|
96
|
+
}
|
|
97
|
+
if (Date.parse(g.expiresAt) > mandateExpiryMs) {
|
|
98
|
+
throw new Error(`grant expiresAt (${g.expiresAt}) must be <= mandate expiresAt (${input.expiresAt})`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
68
102
|
const authored = {
|
|
69
103
|
id, scope: input.scope, agents: input.agents, authorities: input.authorities,
|
|
70
104
|
author: input.author, createdAt, expiresAt: input.expiresAt,
|
|
105
|
+
// Append-only-when-non-empty: never set `grants` for a no-grant issuance, so the
|
|
106
|
+
// canonical bytes (and thus the proof) are byte-for-byte identical to before this
|
|
107
|
+
// extension existed.
|
|
108
|
+
...(input.grants && input.grants.length > 0 ? { grants: input.grants } : {}),
|
|
71
109
|
};
|
|
72
110
|
const mandate = {
|
|
73
111
|
...authored,
|
|
@@ -79,6 +117,54 @@ export class MandateStore {
|
|
|
79
117
|
this.writeAll(all);
|
|
80
118
|
return mandate;
|
|
81
119
|
}
|
|
120
|
+
/**
|
|
121
|
+
* Add user→agent authority grant(s) to an existing mandate and RE-SIGN it so the
|
|
122
|
+
* `authProof` covers the new grants (the only path that can sign a grant in — the
|
|
123
|
+
* PIN-gated route is the sole caller). Grants are validated and clamped against the
|
|
124
|
+
* mandate's own expiry: a grant's `expiresAt` may never exceed the mandate's.
|
|
125
|
+
*
|
|
126
|
+
* Returns `{ ok: false }` with a reason for a missing/revoked mandate or an invalid
|
|
127
|
+
* grant; `{ ok: true, mandate }` on success. Never partially applies.
|
|
128
|
+
*/
|
|
129
|
+
addGrants(id, grants) {
|
|
130
|
+
const all = this.readAll();
|
|
131
|
+
const idx = all.findIndex((m) => m.id === id);
|
|
132
|
+
if (idx < 0)
|
|
133
|
+
return { ok: false, reason: 'mandate not found' };
|
|
134
|
+
const existing = all[idx];
|
|
135
|
+
if (existing.revoked)
|
|
136
|
+
return { ok: false, reason: 'mandate is revoked' };
|
|
137
|
+
if (!Array.isArray(grants) || grants.length === 0)
|
|
138
|
+
return { ok: false, reason: 'no grants provided' };
|
|
139
|
+
const mandateExpiry = Date.parse(existing.expiresAt);
|
|
140
|
+
for (const g of grants) {
|
|
141
|
+
if (!g || typeof g.floorAction !== 'string' || !g.floorAction
|
|
142
|
+
|| typeof g.grantedTo !== 'string' || !g.grantedTo
|
|
143
|
+
|| typeof g.authorizedBy !== 'string' || !g.authorizedBy
|
|
144
|
+
|| typeof g.expiresAt !== 'string' || isNaN(Date.parse(g.expiresAt))) {
|
|
145
|
+
return { ok: false, reason: 'each grant needs non-empty floorAction, grantedTo, authorizedBy, and a valid ISO expiresAt' };
|
|
146
|
+
}
|
|
147
|
+
// A grant can NEVER outlive the delegation that carries it.
|
|
148
|
+
if (Date.parse(g.expiresAt) > mandateExpiry) {
|
|
149
|
+
return { ok: false, reason: `grant expiresAt (${g.expiresAt}) must be <= mandate expiresAt (${existing.expiresAt})` };
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
const mergedGrants = [...(existing.grants ?? []), ...grants];
|
|
153
|
+
const authored = {
|
|
154
|
+
id: existing.id, scope: existing.scope, agents: existing.agents,
|
|
155
|
+
authorities: existing.authorities, author: existing.author,
|
|
156
|
+
createdAt: existing.createdAt, expiresAt: existing.expiresAt,
|
|
157
|
+
grants: mergedGrants,
|
|
158
|
+
};
|
|
159
|
+
const resigned = {
|
|
160
|
+
...authored,
|
|
161
|
+
revoked: existing.revoked,
|
|
162
|
+
authProof: this.d.sign(canonicalMandate(authored)),
|
|
163
|
+
};
|
|
164
|
+
all[idx] = resigned;
|
|
165
|
+
this.writeAll(all);
|
|
166
|
+
return { ok: true, mandate: resigned };
|
|
167
|
+
}
|
|
82
168
|
/** Verify a mandate's authorship proof (T1/T2). */
|
|
83
169
|
verifyAuthorship(m) {
|
|
84
170
|
const { authProof, revoked, ...authored } = m;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MandateStore.js","sourceRoot":"","sources":["../../src/coordination/MandateStore.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAG7B;0BAC0B;AAC1B,SAAS,eAAe,CAAC,KAAc;IACrC,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;IAC9E,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,GAAG,GAAG,KAAK,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,GAAG,CAAC;IAClF,MAAM,GAAG,GAAG,KAAgC,CAAC;IAC7C,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;IACrC,OAAO,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,GAAG,GAAG,eAAe,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,GAAG,CAAC;AAClG,CAAC;AAED
|
|
1
|
+
{"version":3,"file":"MandateStore.js","sourceRoot":"","sources":["../../src/coordination/MandateStore.ts"],"names":[],"mappings":"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"}
|
|
@@ -24,6 +24,34 @@ export interface Authority {
|
|
|
24
24
|
* never from an agent's assertion. */
|
|
25
25
|
requiresCondition?: string;
|
|
26
26
|
}
|
|
27
|
+
/**
|
|
28
|
+
* A user→agent authority grant carried INSIDE a signed mandate. This is the
|
|
29
|
+
* extension that lets the human (the mandate author) delegate a Slack FLOOR
|
|
30
|
+
* action (money / prod-deploy / credentials / destructive / external / grant)
|
|
31
|
+
* to a SPECIFIC verified Slack user for a bounded time — the same deny-by-default,
|
|
32
|
+
* signed, expiring, revocable model the mandate already enforces for A2A authority.
|
|
33
|
+
*
|
|
34
|
+
* The grant is covered by the mandate's `authProof` (it is appended to the canonical
|
|
35
|
+
* byte sequence when present), so an agent cannot mint or widen a grant: only the
|
|
36
|
+
* PIN-gated issuance path can sign one in.
|
|
37
|
+
*
|
|
38
|
+
* `expiresAt` is independent of (and MUST be <= ) the mandate's own `expiresAt` —
|
|
39
|
+
* the grant can never outlive the delegation that carries it. The store-side query
|
|
40
|
+
* clamps to the mandate expiry, so a grant is void the moment EITHER clock passes.
|
|
41
|
+
*/
|
|
42
|
+
export interface UserAuthorityGrant {
|
|
43
|
+
/** The Slack FLOOR action this grant authorizes (e.g. 'prod-deploy'). Matched
|
|
44
|
+
* against the gate's `scope` (the floorAction) on lookup. */
|
|
45
|
+
floorAction: string;
|
|
46
|
+
/** The VERIFIED Slack user id (U…) this grant is for — never a content name. */
|
|
47
|
+
grantedTo: string;
|
|
48
|
+
/** Who authorized it (the human author / operator). Requester ≠ authorizer. */
|
|
49
|
+
authorizedBy: string;
|
|
50
|
+
/** ISO timestamp after which the grant is void. MUST be <= the mandate's expiresAt. */
|
|
51
|
+
expiresAt: string;
|
|
52
|
+
/** Optional bounds carried for audit/future enforcement (advisory at the floor). */
|
|
53
|
+
bounds?: Record<string, unknown>;
|
|
54
|
+
}
|
|
27
55
|
/** The human-authored delegation. `authProof` covers all other fields. */
|
|
28
56
|
export interface CoordinationMandate {
|
|
29
57
|
id: string;
|
|
@@ -41,6 +69,13 @@ export interface CoordinationMandate {
|
|
|
41
69
|
at: string;
|
|
42
70
|
reason: string;
|
|
43
71
|
} | null;
|
|
72
|
+
/**
|
|
73
|
+
* Optional user→agent authority grants (the Slack-floor delegation extension).
|
|
74
|
+
* Covered by `authProof` ONLY when present and non-empty — a mandate with no
|
|
75
|
+
* grants canonicalizes byte-for-byte identically to a pre-extension mandate, so
|
|
76
|
+
* existing signed mandates keep verifying (backward-compat is non-negotiable).
|
|
77
|
+
*/
|
|
78
|
+
grants?: UserAuthorityGrant[];
|
|
44
79
|
/** Authorship proof — an HMAC over the canonical (proof-excluded) mandate bytes,
|
|
45
80
|
* produced ONLY by the PIN-gated issuance path. An agent cannot mint or widen its
|
|
46
81
|
* own mandate without it; a forged/edited mandate fails verification. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/coordination/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,qDAAqD;AACrD,MAAM,WAAW,SAAS;IACxB,mHAAmH;IACnH,MAAM,EAAE,MAAM,CAAC;IACf,8EAA8E;IAC9E,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC;;2CAEuC;IACvC,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,0EAA0E;AAC1E,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,MAAM,CAAC;IACX,iCAAiC;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,mEAAmE;IACnE,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACzB,WAAW,EAAE,SAAS,EAAE,CAAC;IACzB,0DAA0D;IAC1D,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,iDAAiD;IACjD,OAAO,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAC/C;;8EAE0E;IAC1E,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,MAAM,eAAe,GAAG,OAAO,GAAG,MAAM,CAAC;AAE/C,uEAAuE;AACvE,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,uEAAuE;IACvE,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,0EAA0E;AAC1E,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,eAAe,CAAC;IAC1B,yDAAyD;IACzD,MAAM,EAAE,MAAM,CAAC;IACf,iFAAiF;IACjF,eAAe,EAAE,OAAO,GAAG,IAAI,CAAC;IAChC,qFAAqF;IACrF,QAAQ,EAAE,MAAM,CAAC;IACjB,6DAA6D;IAC7D,IAAI,EAAE,MAAM,CAAC;CACd"}
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/coordination/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,qDAAqD;AACrD,MAAM,WAAW,SAAS;IACxB,mHAAmH;IACnH,MAAM,EAAE,MAAM,CAAC;IACf,8EAA8E;IAC9E,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC;;2CAEuC;IACvC,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,WAAW,kBAAkB;IACjC;kEAC8D;IAC9D,WAAW,EAAE,MAAM,CAAC;IACpB,gFAAgF;IAChF,SAAS,EAAE,MAAM,CAAC;IAClB,+EAA+E;IAC/E,YAAY,EAAE,MAAM,CAAC;IACrB,uFAAuF;IACvF,SAAS,EAAE,MAAM,CAAC;IAClB,oFAAoF;IACpF,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAClC;AAED,0EAA0E;AAC1E,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,MAAM,CAAC;IACX,iCAAiC;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,mEAAmE;IACnE,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACzB,WAAW,EAAE,SAAS,EAAE,CAAC;IACzB,0DAA0D;IAC1D,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,iDAAiD;IACjD,OAAO,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAC/C;;;;;OAKG;IACH,MAAM,CAAC,EAAE,kBAAkB,EAAE,CAAC;IAC9B;;8EAE0E;IAC1E,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,MAAM,eAAe,GAAG,OAAO,GAAG,MAAM,CAAC;AAE/C,uEAAuE;AACvE,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,uEAAuE;IACvE,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,0EAA0E;AAC1E,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,eAAe,CAAC;IAC1B,yDAAyD;IACzD,MAAM,EAAE,MAAM,CAAC;IACf,iFAAiF;IACjF,eAAe,EAAE,OAAO,GAAG,IAAI,CAAC;IAChC,qFAAqF;IACrF,QAAQ,EAAE,MAAM,CAAC;IACjB,6DAA6D;IAC7D,IAAI,EAAE,MAAM,CAAC;CACd"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LlmIntentClassifier — the JUDGMENT-BAND intent classifier (Slack permission gate,
|
|
3
|
+
* Pillar 2, §6.5–6.6 / §6.6 "Layer 1 INTENT + SENSITIVITY").
|
|
4
|
+
*
|
|
5
|
+
* It implements the SAME `IntentClassifier` interface as `HeuristicIntentClassifier`
|
|
6
|
+
* and is interchangeable with it at the gate's `classifier` slot. The difference is
|
|
7
|
+
* the judgment band ABOVE the floor: an LLM call refines the sensitivity tier,
|
|
8
|
+
* directedness, and a non-floor action label more accurately than keyword matching.
|
|
9
|
+
*
|
|
10
|
+
* ── Two invariants this class is built around ──────────────────────────────────
|
|
11
|
+
*
|
|
12
|
+
* 1. THE FLOOR STAYS DETERMINISTIC. This class NEVER lets the LLM decide that a
|
|
13
|
+
* floor action ISN'T one, and it never lets the LLM downgrade a heuristic-detected
|
|
14
|
+
* floor candidate. It runs the deterministic `HeuristicIntentClassifier` FIRST;
|
|
15
|
+
* when the heuristic flags a real floor action (a `floorAction`) OR the
|
|
16
|
+
* ambiguous-deploy low-confidence case (action 'ambiguous', tier 4, low
|
|
17
|
+
* confidence → the gate routes to CLARIFY), that verdict is returned AS-IS and the
|
|
18
|
+
* LLM is never consulted. The LLM can only refine the NON-floor judgment band.
|
|
19
|
+
* (Floor enumeration + role authority remain in RolePolicy / SlackPermissionGate;
|
|
20
|
+
* this class only feeds them an intent and must not be the thing that misses a
|
|
21
|
+
* floor action.)
|
|
22
|
+
*
|
|
23
|
+
* 2. NO SILENT DEGRADATION TO A BRITTLE — OR WIDER — FALLBACK
|
|
24
|
+
* (docs/specs/no-silent-degradation-to-brittle-fallback.md,
|
|
25
|
+
* docs/signal-vs-authority.md). On ANY LLM failure (no provider, throw, timeout,
|
|
26
|
+
* empty/unparseable response, or a response that would WIDEN access beyond the
|
|
27
|
+
* deterministic floor read) we fall back to the `HeuristicIntentClassifier`
|
|
28
|
+
* result — which itself routes ambiguity to a tier-4 low-confidence 'ambiguous'
|
|
29
|
+
* (→ the gate CLARIFIES, a safe non-allow). A classification failure must NEVER
|
|
30
|
+
* return a confident low-tier "allow"-shaped intent on what might be sensitive.
|
|
31
|
+
* The fallback is therefore at-least-as-conservative as the deterministic floor.
|
|
32
|
+
*
|
|
33
|
+
* The LLM is reached through instar's internal `IntelligenceProvider` (the same
|
|
34
|
+
* injected-provider pattern as `TopicIntentExtractor` / `MessagingToneGate`), NOT a
|
|
35
|
+
* raw API call. A `fast` model tier is used, and the call is marked `gating: true`
|
|
36
|
+
* so the IntelligenceRouter will provider-swap on failure before the error
|
|
37
|
+
* propagates (and we then fail closed to the heuristic).
|
|
38
|
+
*
|
|
39
|
+
* Design: docs/specs/SLACK-ORG-INTEGRATION-SPEC.md §6.5–6.6.
|
|
40
|
+
*/
|
|
41
|
+
import type { IntelligenceProvider } from '../core/types.js';
|
|
42
|
+
import type { RequestIntent } from './types.js';
|
|
43
|
+
import { type IntentClassifier } from './IntentClassifier.js';
|
|
44
|
+
/** Observability hook reasons for why a classification fell back to the heuristic. */
|
|
45
|
+
export type LlmIntentDegradeReason = 'no-intelligence' | 'error' | 'unparseable' | 'floor-deterministic';
|
|
46
|
+
export interface LlmIntentClassifierDeps {
|
|
47
|
+
/**
|
|
48
|
+
* The internal LLM provider (injected — never a direct framework import). When
|
|
49
|
+
* absent, every classification degrades to the heuristic (fail-closed).
|
|
50
|
+
*/
|
|
51
|
+
intelligence?: IntelligenceProvider;
|
|
52
|
+
/**
|
|
53
|
+
* The deterministic floor + fallback classifier. Defaults to a fresh
|
|
54
|
+
* `HeuristicIntentClassifier`. Injectable for tests.
|
|
55
|
+
*/
|
|
56
|
+
heuristic?: IntentClassifier;
|
|
57
|
+
/** Per-call timeout for the LLM (ms). Default 8000. */
|
|
58
|
+
timeoutMs?: number;
|
|
59
|
+
/**
|
|
60
|
+
* Optional observability hook — fires on every degrade-to-heuristic path with the
|
|
61
|
+
* reason. Best-effort: it never affects the returned verdict (which is always the
|
|
62
|
+
* safe heuristic result on degrade).
|
|
63
|
+
*/
|
|
64
|
+
onDegrade?: (reason: LlmIntentDegradeReason) => void;
|
|
65
|
+
}
|
|
66
|
+
export declare class LlmIntentClassifier implements IntentClassifier {
|
|
67
|
+
private readonly intelligence?;
|
|
68
|
+
private readonly heuristic;
|
|
69
|
+
private readonly timeoutMs;
|
|
70
|
+
private readonly onDegrade?;
|
|
71
|
+
constructor(deps?: LlmIntentClassifierDeps);
|
|
72
|
+
classify(text: string, ctx: {
|
|
73
|
+
directed: boolean;
|
|
74
|
+
}): Promise<RequestIntent>;
|
|
75
|
+
private report;
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=LlmIntentClassifier.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LlmIntentClassifier.d.ts","sourceRoot":"","sources":["../../src/permissions/LlmIntentClassifier.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AAEH,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAC7D,OAAO,KAAK,EAAE,aAAa,EAAmB,MAAM,YAAY,CAAC;AACjE,OAAO,EAA6B,KAAK,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAEzF,sFAAsF;AACtF,MAAM,MAAM,sBAAsB,GAC9B,iBAAiB,GACjB,OAAO,GACP,aAAa,GACb,qBAAqB,CAAC;AAE1B,MAAM,WAAW,uBAAuB;IACtC;;;OAGG;IACH,YAAY,CAAC,EAAE,oBAAoB,CAAC;IACpC;;;OAGG;IACH,SAAS,CAAC,EAAE,gBAAgB,CAAC;IAC7B,uDAAuD;IACvD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;OAIG;IACH,SAAS,CAAC,EAAE,CAAC,MAAM,EAAE,sBAAsB,KAAK,IAAI,CAAC;CACtD;AAYD,qBAAa,mBAAoB,YAAW,gBAAgB;IAC1D,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAuB;IACrD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAmB;IAC7C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAC,CAA2C;gBAE1D,IAAI,GAAE,uBAA4B;IAOxC,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE;QAAE,QAAQ,EAAE,OAAO,CAAA;KAAE,GAAG,OAAO,CAAC,aAAa,CAAC;IA0DhF,OAAO,CAAC,MAAM;CAOf"}
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LlmIntentClassifier — the JUDGMENT-BAND intent classifier (Slack permission gate,
|
|
3
|
+
* Pillar 2, §6.5–6.6 / §6.6 "Layer 1 INTENT + SENSITIVITY").
|
|
4
|
+
*
|
|
5
|
+
* It implements the SAME `IntentClassifier` interface as `HeuristicIntentClassifier`
|
|
6
|
+
* and is interchangeable with it at the gate's `classifier` slot. The difference is
|
|
7
|
+
* the judgment band ABOVE the floor: an LLM call refines the sensitivity tier,
|
|
8
|
+
* directedness, and a non-floor action label more accurately than keyword matching.
|
|
9
|
+
*
|
|
10
|
+
* ── Two invariants this class is built around ──────────────────────────────────
|
|
11
|
+
*
|
|
12
|
+
* 1. THE FLOOR STAYS DETERMINISTIC. This class NEVER lets the LLM decide that a
|
|
13
|
+
* floor action ISN'T one, and it never lets the LLM downgrade a heuristic-detected
|
|
14
|
+
* floor candidate. It runs the deterministic `HeuristicIntentClassifier` FIRST;
|
|
15
|
+
* when the heuristic flags a real floor action (a `floorAction`) OR the
|
|
16
|
+
* ambiguous-deploy low-confidence case (action 'ambiguous', tier 4, low
|
|
17
|
+
* confidence → the gate routes to CLARIFY), that verdict is returned AS-IS and the
|
|
18
|
+
* LLM is never consulted. The LLM can only refine the NON-floor judgment band.
|
|
19
|
+
* (Floor enumeration + role authority remain in RolePolicy / SlackPermissionGate;
|
|
20
|
+
* this class only feeds them an intent and must not be the thing that misses a
|
|
21
|
+
* floor action.)
|
|
22
|
+
*
|
|
23
|
+
* 2. NO SILENT DEGRADATION TO A BRITTLE — OR WIDER — FALLBACK
|
|
24
|
+
* (docs/specs/no-silent-degradation-to-brittle-fallback.md,
|
|
25
|
+
* docs/signal-vs-authority.md). On ANY LLM failure (no provider, throw, timeout,
|
|
26
|
+
* empty/unparseable response, or a response that would WIDEN access beyond the
|
|
27
|
+
* deterministic floor read) we fall back to the `HeuristicIntentClassifier`
|
|
28
|
+
* result — which itself routes ambiguity to a tier-4 low-confidence 'ambiguous'
|
|
29
|
+
* (→ the gate CLARIFIES, a safe non-allow). A classification failure must NEVER
|
|
30
|
+
* return a confident low-tier "allow"-shaped intent on what might be sensitive.
|
|
31
|
+
* The fallback is therefore at-least-as-conservative as the deterministic floor.
|
|
32
|
+
*
|
|
33
|
+
* The LLM is reached through instar's internal `IntelligenceProvider` (the same
|
|
34
|
+
* injected-provider pattern as `TopicIntentExtractor` / `MessagingToneGate`), NOT a
|
|
35
|
+
* raw API call. A `fast` model tier is used, and the call is marked `gating: true`
|
|
36
|
+
* so the IntelligenceRouter will provider-swap on failure before the error
|
|
37
|
+
* propagates (and we then fail closed to the heuristic).
|
|
38
|
+
*
|
|
39
|
+
* Design: docs/specs/SLACK-ORG-INTEGRATION-SPEC.md §6.5–6.6.
|
|
40
|
+
*/
|
|
41
|
+
import { HeuristicIntentClassifier } from './IntentClassifier.js';
|
|
42
|
+
const VALID_TIERS = new Set([0, 1, 2, 3, 4]);
|
|
43
|
+
export class LlmIntentClassifier {
|
|
44
|
+
intelligence;
|
|
45
|
+
heuristic;
|
|
46
|
+
timeoutMs;
|
|
47
|
+
onDegrade;
|
|
48
|
+
constructor(deps = {}) {
|
|
49
|
+
this.intelligence = deps.intelligence;
|
|
50
|
+
this.heuristic = deps.heuristic ?? new HeuristicIntentClassifier();
|
|
51
|
+
this.timeoutMs = deps.timeoutMs ?? 8000;
|
|
52
|
+
this.onDegrade = deps.onDegrade;
|
|
53
|
+
}
|
|
54
|
+
async classify(text, ctx) {
|
|
55
|
+
// ── 1. Deterministic floor read FIRST (never delegated to the LLM) ──────────
|
|
56
|
+
// The heuristic is the floor authority. Whatever it returns is the conservative
|
|
57
|
+
// baseline we will never widen past.
|
|
58
|
+
const floorRead = await this.heuristic.classify(text, ctx);
|
|
59
|
+
// If the heuristic flagged a real floor action OR the ambiguous-deploy
|
|
60
|
+
// possibly-floor case (tier 4, no floorAction, low confidence → the gate
|
|
61
|
+
// CLARIFIES), return it AS-IS. The LLM must not get the chance to downgrade a
|
|
62
|
+
// floor candidate into an allow-shaped low tier.
|
|
63
|
+
if (floorRead.floorAction || floorRead.tier >= 4) {
|
|
64
|
+
this.report('floor-deterministic');
|
|
65
|
+
return floorRead;
|
|
66
|
+
}
|
|
67
|
+
// ── 2. No provider → fail closed to the heuristic ───────────────────────────
|
|
68
|
+
if (!this.intelligence) {
|
|
69
|
+
this.report('no-intelligence');
|
|
70
|
+
return floorRead;
|
|
71
|
+
}
|
|
72
|
+
// ── 3. Judgment band: refine the NON-floor intent via the LLM ───────────────
|
|
73
|
+
let raw;
|
|
74
|
+
try {
|
|
75
|
+
const { systemPrompt, userPrompt } = buildIntentPrompt(text, ctx);
|
|
76
|
+
raw = await this.intelligence.evaluate(`${systemPrompt}\n\n${userPrompt}`, {
|
|
77
|
+
model: 'fast',
|
|
78
|
+
temperature: 0,
|
|
79
|
+
maxTokens: 200,
|
|
80
|
+
timeoutMs: this.timeoutMs,
|
|
81
|
+
attribution: {
|
|
82
|
+
component: 'LlmIntentClassifier',
|
|
83
|
+
category: 'gate',
|
|
84
|
+
// SAFETY-GATING: this classification feeds an authority gate, so the
|
|
85
|
+
// router provider-swaps on failure before the error reaches us — and if
|
|
86
|
+
// every provider is down the throw lands in the catch below and we fail
|
|
87
|
+
// CLOSED to the heuristic. We never silently degrade to a wider answer.
|
|
88
|
+
gating: true,
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
// network/timeout/provider failure / circuit open → fail closed to the
|
|
94
|
+
// deterministic heuristic (which clarifies on ambiguity). NEVER a silent allow.
|
|
95
|
+
this.report('error');
|
|
96
|
+
return floorRead;
|
|
97
|
+
}
|
|
98
|
+
const parsed = parseLlmVerdict(raw);
|
|
99
|
+
if (!parsed) {
|
|
100
|
+
// Unparseable LLM output is a classification FAILURE → fail closed.
|
|
101
|
+
this.report('unparseable');
|
|
102
|
+
return floorRead;
|
|
103
|
+
}
|
|
104
|
+
// ── 4. Reconcile: the LLM may only refine WITHIN the non-floor band ─────────
|
|
105
|
+
return reconcile(floorRead, parsed, ctx);
|
|
106
|
+
}
|
|
107
|
+
report(reason) {
|
|
108
|
+
try {
|
|
109
|
+
this.onDegrade?.(reason);
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
/* observability is best-effort and must never affect the verdict */
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Merge the deterministic floor read with the LLM's judgment-band verdict under a
|
|
118
|
+
* FAIL-CLOSED, never-widen rule:
|
|
119
|
+
*
|
|
120
|
+
* - The floor read is the conservative baseline. If the LLM tries to assert a
|
|
121
|
+
* floor action OR a tier ≥ 4, that is OUT OF ITS LANE — the floor is
|
|
122
|
+
* deterministic, so we DROP the LLM result and return the (non-floor) heuristic
|
|
123
|
+
* read. (A genuine floor would already have short-circuited before the LLM ran.)
|
|
124
|
+
* - Otherwise the LLM may set the tier in [0,3], the action label, and refine
|
|
125
|
+
* directedness. Directedness can only be NARROWED (true→false), never widened:
|
|
126
|
+
* an inbound message the gate already treats as undirected must stay undirected;
|
|
127
|
+
* the LLM cannot promote overheard chatter into a directed command.
|
|
128
|
+
* - floorAction is ALWAYS dropped here (left undefined) — the only floor signal
|
|
129
|
+
* that reaches the gate comes from the deterministic short-circuit above.
|
|
130
|
+
*/
|
|
131
|
+
function reconcile(floorRead, llm, ctx) {
|
|
132
|
+
// The LLM tried to claim a floor-level tier — not its lane. Keep the safe
|
|
133
|
+
// deterministic (non-floor) read rather than trusting an LLM floor assertion.
|
|
134
|
+
if (llm.tier >= 4) {
|
|
135
|
+
return floorRead;
|
|
136
|
+
}
|
|
137
|
+
// Directedness: never widen. The caller's ctx.directed is the structural truth
|
|
138
|
+
// (a mention / clear ask was detected upstream). The LLM may only confirm it or
|
|
139
|
+
// narrow it to false (e.g. "this reads as overheard musing, not a command").
|
|
140
|
+
const directed = ctx.directed && llm.directed;
|
|
141
|
+
// Tier: NEVER widen. The LLM may only ESCALATE the heuristic's conservative tier
|
|
142
|
+
// (raise sensitivity), never lower it — a LOWER tier is a WIDER gate verdict (the
|
|
143
|
+
// gate has an unconditional tier-0 allow + role-ceiling checks), so an unbounded
|
|
144
|
+
// llm.tier would let prompt-injected message content downgrade e.g. T3→T0 and widen
|
|
145
|
+
// access (refuse→allow). Clamp to the deterministic floor read, mirroring the
|
|
146
|
+
// one-way `directed` rule above. Letting the LLM CORRECT heuristic over-classification
|
|
147
|
+
// downward is a separate, confidence-gated design decision — not an unbounded side
|
|
148
|
+
// effect of untrusted message content. (Caught by the Phase-5 adversarial review.)
|
|
149
|
+
const tier = Math.max(floorRead.tier, llm.tier);
|
|
150
|
+
return {
|
|
151
|
+
action: llm.action || floorRead.action,
|
|
152
|
+
tier,
|
|
153
|
+
floorAction: undefined, // floor is deterministic-only; never set from the LLM
|
|
154
|
+
confidence: llm.confidence,
|
|
155
|
+
directed,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Validate + clamp the LLM's raw JSON into a safe judgment-band verdict.
|
|
160
|
+
* Returns null when the payload can't be read as a verdict (→ caller fails closed).
|
|
161
|
+
*/
|
|
162
|
+
function parseLlmVerdict(raw) {
|
|
163
|
+
if (!raw || typeof raw !== 'string')
|
|
164
|
+
return null;
|
|
165
|
+
// Tolerate prose/markdown around the JSON object (some providers wrap it).
|
|
166
|
+
const start = raw.indexOf('{');
|
|
167
|
+
const end = raw.lastIndexOf('}');
|
|
168
|
+
if (start === -1 || end === -1 || end < start)
|
|
169
|
+
return null;
|
|
170
|
+
let obj;
|
|
171
|
+
try {
|
|
172
|
+
obj = JSON.parse(raw.slice(start, end + 1));
|
|
173
|
+
}
|
|
174
|
+
catch {
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
177
|
+
const tierNum = typeof obj.tier === 'number' ? obj.tier : Number(obj.tier);
|
|
178
|
+
if (!Number.isFinite(tierNum) || !VALID_TIERS.has(tierNum))
|
|
179
|
+
return null;
|
|
180
|
+
const action = typeof obj.action === 'string' && obj.action.trim() ? obj.action.trim() : '';
|
|
181
|
+
if (!action)
|
|
182
|
+
return null;
|
|
183
|
+
let confidence = typeof obj.confidence === 'number' ? obj.confidence : Number(obj.confidence);
|
|
184
|
+
if (!Number.isFinite(confidence))
|
|
185
|
+
confidence = 0.5;
|
|
186
|
+
confidence = Math.max(0, Math.min(1, confidence));
|
|
187
|
+
const directed = obj.directed === true || obj.directed === 'true';
|
|
188
|
+
return { action, tier: tierNum, directed, confidence };
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Build the intent-classification prompt. It is explicit that the FLOOR is handled
|
|
192
|
+
* elsewhere: the model is told NOT to grant/allow anything and to classify only the
|
|
193
|
+
* non-floor sensitivity band, and to use tier 4 ONLY as a "this might be sensitive,
|
|
194
|
+
* please clarify" signal (which the gate treats conservatively).
|
|
195
|
+
*/
|
|
196
|
+
function buildIntentPrompt(text, ctx) {
|
|
197
|
+
const systemPrompt = [
|
|
198
|
+
'You classify a single Slack message into an INTENT for an authorization gate.',
|
|
199
|
+
'You do NOT decide whether the action is allowed — a separate deterministic gate does that.',
|
|
200
|
+
'Return ONLY a compact JSON object, no prose, with exactly these keys:',
|
|
201
|
+
' "action": short verb-phrase label, e.g. "summarize", "post-note", "run-job", "discuss".',
|
|
202
|
+
' "tier": integer sensitivity 0-4:',
|
|
203
|
+
' 0 = ambient chatter / reaction, no action requested',
|
|
204
|
+
' 1 = read/inform (summarize, answer, look up, draft but NOT send)',
|
|
205
|
+
' 2 = low-write (post a message/doc, file a ticket, create a calendar hold)',
|
|
206
|
+
' 3 = operational (run a job, modify non-production state, schedule, small spend)',
|
|
207
|
+
' 4 = potentially-PRIVILEGED — use ONLY when it MIGHT touch money, a production',
|
|
208
|
+
' deploy, credentials, destructive data ops, an external send, or granting',
|
|
209
|
+
' authority. When unsure between 3 and 4, choose 4 (safer; the gate clarifies).',
|
|
210
|
+
' "directed": true if the message is a request aimed AT the agent; false if it is overheard',
|
|
211
|
+
' chatter or musing not addressed to the agent.',
|
|
212
|
+
' "confidence": 0.0-1.0, your confidence in this classification. Be honest; low confidence on a',
|
|
213
|
+
' possibly-sensitive message is the correct, safe answer.',
|
|
214
|
+
'Never invent authorization. Never output anything but the JSON object.',
|
|
215
|
+
].join('\n');
|
|
216
|
+
const userPrompt = [
|
|
217
|
+
`directed_hint: ${ctx.directed ? 'the upstream router saw a mention/clear ask' : 'no mention detected'}`,
|
|
218
|
+
'message:',
|
|
219
|
+
'"""',
|
|
220
|
+
(text || '').slice(0, 2000),
|
|
221
|
+
'"""',
|
|
222
|
+
].join('\n');
|
|
223
|
+
return { systemPrompt, userPrompt };
|
|
224
|
+
}
|
|
225
|
+
//# sourceMappingURL=LlmIntentClassifier.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LlmIntentClassifier.js","sourceRoot":"","sources":["../../src/permissions/LlmIntentClassifier.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MandateBackedGrantStore — resolves a Slack FLOOR-action grant for a verified
|
|
3
|
+
* principal from the SIGNED Coordination Mandate(s).
|
|
4
|
+
*
|
|
5
|
+
* This is the production `GrantStore` for `SlackPermissionGate`: instead of an
|
|
6
|
+
* ad-hoc grant table, a floor-action grant is a `UserAuthorityGrant` carried inside
|
|
7
|
+
* a mandate and covered by the mandate's `authProof`. That makes the grant:
|
|
8
|
+
* - deny-by-default (no mandate / no matching grant → undefined → refuse)
|
|
9
|
+
* - signed (an agent cannot mint or widen a grant; only the PIN-gated
|
|
10
|
+
* issuance/grant route can sign one in)
|
|
11
|
+
* - bounded + expiring (the grant has its own expiry, clamped to never outlive the
|
|
12
|
+
* mandate that carries it)
|
|
13
|
+
* - revocable (revoking the mandate voids every grant it carries)
|
|
14
|
+
*
|
|
15
|
+
* `activeGrant` returns a grant ONLY when, for a mandate that is authorship-valid,
|
|
16
|
+
* not expired, and not revoked, there exists a grant whose `grantedTo` equals the
|
|
17
|
+
* verified Slack user id, whose `floorAction` equals the requested scope, and whose
|
|
18
|
+
* effective expiry (min of grant.expiresAt and mandate.expiresAt) is still in the
|
|
19
|
+
* future. Otherwise `undefined` — the gate then falls back to the role floor.
|
|
20
|
+
*
|
|
21
|
+
* Design: docs/specs/SLACK-ORG-INTEGRATION-SPEC.md (Pillar 2, floor grants) +
|
|
22
|
+
* docs/specs/coordination-mandate.md (the signed-delegation model the grant rides on).
|
|
23
|
+
*/
|
|
24
|
+
import type { GrantStore } from './SlackPermissionGate.js';
|
|
25
|
+
import type { AuthorityGrant } from './types.js';
|
|
26
|
+
import type { MandateStore } from '../coordination/MandateStore.js';
|
|
27
|
+
export interface MandateBackedGrantStoreDeps {
|
|
28
|
+
/** The signed mandate store the grants live in. */
|
|
29
|
+
store: MandateStore;
|
|
30
|
+
}
|
|
31
|
+
export declare class MandateBackedGrantStore implements GrantStore {
|
|
32
|
+
private readonly store;
|
|
33
|
+
constructor(deps: MandateBackedGrantStoreDeps);
|
|
34
|
+
/**
|
|
35
|
+
* Resolve an active floor-action grant for `slackUserId` + `scope` as of `now` (ms).
|
|
36
|
+
* Returns the FIRST matching grant across all mandates; `undefined` if none.
|
|
37
|
+
*/
|
|
38
|
+
activeGrant(slackUserId: string, scope: string, now: number): AuthorityGrant | undefined;
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=MandateBackedGrantStore.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MandateBackedGrantStore.d.ts","sourceRoot":"","sources":["../../src/permissions/MandateBackedGrantStore.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAC3D,OAAO,KAAK,EAAE,cAAc,EAAgC,MAAM,YAAY,CAAC;AAC/E,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iCAAiC,CAAC;AAEpE,MAAM,WAAW,2BAA2B;IAC1C,mDAAmD;IACnD,KAAK,EAAE,YAAY,CAAC;CACrB;AAED,qBAAa,uBAAwB,YAAW,UAAU;IACxD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAe;gBACzB,IAAI,EAAE,2BAA2B;IAI7C;;;OAGG;IACH,WAAW,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS;CAkCzF"}
|