@elinpf/dsh-ops-access-gate 0.3.0 → 0.4.1

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 (2) hide show
  1. package/lib/index.js +21 -2
  2. package/package.json +5 -5
package/lib/index.js CHANGED
@@ -322,7 +322,15 @@ export function apply(ctx, config) {
322
322
  // never issued without a session to key the grant on); approval-required
323
323
  // kinds deny outright — their credential is effectively rw (there is no
324
324
  // read-only shell), so an untracked caller must not get it at all.
325
- const broker = (kind, profileName, agent) => {
325
+ //
326
+ // Per-call tier declaration (request.tier):
327
+ // - 'ro' = deliberate downgrade: the session may hold an rw grant, but this
328
+ // call only reads. Serve ro; the grant is not exercised and no rw-issue
329
+ // is audited. (Lockdown still refuses even this.)
330
+ // - 'rw' = explicit elevation: without a grant this denies LOUDLY with
331
+ // request_access guidance — silently serving ro would leave the agent
332
+ // believing it wrote when it only read.
333
+ const broker = (kind, profileName, agent, request) => {
326
334
  // Deny (ticket 12) outranks every other state: a locked profile refuses
327
335
  // even ro, for session and internal callers alike.
328
336
  if (gate.isDenied(kind, profileName)) {
@@ -337,14 +345,25 @@ export function apply(ctx, config) {
337
345
  }
338
346
  const authorized = gate.isAuthorized(agent.id, kind, profileName);
339
347
  // Approval-required kinds (ssh): the credential lives in the ro registry —
340
- // the grant is a timed pass to use it at all.
348
+ // the grant is a timed pass to use it at all. There IS no rw tier, so an
349
+ // explicit rw request is a category error worth teaching.
341
350
  if (config.approvalRequiredKinds.includes(kind)) {
351
+ if (request?.tier === 'rw') {
352
+ return { deny: kind + ' has no rw tier — its single credential is effectively rw already, and the grant IS the access. Request timed access via the ' + REQUEST_ACCESS + ' tool (profile "' + kind + '/' + profileName + '", with a reason) and call WITHOUT a tier' };
353
+ }
342
354
  if (authorized) {
343
355
  audit({ event: 'gated-issue', session: agent.id, parentSession: parentSessionOf(agent), kind, name: profileName });
344
356
  return 'ro';
345
357
  }
346
358
  return { deny: kind + ' has no read-only tier; request timed access via the ' + REQUEST_ACCESS + ' tool (profile "' + kind + '/' + profileName + '", with a reason)' };
347
359
  }
360
+ // Deliberate downgrade: serve ro without touching the grant.
361
+ if (request?.tier === 'ro')
362
+ return 'ro';
363
+ if (request?.tier === 'rw' && !authorized) {
364
+ audit({ event: 'rw-denied', session: agent.id, parentSession: parentSessionOf(agent), kind, name: profileName });
365
+ return { deny: 'explicit rw requested for ' + kind + '/' + profileName + ' but this session holds no grant — request timed access via the ' + REQUEST_ACCESS + ' tool (with a reason), or drop the tier to use the read-only credential' };
366
+ }
348
367
  if (authorized) {
349
368
  audit({ event: 'rw-issue', session: agent.id, parentSession: parentSessionOf(agent), kind, name: profileName });
350
369
  return 'rw';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elinpf/dsh-ops-access-gate",
3
- "version": "0.3.0",
3
+ "version": "0.4.1",
4
4
  "description": "Ops access gate — per-session credential brokering. Owns an in-process grant ledger keyed by agent.id and registers a pure-decision broker (ro/rw) into the ops-access seam. Never touches credential fields.",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
@@ -34,14 +34,14 @@
34
34
  },
35
35
  "dependencies": {
36
36
  "@deepseek-ai/schemastery": "^3.18.1",
37
- "@elinpf/dsh-ops-panel": "^0.3.0"
37
+ "@elinpf/dsh-ops-panel": "^0.4.1"
38
38
  },
39
39
  "peerDependencies": {
40
40
  "@deepseek-ai/cordis": "^4.0.1",
41
41
  "@deepseek-ai/dsh-invariants": "^0.1.0-rc.8",
42
42
  "@deepseek-ai/dsh-llm": "^0.1.0-rc.8",
43
43
  "@deepseek-ai/dsh-tools": "^0.1.0-rc.8",
44
- "@elinpf/dsh-ops-access": "^0.3.0"
44
+ "@elinpf/dsh-ops-access": "^0.4.1"
45
45
  },
46
46
  "devDependencies": {
47
47
  "@deepseek-ai/cordis": "4.0.1",
@@ -51,8 +51,8 @@
51
51
  "typescript": "^5.4.0",
52
52
  "vitest": "^4.1.11",
53
53
  "zod": "^4.4.3",
54
- "@elinpf/dsh-ops-panel": "0.3.0",
55
- "@elinpf/dsh-ops-access": "0.3.0"
54
+ "@elinpf/dsh-ops-panel": "0.4.1",
55
+ "@elinpf/dsh-ops-access": "0.4.1"
56
56
  },
57
57
  "license": "MIT",
58
58
  "publishConfig": {