@ultimat3/policy
Advanced tools
+8
-0
@@ -35,2 +35,10 @@ # @ultimat3/policy | ||
| adapter to `surfaces.ts` — nothing else. | ||
| - **`enforce()` dispatches with `Object.hasOwn`, never a bare index** (`As of 2026-08-23`). The | ||
| adapter table is an object literal and so inherits `Object.prototype`: `adapters['valueOf']` | ||
| answered a FUNCTION, so `enforce('valueOf' as Surface, …)` called it with the table as receiver | ||
| and returned the table typed as a `SurfaceDenial` — truthy, so the public authz dispatcher failed | ||
| **closed with a denial carrying no code and no reason**. Same hazard, same fix and same reason as | ||
| the role map below (`Object.hasOwn`, `defineProperty`). An unknown surface is `X_POLICY_SURFACE_UNKNOWN`, whose `fix:` | ||
| lists `Object.keys(adapters)` so a fifth surface joins it by existing. Pinned in | ||
| `surfaces.test.ts`. | ||
| - **A derived question about a policy TREE is answered in this PACKAGE, once.** `policyPermissions` | ||
@@ -37,0 +45,0 @@ (in `policy.ts`) and `admitsAnonymous` (in `policy-anonymous.ts`) both walk the combinators |
+2
-2
| { | ||
| "name": "@ultimat3/policy", | ||
| "version": "9.0.0", | ||
| "version": "10.0.0", | ||
| "description": "The one authz rule, evaluated identically in every surface", | ||
@@ -34,4 +34,4 @@ "license": "MIT", | ||
| "dependencies": { | ||
| "@ultimat3/core": "9.0.0" | ||
| "@ultimat3/core": "10.0.0" | ||
| } | ||
| } |
+8
-1
@@ -121,2 +121,8 @@ # @ultimat3/policy 🔐 | ||
| `enforce(surface, policy, args)` dispatches over that table with `Object.hasOwn`, and a surface | ||
| with no adapter is `X_POLICY_SURFACE_UNKNOWN`. Not a formality: the table is an object literal, so | ||
| it inherits `Object.prototype` — `enforce('valueOf' as Surface, …)` used to call | ||
| `Object.prototype.valueOf` with the table as its receiver and return a truthy value, so an authz | ||
| dispatch failed **closed with a `SurfaceDenial` no caller could read**. | ||
| ## Permissions and roles | ||
@@ -174,3 +180,4 @@ | ||
| `X_FORBIDDEN` · `X_POLICY_MISSING` · `X_PERMISSION_UNKNOWN` · `X_ROLE_REDEFINED` | ||
| `X_FORBIDDEN` · `X_POLICY_MISSING` · `X_PERMISSION_UNKNOWN` · `X_POLICY_SURFACE_UNKNOWN` · | ||
| `X_ROLE_REDEFINED` | ||
@@ -177,0 +184,0 @@ A missing policy is a **type** error, not a throw: `ActionDef.policy` is required, so an action |
+22
-2
@@ -6,3 +6,3 @@ // The policy layer's stable error codes. `X_POLICY_MISSING` is enforced by the TYPE system, | ||
| // a policy resolved by name — and `policyMissing()` is how such a site says it. | ||
| import { nearestName, registerErrorCodes, UltimateError } from '@ultimat3/core'; | ||
| import { nearestName, registerErrorCodes, renderCauseValue, UltimateError } from '@ultimat3/core'; | ||
@@ -13,2 +13,3 @@ export const POLICY_ERROR_CODES = [ | ||
| 'X_PERMISSION_UNKNOWN', | ||
| 'X_POLICY_SURFACE_UNKNOWN', | ||
| 'X_ROLE_REDEFINED', | ||
@@ -23,2 +24,3 @@ ] as const; | ||
| X_PERMISSION_UNKNOWN: 'permission string is not in the permission set', | ||
| X_POLICY_SURFACE_UNKNOWN: 'enforce() was handed a surface no adapter answers to', | ||
| X_ROLE_REDEFINED: 'two modules define the same role differently', | ||
@@ -43,3 +45,2 @@ }; | ||
| fix: init.fix, | ||
| docs: `https://ultimate.dev/errors/${init.code}`, | ||
| }); | ||
@@ -112,1 +113,20 @@ } | ||
| }; | ||
| /** | ||
| * `enforce()` was handed a surface with no adapter. Thrown rather than denied, because the value | ||
| * this reports on is one an index would have resolved to something: every object literal inherits | ||
| * `Object.prototype`, so `adapters['valueOf']` answers a function and `adapters['constructor']` | ||
| * answers a constructor. Both are truthy, so the dispatcher fails CLOSED and returns a | ||
| * `SurfaceDenial` no caller can read — a refusal with no code and no reason, which is worse than | ||
| * the refusal it is standing in for. | ||
| * | ||
| * `known` comes from the adapter table itself, so a fifth surface joins this fix line by existing. | ||
| * The received value goes through `renderCauseValue` and never `${}`: the parameter is typed | ||
| * `Surface`, and the call site this guard exists for is one no type reached. | ||
| */ | ||
| export const surfaceUnknown = (surface: unknown, known: readonly string[]): PolicyError => | ||
| new PolicyError({ | ||
| code: 'X_POLICY_SURFACE_UNKNOWN', | ||
| cause: `enforce() was handed surface ${renderCauseValue(surface)}, which no policy adapter answers to`, | ||
| fix: `pass one of ${known.join(', ')} — e.g. enforce('${known[0] ?? 'http'}', policy, args)`, | ||
| }); |
+18
-3
@@ -12,3 +12,3 @@ // Proof that one policy covers every surface. Each adapter is the same three lines: | ||
| import { forbidden } from './errors'; | ||
| import { forbidden, surfaceUnknown } from './errors'; | ||
| import { codeOf, type EvaluateArgs, evaluate, type PolicyEvaluation, reasonOf } from './evaluate'; | ||
@@ -125,3 +125,15 @@ import type { Policy } from './policy'; | ||
| /** Dispatcher for code that is generic over surfaces (the action projector). */ | ||
| /** The surfaces that have an adapter, derived from the table so the two cannot disagree. */ | ||
| const SURFACES: readonly string[] = Object.keys(adapters); | ||
| /** | ||
| * Dispatcher for code that is generic over surfaces (the action projector). | ||
| * | ||
| * `Object.hasOwn` and not a truthiness check on `adapters[surface]`: the table is an object | ||
| * literal, so it inherits `Object.prototype`, and `enforce('valueOf' as Surface, …)` called | ||
| * `Object.prototype.valueOf` with `adapters` as its receiver — a truthy return, so the call failed | ||
| * CLOSED, and the adapter table typed as a `SurfaceDenial`, so nothing downstream could say what | ||
| * was denied. Every in-repo caller passes a literal; a config-driven table, a surface name off the | ||
| * wire or a JS host does not, and this is a public authz entry point. | ||
| */ | ||
| export const enforce = <I, R = unknown>( | ||
@@ -131,3 +143,6 @@ surface: Surface, | ||
| args: EvaluateArgs<I, R>, | ||
| ): SurfaceDenial | undefined => adapters[surface](policy, args); | ||
| ): SurfaceDenial | undefined => { | ||
| if (!Object.hasOwn(adapters, surface)) throw surfaceUnknown(surface, SURFACES); | ||
| return adapters[surface](policy, args); | ||
| }; | ||
@@ -134,0 +149,0 @@ /** For call sites that would rather throw than branch. Same decision, same reason. */ |
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
81652
4.18%1333
2.54%191
3.8%0
-100%+ Added
- Removed
Updated