@watchlight/engine 0.1.0 → 0.2.0
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/README.md +33 -1
- package/index.d.ts +55 -1
- package/package.json +8 -1
- package/wasm/wl_apdp_wasm.js +7 -7
- package/wasm/wl_apdp_wasm_bg.wasm +0 -0
package/README.md
CHANGED
|
@@ -50,6 +50,38 @@ console.log(atten.decision); // "Allow" (child ⊆ parent) — a superset would
|
|
|
50
50
|
WebAssembly has no blocking wait, so they return Promises). `attenuateScope`
|
|
51
51
|
is synchronous. TypeScript types ship in `index.d.ts`.
|
|
52
52
|
|
|
53
|
+
### Obligations on permits
|
|
54
|
+
|
|
55
|
+
A `permit` can attach constraints to its own Allow with `@obligate_*` Cedar
|
|
56
|
+
annotations. They are validated at `addPolicy` time and returned on the
|
|
57
|
+
decision — per policy on `details.policy_results[].obligations` (applicable
|
|
58
|
+
permits only) and merged on `details.obligations` — on `Allow` only. A Deny
|
|
59
|
+
carries none; every field is omitted when unset.
|
|
60
|
+
|
|
61
|
+
```js
|
|
62
|
+
await engine.addPolicy({
|
|
63
|
+
name: "list-customers",
|
|
64
|
+
code: `
|
|
65
|
+
@obligate_redact("ssn, card.pan") // comma-separated field names (at least one)
|
|
66
|
+
@obligate_max_items("8") // decimal integer 1..=4294967295
|
|
67
|
+
@obligate_log_values("false") // exactly "true" or "false"
|
|
68
|
+
@obligate_watermark("acme") // unknown keys pass through raw: extra.watermark
|
|
69
|
+
permit(principal, action == Action::"list_customers", resource);
|
|
70
|
+
`,
|
|
71
|
+
});
|
|
72
|
+
const resp = await engine.authorize({ principal: 'User::"carol"', action: 'Action::"list_customers"', resource: 'Document::"crm"' });
|
|
73
|
+
resp.details.obligations;
|
|
74
|
+
// { redact: ["ssn", "card.pan"], max_items: 8, log_values: false }
|
|
75
|
+
resp.details.policy_results[0].obligations.extra; // { watermark: "acme" }
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Merge across several applicable permits is deterministic: `redact` = union,
|
|
79
|
+
`max_items` = min, `log_values` = logical AND; `extra` is never merged (read
|
|
80
|
+
it per policy). A malformed value — `@obligate_max_items("eight")`,
|
|
81
|
+
`@obligate_log_values("yes")`, an empty `@obligate_redact` — or any
|
|
82
|
+
`@obligate_*` on a `forbid` **rejects the policy** at `addPolicy`; an
|
|
83
|
+
obligation is never silently dropped.
|
|
84
|
+
|
|
53
85
|
## Graduation to Enterprise
|
|
54
86
|
|
|
55
87
|
The same code moves to the networked Watchlight control plane (signed lineage,
|
|
@@ -64,7 +96,7 @@ and (optionally, for a smaller artifact) a modern [`binaryen`](https://github.co
|
|
|
64
96
|
```bash
|
|
65
97
|
rustup target add wasm32-unknown-unknown
|
|
66
98
|
npm run build # → ./wasm (regenerated; not committed)
|
|
67
|
-
npm test # Node conformance (
|
|
99
|
+
npm test # Node conformance (decisions, obligations, load-time rejection)
|
|
68
100
|
npm run conformance # Node + Python parity (needs: pip install watchlight-engine)
|
|
69
101
|
```
|
|
70
102
|
|
package/index.d.ts
CHANGED
|
@@ -23,11 +23,65 @@ export interface AuthorizationRequest {
|
|
|
23
23
|
[k: string]: unknown;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
/**
|
|
27
|
+
* Obligations a `permit` declares via `@obligate_*` Cedar annotations —
|
|
28
|
+
* constraints the caller MUST honour when acting on an `Allow`. Parsed and
|
|
29
|
+
* validated by the engine at `addPolicy` time (a malformed value rejects the
|
|
30
|
+
* policy). Every field is omitted when unset.
|
|
31
|
+
*
|
|
32
|
+
* - `@obligate_redact("ssn, card.pan")` → `redact` (comma-separated, trimmed,
|
|
33
|
+
* de-duplicated, at least one entry)
|
|
34
|
+
* - `@obligate_max_items("8")` → `max_items` (decimal integer, 1..=4294967295)
|
|
35
|
+
* - `@obligate_log_values("false")` → `log_values` (exactly `true`/`false`)
|
|
36
|
+
* - any other `@obligate_<name>("raw")` → `extra[name] = "raw"`, uninterpreted
|
|
37
|
+
*/
|
|
38
|
+
export interface Obligations {
|
|
39
|
+
redact?: string[];
|
|
40
|
+
max_items?: number;
|
|
41
|
+
log_values?: boolean;
|
|
42
|
+
/** Unknown `@obligate_*` keys, raw. Never interpreted or merged by the engine. */
|
|
43
|
+
extra?: Record<string, string>;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** One entry of `details.policy_results`. */
|
|
47
|
+
export interface PolicyResult {
|
|
48
|
+
policy_id: string;
|
|
49
|
+
policy_name?: string;
|
|
50
|
+
/** `true` when this policy determined the decision. */
|
|
51
|
+
applicable: boolean;
|
|
52
|
+
decision: string;
|
|
53
|
+
reason: string;
|
|
54
|
+
evaluation_time_ms: number;
|
|
55
|
+
/** The `@enforcement_effect("…")` annotation value, when declared. */
|
|
56
|
+
enforcement_effect?: string;
|
|
57
|
+
/**
|
|
58
|
+
* Present ONLY on a `permit` that carried a final `"Allow"`: the
|
|
59
|
+
* determining permit(s) on a Cedar Allow, or — when an `observe` /
|
|
60
|
+
* `attenuate` forbid downgraded a Cedar Deny to Allow (monitor mode) —
|
|
61
|
+
* every permit whose own conditions matched (`applicable` stays `false`
|
|
62
|
+
* there). Absent on every forbid and on every Deny.
|
|
63
|
+
*/
|
|
64
|
+
obligations?: Obligations;
|
|
65
|
+
[k: string]: unknown;
|
|
66
|
+
}
|
|
67
|
+
|
|
26
68
|
/** The engine's decision. Fail-closed: an unmatched request is `"Deny"`. */
|
|
27
69
|
export interface AuthorizationResponse {
|
|
28
70
|
decision: "Allow" | "Deny" | string;
|
|
29
71
|
reason?: string;
|
|
30
|
-
details?:
|
|
72
|
+
details?: {
|
|
73
|
+
policy_results?: PolicyResult[];
|
|
74
|
+
/**
|
|
75
|
+
* Deterministic merge of every carrying permit's obligations, present
|
|
76
|
+
* ONLY on a final `"Allow"` and only when non-empty: `redact` = union,
|
|
77
|
+
* `max_items` = min, `log_values` = logical AND. `extra` is NOT merged —
|
|
78
|
+
* read it per policy from `policy_results[].obligations.extra`. A
|
|
79
|
+
* monitor-mode downgrade that cannot attach its permits' obligations is
|
|
80
|
+
* refused by the engine (the decision stays `"Deny"`).
|
|
81
|
+
*/
|
|
82
|
+
obligations?: Obligations;
|
|
83
|
+
[k: string]: unknown;
|
|
84
|
+
};
|
|
31
85
|
[k: string]: unknown;
|
|
32
86
|
}
|
|
33
87
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@watchlight/engine",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Watchlight Developer Edition authorization engine for Node — the real wl-apdp Cedar pipeline (delegation → intent → goal → policy selection → Cedar → strict-subset attenuation → enforcement effects) compiled to WebAssembly. In-process, fail-closed, zero infrastructure.",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "./index.js",
|
|
@@ -34,6 +34,13 @@
|
|
|
34
34
|
],
|
|
35
35
|
"license": "SEE LICENSE IN LICENSE",
|
|
36
36
|
"homepage": "https://www.watchlight.ai",
|
|
37
|
+
"repository": {
|
|
38
|
+
"type": "git",
|
|
39
|
+
"url": "git+https://github.com/watchlight-ai-beacon/watchlight-de.git"
|
|
40
|
+
},
|
|
41
|
+
"bugs": {
|
|
42
|
+
"url": "https://github.com/watchlight-ai-beacon/watchlight-de/issues"
|
|
43
|
+
},
|
|
37
44
|
"publishConfig": {
|
|
38
45
|
"access": "public"
|
|
39
46
|
}
|
package/wasm/wl_apdp_wasm.js
CHANGED
|
@@ -169,7 +169,7 @@ function __wbg_get_imports() {
|
|
|
169
169
|
const a = state0.a;
|
|
170
170
|
state0.a = 0;
|
|
171
171
|
try {
|
|
172
|
-
return
|
|
172
|
+
return __wasm_bindgen_func_elem_3842(a, state0.b, arg0, arg1);
|
|
173
173
|
} finally {
|
|
174
174
|
state0.a = a;
|
|
175
175
|
}
|
|
@@ -224,8 +224,8 @@ function __wbg_get_imports() {
|
|
|
224
224
|
return addHeapObject(ret);
|
|
225
225
|
},
|
|
226
226
|
__wbindgen_cast_0000000000000001: function(arg0, arg1) {
|
|
227
|
-
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [Externref], shim_idx:
|
|
228
|
-
const ret = makeMutClosure(arg0, arg1,
|
|
227
|
+
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [Externref], shim_idx: 482, ret: Result(Unit), inner_ret: Some(Result(Unit)) }, mutable: true }) -> Externref`.
|
|
228
|
+
const ret = makeMutClosure(arg0, arg1, __wasm_bindgen_func_elem_3812);
|
|
229
229
|
return addHeapObject(ret);
|
|
230
230
|
},
|
|
231
231
|
__wbindgen_cast_0000000000000002: function(arg0, arg1) {
|
|
@@ -247,10 +247,10 @@ function __wbg_get_imports() {
|
|
|
247
247
|
};
|
|
248
248
|
}
|
|
249
249
|
|
|
250
|
-
function
|
|
250
|
+
function __wasm_bindgen_func_elem_3812(arg0, arg1, arg2) {
|
|
251
251
|
try {
|
|
252
252
|
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
|
|
253
|
-
wasm.
|
|
253
|
+
wasm.__wasm_bindgen_func_elem_3812(retptr, arg0, arg1, addHeapObject(arg2));
|
|
254
254
|
var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
|
|
255
255
|
var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
|
|
256
256
|
if (r1) {
|
|
@@ -261,8 +261,8 @@ function __wasm_bindgen_func_elem_3744(arg0, arg1, arg2) {
|
|
|
261
261
|
}
|
|
262
262
|
}
|
|
263
263
|
|
|
264
|
-
function
|
|
265
|
-
wasm.
|
|
264
|
+
function __wasm_bindgen_func_elem_3842(arg0, arg1, arg2, arg3) {
|
|
265
|
+
wasm.__wasm_bindgen_func_elem_3842(arg0, arg1, addHeapObject(arg2), addHeapObject(arg3));
|
|
266
266
|
}
|
|
267
267
|
|
|
268
268
|
const PolicyEngineFinalization = (typeof FinalizationRegistry === 'undefined')
|
|
Binary file
|