instar 1.3.438 → 1.3.440
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/.claude/skills/autonomous/SKILL.md +24 -6
- package/dist/commands/server.d.ts.map +1 -1
- package/dist/commands/server.js +31 -0
- package/dist/commands/server.js.map +1 -1
- package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
- package/dist/core/PostUpdateMigrator.js +20 -10
- package/dist/core/PostUpdateMigrator.js.map +1 -1
- package/dist/core/types.d.ts +8 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/messaging/slack/SlackAdapter.d.ts +13 -0
- package/dist/messaging/slack/SlackAdapter.d.ts.map +1 -1
- package/dist/messaging/slack/SlackAdapter.js +23 -0
- package/dist/messaging/slack/SlackAdapter.js.map +1 -1
- package/dist/messaging/slack/types.d.ts +11 -0
- package/dist/messaging/slack/types.d.ts.map +1 -1
- package/dist/messaging/slack/types.js.map +1 -1
- package/dist/permissions/AnomalyScorer.d.ts +54 -0
- package/dist/permissions/AnomalyScorer.d.ts.map +1 -0
- package/dist/permissions/AnomalyScorer.js +68 -0
- package/dist/permissions/AnomalyScorer.js.map +1 -0
- package/dist/permissions/IntentClassifier.d.ts +33 -0
- package/dist/permissions/IntentClassifier.d.ts.map +1 -0
- package/dist/permissions/IntentClassifier.js +88 -0
- package/dist/permissions/IntentClassifier.js.map +1 -0
- package/dist/permissions/PermissionDecisionLedger.d.ts +42 -0
- package/dist/permissions/PermissionDecisionLedger.d.ts.map +1 -0
- package/dist/permissions/PermissionDecisionLedger.js +61 -0
- package/dist/permissions/PermissionDecisionLedger.js.map +1 -0
- package/dist/permissions/RolePolicy.d.ts +34 -0
- package/dist/permissions/RolePolicy.d.ts.map +1 -0
- package/dist/permissions/RolePolicy.js +60 -0
- package/dist/permissions/RolePolicy.js.map +1 -0
- package/dist/permissions/SlackPermissionGate.d.ts +62 -0
- package/dist/permissions/SlackPermissionGate.d.ts.map +1 -0
- package/dist/permissions/SlackPermissionGate.js +117 -0
- package/dist/permissions/SlackPermissionGate.js.map +1 -0
- package/dist/permissions/SlackPermissionObserver.d.ts +37 -0
- package/dist/permissions/SlackPermissionObserver.d.ts.map +1 -0
- package/dist/permissions/SlackPermissionObserver.js +39 -0
- package/dist/permissions/SlackPermissionObserver.js.map +1 -0
- package/dist/permissions/SlackPrincipalResolver.d.ts +39 -0
- package/dist/permissions/SlackPrincipalResolver.d.ts.map +1 -0
- package/dist/permissions/SlackPrincipalResolver.js +60 -0
- package/dist/permissions/SlackPrincipalResolver.js.map +1 -0
- package/dist/permissions/index.d.ts +13 -0
- package/dist/permissions/index.d.ts.map +1 -0
- package/dist/permissions/index.js +13 -0
- package/dist/permissions/index.js.map +1 -0
- package/dist/permissions/testing/SlackScenarioHarness.d.ts +35 -0
- package/dist/permissions/testing/SlackScenarioHarness.d.ts.map +1 -0
- package/dist/permissions/testing/SlackScenarioHarness.js +130 -0
- package/dist/permissions/testing/SlackScenarioHarness.js.map +1 -0
- package/dist/permissions/types.d.ts +108 -0
- package/dist/permissions/types.d.ts.map +1 -0
- package/dist/permissions/types.js +21 -0
- package/dist/permissions/types.js.map +1 -0
- 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 +44 -0
- package/dist/server/routes.js.map +1 -1
- package/dist/users/UserManager.d.ts +9 -0
- package/dist/users/UserManager.d.ts.map +1 -1
- package/dist/users/UserManager.js +21 -0
- package/dist/users/UserManager.js.map +1 -1
- package/package.json +1 -1
- package/src/data/builtin-manifest.json +64 -64
- package/src/data/state-coherence-registry.json +12 -0
- package/upgrades/1.3.439.md +23 -0
- package/upgrades/1.3.440.md +25 -0
- package/upgrades/side-effects/autonomous-setup-race-hardening.md +56 -0
- package/upgrades/side-effects/slack-org-permission-gate.md +80 -0
|
@@ -113,6 +113,18 @@
|
|
|
113
113
|
],
|
|
114
114
|
"grandfathered": false
|
|
115
115
|
},
|
|
116
|
+
{
|
|
117
|
+
"category": "slack-permission-decisions",
|
|
118
|
+
"description": "Observe-only ledger of Slack org permission-gate decisions (allow/clarify/refuse/step-up) with the verified principal + basis. Append-only; feeds false-positive-rate measurement before any enforcement is enabled. See docs/specs/SLACK-ORG-INTEGRATION-SPEC.md.",
|
|
119
|
+
"scope": "machine-local",
|
|
120
|
+
"freshness": "eventual",
|
|
121
|
+
"conflictShape": "append-only",
|
|
122
|
+
"transport": "none",
|
|
123
|
+
"paths": [
|
|
124
|
+
"state/slack-permission-decisions.jsonl"
|
|
125
|
+
],
|
|
126
|
+
"grandfathered": false
|
|
127
|
+
},
|
|
116
128
|
{
|
|
117
129
|
"category": "topic-placement-history",
|
|
118
130
|
"description": "Topic<->machine placement history (does not exist as its own store yet; the journal's first stream materializes it). Distinct from current-owner pool state.",
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Upgrade Guide — vNEXT
|
|
2
|
+
|
|
3
|
+
<!-- assembled-by: assemble-next-md -->
|
|
4
|
+
<!-- bump: patch -->
|
|
5
|
+
|
|
6
|
+
## What Changed
|
|
7
|
+
|
|
8
|
+
fix(autonomous): close the autonomous-skill **setup-race** so concurrent autonomous sessions never collide on a shared state file. The stop-hook already resolves a per-topic state file (`.instar/autonomous/<topicId>.local.md`) and migrates the legacy single file — but the *skill's* setup step still wrote the legacy single file `.instar/autonomous-state.local.md`, so two sessions booting near-simultaneously could race on it in the window before the hook migrates. The skill now writes the per-topic file **directly** at setup, skipping the shared path entirely. The hook's reading logic is unchanged (per-topic preferred, legacy fallback + migrate retained for in-flight older jobs).
|
|
9
|
+
|
|
10
|
+
## What to Tell Your User
|
|
11
|
+
|
|
12
|
+
Nothing user-facing. This is an internal robustness fix so multiple autonomous sessions run cleanly side by side without clobbering each other's state.
|
|
13
|
+
|
|
14
|
+
## Summary of New Capabilities
|
|
15
|
+
|
|
16
|
+
- `.claude/skills/autonomous/SKILL.md` — setup step writes the per-topic state file `.instar/autonomous/<topicId>.local.md` directly (keyed on `report_topic`); cancel + hook-config sections updated to match.
|
|
17
|
+
- `PostUpdateMigrator` — the existing autonomous-SKILL.md migration marker is bumped so existing agents re-deploy the corrected skill on update (idempotent; customized skills left untouched).
|
|
18
|
+
- The autonomous stop-hook is **unchanged** (it already reads per-topic + migrates legacy).
|
|
19
|
+
|
|
20
|
+
## Evidence
|
|
21
|
+
|
|
22
|
+
- Migration suite + autonomous sweep green: `PostUpdateMigrator-autonomousHookPath` (12), plus `PostUpdateMigrator-autonomousStopHook` / `autonomous-skill-deployment` / `migration-parity` (47 total), and `autonomous-state-location` / `autonomous-multi-session` / `autonomous-stop-hook-topic-keyed` (41). `tsc --noEmit` clean.
|
|
23
|
+
- New tests assert: a fix-1 SKILL.md (old marker, no per-topic marker) re-deploys to per-topic; second run is a no-op (idempotent); the bundled SKILL.md instructs writing the per-topic file the hook reads.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Upgrade Guide — vNEXT
|
|
2
|
+
|
|
3
|
+
<!-- assembled-by: assemble-next-md -->
|
|
4
|
+
<!-- bump: patch -->
|
|
5
|
+
|
|
6
|
+
## What Changed
|
|
7
|
+
|
|
8
|
+
feat(permissions): **Slack organizational permission gate — Slice 0** (dark, observe-only). The first vertical slice of a conversational authority system for Slack: a verified-principal → role/tier → deterministic Layer-0 floor → relationship-anomaly step-up gate that returns `allow | clarify | refuse | step-up` with a conversational refusal. Ships **additive and dark-by-default** (config `slack.permissionGate.observeOnly`); when enabled it only *logs* verdicts and blocks nothing. Closes the long-standing defect that `UserManager.hasPermission()` had **zero callsites** — permissions were a context hint, not a code gate.
|
|
9
|
+
|
|
10
|
+
## What to Tell Your User
|
|
11
|
+
|
|
12
|
+
Nothing yet — this ships **dark / observe-only** (agent-only, experimental). No user-facing behavior changes until an operator explicitly turns it on, and even then it only watches and logs (does not block) so its false-positive rate can be measured before any enforcement.
|
|
13
|
+
|
|
14
|
+
## Summary of New Capabilities
|
|
15
|
+
|
|
16
|
+
- `src/permissions/`: RolePolicy (role→tier ceilings + enumerated floor), HeuristicIntentClassifier (deterministic, fail-closed floor), AnomalyScorer (Pillar-3 step-up hook), PermissionDecisionLedger (observe-only JSONL), SlackPermissionGate, SlackPrincipalResolver, SlackPermissionObserver + scenario harness.
|
|
17
|
+
- `UserProfile.slackUserId` / `orgRole` (additive, optional); `UserManager.resolveFromSlackUserId`.
|
|
18
|
+
- Observe-only `SlackAdapter` wiring + dark server-startup wiring (`SlackConfig.permissionGate`).
|
|
19
|
+
- Routes: `GET /permissions/decisions` (decision ledger), `GET /permissions/scenario-suite` (deterministic demonstration).
|
|
20
|
+
|
|
21
|
+
## Evidence
|
|
22
|
+
|
|
23
|
+
- 38 new tests green (35 unit + 3 integration); tsc + lint clean; 90-test no-regression across touched modules (slack adapter, user-manager, permissions).
|
|
24
|
+
- The six worked-example scenarios pass deterministically via `GET /permissions/scenario-suite`.
|
|
25
|
+
- Spec: `docs/specs/SLACK-ORG-INTEGRATION-SPEC.md` (approved); side-effects review: `upgrades/side-effects/slack-org-permission-gate.md`.
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Side-Effects Review — Autonomous setup-race hardening (per-topic state write)
|
|
2
|
+
|
|
3
|
+
**Version / slug:** `autonomous-setup-race-hardening`
|
|
4
|
+
**Date:** 2026-06-08
|
|
5
|
+
**Author:** Instar Agent (echo)
|
|
6
|
+
**Second-pass reviewer:** not required (see Phase 5 note below)
|
|
7
|
+
|
|
8
|
+
## Summary of the change
|
|
9
|
+
|
|
10
|
+
The autonomous stop-hook already resolves a **per-topic** state file (`.instar/autonomous/<topicId>.local.md`) and migrates the legacy single file into it. The only gap was that the autonomous **skill's** setup step still instructed writing the legacy single file `.instar/autonomous-state.local.md` — so two autonomous sessions booting near-simultaneously could both write that shared file and clobber each other in the window before the hook migrates. This change makes the skill write the per-topic file **directly** at setup. Files: `.claude/skills/autonomous/SKILL.md` (the setup write step + cancel/hook-config references), `src/core/PostUpdateMigrator.ts` (bumps the existing autonomous-SKILL.md upgrade marker so existing agents re-deploy the corrected skill), `tests/unit/PostUpdateMigrator-autonomousHookPath.test.ts` (+3 tests). The stop-hook's reading/decision logic is **untouched**.
|
|
11
|
+
|
|
12
|
+
## Decision-point inventory
|
|
13
|
+
|
|
14
|
+
- `autonomous-stop-hook.sh` state resolution (per-topic read + legacy migrate) — **pass-through** — unchanged; it already prefers the per-topic file and keeps the legacy fallback for in-flight older jobs.
|
|
15
|
+
- `PostUpdateMigrator` autonomous-SKILL.md upgrade — **modify (marker bump only)** — re-deploys the corrected bundled SKILL.md to existing agents; same idempotent `upgrade()` mechanism, no new migration machinery.
|
|
16
|
+
- Skill setup state-file write — **modify** — writes the per-topic path instead of the legacy single path. This is instruction content, not runtime decision logic.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## 1. Over-block
|
|
21
|
+
|
|
22
|
+
**What legitimate inputs does this change reject that it shouldn't?** No block/allow surface — over-block not applicable. The change relocates where a state file is written; it gates nothing.
|
|
23
|
+
|
|
24
|
+
## 2. Under-block
|
|
25
|
+
|
|
26
|
+
**What failure modes does this still miss?** None introduced. The one residual it does NOT address: a session with no resolvable `report_topic` still falls back to the legacy single file (documented in the skill) — that path is rare and the hook's legacy migrate still handles it. This is intentional back-compat, not a missed failure.
|
|
27
|
+
|
|
28
|
+
## 3. Level-of-abstraction fit
|
|
29
|
+
|
|
30
|
+
Correct layer. The race was created by the **skill** writing the shared path; the fix is in the skill (write per-topic directly). The hook already owned per-topic resolution at the right layer and is left untouched — we feed it the file it already prefers rather than adding a parallel mechanism.
|
|
31
|
+
|
|
32
|
+
## 4. Signal vs authority compliance
|
|
33
|
+
|
|
34
|
+
Compliant. No blocking authority is added or changed. The decision-making authority (the stop-hook, which blocks/allows session exit) is untouched. This change only changes which file the skill writes and re-deploys that instruction — it adds no brittle check with blocking power. (`docs/signal-vs-authority.md`.)
|
|
35
|
+
|
|
36
|
+
## 5. Interactions
|
|
37
|
+
|
|
38
|
+
- Does NOT shadow or get shadowed by the hook (the hook reads the same per-topic file the skill now writes — they agree).
|
|
39
|
+
- The migration marker bump rides the existing `upgrade('.claude/skills/autonomous/SKILL.md', …)` call; it does not double-fire (one marker carries cumulative SKILL.md fixes because `upgrade()` re-deploys the whole bundled file). Idempotent: a second run finds the new marker and no-ops.
|
|
40
|
+
- `setup-autonomous.sh` already wrote the per-topic path when `REPORT_TOPIC` is set — unchanged, no race with the skill's in-context write.
|
|
41
|
+
|
|
42
|
+
## 6. External surfaces
|
|
43
|
+
|
|
44
|
+
No user-facing surface. Internal robustness only. Existing agents receive the corrected skill on their next update via the migration; new agents get it via `init`. No new config, route, or API. No dependence on timing beyond the boot window it closes.
|
|
45
|
+
|
|
46
|
+
## 7. Rollback cost
|
|
47
|
+
|
|
48
|
+
Low. Back-out is a revert of 3 files + a marker re-bump (or simply shipping a follow-up that restores the prior marker). No data migration, no agent-state repair: the hook's legacy fallback + migrate path means even a mixed fleet (some agents on the old skill writing legacy, some on the new writing per-topic) keeps working — the hook migrates legacy regardless. Worst case is the narrow boot-race returns until re-fixed; nothing is corrupted.
|
|
49
|
+
|
|
50
|
+
## Phase 1 — Principle check
|
|
51
|
+
|
|
52
|
+
The change involves **no new decision point**. It relocates a state-file write and re-deploys that instruction; the gate/authority that makes the session-continuation decision (the stop-hook) is unchanged. Signal-vs-authority therefore applies only as "no new authority added."
|
|
53
|
+
|
|
54
|
+
## Phase 5 — Second-pass note
|
|
55
|
+
|
|
56
|
+
The Phase-5 trigger list includes session-lifecycle changes. This change is session-lifecycle **adjacent** (it affects where the autonomous loop's state file lives) but changes **no decision logic** — the stop-hook that drives spawn/continue/exit is byte-for-byte unchanged (verified against the diff). Declared **not-required**: there is no new or modified block/allow decision for a reviewer to audit. The substantive review above stands on the verified diff.
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# Side-Effects Review — Slack org permission gate (Slice 0)
|
|
2
|
+
|
|
3
|
+
**Version / slug:** `slack-org-permission-gate`
|
|
4
|
+
**Date:** `2026-06-08`
|
|
5
|
+
**Author:** `Echo (instar-dev agent)`
|
|
6
|
+
**Second-pass reviewer:** `not required (single-author; observe-only, dark-by-default)`
|
|
7
|
+
|
|
8
|
+
## Summary of the change
|
|
9
|
+
|
|
10
|
+
Adds the first vertical slice of the Slack organizational permission system
|
|
11
|
+
(`docs/specs/SLACK-ORG-INTEGRATION-SPEC.md`). New `src/permissions/` module: a
|
|
12
|
+
`SlackPermissionGate` that turns a verified principal + natural-language request into
|
|
13
|
+
an `allow | clarify | refuse | step-up` verdict, composed from `RolePolicy`
|
|
14
|
+
(role→tier ceilings + an enumerated, deterministic floor), `HeuristicIntentClassifier`
|
|
15
|
+
(conservative floor detection), `AnomalyScorer` (relationship step-up hook),
|
|
16
|
+
`SlackPrincipalResolver` (verified-Slack-id → principal), and an observe-only
|
|
17
|
+
`PermissionDecisionLedger`. Wired into `SlackAdapter._handleMessage` via an optional
|
|
18
|
+
`SlackPermissionObserver` and into server startup behind a `permissionGate.observeOnly`
|
|
19
|
+
config flag (DARK by default). Adds `UserProfile.slackUserId`/`orgRole`,
|
|
20
|
+
`UserManager.resolveFromSlackUserId`, two read routes (`/permissions/decisions`,
|
|
21
|
+
`/permissions/scenario-suite`), and a state-registry entry for the ledger.
|
|
22
|
+
|
|
23
|
+
## Decision-point inventory
|
|
24
|
+
|
|
25
|
+
- `SlackPermissionGate.evaluate` — **add** — the authority decision (allow/clarify/refuse/step-up). Ships OBSERVE-ONLY (logs, never blocks).
|
|
26
|
+
- `RolePolicy` (floor + ceilings) — **add** — deterministic Layer-0 floor; pure, no I/O.
|
|
27
|
+
- `SlackAdapter._handleMessage` observe call — **add (pass-through)** — fire-and-forget; does not alter the existing flow.
|
|
28
|
+
- `UserProfile` / `UserManager` — **add** — additive optional fields + one new resolution method; no existing path changed.
|
|
29
|
+
|
|
30
|
+
## 1. Over-block
|
|
31
|
+
|
|
32
|
+
Observe-only: it blocks **nothing** today, so over-block has no live effect — it can only over-LOG a "would-refuse." Measured over-refusals in the ledger are exactly the FP signal we want before enabling enforcement. When enforcement is later turned on, the known over-block risks are: (a) the heuristic classifier tags a benign message as a floor action (e.g. "delete this slack message" → matches destructive-data) and refuses; (b) a relayed-but-legitimate request ("the client asked us to email them") trips external-send. Both are why enforcement stays off until the observed FP rate is acceptable (§11).
|
|
33
|
+
|
|
34
|
+
## 2. Under-block
|
|
35
|
+
|
|
36
|
+
The deterministic heuristic classifier will miss obfuscated floor requests (e.g. "send forty grand to…" spelled out, or a deploy phrased without the word prod). This is acceptable for the floor because: (a) observe-only today, and (b) the production design adds an LLM classifier ABOVE the floor for ambiguity — but the floor itself must stay deterministic/fail-closed (a missed floor classification falls through to a non-floor tier and is still role-gated). Also: the anomaly scorer is a coarse heuristic (urgency + atypical-action) and will under-flag a calm, in-character-looking compromise — Pillar 3 ships observe-only precisely to measure this before it gates.
|
|
37
|
+
|
|
38
|
+
## 3. Level-of-abstraction fit
|
|
39
|
+
|
|
40
|
+
Correct layering. The **floor** is a low-level deterministic primitive (cheap, conservative, fail-closed) — appropriate because the dangerous path must NOT depend on an LLM that could fail-open (per the "no silent degradation to brittle fallback" standard). The **judgment band** is authority-level and is designed to be LLM-backed in production via the injectable `IntentClassifier` interface (the heuristic is the deterministic test/fallback path, and it routes ambiguity to CLARIFY, never to a silent allow). The gate does not re-implement `ExternalOperationGate`; the spec wires the floor THROUGH it + the Coordination Mandate in Phase 1 (this slice stands alone as the decision core).
|
|
41
|
+
|
|
42
|
+
## 4. Signal vs authority compliance
|
|
43
|
+
|
|
44
|
+
**Required reference:** docs/signal-vs-authority.md
|
|
45
|
+
|
|
46
|
+
- [x] No — this change has no block/allow surface **yet**. It ships observe-only: the gate produces a logged verdict (a signal) and never blocks delivery.
|
|
47
|
+
|
|
48
|
+
The gate is structurally an authority (it WILL block when `enforce` is enabled), but its blocking logic is NOT brittle: the floor is deterministic-conservative-fail-closed, and the judgment band is designed for LLM backing with conversational context. **Follow-up flagged:** before `enforce: true` is ever set (a later phase), this artifact's §1/§2 must be revisited with real FP-rate data from the ledger, and the judgment-band LLM classifier must be the one holding authority — not the heuristic fallback.
|
|
49
|
+
|
|
50
|
+
## 5. Interactions
|
|
51
|
+
|
|
52
|
+
- **Shadowing:** the observe call runs AFTER the fail-closed `authorizedUserIds` AuthGate and BEFORE the `mention-only` skip, so it sees every authorized message (directed or overheard) without changing whether the message is processed. It shadows nothing (it has no return effect).
|
|
53
|
+
- **Double-fire:** none — it only writes to its own ledger; it does not act on the event.
|
|
54
|
+
- **Races:** the ledger is append-only single-writer per process; the observe call is `void`'d (fire-and-forget) so it cannot delay the handler.
|
|
55
|
+
- **Feedback loops:** none — the ledger is read-only observability; nothing consumes it to change behavior.
|
|
56
|
+
|
|
57
|
+
## 6. External surfaces
|
|
58
|
+
|
|
59
|
+
- **Other agents / install base:** none — the whole feature is dark by default (no config = no gate attached); pure no-op for every existing agent.
|
|
60
|
+
- **External systems (Slack):** none — no new Slack API calls; the observe path is local.
|
|
61
|
+
- **Persistent state:** one new append-only JSONL ledger (`state/slack-permission-decisions.jsonl`), registered in `state-coherence-registry.json` (machine-local, append-only, transport none). Created lazily; bounded by `readRecent(limit)`.
|
|
62
|
+
- **HTTP:** two new authenticated GET routes (Bearer-gated like all routes); read-only.
|
|
63
|
+
|
|
64
|
+
## 7. Rollback cost
|
|
65
|
+
|
|
66
|
+
Pure additive code + one optional config flag. Back-out = revert the commits and ship a patch. No migration needed: `UserProfile.slackUserId`/`orgRole` are optional fields (absent on existing profiles = no effect); the ledger file is observe-only and can be deleted with zero consequence. No agent-state repair, no user-visible regression during rollback (the feature is dark on every install).
|
|
67
|
+
|
|
68
|
+
## Conclusion
|
|
69
|
+
|
|
70
|
+
The review produced no design changes — the slice was built observe-only and floor-deterministic by intent, which is exactly what the signal-vs-authority and "no silent degradation" standards require for a gate that will eventually hold blocking authority. One concern is flagged for the NEXT phase (not this one): the §4 follow-up requiring an FP-rate review and an LLM-backed judgment band before `enforce` is ever enabled. The change is clear to ship as a dark, observe-only foundation pending the spec's `approved: true`.
|
|
71
|
+
|
|
72
|
+
## Evidence pointers
|
|
73
|
+
|
|
74
|
+
- 38 tests green: `tests/unit/slack-permission-gate.test.ts` (23), `tests/unit/slack-principal-resolver.test.ts` (7), `tests/unit/slack-permission-wiring.test.ts` (5), `tests/integration/permissions-routes.test.ts` (3).
|
|
75
|
+
- The six worked-example rows + their expected/actual verdicts: `GET /permissions/scenario-suite` (and `src/permissions/testing/SlackScenarioHarness.ts`).
|
|
76
|
+
- No-regression: 90 tests green across touched modules (slack adapter, user-manager, permissions).
|
|
77
|
+
|
|
78
|
+
## Follow-up (capabilities classification, 2026-06-09)
|
|
79
|
+
|
|
80
|
+
The `/permissions/*` routes are registered in `routes.ts`, so the capabilities-discoverability lint requires the prefix to be classified. **Decision: `INTERNAL_PREFIXES`** in `src/server/CapabilityIndex.ts` (agent-invisible) — Slice 0 is dark/observe-only and its routes (registration approval, the decision ledger, the scenario suite) are operator/internal, not a user-surfaced capability. When the enforce path ships and the gate becomes a live capability, the prefix should graduate to `CAPABILITY_INDEX`. Side-effect: the permission gate is intentionally NOT advertised in `/capabilities` while dark (consistent with §6 "dark by default").
|