instar 1.3.439 → 1.3.441

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/dist/commands/server.d.ts.map +1 -1
  2. package/dist/commands/server.js +31 -0
  3. package/dist/commands/server.js.map +1 -1
  4. package/dist/core/types.d.ts +8 -0
  5. package/dist/core/types.d.ts.map +1 -1
  6. package/dist/core/types.js.map +1 -1
  7. package/dist/messaging/slack/SlackAdapter.d.ts +13 -0
  8. package/dist/messaging/slack/SlackAdapter.d.ts.map +1 -1
  9. package/dist/messaging/slack/SlackAdapter.js +43 -0
  10. package/dist/messaging/slack/SlackAdapter.js.map +1 -1
  11. package/dist/messaging/slack/types.d.ts +11 -0
  12. package/dist/messaging/slack/types.d.ts.map +1 -1
  13. package/dist/messaging/slack/types.js.map +1 -1
  14. package/dist/permissions/AnomalyScorer.d.ts +54 -0
  15. package/dist/permissions/AnomalyScorer.d.ts.map +1 -0
  16. package/dist/permissions/AnomalyScorer.js +68 -0
  17. package/dist/permissions/AnomalyScorer.js.map +1 -0
  18. package/dist/permissions/IntentClassifier.d.ts +33 -0
  19. package/dist/permissions/IntentClassifier.d.ts.map +1 -0
  20. package/dist/permissions/IntentClassifier.js +88 -0
  21. package/dist/permissions/IntentClassifier.js.map +1 -0
  22. package/dist/permissions/PermissionDecisionLedger.d.ts +42 -0
  23. package/dist/permissions/PermissionDecisionLedger.d.ts.map +1 -0
  24. package/dist/permissions/PermissionDecisionLedger.js +61 -0
  25. package/dist/permissions/PermissionDecisionLedger.js.map +1 -0
  26. package/dist/permissions/RolePolicy.d.ts +34 -0
  27. package/dist/permissions/RolePolicy.d.ts.map +1 -0
  28. package/dist/permissions/RolePolicy.js +60 -0
  29. package/dist/permissions/RolePolicy.js.map +1 -0
  30. package/dist/permissions/SlackPermissionGate.d.ts +62 -0
  31. package/dist/permissions/SlackPermissionGate.d.ts.map +1 -0
  32. package/dist/permissions/SlackPermissionGate.js +117 -0
  33. package/dist/permissions/SlackPermissionGate.js.map +1 -0
  34. package/dist/permissions/SlackPermissionObserver.d.ts +37 -0
  35. package/dist/permissions/SlackPermissionObserver.d.ts.map +1 -0
  36. package/dist/permissions/SlackPermissionObserver.js +39 -0
  37. package/dist/permissions/SlackPermissionObserver.js.map +1 -0
  38. package/dist/permissions/SlackPrincipalResolver.d.ts +39 -0
  39. package/dist/permissions/SlackPrincipalResolver.d.ts.map +1 -0
  40. package/dist/permissions/SlackPrincipalResolver.js +60 -0
  41. package/dist/permissions/SlackPrincipalResolver.js.map +1 -0
  42. package/dist/permissions/SlackUserRegistry.d.ts +64 -0
  43. package/dist/permissions/SlackUserRegistry.d.ts.map +1 -0
  44. package/dist/permissions/SlackUserRegistry.js +114 -0
  45. package/dist/permissions/SlackUserRegistry.js.map +1 -0
  46. package/dist/permissions/index.d.ts +14 -0
  47. package/dist/permissions/index.d.ts.map +1 -0
  48. package/dist/permissions/index.js +14 -0
  49. package/dist/permissions/index.js.map +1 -0
  50. package/dist/permissions/testing/SlackScenarioHarness.d.ts +35 -0
  51. package/dist/permissions/testing/SlackScenarioHarness.d.ts.map +1 -0
  52. package/dist/permissions/testing/SlackScenarioHarness.js +130 -0
  53. package/dist/permissions/testing/SlackScenarioHarness.js.map +1 -0
  54. package/dist/permissions/types.d.ts +108 -0
  55. package/dist/permissions/types.d.ts.map +1 -0
  56. package/dist/permissions/types.js +21 -0
  57. package/dist/permissions/types.js.map +1 -0
  58. package/dist/server/CapabilityIndex.d.ts.map +1 -1
  59. package/dist/server/CapabilityIndex.js +1 -0
  60. package/dist/server/CapabilityIndex.js.map +1 -1
  61. package/dist/server/routes.d.ts.map +1 -1
  62. package/dist/server/routes.js +104 -0
  63. package/dist/server/routes.js.map +1 -1
  64. package/dist/users/UserManager.d.ts +9 -0
  65. package/dist/users/UserManager.d.ts.map +1 -1
  66. package/dist/users/UserManager.js +21 -0
  67. package/dist/users/UserManager.js.map +1 -1
  68. package/package.json +1 -1
  69. package/src/data/builtin-manifest.json +46 -46
  70. package/src/data/state-coherence-registry.json +24 -0
  71. package/upgrades/1.3.440.md +25 -0
  72. package/upgrades/1.3.441.md +26 -0
  73. package/upgrades/side-effects/slack-org-permission-gate.md +80 -0
  74. package/upgrades/side-effects/slack-org-permissions-phase1.md +68 -0
@@ -0,0 +1,88 @@
1
+ /**
2
+ * IntentClassifier — turn a natural-language request into a {action, tier, floorAction, confidence}.
3
+ *
4
+ * Two implementations:
5
+ * - HeuristicIntentClassifier (here): deterministic, keyword-based, conservative.
6
+ * Used for the floor (the dangerous path must NOT depend on an LLM — fail-closed)
7
+ * and for the deterministic CI scenario suite. When uncertain whether something is
8
+ * a floor action, it leans floor + low-confidence so the gate routes to CLARIFY.
9
+ * - LlmIntentClassifier (LlmIntentClassifier.ts): wraps an IntelligenceProvider for the
10
+ * judgment band. Per the "no silent degradation to brittle fallback" standard, it is
11
+ * only used ABOVE the floor; floor detection stays deterministic here.
12
+ *
13
+ * Design: docs/specs/SLACK-ORG-INTEGRATION-SPEC.md §6.5–6.6.
14
+ */
15
+ const DEPLOY_VERB = /\b(deploy|deploys|deploying|ship|shipping|push|pushing|release|releasing|roll\s?out|cut a release)\b/;
16
+ const PROD_NOUN = /\b(prod|production|live|the prod|to prod)\b/;
17
+ const MONEY = /\b(wire|wiring|transfer|remit|pay|payment|send money|payout)\b|\$\s?\d/;
18
+ const CRED_NOUN = /\b(api key|apikey|token|password|secret|credential|credentials|private key)\b/;
19
+ const CRED_VERB = /\b(give|share|send|show|reveal|access|rotate|get|fetch|export|print)\b/;
20
+ const DESTRUCTIVE_VERB = /\b(delete|drop|wipe|nuke|truncate|purge|destroy)\b|\brm\s+-rf\b/;
21
+ const DESTRUCTIVE_NOUN = /\b(db|database|table|tables|data|records|prod|production|staging|everything|the whole)\b/;
22
+ const GRANT_VERB = /\b(make|give|grant|promote|elevate|add)\b/;
23
+ const GRANT_NOUN = /\b(admin|owner|root|access|authority|permission|permissions|privileges)\b/;
24
+ const EXTERNAL_VERB = /\b(email|e-mail|send|reply|forward|message)\b/;
25
+ const EXTERNAL_NOUN = /\b(client|clients|customer|customers|vendor|vendors|external|outside|outsider|partner|press|public)\b/;
26
+ const READ_VERB = /\b(summar\w*|what is|what's|look up|lookup|find out|explain|status|draft|tell me|show me|recap|describe|list)\b/;
27
+ const WRITE_VERB = /\b(post|create|file a ticket|open a ticket|schedule|add a|write up|note)\b/;
28
+ const OP_VERB = /\b(run|trigger|kick off|start|rerun|re-run)\b/;
29
+ const OP_NOUN = /\b(job|task|script|pipeline|staging|test|tests|build)\b/;
30
+ /** Does the text CLAIM an authorization from some named party ("X said it's fine")? */
31
+ export function mentionsClaimedAuthority(text) {
32
+ const t = text.toLowerCase();
33
+ return /\b(\w+\s+)?(said|told me|says|approved|authorized|okayed|gave the ok|signed off)\b/.test(t)
34
+ && /\b(it'?s fine|approved|ok(ay)?|go ahead|do it|fine|allowed|permission)\b/.test(t);
35
+ }
36
+ function intent(action, tier, confidence, directed, floorAction) {
37
+ return { action, tier, floorAction, confidence, directed };
38
+ }
39
+ /**
40
+ * Deterministic, conservative classifier. Floor detection is ordered first and
41
+ * leans toward treating an ambiguous "ship/deploy" as a possible floor action at
42
+ * LOW confidence so the gate clarifies rather than guesses.
43
+ */
44
+ export class HeuristicIntentClassifier {
45
+ // eslint-disable-next-line @typescript-eslint/require-await
46
+ async classify(text, ctx) {
47
+ const t = (text || '').toLowerCase();
48
+ const directed = ctx.directed;
49
+ // ── Layer 0 floor detection (deterministic, conservative) ──
50
+ if (DEPLOY_VERB.test(t) && PROD_NOUN.test(t)) {
51
+ return intent('prod-deploy', 4, 0.92, directed, 'prod-deploy');
52
+ }
53
+ if (MONEY.test(t)) {
54
+ return intent('money-movement', 4, 0.9, directed, 'money-movement');
55
+ }
56
+ if (CRED_NOUN.test(t) && CRED_VERB.test(t)) {
57
+ return intent('credential-access', 4, 0.88, directed, 'credential-access');
58
+ }
59
+ if (DESTRUCTIVE_VERB.test(t) && DESTRUCTIVE_NOUN.test(t)) {
60
+ return intent('destructive-data', 4, 0.88, directed, 'destructive-data');
61
+ }
62
+ if (GRANT_VERB.test(t) && GRANT_NOUN.test(t)) {
63
+ return intent('grant-authority', 4, 0.88, directed, 'grant-authority');
64
+ }
65
+ if (EXTERNAL_VERB.test(t) && EXTERNAL_NOUN.test(t)) {
66
+ return intent('external-send', 4, 0.82, directed, 'external-send');
67
+ }
68
+ // ── Ambiguous deploy/ship with no object ("ship it") — possibly floor, low confidence ──
69
+ if (DEPLOY_VERB.test(t)) {
70
+ // Looks like a deploy/ship but no prod/object marker → could be a floor deploy or a
71
+ // harmless "send the draft". Treat as possibly-floor at LOW confidence → CLARIFY.
72
+ return intent('ambiguous', 4, 0.4, directed);
73
+ }
74
+ // ── Non-floor tiers ──
75
+ if (OP_VERB.test(t) && OP_NOUN.test(t)) {
76
+ return intent('operational', 3, 0.78, directed);
77
+ }
78
+ if (WRITE_VERB.test(t)) {
79
+ return intent('low-write', 2, 0.78, directed);
80
+ }
81
+ if (READ_VERB.test(t)) {
82
+ return intent('read', 1, 0.8, directed);
83
+ }
84
+ // ── Default: ambient discussion ──
85
+ return intent('discussion', 0, 0.6, directed);
86
+ }
87
+ }
88
+ //# sourceMappingURL=IntentClassifier.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"IntentClassifier.js","sourceRoot":"","sources":["../../src/permissions/IntentClassifier.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,42 @@
1
+ /**
2
+ * PermissionDecisionLedger — observe-only, append-only record of every permission
3
+ * verdict (allow / clarify / refuse / step-up) with the verified principal + basis.
4
+ *
5
+ * Purpose: measure the gate's decisions (and false-positive rate) against real
6
+ * traffic BEFORE enforcement is ever switched on. Writing here never blocks or
7
+ * mutates the message path — a ledger failure is swallowed.
8
+ *
9
+ * State category: `slack-permission-decisions` (state-coherence-registry.json).
10
+ * Design: docs/specs/SLACK-ORG-INTEGRATION-SPEC.md §6.10, §11.
11
+ */
12
+ import type { PermissionVerdict } from './types.js';
13
+ export interface PermissionLedgerEntry {
14
+ ts: string;
15
+ decision: string;
16
+ basis: string;
17
+ action: string;
18
+ tier: number;
19
+ floorAction?: string;
20
+ slackUserId?: string;
21
+ userId: string | null;
22
+ role: string;
23
+ registered: boolean;
24
+ directed: boolean;
25
+ confidence: number;
26
+ anomalyScore?: number;
27
+ channel?: string;
28
+ /** Observe-only marker: would this verdict have been ENFORCED (vs just logged)? */
29
+ enforced: boolean;
30
+ }
31
+ export declare class PermissionDecisionLedger {
32
+ private readonly file;
33
+ constructor(stateDir: string);
34
+ get path(): string;
35
+ record(verdict: PermissionVerdict, ctx?: {
36
+ channel?: string;
37
+ enforced?: boolean;
38
+ }): void;
39
+ /** Most-recent entries (for the read route + FP-rate measurement). */
40
+ readRecent(limit?: number): PermissionLedgerEntry[];
41
+ }
42
+ //# sourceMappingURL=PermissionDecisionLedger.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PermissionDecisionLedger.d.ts","sourceRoot":"","sources":["../../src/permissions/PermissionDecisionLedger.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAIH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAEpD,MAAM,WAAW,qBAAqB;IACpC,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,OAAO,CAAC;IACpB,QAAQ,EAAE,OAAO,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,mFAAmF;IACnF,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,qBAAa,wBAAwB;IACnC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAS;gBAElB,QAAQ,EAAE,MAAM;IAK5B,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED,MAAM,CAAC,OAAO,EAAE,iBAAiB,EAAE,GAAG,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI;IA0BxF,sEAAsE;IACtE,UAAU,CAAC,KAAK,SAAM,GAAG,qBAAqB,EAAE;CASjD"}
@@ -0,0 +1,61 @@
1
+ /**
2
+ * PermissionDecisionLedger — observe-only, append-only record of every permission
3
+ * verdict (allow / clarify / refuse / step-up) with the verified principal + basis.
4
+ *
5
+ * Purpose: measure the gate's decisions (and false-positive rate) against real
6
+ * traffic BEFORE enforcement is ever switched on. Writing here never blocks or
7
+ * mutates the message path — a ledger failure is swallowed.
8
+ *
9
+ * State category: `slack-permission-decisions` (state-coherence-registry.json).
10
+ * Design: docs/specs/SLACK-ORG-INTEGRATION-SPEC.md §6.10, §11.
11
+ */
12
+ import fs from 'node:fs';
13
+ import path from 'node:path';
14
+ export class PermissionDecisionLedger {
15
+ file;
16
+ constructor(stateDir) {
17
+ /* state-registry: slack-permission-decisions */
18
+ this.file = path.join(stateDir, 'slack-permission-decisions.jsonl');
19
+ }
20
+ get path() {
21
+ return this.file;
22
+ }
23
+ record(verdict, ctx) {
24
+ const entry = {
25
+ ts: verdict.evaluatedAt,
26
+ decision: verdict.decision,
27
+ basis: verdict.basis,
28
+ action: verdict.intent.action,
29
+ tier: verdict.intent.tier,
30
+ floorAction: verdict.intent.floorAction,
31
+ slackUserId: verdict.principal.slackUserId,
32
+ userId: verdict.principal.userId,
33
+ role: verdict.principal.role,
34
+ registered: verdict.principal.registered,
35
+ directed: verdict.intent.directed,
36
+ confidence: verdict.intent.confidence,
37
+ anomalyScore: verdict.anomaly?.score,
38
+ channel: ctx?.channel,
39
+ enforced: ctx?.enforced ?? false,
40
+ };
41
+ try {
42
+ fs.mkdirSync(path.dirname(this.file), { recursive: true });
43
+ fs.appendFileSync(this.file, JSON.stringify(entry) + '\n');
44
+ }
45
+ catch {
46
+ // Observe-only ledger must NEVER break the message path.
47
+ }
48
+ }
49
+ /** Most-recent entries (for the read route + FP-rate measurement). */
50
+ readRecent(limit = 100) {
51
+ try {
52
+ const raw = fs.readFileSync(this.file, 'utf8');
53
+ const lines = raw.split('\n').filter(Boolean);
54
+ return lines.slice(-limit).map((l) => JSON.parse(l));
55
+ }
56
+ catch {
57
+ return [];
58
+ }
59
+ }
60
+ }
61
+ //# sourceMappingURL=PermissionDecisionLedger.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PermissionDecisionLedger.js","sourceRoot":"","sources":["../../src/permissions/PermissionDecisionLedger.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAsB7B,MAAM,OAAO,wBAAwB;IAClB,IAAI,CAAS;IAE9B,YAAY,QAAgB;QAC1B,gDAAgD;QAChD,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,kCAAkC,CAAC,CAAC;IACtE,CAAC;IAED,IAAI,IAAI;QACN,OAAO,IAAI,CAAC,IAAI,CAAC;IACnB,CAAC;IAED,MAAM,CAAC,OAA0B,EAAE,GAA8C;QAC/E,MAAM,KAAK,GAA0B;YACnC,EAAE,EAAE,OAAO,CAAC,WAAW;YACvB,QAAQ,EAAE,OAAO,CAAC,QAAQ;YAC1B,KAAK,EAAE,OAAO,CAAC,KAAK;YACpB,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM;YAC7B,IAAI,EAAE,OAAO,CAAC,MAAM,CAAC,IAAI;YACzB,WAAW,EAAE,OAAO,CAAC,MAAM,CAAC,WAAW;YACvC,WAAW,EAAE,OAAO,CAAC,SAAS,CAAC,WAAW;YAC1C,MAAM,EAAE,OAAO,CAAC,SAAS,CAAC,MAAM;YAChC,IAAI,EAAE,OAAO,CAAC,SAAS,CAAC,IAAI;YAC5B,UAAU,EAAE,OAAO,CAAC,SAAS,CAAC,UAAU;YACxC,QAAQ,EAAE,OAAO,CAAC,MAAM,CAAC,QAAQ;YACjC,UAAU,EAAE,OAAO,CAAC,MAAM,CAAC,UAAU;YACrC,YAAY,EAAE,OAAO,CAAC,OAAO,EAAE,KAAK;YACpC,OAAO,EAAE,GAAG,EAAE,OAAO;YACrB,QAAQ,EAAE,GAAG,EAAE,QAAQ,IAAI,KAAK;SACjC,CAAC;QACF,IAAI,CAAC;YACH,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;YAC3D,EAAE,CAAC,cAAc,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,CAAC;QAC7D,CAAC;QAAC,MAAM,CAAC;YACP,yDAAyD;QAC3D,CAAC;IACH,CAAC;IAED,sEAAsE;IACtE,UAAU,CAAC,KAAK,GAAG,GAAG;QACpB,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;YAC/C,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;YAC9C,OAAO,KAAK,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAA0B,CAAC,CAAC;QAChF,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,EAAE,CAAC;QACZ,CAAC;IACH,CAAC;CACF"}
@@ -0,0 +1,34 @@
1
+ /**
2
+ * RolePolicy — the deterministic role→authority model (Layer 0 + role ceilings).
3
+ *
4
+ * Pure, synchronous, fully unit-testable. No LLM, no I/O. This is the part of the
5
+ * permission system that is STRUCTURE, not judgment: the floor and the role
6
+ * ceilings are enumerated here and cannot be talked around.
7
+ *
8
+ * Design: docs/specs/SLACK-ORG-INTEGRATION-SPEC.md §6.4–6.6.
9
+ */
10
+ import type { OrgRole, SensitivityTier, FloorAction } from './types.js';
11
+ /** The enumerated floor — actions that are NEVER discretionary (Layer 0). */
12
+ export declare const FLOOR_ACTIONS: readonly FloorAction[];
13
+ export declare function isFloorAction(x: string | undefined | null): x is FloorAction;
14
+ export interface RolePolicyConfig {
15
+ /** Override one or more role ceilings (org-configurable). */
16
+ roleCeilings?: Partial<Record<OrgRole, SensitivityTier>>;
17
+ /** Override which roles may authorize floor actions without a grant. */
18
+ floorAuthorizedRoles?: OrgRole[];
19
+ }
20
+ export declare class RolePolicy {
21
+ private readonly ceilings;
22
+ private readonly floorRoles;
23
+ constructor(config?: RolePolicyConfig);
24
+ /** The highest tier this role may authorize on its own. */
25
+ ceilingForRole(role: OrgRole): SensitivityTier;
26
+ /** Does this role's ceiling cover the requested tier? */
27
+ roleCoversTier(role: OrgRole, tier: SensitivityTier): boolean;
28
+ /**
29
+ * May this role authorize a FLOOR action without a separate grant?
30
+ * Default: only `owner`. (An `admin` has a T4 ceiling but is NOT floor-authorized.)
31
+ */
32
+ roleCanAuthorizeFloor(role: OrgRole): boolean;
33
+ }
34
+ //# sourceMappingURL=RolePolicy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RolePolicy.d.ts","sourceRoot":"","sources":["../../src/permissions/RolePolicy.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAmBxE,6EAA6E;AAC7E,eAAO,MAAM,aAAa,EAAE,SAAS,WAAW,EAO/C,CAAC;AAEF,wBAAgB,aAAa,CAAC,CAAC,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,GAAG,CAAC,IAAI,WAAW,CAE5E;AAED,MAAM,WAAW,gBAAgB;IAC/B,6DAA6D;IAC7D,YAAY,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,OAAO,EAAE,eAAe,CAAC,CAAC,CAAC;IACzD,wEAAwE;IACxE,oBAAoB,CAAC,EAAE,OAAO,EAAE,CAAC;CAClC;AAED,qBAAa,UAAU;IACrB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAmC;IAC5D,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAuB;gBAEtC,MAAM,GAAE,gBAAqB;IAKzC,2DAA2D;IAC3D,cAAc,CAAC,IAAI,EAAE,OAAO,GAAG,eAAe;IAI9C,yDAAyD;IACzD,cAAc,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,eAAe,GAAG,OAAO;IAI7D;;;OAGG;IACH,qBAAqB,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO;CAG9C"}
@@ -0,0 +1,60 @@
1
+ /**
2
+ * RolePolicy — the deterministic role→authority model (Layer 0 + role ceilings).
3
+ *
4
+ * Pure, synchronous, fully unit-testable. No LLM, no I/O. This is the part of the
5
+ * permission system that is STRUCTURE, not judgment: the floor and the role
6
+ * ceilings are enumerated here and cannot be talked around.
7
+ *
8
+ * Design: docs/specs/SLACK-ORG-INTEGRATION-SPEC.md §6.4–6.6.
9
+ */
10
+ /** Default ceiling tier per role — the highest tier a role may authorize on its own. */
11
+ const DEFAULT_ROLE_CEILING = {
12
+ guest: 0, // can be heard; cannot direct actions
13
+ member: 1, // reads, summaries, drafts
14
+ contributor: 2, // low-risk writes
15
+ operator: 3, // operational actions (non-prod)
16
+ admin: 4, // everything EXCEPT the floor without a grant (see canAuthorizeFloor)
17
+ owner: 4, // everything, and may authorize floor actions + issue grants
18
+ };
19
+ /**
20
+ * Roles that may authorize a FLOOR action without a separate grant. By default
21
+ * ONLY the owner. An `admin` has a T4 ceiling but still cannot reach a floor
22
+ * action without a grant — the floor is a harder boundary than the tier ceiling.
23
+ */
24
+ const DEFAULT_FLOOR_AUTHORIZED_ROLES = ['owner'];
25
+ /** The enumerated floor — actions that are NEVER discretionary (Layer 0). */
26
+ export const FLOOR_ACTIONS = [
27
+ 'money-movement',
28
+ 'prod-deploy',
29
+ 'credential-access',
30
+ 'destructive-data',
31
+ 'external-send',
32
+ 'grant-authority',
33
+ ];
34
+ export function isFloorAction(x) {
35
+ return !!x && FLOOR_ACTIONS.includes(x);
36
+ }
37
+ export class RolePolicy {
38
+ ceilings;
39
+ floorRoles;
40
+ constructor(config = {}) {
41
+ this.ceilings = { ...DEFAULT_ROLE_CEILING, ...(config.roleCeilings ?? {}) };
42
+ this.floorRoles = new Set(config.floorAuthorizedRoles ?? DEFAULT_FLOOR_AUTHORIZED_ROLES);
43
+ }
44
+ /** The highest tier this role may authorize on its own. */
45
+ ceilingForRole(role) {
46
+ return this.ceilings[role] ?? 0;
47
+ }
48
+ /** Does this role's ceiling cover the requested tier? */
49
+ roleCoversTier(role, tier) {
50
+ return this.ceilingForRole(role) >= tier;
51
+ }
52
+ /**
53
+ * May this role authorize a FLOOR action without a separate grant?
54
+ * Default: only `owner`. (An `admin` has a T4 ceiling but is NOT floor-authorized.)
55
+ */
56
+ roleCanAuthorizeFloor(role) {
57
+ return this.floorRoles.has(role);
58
+ }
59
+ }
60
+ //# sourceMappingURL=RolePolicy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RolePolicy.js","sourceRoot":"","sources":["../../src/permissions/RolePolicy.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,wFAAwF;AACxF,MAAM,oBAAoB,GAAqC;IAC7D,KAAK,EAAE,CAAC,EAAE,sCAAsC;IAChD,MAAM,EAAE,CAAC,EAAE,2BAA2B;IACtC,WAAW,EAAE,CAAC,EAAE,kBAAkB;IAClC,QAAQ,EAAE,CAAC,EAAE,iCAAiC;IAC9C,KAAK,EAAE,CAAC,EAAE,sEAAsE;IAChF,KAAK,EAAE,CAAC,EAAE,6DAA6D;CACxE,CAAC;AAEF;;;;GAIG;AACH,MAAM,8BAA8B,GAAuB,CAAC,OAAO,CAAC,CAAC;AAErE,6EAA6E;AAC7E,MAAM,CAAC,MAAM,aAAa,GAA2B;IACnD,gBAAgB;IAChB,aAAa;IACb,mBAAmB;IACnB,kBAAkB;IAClB,eAAe;IACf,iBAAiB;CAClB,CAAC;AAEF,MAAM,UAAU,aAAa,CAAC,CAA4B;IACxD,OAAO,CAAC,CAAC,CAAC,IAAK,aAAmC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;AACjE,CAAC;AASD,MAAM,OAAO,UAAU;IACJ,QAAQ,CAAmC;IAC3C,UAAU,CAAuB;IAElD,YAAY,SAA2B,EAAE;QACvC,IAAI,CAAC,QAAQ,GAAG,EAAE,GAAG,oBAAoB,EAAE,GAAG,CAAC,MAAM,CAAC,YAAY,IAAI,EAAE,CAAC,EAAE,CAAC;QAC5E,IAAI,CAAC,UAAU,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,oBAAoB,IAAI,8BAA8B,CAAC,CAAC;IAC3F,CAAC;IAED,2DAA2D;IAC3D,cAAc,CAAC,IAAa;QAC1B,OAAO,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAClC,CAAC;IAED,yDAAyD;IACzD,cAAc,CAAC,IAAa,EAAE,IAAqB;QACjD,OAAO,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC;IAC3C,CAAC;IAED;;;OAGG;IACH,qBAAqB,CAAC,IAAa;QACjC,OAAO,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IACnC,CAAC;CACF"}
@@ -0,0 +1,62 @@
1
+ /**
2
+ * SlackPermissionGate — the conversational authority gate (Pillar 2, Slice 0).
3
+ *
4
+ * Given a VERIFIED principal and a natural-language request, returns a
5
+ * {allow | clarify | refuse | step-up} verdict with a conversational message.
6
+ * It composes, in order:
7
+ *
8
+ * overheard? → undirected actionable request is never actioned (§6.9)
9
+ * tier 0 / chat? → no authority needed
10
+ * unregistered? → refuse + route to registration
11
+ * ambiguous + possibly-floor (low confidence)? → clarify, never guess
12
+ * FLOOR action? → Layer 0: needs owner role OR an explicit grant; else refuse
13
+ * tiered action? → role ceiling must cover the tier
14
+ * would-allow a FLOOR action + anomalous? → step-up (anomaly raises the bar, §7.4)
15
+ *
16
+ * Layer 0 (floor) is deterministic and fail-closed. The judgment band may consult
17
+ * an LLM classifier, but the floor never depends on one.
18
+ *
19
+ * Design: docs/specs/SLACK-ORG-INTEGRATION-SPEC.md §6.6–6.9, §7.4.
20
+ */
21
+ import type { PermissionVerdict, Principal, AuthorityGrant } from './types.js';
22
+ import { RolePolicy } from './RolePolicy.js';
23
+ import type { IntentClassifier } from './IntentClassifier.js';
24
+ import type { AnomalyScorer } from './AnomalyScorer.js';
25
+ /** Lookup for active authority grants (a Coordination Mandate, in production). */
26
+ export interface GrantStore {
27
+ activeGrant(slackUserId: string, scope: string, now: number): AuthorityGrant | undefined;
28
+ }
29
+ export interface SlackPermissionGateDeps {
30
+ rolePolicy?: RolePolicy;
31
+ classifier: IntentClassifier;
32
+ anomalyScorer?: AnomalyScorer;
33
+ grants?: GrantStore;
34
+ /** Anomaly score at/above which a would-be-allowed floor action requires step-up. Default 0.5. */
35
+ stepUpThreshold?: number;
36
+ /** Confidence below which a possibly-floor action routes to clarify. Default 0.6. */
37
+ clarifyThreshold?: number;
38
+ /** Out-of-band channels offered for step-up, in priority order. */
39
+ stepUpChannels?: string[];
40
+ /** Injectable clock for deterministic tests. */
41
+ now?: () => number;
42
+ }
43
+ export interface EvaluateInput {
44
+ principal: Principal;
45
+ text: string;
46
+ /** Was the request directed at the agent (mention / clear ask)? */
47
+ directed: boolean;
48
+ channel?: string;
49
+ }
50
+ export declare class SlackPermissionGate {
51
+ private readonly rolePolicy;
52
+ private readonly classifier;
53
+ private readonly anomaly;
54
+ private readonly grants?;
55
+ private readonly stepUpThreshold;
56
+ private readonly clarifyThreshold;
57
+ private readonly stepUpChannels;
58
+ private readonly now;
59
+ constructor(deps: SlackPermissionGateDeps);
60
+ evaluate(input: EvaluateInput): Promise<PermissionVerdict>;
61
+ }
62
+ //# sourceMappingURL=SlackPermissionGate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SlackPermissionGate.d.ts","sourceRoot":"","sources":["../../src/permissions/SlackPermissionGate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAiB,SAAS,EAAe,cAAc,EAAE,MAAM,YAAY,CAAC;AAC3G,OAAO,EAAE,UAAU,EAAiB,MAAM,iBAAiB,CAAC;AAC5D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAE9D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAGxD,kFAAkF;AAClF,MAAM,WAAW,UAAU;IACzB,WAAW,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS,CAAC;CAC1F;AAED,MAAM,WAAW,uBAAuB;IACtC,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,UAAU,EAAE,gBAAgB,CAAC;IAC7B,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB,kGAAkG;IAClG,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,qFAAqF;IACrF,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,mEAAmE;IACnE,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,gDAAgD;IAChD,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,SAAS,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,mEAAmE;IACnE,QAAQ,EAAE,OAAO,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAeD,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAa;IACxC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAmB;IAC9C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAgB;IACxC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAa;IACrC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAS;IACzC,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAS;IAC1C,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAW;IAC1C,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAe;gBAEvB,IAAI,EAAE,uBAAuB;IAWnC,QAAQ,CAAC,KAAK,EAAE,aAAa,GAAG,OAAO,CAAC,iBAAiB,CAAC;CAsGjE"}
@@ -0,0 +1,117 @@
1
+ /**
2
+ * SlackPermissionGate — the conversational authority gate (Pillar 2, Slice 0).
3
+ *
4
+ * Given a VERIFIED principal and a natural-language request, returns a
5
+ * {allow | clarify | refuse | step-up} verdict with a conversational message.
6
+ * It composes, in order:
7
+ *
8
+ * overheard? → undirected actionable request is never actioned (§6.9)
9
+ * tier 0 / chat? → no authority needed
10
+ * unregistered? → refuse + route to registration
11
+ * ambiguous + possibly-floor (low confidence)? → clarify, never guess
12
+ * FLOOR action? → Layer 0: needs owner role OR an explicit grant; else refuse
13
+ * tiered action? → role ceiling must cover the tier
14
+ * would-allow a FLOOR action + anomalous? → step-up (anomaly raises the bar, §7.4)
15
+ *
16
+ * Layer 0 (floor) is deterministic and fail-closed. The judgment band may consult
17
+ * an LLM classifier, but the floor never depends on one.
18
+ *
19
+ * Design: docs/specs/SLACK-ORG-INTEGRATION-SPEC.md §6.6–6.9, §7.4.
20
+ */
21
+ import { RolePolicy, isFloorAction } from './RolePolicy.js';
22
+ import { mentionsClaimedAuthority } from './IntentClassifier.js';
23
+ import { NullAnomalyScorer } from './AnomalyScorer.js';
24
+ const FLOOR_LABEL = {
25
+ 'money-movement': 'money transfer',
26
+ 'prod-deploy': 'production deploy',
27
+ 'credential-access': 'credential access',
28
+ 'destructive-data': 'destructive data operation',
29
+ 'external-send': 'external send to an outside party',
30
+ 'grant-authority': 'permission/role change',
31
+ };
32
+ function floorLabel(f, fallback) {
33
+ return f ? FLOOR_LABEL[f] : fallback;
34
+ }
35
+ export class SlackPermissionGate {
36
+ rolePolicy;
37
+ classifier;
38
+ anomaly;
39
+ grants;
40
+ stepUpThreshold;
41
+ clarifyThreshold;
42
+ stepUpChannels;
43
+ now;
44
+ constructor(deps) {
45
+ this.rolePolicy = deps.rolePolicy ?? new RolePolicy();
46
+ this.classifier = deps.classifier;
47
+ this.anomaly = deps.anomalyScorer ?? new NullAnomalyScorer();
48
+ this.grants = deps.grants;
49
+ this.stepUpThreshold = deps.stepUpThreshold ?? 0.5;
50
+ this.clarifyThreshold = deps.clarifyThreshold ?? 0.6;
51
+ this.stepUpChannels = deps.stepUpChannels ?? ['your known Telegram', 'a second admin'];
52
+ this.now = deps.now ?? (() => Date.now());
53
+ }
54
+ async evaluate(input) {
55
+ const now = this.now();
56
+ const evaluatedAt = new Date(now).toISOString();
57
+ const { principal, text } = input;
58
+ const intent = await this.classifier.classify(text, { directed: input.directed });
59
+ const base = { principal, intent, evaluatedAt };
60
+ const verdict = (decision, basis, message, extra) => ({ decision, basis, message, ...base, ...extra });
61
+ // ── overheard ≠ command (§6.9): an undirected actionable request is never actioned ──
62
+ if (!intent.directed) {
63
+ if (intent.tier >= 1) {
64
+ return verdict('refuse', 'overheard', '');
65
+ }
66
+ return verdict('allow', 'ambient-noop', '');
67
+ }
68
+ // ── tier 0: plain conversation needs no authority ──
69
+ if (intent.tier === 0) {
70
+ return verdict('allow', 'within-authority', '');
71
+ }
72
+ const claimsAuthority = mentionsClaimedAuthority(text);
73
+ // ── unregistered principal cannot direct actions ──
74
+ if (!principal.registered) {
75
+ return verdict('refuse', 'unregistered', `I don't have you registered yet, ${principal.name}, so I can't action requests for you. ` +
76
+ `I've flagged an admin to set you up — once that's done I'll be able to help.`);
77
+ }
78
+ // ── ambiguous + possibly-floor (low confidence) → clarify, never guess ──
79
+ if (intent.tier >= 3 && intent.confidence < this.clarifyThreshold) {
80
+ return verdict('clarify', 'ambiguous-intent', `Happy to — just to be sure I do the right thing, ${principal.name}: do you mean deploy/ship to ` +
81
+ `production, or something lighter like sending a draft? They're pretty different and one of them needs sign-off.`);
82
+ }
83
+ // ── FLOOR action (Layer 0, deterministic, fail-closed) ──
84
+ if (isFloorAction(intent.floorAction)) {
85
+ const label = floorLabel(intent.floorAction, intent.action);
86
+ const granted = principal.slackUserId
87
+ ? this.grants?.activeGrant(principal.slackUserId, intent.floorAction, now)
88
+ : undefined;
89
+ const authorizedByRole = this.rolePolicy.roleCanAuthorizeFloor(principal.role);
90
+ if (!granted && !authorizedByRole) {
91
+ if (claimsAuthority) {
92
+ return verdict('refuse', 'content-name-not-authority', `I can't act on an instruction relayed in a message, ${principal.name} — authority has to come from a ` +
93
+ `verified grant, not a mention of who said it's OK. If an owner wants to grant this, they can do it ` +
94
+ `directly and I'll have it on record.`);
95
+ }
96
+ return verdict('refuse', 'floor-no-grant', `I can't run a ${label} on a ${principal.role}'s request, ${principal.name} — that's a protected action ` +
97
+ `that needs an explicit grant. Want me to ask an owner to approve it, or to grant you a time-boxed ` +
98
+ `authority for this one?`);
99
+ }
100
+ // Would-allow a floor action → relationship anomaly may RAISE the bar to step-up (§7.4).
101
+ const anomaly = await this.anomaly.assess(principal, intent, text);
102
+ if (anomaly.score >= this.stepUpThreshold) {
103
+ return verdict('step-up', 'anomaly-stepup', `This is a bit different from what you usually ask, ${principal.name}, and a ${label} is a protected ` +
104
+ `action — so I want to confirm it's really you before I move on it. I've sent a confirmation to ` +
105
+ `${this.stepUpChannels[0]}; once you confirm there, I'll proceed.`, { anomaly, stepUp: { channels: this.stepUpChannels, reason: anomaly.reasons.join('; ') || 'out-of-character request' } });
106
+ }
107
+ return verdict('allow', granted ? 'floor-granted' : 'within-authority', '', { anomaly });
108
+ }
109
+ // ── tiered (non-floor) action: role ceiling must cover the tier ──
110
+ if (this.rolePolicy.roleCoversTier(principal.role, intent.tier)) {
111
+ return verdict('allow', 'within-authority', '');
112
+ }
113
+ return verdict('refuse', 'role-ceiling', `That's above what a ${principal.role} can authorize on their own, ${principal.name}. ` +
114
+ `I can ask someone with the authority to sign off — want me to?`);
115
+ }
116
+ }
117
+ //# sourceMappingURL=SlackPermissionGate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SlackPermissionGate.js","sourceRoot":"","sources":["../../src/permissions/SlackPermissionGate.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,37 @@
1
+ /**
2
+ * SlackPermissionObserver — the single object the SlackAdapter calls to evaluate a
3
+ * message against the permission gate. Composes resolver → gate → ledger.
4
+ *
5
+ * Slice 0 ships OBSERVE-ONLY: `observe()` evaluates and RECORDS the verdict but
6
+ * never blocks the message path (enforce=false). This lets us measure the gate's
7
+ * decisions (and false-positive rate) against real traffic before enforcement is
8
+ * ever switched on (§11). It must NEVER throw — a failure here cannot break delivery.
9
+ *
10
+ * Design: docs/specs/SLACK-ORG-INTEGRATION-SPEC.md §6.6, §6.10, §11.
11
+ */
12
+ import type { PermissionVerdict } from './types.js';
13
+ import type { SlackPrincipalResolver } from './SlackPrincipalResolver.js';
14
+ import type { SlackPermissionGate } from './SlackPermissionGate.js';
15
+ import type { PermissionDecisionLedger } from './PermissionDecisionLedger.js';
16
+ export interface ObserveInput {
17
+ slackUserId: string;
18
+ displayName?: string;
19
+ text: string;
20
+ directed: boolean;
21
+ channel?: string;
22
+ }
23
+ export interface SlackPermissionObserverDeps {
24
+ resolver: SlackPrincipalResolver;
25
+ gate: SlackPermissionGate;
26
+ ledger: PermissionDecisionLedger;
27
+ /** When true the observer would ENFORCE (block). Slice 0 ships observe-only (false). */
28
+ enforce?: boolean;
29
+ }
30
+ export declare class SlackPermissionObserver {
31
+ private readonly deps;
32
+ constructor(deps: SlackPermissionObserverDeps);
33
+ /** Resolve → evaluate → record (observe-only). Returns the verdict; never throws. */
34
+ observe(input: ObserveInput): Promise<PermissionVerdict | null>;
35
+ get enforcing(): boolean;
36
+ }
37
+ //# sourceMappingURL=SlackPermissionObserver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SlackPermissionObserver.d.ts","sourceRoot":"","sources":["../../src/permissions/SlackPermissionObserver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AACpD,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AAC1E,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AACpE,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAC;AAE9E,MAAM,WAAW,YAAY;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,OAAO,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,EAAE,sBAAsB,CAAC;IACjC,IAAI,EAAE,mBAAmB,CAAC;IAC1B,MAAM,EAAE,wBAAwB,CAAC;IACjC,wFAAwF;IACxF,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,qBAAa,uBAAuB;IACtB,OAAO,CAAC,QAAQ,CAAC,IAAI;gBAAJ,IAAI,EAAE,2BAA2B;IAE9D,qFAAqF;IAC/E,OAAO,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC;IAiBrE,IAAI,SAAS,IAAI,OAAO,CAEvB;CACF"}
@@ -0,0 +1,39 @@
1
+ /**
2
+ * SlackPermissionObserver — the single object the SlackAdapter calls to evaluate a
3
+ * message against the permission gate. Composes resolver → gate → ledger.
4
+ *
5
+ * Slice 0 ships OBSERVE-ONLY: `observe()` evaluates and RECORDS the verdict but
6
+ * never blocks the message path (enforce=false). This lets us measure the gate's
7
+ * decisions (and false-positive rate) against real traffic before enforcement is
8
+ * ever switched on (§11). It must NEVER throw — a failure here cannot break delivery.
9
+ *
10
+ * Design: docs/specs/SLACK-ORG-INTEGRATION-SPEC.md §6.6, §6.10, §11.
11
+ */
12
+ export class SlackPermissionObserver {
13
+ deps;
14
+ constructor(deps) {
15
+ this.deps = deps;
16
+ }
17
+ /** Resolve → evaluate → record (observe-only). Returns the verdict; never throws. */
18
+ async observe(input) {
19
+ try {
20
+ const principal = this.deps.resolver.resolve(input.slackUserId, input.displayName);
21
+ const verdict = await this.deps.gate.evaluate({
22
+ principal,
23
+ text: input.text,
24
+ directed: input.directed,
25
+ channel: input.channel,
26
+ });
27
+ this.deps.ledger.record(verdict, { channel: input.channel, enforced: this.enforcing });
28
+ return verdict;
29
+ }
30
+ catch {
31
+ // Observe-only: a gate/ledger failure must NEVER break the message path.
32
+ return null;
33
+ }
34
+ }
35
+ get enforcing() {
36
+ return this.deps.enforce ?? false;
37
+ }
38
+ }
39
+ //# sourceMappingURL=SlackPermissionObserver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SlackPermissionObserver.js","sourceRoot":"","sources":["../../src/permissions/SlackPermissionObserver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAuBH,MAAM,OAAO,uBAAuB;IACL;IAA7B,YAA6B,IAAiC;QAAjC,SAAI,GAAJ,IAAI,CAA6B;IAAG,CAAC;IAElE,qFAAqF;IACrF,KAAK,CAAC,OAAO,CAAC,KAAmB;QAC/B,IAAI,CAAC;YACH,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,KAAK,CAAC,WAAW,EAAE,KAAK,CAAC,WAAW,CAAC,CAAC;YACnF,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC;gBAC5C,SAAS;gBACT,IAAI,EAAE,KAAK,CAAC,IAAI;gBAChB,QAAQ,EAAE,KAAK,CAAC,QAAQ;gBACxB,OAAO,EAAE,KAAK,CAAC,OAAO;aACvB,CAAC,CAAC;YACH,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,QAAQ,EAAE,IAAI,CAAC,SAAS,EAAE,CAAC,CAAC;YACvF,OAAO,OAAO,CAAC;QACjB,CAAC;QAAC,MAAM,CAAC;YACP,yEAAyE;YACzE,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;IAED,IAAI,SAAS;QACX,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,IAAI,KAAK,CAAC;IACpC,CAAC;CACF"}
@@ -0,0 +1,39 @@
1
+ /**
2
+ * SlackPrincipalResolver — resolve a VERIFIED Slack user id into a Principal.
3
+ *
4
+ * Identity is bound from the authenticated Slack `U…` id (Know Your Principal),
5
+ * NEVER from a name in message content. An unknown id resolves to an unregistered
6
+ * guest (the gate then refuses any actionable request and routes to registration).
7
+ *
8
+ * Decoupled from core: it depends only on a minimal `UserLookup` interface, which
9
+ * `UserManager` satisfies structurally — so the permission system never imports core.
10
+ *
11
+ * Design: docs/specs/SLACK-ORG-INTEGRATION-SPEC.md §6.2.
12
+ */
13
+ import type { OrgRole, Principal } from './types.js';
14
+ /** The minimal user-store shape the resolver needs (UserManager satisfies this). */
15
+ export interface ResolvedUserRecord {
16
+ id: string;
17
+ name: string;
18
+ permissions: string[];
19
+ orgRole?: string;
20
+ }
21
+ export interface UserLookup {
22
+ resolveFromSlackUserId(slackUserId: string): ResolvedUserRecord | null;
23
+ }
24
+ /**
25
+ * Derive an OrgRole. An explicit, valid `orgRole` wins; otherwise the highest role
26
+ * named in `permissions` wins; otherwise default to `member` (a registered user is
27
+ * at least a member). Legacy `permissions: ['admin']` therefore maps to admin.
28
+ */
29
+ export declare function deriveRole(permissions?: string[], orgRole?: string): OrgRole;
30
+ export declare class SlackPrincipalResolver {
31
+ private readonly users;
32
+ constructor(users: UserLookup);
33
+ /**
34
+ * @param slackUserId the AUTHENTICATED Slack user id (U…) from the event envelope
35
+ * @param displayName display name (for messages only — never a basis for authority)
36
+ */
37
+ resolve(slackUserId: string, displayName?: string): Principal;
38
+ }
39
+ //# sourceMappingURL=SlackPrincipalResolver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SlackPrincipalResolver.d.ts","sourceRoot":"","sources":["../../src/permissions/SlackPrincipalResolver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAGrD,oFAAoF;AACpF,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,UAAU;IACzB,sBAAsB,CAAC,WAAW,EAAE,MAAM,GAAG,kBAAkB,GAAG,IAAI,CAAC;CACxE;AAKD;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,WAAW,GAAE,MAAM,EAAO,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAQhF;AAED,qBAAa,sBAAsB;IACrB,OAAO,CAAC,QAAQ,CAAC,KAAK;gBAAL,KAAK,EAAE,UAAU;IAE9C;;;OAGG;IACH,OAAO,CAAC,WAAW,EAAE,MAAM,EAAE,WAAW,SAAK,GAAG,SAAS;CAmB1D"}