@astram/guard 0.20.2 → 0.21.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.
@@ -8,6 +8,12 @@
8
8
  * (`decide`) passes the attribute through ONLY when `operationOf(principal)` exists and its
9
9
  * `operationKey` and `stepKey` equal the marker's; in every other case it is removed before the
10
10
  * inner `decide()`, so the baseline decision stands (Shield #1, #9).
11
+ *
12
+ * G-025 (ADR-0048): the same seal for the separation-of-duties facts. Gate 6 reads
13
+ * `shield.separationOfDuties`; only a facts object the engine built
14
+ * (`isEngineSeparationOfDutiesFacts`, a module-private WeakSet) passes. Any other value is
15
+ * removed, so a bound action then fails closed (`separation_of_duties_unverified`) instead of a
16
+ * forged empty set skipping the rule.
11
17
  */
12
18
  import type { DecisionAuthority } from '@astram/core';
13
19
  export declare function withOperationMarkerCheck(inner: DecisionAuthority): DecisionAuthority;
@@ -1 +1 @@
1
- {"version":3,"file":"operation-marker-check.d.ts","sourceRoot":"","sources":["../../src/authz/operation-marker-check.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAKtD,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,iBAAiB,GAAG,iBAAiB,CAoBpF"}
1
+ {"version":3,"file":"operation-marker-check.d.ts","sourceRoot":"","sources":["../../src/authz/operation-marker-check.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAMtD,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,iBAAiB,GAAG,iBAAiB,CA2BpF"}
@@ -3,22 +3,30 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.withOperationMarkerCheck = withOperationMarkerCheck;
4
4
  const engine_1 = require("@astram/engine");
5
5
  const MARKER = 'shield.delegation';
6
+ const SOD_FACTS = 'shield.separationOfDuties';
6
7
  function withOperationMarkerCheck(inner) {
7
8
  return {
8
9
  decide: (principal, action, resource, context) => {
9
10
  const attributes = context.attributes;
10
- if (attributes === undefined || !(MARKER in attributes)) {
11
+ if (attributes === undefined || !(MARKER in attributes || SOD_FACTS in attributes)) {
11
12
  return inner.decide(principal, action, resource, context);
12
13
  }
13
- const raw = attributes[MARKER];
14
- const binding = (0, engine_1.operationOf)(principal);
15
- const { operationKey, stepKey } = typeof raw === 'object' && raw !== null ? raw : {};
16
- const genuine = binding !== undefined &&
17
- binding.operationKey === operationKey &&
18
- binding.stepKey === stepKey;
19
- if (genuine)
20
- return inner.decide(principal, action, resource, context);
21
- const { [MARKER]: _forged, ...rest } = attributes;
14
+ // Either key is present (own or inherited): forward an own-property copy, so an inherited
15
+ // key never reaches core (review R3 #3), and only genuine own values survive.
16
+ const rest = { ...attributes };
17
+ if (Object.hasOwn(rest, MARKER)) {
18
+ const raw = rest[MARKER];
19
+ const binding = (0, engine_1.operationOf)(principal);
20
+ const { operationKey, stepKey } = typeof raw === 'object' && raw !== null ? raw : {};
21
+ const genuine = binding !== undefined &&
22
+ binding.operationKey === operationKey &&
23
+ binding.stepKey === stepKey;
24
+ if (!genuine)
25
+ delete rest[MARKER];
26
+ }
27
+ if (Object.hasOwn(rest, SOD_FACTS) && !(0, engine_1.isEngineSeparationOfDutiesFacts)(rest[SOD_FACTS])) {
28
+ delete rest[SOD_FACTS];
29
+ }
22
30
  return inner.decide(principal, action, resource, { ...context, attributes: rest });
23
31
  },
24
32
  };
@@ -1 +1 @@
1
- {"version":3,"file":"module-registry.bridge.d.ts","sourceRoot":"","sources":["../../src/identity/module-registry.bridge.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EACV,cAAc,EAIf,MAAM,cAAc,CAAC;AACtB,OAAO,KAAK,EAAc,eAAe,EAAE,MAAM,gBAAgB,CAAC;AASlE,wBAAgB,2BAA2B,CAAC,MAAM,EAAE,SAAS,eAAe,EAAE,GAAG,cAAc,CAwB9F"}
1
+ {"version":3,"file":"module-registry.bridge.d.ts","sourceRoot":"","sources":["../../src/identity/module-registry.bridge.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EACV,cAAc,EAIf,MAAM,cAAc,CAAC;AACtB,OAAO,KAAK,EAAc,eAAe,EAAE,MAAM,gBAAgB,CAAC;AASlE,wBAAgB,2BAA2B,CAAC,MAAM,EAAE,SAAS,eAAe,EAAE,GAAG,cAAc,CA+B9F"}
@@ -8,7 +8,8 @@ exports.buildIdentityModuleRegistry = buildIdentityModuleRegistry;
8
8
  * are its declared vocabulary (`actionsOf` = engine `effectiveActions`, ADR-0038): CRUD plus
9
9
  * extras, or exactly the declared list for `crudActions: false`; an invalid declaration throws
10
10
  * `RegistrationError` here. Every action is `gated` with the type's `access.plane` — nothing
11
- * open unless reclassified. `register()` throws on duplicate typeKey/permissionKey.
11
+ * open unless reclassified. G-025: field descriptors carry `referencesTypeKey` (engine
12
+ * `referencedTypeOf`) and types carry their separation-of-duties action lists. `register()` throws on duplicate typeKey/permissionKey.
12
13
  */
13
14
  const core_1 = require("@astram/core");
14
15
  const engine_1 = require("@astram/engine");
@@ -25,8 +26,15 @@ function buildIdentityModuleRegistry(inputs) {
25
26
  return {
26
27
  typeKey: r.typeKey,
27
28
  label: r.typeKey,
28
- fields: r.fields.map((f) => ({ key: f.key, label: f.key })),
29
+ fields: r.fields.map((f) => {
30
+ const ref = (0, engine_1.referencedTypeOf)(f);
31
+ return { key: f.key, label: f.key, ...(ref ? { referencesTypeKey: ref } : {}) };
32
+ }),
29
33
  ...(Object.keys(access).length > 0 ? { access } : {}),
34
+ // Separation of duties (G-025): only the action lists reach core.
35
+ ...(r.separationOfDuties?.length
36
+ ? { separationOfDuties: r.separationOfDuties.map((d) => ({ actions: [...d.actions] })) }
37
+ : {}),
30
38
  };
31
39
  });
32
40
  const actions = regs.flatMap((r) => (0, identity_actions_js_1.actionsOf)(r).map((actionKey) => ({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astram/guard",
3
- "version": "0.20.2",
3
+ "version": "0.21.0",
4
4
  "license": "MIT",
5
5
  "description": "Astram framework — the enforcement guard + real-world adapters (the 'hands'). One global deny-by-default gate every request passes through (ADR-0002), classification decorators, boot-pool assertion (ADR-0018), the forRoot() registration seam, and the DB/Redis/crypto adapters that connect the sealed core to a real backend. Depends on @astram/core (ADR-0003, ADR-0023).",
6
6
  "type": "commonjs",
@@ -36,8 +36,8 @@
36
36
  "argon2": "^0.41.1",
37
37
  "ioredis": "^5.4.1",
38
38
  "mariadb": "^3.5.4",
39
- "@astram/core": "1.11.0",
40
- "@astram/engine": "0.13.0"
39
+ "@astram/core": "1.12.0",
40
+ "@astram/engine": "0.14.0"
41
41
  },
42
42
  "peerDependencies": {
43
43
  "@nestjs/common": "^11",
@@ -45,7 +45,7 @@
45
45
  "@nestjs/platform-express": "^11",
46
46
  "reflect-metadata": "^0.2",
47
47
  "rxjs": "^7",
48
- "@astram/messaging": "^0.1.5"
48
+ "@astram/messaging": "^0.1.6"
49
49
  },
50
50
  "peerDependenciesMeta": {
51
51
  "@astram/messaging": {
@@ -68,7 +68,7 @@
68
68
  "typescript": "^5.7.0",
69
69
  "unplugin-swc": "^1.5.1",
70
70
  "vitest": "^2.1.8",
71
- "@astram/messaging": "0.1.5"
71
+ "@astram/messaging": "0.1.6"
72
72
  },
73
73
  "repository": {
74
74
  "type": "git",