diff --git a/.changeset/remove-dead-compliance-subsystems.md b/.changeset/remove-dead-compliance-subsystems.md
new file mode 100644
index 0000000000..096e23071c
--- /dev/null
+++ b/.changeset/remove-dead-compliance-subsystems.md
@@ -0,0 +1,36 @@
+---
+'@objectstack/spec': minor
+---
+
+BREAKING (pre-launch): remove the three declared-but-never-enforced compliance
+subsystems per ADR-0056 D8 ("design + enforce, or remove"), and mark the AI
+agent `visibility` property EXPERIMENTAL (#1901).
+
+Removed — none of these were read by any runtime path, and compliance-grade
+configuration must never merely look live:
+
+- `ComplianceConfigSchema` / `GDPRConfigSchema` / `HIPAAConfigSchema` (and the
+ rest of `system/compliance.zod.ts`) — there is no data-subject-rights engine,
+ retention enforcer, or BAA gate. FROM `import { ComplianceConfigSchema } from
+ '@objectstack/spec/system'` TO: delete the reference — a real compliance
+ subsystem will be designed top-down when scheduled.
+- `MaskingConfigSchema` / `MaskingRuleSchema` (`system/masking.zod.ts`) — no
+ redaction layer applies them. FROM masking config TO: field-level security
+ (permission-set field rules, enforced by plugin-security's field masker); a
+ subtractive masking/deny layer arrives with ADR-0066 ⑦/⑧ if needed.
+- `RLSConfigSchema` / `RLSAuditEventSchema` / `RLSAuditConfigSchema`
+ (`security/rls.zod.ts`) — the enforced RLS path never read the global config.
+ FROM global `RLSConfig` TO: per-policy `RowLevelSecurityPolicySchema` (the
+ live, enforced surface — unchanged).
+
+Kept, still `[EXPERIMENTAL]`: `EncryptionConfigSchema` (at-rest field
+encryption) — a real enterprise roadmap item with a stable shape; carrying it
+marked costs less than remove-and-re-add (ADR-0087).
+
+Marked `[EXPERIMENTAL — NOT ENFORCED]` (#1901): `AgentSchema.visibility` — the
+chat-access evaluator deliberately excludes it and the agent list route does
+not filter by it, so `private` does not hide an agent. The schema description
+and the authoring form now say so; use `access` / `permissions` (both enforced
+at the chat route since #1884) for real gating. The ADR-0056 D10 conformance
+matrix tracks all dispositions (`agent-visibility` experimental;
+`compliance-configs` / `data-masking` / `rls-config-global` removed).
diff --git a/content/docs/permissions/authorization.mdx b/content/docs/permissions/authorization.mdx
index 14ac3ce36d..3314abedd1 100644
--- a/content/docs/permissions/authorization.mdx
+++ b/content/docs/permissions/authorization.mdx
@@ -143,7 +143,7 @@ one of two doors, each writing only what it owns:
| 2 · Distribution / install / upgrade / uninstall | Install-consent scopes (ADR-0025 — consent ≠ RBAC grants); namespaced, collision-free composition; provenance axis makes uninstall well-defined | ADR-0025/0028/0048/0086 |
| 3 · Environment composition / assignment | Platform-owned assignment records (`sys_user_role` etc.); anti-escalation; union semantics | ADR-0057 D4 |
| 4 · Runtime enforcement | The six-gate chain above; ~18 primitives enforced and CI-guarded | ADR-0056 D10 matrix |
-| 5 · Production / enterprise | Honest `[EXPERIMENTAL]` markers on the unenforced surface (field encryption, masking, compliance configs); enterprise authentication hardening staged per ADR-0069 | ADR-0049/0056 D8, ADR-0069 |
+| 5 · Production / enterprise | ADR-0056 D8 dispositions settled (2026-07): compliance configs, data masking, and the global RLSConfig were **removed** (never enforced); field encryption stays honestly `[EXPERIMENTAL]` (roadmap); agent `visibility` is marked `[EXPERIMENTAL]` pending #1901. Enterprise authentication hardening staged per ADR-0069 | ADR-0049/0056 D8, ADR-0069 |
## Governance: how "declared = enforced" is kept true
diff --git a/content/docs/protocol/objectql/security.mdx b/content/docs/protocol/objectql/security.mdx
index c891e54948..f288271183 100644
--- a/content/docs/protocol/objectql/security.mdx
+++ b/content/docs/protocol/objectql/security.mdx
@@ -198,7 +198,7 @@ rowLevelSecurity:
using: "status == 'active'" # date-window/function predicates (NOW(), arithmetic) are NOT pushdown-able (ADR-0055) — enforce time windows in the app layer or a pre-resolved set
```
-> RLS conditions are compiled to parameterized queries. The default policy when no rule matches is **deny** (`RLSConfigSchema.defaultPolicy`).
+> RLS conditions are compiled to parameterized queries, and the enforcement path fails **closed**: an applicable policy that cannot be compiled denies (returns zero rows) rather than being dropped. (The old global `RLSConfigSchema` was removed in 2026-07 — it was never read by the enforced path; per-policy `RowLevelSecurityPolicySchema` is the live surface.)
---
@@ -251,71 +251,14 @@ fields:
## 4. Data Masking
-> **Status: schema exists, field wiring removed.** `MaskingRuleSchema`
-> (`packages/spec/src/system/masking.zod.ts`, strategies `redact`, `partial`,
-> `hash`, `tokenize`, `randomize`, `nullify`, `substitute`) remains in the
-> System namespace, but the per-field `maskingRule` property was **pruned from
-> the Field schema in 2026-06** as dead surface — it was never read by the
-> runtime (see `docs/audits/2026-06-dead-surface-disposition-plan.md`). To keep
-> a value out of reader hands today, use FLS (`readable: false`), `hidden`, or
-> a `type: 'secret'` field. The examples below show the *schema shapes* for
-> implementers, not a wired runtime capability.
-
-### Partial Masking
-
-```yaml
-# user.object.yml
-fields:
- ssn:
- type: text
- label: SSN
- maskingRule:
- field: ssn
- strategy: partial
- pattern: '\d{5}$' # regex selecting the visible portion
- preserveFormat: true
- preserveLength: true
- exemptRoles: [hr_admin] # roles that see the unmasked value
-```
-
-### Full Redaction
-
-```yaml
-fields:
- credit_card:
- type: text
- label: Credit Card
- maskingRule:
- field: credit_card
- strategy: redact
-```
-
-### Role-Scoped Masking
-
-`roles` lists the roles that see the *masked* value; `exemptRoles` lists those that see the original:
-
-```yaml
-fields:
- salary:
- type: currency
- label: Salary
- maskingRule:
- field: salary
- strategy: partial
- exemptRoles: [hr_manager, hr_admin]
-```
-
-### Hash Masking
-
-```yaml
-fields:
- email:
- type: email
- label: Email
- maskingRule:
- field: email
- strategy: hash
-```
+> **Status: REMOVED (2026-07, ADR-0056 D8 "design + enforce, or remove").**
+> `MaskingRuleSchema` / `MaskingConfigSchema` were deleted from the spec: no
+> redaction layer ever applied them, and the per-field `maskingRule` property
+> had already been pruned in 2026-06. To keep a value out of reader hands, use
+> **FLS** (`readable: false` field permissions — enforced by plugin-security's
+> field masker), `hidden`, or a `type: 'secret'` field. A subtractive
+> masking/deny layer, if needed, arrives with the ADR-0066 ⑦/⑧ muting work.
+> Disposition tracked in the ADR-0056 D10 conformance matrix (`data-masking`).
---
@@ -421,7 +364,7 @@ Object- and field-level history is opt-in metadata, not a free-form `enable.audi
- **Field history** — set `trackHistory: true` on the object (`ObjectCapabilities` in `object.zod.ts`) to record per-field changes.
- **Per-field audit trail** — set `auditTrail: true` on an individual field to track every change with user and timestamp.
-- **RLS audit** — RLS policy evaluations can be logged via `RLSAuditConfigSchema` (`enabled`, `logLevel`, `destination`, `retentionDays`, …). The recorded event shape is `RLSAuditEventSchema` (timestamp, userId, operation, object, policyName, granted, evaluationDurationMs).
+- **RLS audit** — removed (2026-07, ADR-0056 D8): the `RLSAuditConfigSchema`/`RLSAuditEventSchema` shapes were never emitted or read by the runtime RLS path and were deleted from the spec. Enforcement decisions surface today through the security plugin's fail-closed warnings and the standard audit log (`plugin-audit`), not a dedicated RLS event stream.
```yaml
# account.object.yml
diff --git a/content/docs/protocol/objectql/types.mdx b/content/docs/protocol/objectql/types.mdx
index fff208d5a6..3c5e7a6a72 100644
--- a/content/docs/protocol/objectql/types.mdx
+++ b/content/docs/protocol/objectql/types.mdx
@@ -950,9 +950,10 @@ api_key:
> The per-field `maskingRule` and `encryptionConfig` properties were **pruned
> from the Field schema in 2026-06** — they were declared surface with no
-> runtime consumer. The `MaskingRuleSchema`/`EncryptionConfigSchema` shapes
-> remain in the System namespace for implementers; see
-> [Security & Access Control](/docs/protocol/objectql/security) for their status.
+> runtime consumer. In 2026-07 the masking shapes were removed from the spec
+> entirely (ADR-0056 D8); `EncryptionConfigSchema` remains in the System
+> namespace as `[EXPERIMENTAL]` roadmap surface. See
+> [Security & Access Control](/docs/protocol/objectql/security) for status.
## Type Selection Guide
diff --git a/content/docs/references/ai/agent.mdx b/content/docs/references/ai/agent.mdx
index 820ea3651e..efb111c10e 100644
--- a/content/docs/references/ai/agent.mdx
+++ b/content/docs/references/ai/agent.mdx
@@ -84,7 +84,7 @@ const result = AIKnowledge.parse(data);
| **access** | `string[]` | optional | Who can chat with this agent |
| **permissions** | `string[]` | optional | Required permissions or roles |
| **tenantId** | `string` | optional | Tenant/Organization ID |
-| **visibility** | `Enum<'global' \| 'organization' \| 'private'>` | ✅ | |
+| **visibility** | `Enum<'global' \| 'organization' \| 'private'>` | ✅ | [EXPERIMENTAL — NOT ENFORCED, #1901] Intended listing scope. No runtime consumer yet; use access/permissions for real gating. |
| **planning** | `Object` | optional | Autonomous reasoning and planning configuration |
| **memory** | `Object` | optional | Agent memory management |
| **guardrails** | `Object` | optional | Safety guardrails for the agent |
diff --git a/content/docs/references/cloud/app-store.mdx b/content/docs/references/cloud/app-store.mdx
index 8ccccfb6ac..0077cbc064 100644
--- a/content/docs/references/cloud/app-store.mdx
+++ b/content/docs/references/cloud/app-store.mdx
@@ -21,9 +21,10 @@ installing, and managing marketplace apps from within ObjectOS.
## Customer Journey
-```mermaid
-flowchart LR
- Discover --> Evaluate --> Install --> Configure --> Use --> Rate["Rate / Review"] --> Manage
+```
+
+Discover → Evaluate → Install → Configure → Use → Rate/Review → Manage
+
```
## Key Concepts
diff --git a/content/docs/references/cloud/developer-portal.mdx b/content/docs/references/cloud/developer-portal.mdx
index 142a6c4ded..6dd328f84e 100644
--- a/content/docs/references/cloud/developer-portal.mdx
+++ b/content/docs/references/cloud/developer-portal.mdx
@@ -11,9 +11,10 @@ Defines schemas for the developer-facing side of the marketplace ecosystem.
Covers the complete developer journey:
-```mermaid
-flowchart LR
- Register --> Create["Create App"] --> Develop --> Validate --> Build --> Submit --> Monitor --> Iterate
+```
+
+Register → Create App → Develop → Validate → Build → Submit → Monitor → Iterate
+
```
## Architecture Alignment
diff --git a/content/docs/references/cloud/marketplace.mdx b/content/docs/references/cloud/marketplace.mdx
index 47ddbdc459..025b13d553 100644
--- a/content/docs/references/cloud/marketplace.mdx
+++ b/content/docs/references/cloud/marketplace.mdx
@@ -47,12 +47,18 @@ platform, and customers who install plugins.
## Platform Management Flow
-```mermaid
-flowchart LR
- A["1 · Receive
accept submissions from verified publishers"] --> B["2 · Scan
automated security + compatibility check"]
- B --> C["3 · Review
human review for quality + policy"]
- C --> D["4 · Catalog
index in marketplace search"]
- D --> E["5 · Monitor
track installs, ratings, issues, SLAs"]
+```
+
+1. Receive → Accept submissions from verified publishers
+
+2. Scan → Automated security scan and compatibility check
+
+3. Review → Human review for quality and policy compliance
+
+4. Catalog → Index in marketplace search catalog
+
+5. Monitor → Track installs, ratings, issues, and enforce SLAs
+
```
diff --git a/content/docs/references/data/field.mdx b/content/docs/references/data/field.mdx
index cab5a45d7f..6ab68ff49e 100644
--- a/content/docs/references/data/field.mdx
+++ b/content/docs/references/data/field.mdx
@@ -14,8 +14,8 @@ Field Type Enum
## TypeScript Usage
```typescript
-import { Address, ComputedFieldCache, CurrencyConfig, CurrencyValue, DataQualityRules, FieldType, FileAttachmentConfig, LocationCoordinates, SelectOption, VectorConfig } from '@objectstack/spec/data';
-import type { Address, ComputedFieldCache, CurrencyConfig, CurrencyValue, DataQualityRules, FieldType, FileAttachmentConfig, LocationCoordinates, SelectOption, VectorConfig } from '@objectstack/spec/data';
+import { Address, ComputedFieldCache, CurrencyConfig, CurrencyValue, DataQualityRules, FieldType, FileAttachmentConfig, LocationCoordinates, VectorConfig } from '@objectstack/spec/data';
+import type { Address, ComputedFieldCache, CurrencyConfig, CurrencyValue, DataQualityRules, FieldType, FileAttachmentConfig, LocationCoordinates, VectorConfig } from '@objectstack/spec/data';
// Validate data
const result = Address.parse(data);
@@ -194,20 +194,6 @@ const result = Address.parse(data);
| **accuracy** | `number` | optional | Accuracy in meters |
----
-
-## SelectOption
-
-### Properties
-
-| Property | Type | Required | Description |
-| :--- | :--- | :--- | :--- |
-| **label** | `string` | ✅ | Display label (human-readable, any case allowed) |
-| **value** | `string` | ✅ | Stored value (lowercase machine identifier) |
-| **color** | `string` | optional | Color code for badges/charts |
-| **default** | `boolean` | optional | Is default option |
-
-
---
## VectorConfig
diff --git a/content/docs/references/data/object.mdx b/content/docs/references/data/object.mdx
index 66549bf583..6dc1cd6d87 100644
--- a/content/docs/references/data/object.mdx
+++ b/content/docs/references/data/object.mdx
@@ -14,8 +14,8 @@ API Operations Enum
## TypeScript Usage
```typescript
-import { ApiMethod, Index, ObjectAccessConfig, ObjectCapabilities, ObjectExternalBinding, ObjectFieldGroup, ObjectOwnershipEnum, SoftDeleteConfig, TenancyConfig, VersioningConfig } from '@objectstack/spec/data';
-import type { ApiMethod, Index, ObjectAccessConfig, ObjectCapabilities, ObjectExternalBinding, ObjectFieldGroup, ObjectOwnershipEnum, SoftDeleteConfig, TenancyConfig, VersioningConfig } from '@objectstack/spec/data';
+import { ApiMethod, Index, ObjectAccessConfig, ObjectCapabilities, ObjectExternalBinding, ObjectFieldGroup, ObjectOwnershipEnum, ObjectRequiredPermissions, PerOperationRequiredPermissions, SoftDeleteConfig, TenancyConfig, VersioningConfig } from '@objectstack/spec/data';
+import type { ApiMethod, Index, ObjectAccessConfig, ObjectCapabilities, ObjectExternalBinding, ObjectFieldGroup, ObjectOwnershipEnum, ObjectRequiredPermissions, PerOperationRequiredPermissions, SoftDeleteConfig, TenancyConfig, VersioningConfig } from '@objectstack/spec/data';
// Validate data
const result = ApiMethod.parse(data);
@@ -135,6 +135,48 @@ External datasource binding (ADR-0015)
* `extend`
+---
+
+## ObjectRequiredPermissions
+
+### Union Options
+
+This schema accepts one of the following structures:
+
+#### Option 1
+
+Type: `string[]`
+
+---
+
+#### Option 2
+
+### Properties
+
+| Property | Type | Required | Description |
+| :--- | :--- | :--- | :--- |
+| **read** | `string[]` | optional | Capabilities required to read (find/findOne/count/aggregate). |
+| **create** | `string[]` | optional | Capabilities required to create (insert). |
+| **update** | `string[]` | optional | Capabilities required to update (update/transfer/restore). |
+| **delete** | `string[]` | optional | Capabilities required to delete (delete/purge). |
+
+---
+
+
+---
+
+## PerOperationRequiredPermissions
+
+### Properties
+
+| Property | Type | Required | Description |
+| :--- | :--- | :--- | :--- |
+| **read** | `string[]` | optional | Capabilities required to read (find/findOne/count/aggregate). |
+| **create** | `string[]` | optional | Capabilities required to create (insert). |
+| **update** | `string[]` | optional | Capabilities required to update (update/transfer/restore). |
+| **delete** | `string[]` | optional | Capabilities required to delete (delete/purge). |
+
+
---
## SoftDeleteConfig
diff --git a/content/docs/references/data/seed-loader.mdx b/content/docs/references/data/seed-loader.mdx
index cd479c7a1f..6986738a48 100644
--- a/content/docs/references/data/seed-loader.mdx
+++ b/content/docs/references/data/seed-loader.mdx
@@ -21,13 +21,20 @@ relationship resolution, dependency ordering, and multi-pass insertion.
## Loading Flow
-```mermaid
-flowchart TD
- A["1 · Build object dependency graph
from field metadata (lookup / master_detail)"] --> B["2 · Topological sort
→ insert order (parents before children)"]
- B --> C["3 · Pass 1 — insert/upsert records,
resolve references via externalId"]
- C --> D["4 · Pass 2 — fill deferred references
(circular / delayed dependencies)"]
- D --> E["5 · Validate & report unresolved references"]
- E --> F["6 · Return structured result
with per-object stats"]
+```
+
+1. Build object dependency graph from field metadata (lookup/master_detail)
+
+2. Topological sort → determine insert order (parents before children)
+
+3. Pass 1: Insert/upsert records, resolve references via externalId
+
+4. Pass 2: Fill deferred references (circular/delayed dependencies)
+
+5. Validate & report unresolved references
+
+6. Return structured result with per-object stats
+
```
diff --git a/content/docs/references/kernel/execution-context.mdx b/content/docs/references/kernel/execution-context.mdx
index 578058afdf..9e988379d2 100644
--- a/content/docs/references/kernel/execution-context.mdx
+++ b/content/docs/references/kernel/execution-context.mdx
@@ -61,6 +61,7 @@ const result = ExecutionContext.parse(data);
| **org_user_ids** | `string[]` | optional | |
| **rlsMembership** | `Record` | optional | |
| **isSystem** | `boolean` | ✅ | |
+| **skipTriggers** | `boolean` | optional | |
| **accessToken** | `string` | optional | |
| **transaction** | `any` | optional | |
| **traceId** | `string` | optional | |
diff --git a/content/docs/references/kernel/package-upgrade.mdx b/content/docs/references/kernel/package-upgrade.mdx
index ae99a8c1f0..f1d3ca6423 100644
--- a/content/docs/references/kernel/package-upgrade.mdx
+++ b/content/docs/references/kernel/package-upgrade.mdx
@@ -25,13 +25,20 @@ and rollback capabilities.
## Upgrade Flow
-```mermaid
-flowchart LR
- A["1 · PreCheck
validate compatibility, deps"] --> B["2 · Plan
upgrade plan + metadata diff"]
- B --> C["3 · Snapshot
backup metadata + customizations"]
- C --> D["4 · Execute
apply new metadata (3-way merge)"]
- D --> E["5 · Validate
post-upgrade health checks"]
- E --> F["6 · Commit
finalize (or Rollback on failure)"]
+```
+
+1. PreCheck → Validate compatibility, check dependencies
+
+2. Plan → Generate upgrade plan with metadata diff
+
+3. Snapshot → Backup current state (metadata + customizations)
+
+4. Execute → Apply new package metadata with 3-way merge
+
+5. Validate → Run post-upgrade health checks
+
+6. Commit → Finalize upgrade (or Rollback on failure)
+
```
diff --git a/content/docs/references/security/rls.mdx b/content/docs/references/security/rls.mdx
index 0bba7d4c77..5a37bc3f5f 100644
--- a/content/docs/references/security/rls.mdx
+++ b/content/docs/references/security/rls.mdx
@@ -150,69 +150,13 @@ ObjectStack RLS:
## TypeScript Usage
```typescript
-import { RLSAuditConfig, RLSAuditEvent, RLSConfig, RLSEvaluationResult, RLSOperation, RLSUserContext, RowLevelSecurityPolicy } from '@objectstack/spec/security';
-import type { RLSAuditConfig, RLSAuditEvent, RLSConfig, RLSEvaluationResult, RLSOperation, RLSUserContext, RowLevelSecurityPolicy } from '@objectstack/spec/security';
+import { RLSEvaluationResult, RLSOperation, RLSUserContext, RowLevelSecurityPolicy } from '@objectstack/spec/security';
+import type { RLSEvaluationResult, RLSOperation, RLSUserContext, RowLevelSecurityPolicy } from '@objectstack/spec/security';
// Validate data
-const result = RLSAuditConfig.parse(data);
+const result = RLSEvaluationResult.parse(data);
```
----
-
-## RLSAuditConfig
-
-### Properties
-
-| Property | Type | Required | Description |
-| :--- | :--- | :--- | :--- |
-| **enabled** | `boolean` | ✅ | Enable RLS audit logging |
-| **logLevel** | `Enum<'all' \| 'denied_only' \| 'granted_only' \| 'none'>` | ✅ | Which evaluations to log |
-| **destination** | `Enum<'system_log' \| 'audit_trail' \| 'external'>` | ✅ | Audit log destination |
-| **sampleRate** | `number` | ✅ | Sampling rate (0-1) for high-traffic environments |
-| **retentionDays** | `integer` | ✅ | Audit log retention period in days |
-| **includeRowData** | `boolean` | ✅ | Include row data in audit logs (security-sensitive) |
-| **alertOnDenied** | `boolean` | ✅ | Send alerts when access is denied |
-
-
----
-
-## RLSAuditEvent
-
-### Properties
-
-| Property | Type | Required | Description |
-| :--- | :--- | :--- | :--- |
-| **timestamp** | `string` | ✅ | ISO 8601 timestamp of the evaluation |
-| **userId** | `string` | ✅ | User ID whose access was evaluated |
-| **operation** | `Enum<'select' \| 'insert' \| 'update' \| 'delete'>` | ✅ | Database operation being performed |
-| **object** | `string` | ✅ | Target object name |
-| **policyName** | `string` | ✅ | Name of the RLS policy evaluated |
-| **granted** | `boolean` | ✅ | Whether access was granted |
-| **evaluationDurationMs** | `number` | ✅ | Policy evaluation duration in milliseconds |
-| **matchedCondition** | `string` | optional | Which USING/CHECK clause matched |
-| **rowCount** | `number` | optional | Number of rows affected |
-| **metadata** | `Record` | optional | Additional audit event metadata |
-
-
----
-
-## RLSConfig
-
-### Properties
-
-| Property | Type | Required | Description |
-| :--- | :--- | :--- | :--- |
-| **enabled** | `boolean` | ✅ | Enable RLS enforcement globally |
-| **defaultPolicy** | `Enum<'deny' \| 'allow'>` | ✅ | Default action when no policies match |
-| **allowSuperuserBypass** | `boolean` | ✅ | Allow superusers to bypass RLS |
-| **bypassRoles** | `string[]` | optional | Roles that bypass RLS (see all data) |
-| **logEvaluations** | `boolean` | ✅ | Log RLS policy evaluations for debugging |
-| **cacheResults** | `boolean` | ✅ | Cache RLS evaluation results |
-| **cacheTtlSeconds** | `integer` | ✅ | Cache TTL in seconds |
-| **prefetchUserContext** | `boolean` | ✅ | Pre-fetch user context for performance |
-| **audit** | `Object` | optional | RLS audit logging configuration |
-
-
---
## RLSEvaluationResult
diff --git a/content/docs/references/system/compliance.mdx b/content/docs/references/system/compliance.mdx
deleted file mode 100644
index 9a25838179..0000000000
--- a/content/docs/references/system/compliance.mdx
+++ /dev/null
@@ -1,184 +0,0 @@
----
-title: Compliance
-description: Compliance protocol schemas
----
-
-{/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs live in the module folders under content/docs/. */}
-
-Compliance protocol for GDPR, CCPA, HIPAA, SOX, PCI-DSS
-
-
-**Source:** `packages/spec/src/system/compliance.zod.ts`
-
-
-## TypeScript Usage
-
-```typescript
-import { AuditFinding, AuditFindingSeverity, AuditFindingStatus, AuditLogConfig, AuditSchedule, ComplianceConfig, GDPRConfig, HIPAAConfig, PCIDSSConfig } from '@objectstack/spec/system';
-import type { AuditFinding, AuditFindingSeverity, AuditFindingStatus, AuditLogConfig, AuditSchedule, ComplianceConfig, GDPRConfig, HIPAAConfig, PCIDSSConfig } from '@objectstack/spec/system';
-
-// Validate data
-const result = AuditFinding.parse(data);
-```
-
----
-
-## AuditFinding
-
-Audit finding with remediation tracking per ISO 27001:2022 A.5.35
-
-### Properties
-
-| Property | Type | Required | Description |
-| :--- | :--- | :--- | :--- |
-| **id** | `string` | ✅ | Unique finding identifier |
-| **title** | `string` | ✅ | Finding title |
-| **description** | `string` | ✅ | Finding description |
-| **severity** | `Enum<'critical' \| 'major' \| 'minor' \| 'observation'>` | ✅ | Finding severity |
-| **status** | `Enum<'open' \| 'in_remediation' \| 'remediated' \| 'verified' \| 'accepted_risk' \| 'closed'>` | ✅ | Finding status |
-| **controlReference** | `string` | optional | ISO 27001 control reference |
-| **framework** | `Enum<'gdpr' \| 'hipaa' \| 'sox' \| 'pci_dss' \| 'ccpa' \| 'iso27001'>` | optional | Related compliance framework |
-| **identifiedAt** | `number` | ✅ | Identification timestamp |
-| **identifiedBy** | `string` | ✅ | Identifier (auditor name or system) |
-| **remediationPlan** | `string` | optional | Remediation plan |
-| **remediationDeadline** | `number` | optional | Remediation deadline timestamp |
-| **verifiedAt** | `number` | optional | Verification timestamp |
-| **verifiedBy** | `string` | optional | Verifier name or role |
-| **notes** | `string` | optional | Additional notes |
-
-
----
-
-## AuditFindingSeverity
-
-### Allowed Values
-
-* `critical`
-* `major`
-* `minor`
-* `observation`
-
-
----
-
-## AuditFindingStatus
-
-### Allowed Values
-
-* `open`
-* `in_remediation`
-* `remediated`
-* `verified`
-* `accepted_risk`
-* `closed`
-
-
----
-
-## AuditLogConfig
-
-Audit log configuration for compliance and security monitoring
-
-### Properties
-
-| Property | Type | Required | Description |
-| :--- | :--- | :--- | :--- |
-| **enabled** | `boolean` | ✅ | Enable audit logging |
-| **retentionDays** | `number` | ✅ | Number of days to retain audit logs |
-| **immutable** | `boolean` | ✅ | Prevent modification or deletion of audit logs |
-| **signLogs** | `boolean` | ✅ | Cryptographically sign log entries for tamper detection |
-| **events** | `Enum<'create' \| 'read' \| 'update' \| 'delete' \| 'export' \| 'permission-change' \| 'login' \| 'logout' \| 'failed-login'>[]` | ✅ | Event types to capture in the audit log |
-
-
----
-
-## AuditSchedule
-
-Audit schedule for independent security reviews per ISO 27001:2022 A.5.35
-
-### Properties
-
-| Property | Type | Required | Description |
-| :--- | :--- | :--- | :--- |
-| **id** | `string` | ✅ | Unique audit schedule identifier |
-| **title** | `string` | ✅ | Audit title |
-| **scope** | `string[]` | ✅ | Audit scope areas |
-| **framework** | `Enum<'gdpr' \| 'hipaa' \| 'sox' \| 'pci_dss' \| 'ccpa' \| 'iso27001'>` | ✅ | Target compliance framework |
-| **scheduledAt** | `number` | ✅ | Scheduled audit timestamp |
-| **completedAt** | `number` | optional | Completion timestamp |
-| **assessor** | `string` | ✅ | Assessor or audit team |
-| **isExternal** | `boolean` | ✅ | Whether this is an external audit |
-| **recurrenceMonths** | `number` | ✅ | Recurrence interval in months (0 = one-time) |
-| **findings** | `Object[]` | optional | Audit findings |
-
-
----
-
-## ComplianceConfig
-
-Unified compliance configuration spanning GDPR, HIPAA, PCI-DSS, and audit governance
-
-### Properties
-
-| Property | Type | Required | Description |
-| :--- | :--- | :--- | :--- |
-| **gdpr** | `Object` | optional | GDPR compliance settings |
-| **hipaa** | `Object` | optional | HIPAA compliance settings |
-| **pciDss** | `Object` | optional | PCI-DSS compliance settings |
-| **auditLog** | `Object` | ✅ | Audit log configuration |
-| **auditSchedules** | `Object[]` | optional | Scheduled compliance audits (A.5.35) |
-
-
----
-
-## GDPRConfig
-
-GDPR (General Data Protection Regulation) compliance configuration
-
-### Properties
-
-| Property | Type | Required | Description |
-| :--- | :--- | :--- | :--- |
-| **enabled** | `boolean` | ✅ | Enable GDPR compliance controls |
-| **dataSubjectRights** | `Object` | ✅ | Data subject rights configuration per GDPR Articles 15-21 |
-| **legalBasis** | `Enum<'consent' \| 'contract' \| 'legal-obligation' \| 'vital-interests' \| 'public-task' \| 'legitimate-interests'>` | ✅ | Legal basis for data processing under GDPR Article 6 |
-| **consentTracking** | `boolean` | ✅ | Track and record user consent |
-| **dataRetentionDays** | `number` | optional | Maximum data retention period in days |
-| **dataProcessingAgreement** | `string` | optional | URL or reference to the data processing agreement |
-
-
----
-
-## HIPAAConfig
-
-HIPAA (Health Insurance Portability and Accountability Act) compliance configuration
-
-### Properties
-
-| Property | Type | Required | Description |
-| :--- | :--- | :--- | :--- |
-| **enabled** | `boolean` | ✅ | Enable HIPAA compliance controls |
-| **phi** | `Object` | ✅ | Protected Health Information safeguards |
-| **businessAssociateAgreement** | `boolean` | ✅ | BAA is in place with third-party processors |
-
-
----
-
-## PCIDSSConfig
-
-PCI-DSS (Payment Card Industry Data Security Standard) compliance configuration
-
-### Properties
-
-| Property | Type | Required | Description |
-| :--- | :--- | :--- | :--- |
-| **enabled** | `boolean` | ✅ | Enable PCI-DSS compliance controls |
-| **level** | `Enum<'1' \| '2' \| '3' \| '4'>` | ✅ | PCI-DSS compliance level (1 = highest) |
-| **cardDataFields** | `string[]` | ✅ | Field names containing cardholder data |
-| **tokenization** | `boolean` | ✅ | Replace card data with secure tokens |
-| **encryptionInTransit** | `boolean` | ✅ | Encrypt cardholder data during transmission |
-| **encryptionAtRest** | `boolean` | ✅ | Encrypt stored cardholder data |
-
-
----
-
diff --git a/content/docs/references/system/index.mdx b/content/docs/references/system/index.mdx
index 294120d50a..d416d817d8 100644
--- a/content/docs/references/system/index.mdx
+++ b/content/docs/references/system/index.mdx
@@ -13,7 +13,6 @@ This section contains all protocol schemas for the system layer of ObjectStack.
-
@@ -27,7 +26,6 @@ This section contains all protocol schemas for the system layer of ObjectStack.
-
diff --git a/content/docs/references/system/masking.mdx b/content/docs/references/system/masking.mdx
deleted file mode 100644
index c3efcb2d36..0000000000
--- a/content/docs/references/system/masking.mdx
+++ /dev/null
@@ -1,76 +0,0 @@
----
-title: Masking
-description: Masking protocol schemas
----
-
-{/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs live in the module folders under content/docs/. */}
-
-Data masking protocol for PII protection
-
-
-**Source:** `packages/spec/src/system/masking.zod.ts`
-
-
-## TypeScript Usage
-
-```typescript
-import { MaskingConfig, MaskingRule, MaskingStrategy } from '@objectstack/spec/system';
-import type { MaskingConfig, MaskingRule, MaskingStrategy } from '@objectstack/spec/system';
-
-// Validate data
-const result = MaskingConfig.parse(data);
-```
-
----
-
-## MaskingConfig
-
-Top-level data masking configuration for PII protection
-
-### Properties
-
-| Property | Type | Required | Description |
-| :--- | :--- | :--- | :--- |
-| **enabled** | `boolean` | ✅ | Enable data masking |
-| **rules** | `Object[]` | ✅ | List of field-level masking rules |
-| **auditUnmasking** | `boolean` | ✅ | Log when masked data is accessed unmasked |
-
-
----
-
-## MaskingRule
-
-Masking rule for a single field
-
-### Properties
-
-| Property | Type | Required | Description |
-| :--- | :--- | :--- | :--- |
-| **field** | `string` | ✅ | Field name to apply masking to |
-| **strategy** | `Enum<'redact' \| 'partial' \| 'hash' \| 'tokenize' \| 'randomize' \| 'nullify' \| 'substitute'>` | ✅ | Masking strategy to use |
-| **pattern** | `string` | optional | Regex pattern for partial masking |
-| **preserveFormat** | `boolean` | ✅ | Keep the original data format after masking |
-| **preserveLength** | `boolean` | ✅ | Keep the original data length after masking |
-| **roles** | `string[]` | optional | Roles that see masked data |
-| **exemptRoles** | `string[]` | optional | Roles that see unmasked data |
-
-
----
-
-## MaskingStrategy
-
-Data masking strategy for PII protection
-
-### Allowed Values
-
-* `redact`
-* `partial`
-* `hash`
-* `tokenize`
-* `randomize`
-* `nullify`
-* `substitute`
-
-
----
-
diff --git a/content/docs/references/system/meta.json b/content/docs/references/system/meta.json
index 0cb248a689..cba7445bd7 100644
--- a/content/docs/references/system/meta.json
+++ b/content/docs/references/system/meta.json
@@ -8,7 +8,6 @@
"cache",
"change-management",
"collaboration",
- "compliance",
"core-services",
"deploy-bundle",
"disaster-recovery",
@@ -22,7 +21,6 @@
"job",
"license",
"logging",
- "masking",
"message-queue",
"metadata-loader",
"metadata-persistence",
diff --git a/packages/dogfood/test/authz-conformance.matrix.ts b/packages/dogfood/test/authz-conformance.matrix.ts
index 31a672c8df..ab107c4041 100644
--- a/packages/dogfood/test/authz-conformance.matrix.ts
+++ b/packages/dogfood/test/authz-conformance.matrix.ts
@@ -80,10 +80,18 @@ export const AUTHZ_CONFORMANCE: AuthzPrimitive[] = [
note: 'Primitive enforcement unit-proven in plugin-security/security-plugin.test.ts (ADR-0066 posture suite); the per-object declarations are pinned by platform-objects.test.ts "secure-by-default posture" so dropping the flag from a secret store fails CI, not review. Member self-service objects (sys_session, sys_api_key, sys_oauth_application, sys_two_factor) deliberately stay public-posture — the Account app reads them with a member context; row scoping (owner/tenant RLS + _self carve-outs) is their guard.' },
// ── Experimental — declared, NOT enforced (ADR-0049/0056 D8) ───────────
- { id: 'compliance-configs', summary: 'GDPR/HIPAA/PCI configs', state: 'experimental', note: 'no runtime consumer; marked [EXPERIMENTAL] (D8)' },
- { id: 'field-encryption', summary: 'at-rest field encryption', state: 'experimental', note: 'no crypto provider reads the config; marked [EXPERIMENTAL] (D8)' },
- { id: 'data-masking', summary: 'role-based data masking', state: 'experimental', note: 'FLS is the enforced field-visibility path; marked [EXPERIMENTAL] (D8)' },
- { id: 'rls-config-global', summary: 'global RLSConfig / RLSAuditEvent', state: 'experimental', note: 'not read by the RLS path; marked [EXPERIMENTAL] (D8)' },
+ { id: 'field-encryption', summary: 'at-rest field encryption', state: 'experimental',
+ note: 'no crypto provider reads the config; marked [EXPERIMENTAL] (D8). Deliberately KEPT (2026-07 D8 disposition): at-rest encryption is a real enterprise roadmap item with a stable schema shape — removing and re-adding would cost more (ADR-0087) than carrying it marked.' },
+ { id: 'agent-visibility', summary: 'AI agent `visibility` listing scope (#1901)', state: 'experimental',
+ note: 'Intentionally NOT enforced — the chat-access evaluator excludes it (service-ai agent-access.ts) and the agent list route does not filter by it. Schema + authoring form carry EXPERIMENTAL banners (2026-07) so authors are told `private` does not hide the agent; `access`/`permissions` ARE enforced at the chat route (#1884). Enforce when the agent listing surface gains owner/org semantics — #1901.' },
+
+ // ── Removed — by ADR-0056 D8 "design+enforce or remove" (2026-07) ──────
+ { id: 'compliance-configs', summary: 'GDPR/HIPAA/PCI configs', state: 'removed',
+ note: 'REMOVED from spec (system/compliance.zod.ts deleted). Compliance-grade config must never merely look live: a parsed-but-dead `gdpr:` block is a liability in an audit. A real compliance subsystem will be designed top-down (data-subject rights engine, retention enforcer) when scheduled.' },
+ { id: 'data-masking', summary: 'role-based data masking', state: 'removed',
+ note: 'REMOVED from spec (system/masking.zod.ts deleted). FLS (plugin-security field-masker) is the enforced field-visibility path; a masking/deny layer would be redesigned with the ADR-0066 ⑦/⑧ muting work anyway, so the dead config was pure drift risk.' },
+ { id: 'rls-config-global', summary: 'global RLSConfig / RLSAuditEvent', state: 'removed',
+ note: 'REMOVED from spec (rls.zod.ts — RLSConfigSchema/RLSAuditEventSchema/RLSAuditConfigSchema deleted). The enforced RLS path (plugin-security computeRlsFilter) never read them; per-policy RowLevelSecurityPolicySchema is the live surface and is unchanged.' },
{ id: 'requireAuth-default-flip', summary: 'global requireAuth default is secure-by-default (deny anonymous)', state: 'enforced',
enforcement: 'spec/api/rest-server.zod.ts requireAuth default(true) + rest/rest-server.ts normalizeConfig ?? true; explicit requireAuth:false opt-out warns at boot (rest-api-plugin)',
proof: 'showcase-anonymous-deny.dogfood.test.ts',
diff --git a/packages/spec/api-surface.json b/packages/spec/api-surface.json
index 8d0970a454..f706fa4934 100644
--- a/packages/spec/api-surface.json
+++ b/packages/spec/api-surface.json
@@ -528,17 +528,8 @@
"AuditEventTarget (type)",
"AuditEventTargetSchema (const)",
"AuditEventType (type)",
- "AuditFinding (type)",
- "AuditFindingSchema (const)",
- "AuditFindingSeveritySchema (const)",
- "AuditFindingStatusSchema (const)",
- "AuditLogConfig (type)",
- "AuditLogConfigInput (type)",
- "AuditLogConfigSchema (const)",
"AuditRetentionPolicy (type)",
"AuditRetentionPolicySchema (const)",
- "AuditSchedule (type)",
- "AuditScheduleSchema (const)",
"AuditStorageConfig (type)",
"AuditStorageConfigSchema (const)",
"AuthConfig (type)",
@@ -622,9 +613,6 @@
"CollaborativeCursorSchema (const)",
"ComplianceAuditRequirement (type)",
"ComplianceAuditRequirementSchema (const)",
- "ComplianceConfig (type)",
- "ComplianceConfigInput (type)",
- "ComplianceConfigSchema (const)",
"ComplianceEncryptionRequirement (type)",
"ComplianceEncryptionRequirementSchema (const)",
"ComplianceFramework (type)",
@@ -752,12 +740,6 @@
"FileMetadataSchema (const)",
"GCounter (type)",
"GCounterSchema (const)",
- "GDPRConfig (type)",
- "GDPRConfigInput (type)",
- "GDPRConfigSchema (const)",
- "HIPAAConfig (type)",
- "HIPAAConfigInput (type)",
- "HIPAAConfigSchema (const)",
"HistogramBucketConfig (type)",
"HistogramBucketConfigSchema (const)",
"HttpDestinationConfig (type)",
@@ -826,14 +808,6 @@
"LoggingConfig (type)",
"LoggingConfigSchema (const)",
"METADATA_FORM_REGISTRY (const)",
- "MaskingConfig (type)",
- "MaskingConfigInput (type)",
- "MaskingConfigSchema (const)",
- "MaskingRule (type)",
- "MaskingRuleInput (type)",
- "MaskingRuleSchema (const)",
- "MaskingStrategy (type)",
- "MaskingStrategySchema (const)",
"MaskingVisibilityRule (type)",
"MaskingVisibilityRuleSchema (const)",
"MessageFormat (type)",
@@ -955,9 +929,6 @@
"OpenTelemetryCompatibility (type)",
"OpenTelemetryCompatibilitySchema (const)",
"OtelExporterType (type)",
- "PCIDSSConfig (type)",
- "PCIDSSConfigInput (type)",
- "PCIDSSConfigSchema (const)",
"PKG_CONVENTIONS (const)",
"PNCounter (type)",
"PNCounterSchema (const)",
@@ -3880,12 +3851,6 @@
"PermissionSetSchema (const)",
"PlatformCapability (interface)",
"RLS (const)",
- "RLSAuditConfig (type)",
- "RLSAuditConfigSchema (const)",
- "RLSAuditEvent (type)",
- "RLSAuditEventSchema (const)",
- "RLSConfig (type)",
- "RLSConfigSchema (const)",
"RLSEvaluationResult (type)",
"RLSEvaluationResultSchema (const)",
"RLSOperation (type)",
diff --git a/packages/spec/src/ai/agent.form.ts b/packages/spec/src/ai/agent.form.ts
index 9c56665e9d..18c8cf4576 100644
--- a/packages/spec/src/ai/agent.form.ts
+++ b/packages/spec/src/ai/agent.form.ts
@@ -53,7 +53,7 @@ export const agentForm = defineForm({
collapsible: true,
collapsed: true,
fields: [
- { field: 'visibility', helpText: 'Scope: global, organization, or private' },
+ { field: 'visibility', helpText: 'EXPERIMENTAL — not enforced yet (#1901): setting "private" does not hide the agent. Use Access / Permissions below for real gating.' },
{ field: 'access', widget: 'string-tags', helpText: 'User IDs or role names who can chat with this agent' },
{ field: 'permissions', widget: 'string-tags', helpText: 'Required permissions to use this agent' },
{ field: 'tenantId', helpText: 'Restrict to specific organization ID' },
diff --git a/packages/spec/src/ai/agent.zod.ts b/packages/spec/src/ai/agent.zod.ts
index 0ceb84c8c6..9d7d7dddb4 100644
--- a/packages/spec/src/ai/agent.zod.ts
+++ b/packages/spec/src/ai/agent.zod.ts
@@ -169,7 +169,14 @@ export const AgentSchema = lazySchema(() => z.object({
/** Multi-tenancy & Visibility */
tenantId: z.string().optional().describe('Tenant/Organization ID'),
- visibility: z.enum(['global', 'organization', 'private']).default('organization'),
+ // ⚠️ EXPERIMENTAL — NOT ENFORCED (#1901, ADR-0049). The chat-access evaluator
+ // deliberately excludes `visibility` (agent-access.ts) and the agent list
+ // route does not filter by it — setting `private` does NOT hide the agent.
+ // Use `access` / `permissions` (both ENFORCED at the chat route, #1884) to
+ // actually restrict who can use an agent. Enforcement needs owner/org
+ // semantics on the listing surface first; tracked in #1901.
+ visibility: z.enum(['global', 'organization', 'private']).default('organization')
+ .describe('[EXPERIMENTAL — NOT ENFORCED, #1901] Intended listing scope. No runtime consumer yet; use access/permissions for real gating.'),
/** Autonomous Reasoning */
planning: z.object({
diff --git a/packages/spec/src/security/rls.test.ts b/packages/spec/src/security/rls.test.ts
index d52a39481e..28ce5be808 100644
--- a/packages/spec/src/security/rls.test.ts
+++ b/packages/spec/src/security/rls.test.ts
@@ -1,11 +1,8 @@
import { describe, it, expect } from 'vitest';
import {
RowLevelSecurityPolicySchema,
- RLSConfigSchema,
RLSUserContextSchema,
RLSEvaluationResultSchema,
- RLSAuditEventSchema,
- RLSAuditConfigSchema,
RLSOperation,
RLS,
type RowLevelSecurityPolicy,
@@ -172,68 +169,6 @@ describe('Row-Level Security (RLS) Protocol', () => {
});
});
- describe('RLSConfigSchema', () => {
- it('should validate minimal config', () => {
- const config = {};
- const result = RLSConfigSchema.parse(config);
-
- expect(result.enabled).toBe(true);
- expect(result.defaultPolicy).toBe('deny');
- expect(result.allowSuperuserBypass).toBe(true);
- expect(result.logEvaluations).toBe(false);
- expect(result.cacheResults).toBe(true);
- expect(result.cacheTtlSeconds).toBe(300);
- expect(result.prefetchUserContext).toBe(true);
- });
-
- it('should validate complete config', () => {
- const config = {
- enabled: true,
- defaultPolicy: 'deny' as const,
- allowSuperuserBypass: true,
- bypassRoles: ['system_admin', 'data_auditor'],
- logEvaluations: true,
- cacheResults: true,
- cacheTtlSeconds: 600,
- prefetchUserContext: true,
- };
-
- const result = RLSConfigSchema.parse(config);
- expect(result).toEqual(config);
- });
-
- it('should validate defaultPolicy options', () => {
- const denyConfig = { defaultPolicy: 'deny' as const };
- expect(RLSConfigSchema.parse(denyConfig).defaultPolicy).toBe('deny');
-
- const allowConfig = { defaultPolicy: 'allow' as const };
- expect(RLSConfigSchema.parse(allowConfig).defaultPolicy).toBe('allow');
-
- const invalidConfig = { defaultPolicy: 'reject' };
- expect(() => RLSConfigSchema.parse(invalidConfig)).toThrow();
- });
-
- it('should validate bypass roles', () => {
- const config = {
- bypassRoles: ['admin', 'superuser', 'auditor'],
- };
-
- const result = RLSConfigSchema.parse(config);
- expect(result.bypassRoles).toEqual(['admin', 'superuser', 'auditor']);
- });
-
- it('should validate cache TTL as positive integer', () => {
- const validConfig = { cacheTtlSeconds: 600 };
- expect(() => RLSConfigSchema.parse(validConfig)).not.toThrow();
-
- const invalidConfig1 = { cacheTtlSeconds: -100 };
- expect(() => RLSConfigSchema.parse(invalidConfig1)).toThrow();
-
- const invalidConfig2 = { cacheTtlSeconds: 0 };
- expect(() => RLSConfigSchema.parse(invalidConfig2)).toThrow();
- });
- });
-
describe('RLSUserContextSchema', () => {
it('should validate minimal user context', () => {
const context = {
@@ -569,157 +504,4 @@ describe('Row-Level Security (RLS) Protocol', () => {
expect(() => RowLevelSecurityPolicySchema.parse(policy)).not.toThrow();
});
});
-
- // ==========================================
- // RLS Audit Event & Config Tests
- // ==========================================
-
- describe('RLSAuditEventSchema', () => {
- it('should accept minimal audit event', () => {
- const event = RLSAuditEventSchema.parse({
- timestamp: '2024-06-15T10:30:00Z',
- userId: 'user_123',
- operation: 'select',
- object: 'account',
- policyName: 'tenant_isolation',
- granted: true,
- evaluationDurationMs: 2.5,
- });
-
- expect(event.granted).toBe(true);
- expect(event.evaluationDurationMs).toBe(2.5);
- });
-
- it('should accept full audit event', () => {
- const event = RLSAuditEventSchema.parse({
- timestamp: '2024-06-15T10:30:00Z',
- userId: 'user_456',
- operation: 'delete',
- object: 'customer',
- policyName: 'owner_access',
- granted: false,
- evaluationDurationMs: 15.3,
- matchedCondition: 'owner_id == current_user.id',
- rowCount: 0,
- metadata: { source: 'api', requestId: 'req-789' },
- });
-
- expect(event.granted).toBe(false);
- expect(event.matchedCondition).toBe('owner_id == current_user.id');
- expect(event.rowCount).toBe(0);
- expect(event.metadata?.source).toBe('api');
- });
-
- it('should accept all operation types', () => {
- const ops = ['select', 'insert', 'update', 'delete'] as const;
- ops.forEach(operation => {
- expect(() => RLSAuditEventSchema.parse({
- timestamp: '2024-01-01T00:00:00Z',
- userId: 'u1',
- operation,
- object: 'test',
- policyName: 'test_policy',
- granted: true,
- evaluationDurationMs: 1,
- })).not.toThrow();
- });
- });
- });
-
- describe('RLSAuditConfigSchema', () => {
- it('should accept full audit config', () => {
- const config = RLSAuditConfigSchema.parse({
- enabled: true,
- logLevel: 'all',
- destination: 'audit_trail',
- sampleRate: 1.0,
- retentionDays: 365,
- includeRowData: false,
- alertOnDenied: true,
- });
-
- expect(config.enabled).toBe(true);
- expect(config.logLevel).toBe('all');
- expect(config.destination).toBe('audit_trail');
- expect(config.sampleRate).toBe(1.0);
- expect(config.retentionDays).toBe(365);
- });
-
- it('should accept all log levels', () => {
- const levels = ['all', 'denied_only', 'granted_only', 'none'] as const;
- levels.forEach(logLevel => {
- const config = RLSAuditConfigSchema.parse({
- enabled: true,
- logLevel,
- destination: 'system_log',
- sampleRate: 0.5,
- });
- expect(config.logLevel).toBe(logLevel);
- });
- });
-
- it('should accept all destinations', () => {
- const destinations = ['system_log', 'audit_trail', 'external'] as const;
- destinations.forEach(destination => {
- const config = RLSAuditConfigSchema.parse({
- enabled: true,
- logLevel: 'all',
- destination,
- sampleRate: 1,
- });
- expect(config.destination).toBe(destination);
- });
- });
-
- it('should enforce sampleRate range', () => {
- expect(() => RLSAuditConfigSchema.parse({
- enabled: true,
- logLevel: 'all',
- destination: 'system_log',
- sampleRate: -0.1,
- })).toThrow();
-
- expect(() => RLSAuditConfigSchema.parse({
- enabled: true,
- logLevel: 'all',
- destination: 'system_log',
- sampleRate: 1.1,
- })).toThrow();
- });
-
- it('should apply defaults for includeRowData and alertOnDenied', () => {
- const config = RLSAuditConfigSchema.parse({
- enabled: true,
- logLevel: 'denied_only',
- destination: 'external',
- sampleRate: 0.1,
- });
-
- expect(config.includeRowData).toBe(false);
- expect(config.alertOnDenied).toBe(true);
- expect(config.retentionDays).toBe(90);
- });
- });
-
- describe('RLSConfigSchema with audit', () => {
- it('should accept config with audit field', () => {
- const config = RLSConfigSchema.parse({
- enabled: true,
- audit: {
- enabled: true,
- logLevel: 'denied_only',
- destination: 'audit_trail',
- sampleRate: 0.5,
- },
- });
-
- expect(config.audit?.enabled).toBe(true);
- expect(config.audit?.logLevel).toBe('denied_only');
- });
-
- it('should accept config without audit field', () => {
- const config = RLSConfigSchema.parse({});
- expect(config.audit).toBeUndefined();
- });
- });
});
diff --git a/packages/spec/src/security/rls.zod.ts b/packages/spec/src/security/rls.zod.ts
index dbbf5119ef..555d9b594c 100644
--- a/packages/spec/src/security/rls.zod.ts
+++ b/packages/spec/src/security/rls.zod.ts
@@ -417,205 +417,12 @@ export const RowLevelSecurityPolicySchema = lazySchema(() => z.object({
// since 'all' and mixed operation types are valid
}));
-/**
- * RLS Audit Event Schema
- *
- * Records a single RLS policy evaluation event for compliance and debugging.
- */
-// ⚠️ EXPERIMENTAL — NOT ENFORCED (ADR-0056). RLS audit events are not emitted by the
-// runtime RLS path — no consumer writes these. Authoring does NOT change behaviour (per ADR-0049).
-export const RLSAuditEventSchema = lazySchema(() => z.object({
- /** ISO 8601 timestamp of the evaluation */
- timestamp: z.string()
- .describe('ISO 8601 timestamp of the evaluation'),
-
- /** ID of the user whose access was evaluated */
- userId: z.string()
- .describe('User ID whose access was evaluated'),
-
- /** Database operation being performed */
- operation: z.enum(['select', 'insert', 'update', 'delete'])
- .describe('Database operation being performed'),
-
- /** Target object (table) name */
- object: z.string()
- .describe('Target object name'),
-
- /** Name of the RLS policy evaluated */
- policyName: z.string()
- .describe('Name of the RLS policy evaluated'),
-
- /** Whether access was granted */
- granted: z.boolean()
- .describe('Whether access was granted'),
-
- /** Time taken to evaluate the policy in milliseconds */
- evaluationDurationMs: z.number()
- .describe('Policy evaluation duration in milliseconds'),
-
- /** Which USING/CHECK clause matched */
- matchedCondition: z.string()
- .optional()
- .describe('Which USING/CHECK clause matched'),
-
- /** Number of rows affected by the operation */
- rowCount: z.number()
- .optional()
- .describe('Number of rows affected'),
-
- /** Additional metadata for the audit event */
- metadata: z.record(z.string(), z.unknown())
- .optional()
- .describe('Additional audit event metadata'),
-}));
-
-export type RLSAuditEvent = z.infer;
-
-/**
- * RLS Audit Configuration Schema
- *
- * Controls how RLS policy evaluations are logged and monitored.
- */
-export const RLSAuditConfigSchema = lazySchema(() => z.object({
- /** Enable RLS audit logging */
- enabled: z.boolean()
- .describe('Enable RLS audit logging'),
-
- /** Which evaluations to log */
- logLevel: z.enum(['all', 'denied_only', 'granted_only', 'none'])
- .describe('Which evaluations to log'),
-
- /** Where to send audit logs */
- destination: z.enum(['system_log', 'audit_trail', 'external'])
- .describe('Audit log destination'),
-
- /** Sampling rate for high-traffic environments (0-1) */
- sampleRate: z.number()
- .min(0)
- .max(1)
- .describe('Sampling rate (0-1) for high-traffic environments'),
-
- /** Number of days to retain audit logs */
- retentionDays: z.number()
- .int()
- .default(90)
- .describe('Audit log retention period in days'),
-
- /** Whether to include row data in audit logs (security-sensitive) */
- includeRowData: z.boolean()
- .default(false)
- .describe('Include row data in audit logs (security-sensitive)'),
-
- /** Alert when access is denied */
- alertOnDenied: z.boolean()
- .default(true)
- .describe('Send alerts when access is denied'),
-}));
-
-export type RLSAuditConfig = z.infer;
-
-/**
- * RLS Configuration Schema
- *
- * Global configuration for the Row-Level Security system.
- * Defines how RLS is enforced across the entire platform.
- */
-// ⚠️ EXPERIMENTAL — NOT ENFORCED (ADR-0056). Global RLSConfig (defaultPolicy / bypassRoles /
-// caching / audit) is not read by the SecurityPlugin RLS path. Authoring does NOT change behaviour (per ADR-0049).
-export const RLSConfigSchema = lazySchema(() => z.object({
- /**
- * Global RLS enable/disable flag.
- * When false, all RLS policies are ignored (use with caution!).
- *
- * @default true
- */
- enabled: z.boolean()
- .default(true)
- .describe('Enable RLS enforcement globally'),
-
- /**
- * Default behavior when no policies match.
- *
- * - **deny**: Deny access (secure default)
- * - **allow**: Allow access (permissive mode, not recommended)
- *
- * @default "deny"
- */
- defaultPolicy: z.enum(['deny', 'allow'])
- .default('deny')
- .describe('Default action when no policies match'),
-
- /**
- * Whether to allow superusers to bypass RLS.
- * Superusers include system administrators and service accounts.
- *
- * @default true
- */
- allowSuperuserBypass: z.boolean()
- .default(true)
- .describe('Allow superusers to bypass RLS'),
-
- /**
- * List of roles that can bypass RLS.
- * Users with these roles see all records regardless of policies.
- *
- * @example ["system_admin", "data_auditor"]
- */
- bypassRoles: z.array(z.string())
- .optional()
- .describe('Roles that bypass RLS (see all data)'),
-
- /**
- * Whether to log RLS policy evaluations.
- * Useful for debugging and auditing.
- * Can impact performance if enabled globally.
- *
- * @default false
- */
- logEvaluations: z.boolean()
- .default(false)
- .describe('Log RLS policy evaluations for debugging'),
-
- /**
- * Cache RLS policy evaluation results.
- * Can improve performance for frequently accessed records.
- * Cache is invalidated when policies change or user context changes.
- *
- * @default true
- */
- cacheResults: z.boolean()
- .default(true)
- .describe('Cache RLS evaluation results'),
-
- /**
- * Cache TTL in seconds.
- * How long to cache RLS evaluation results.
- *
- * @default 300 (5 minutes)
- */
- cacheTtlSeconds: z.number()
- .int()
- .positive()
- .default(300)
- .describe('Cache TTL in seconds'),
-
- /**
- * Performance optimization: Pre-fetch user context.
- * Load user context once per request instead of per-query.
- *
- * @default true
- */
- prefetchUserContext: z.boolean()
- .default(true)
- .describe('Pre-fetch user context for performance'),
-
- /**
- * Audit logging configuration for RLS evaluations.
- */
- audit: RLSAuditConfigSchema
- .optional()
- .describe('RLS audit logging configuration'),
-}));
+// RLSAuditEventSchema / RLSAuditConfigSchema / RLSConfigSchema were REMOVED
+// per ADR-0056 D8 "design+enforce or remove": the global RLSConfig
+// (defaultPolicy/bypassRoles/caching/audit) and the audit-event shapes were
+// never read by the enforced RLS path (plugin-security computeRlsFilter) —
+// declared-but-inert config. Per-policy RLS (RowLevelSecurityPolicySchema,
+// below/above) is the live, enforced surface and is unchanged.
/**
* User Context Schema
@@ -723,7 +530,6 @@ export const RLSEvaluationResultSchema = lazySchema(() => z.object({
* Type exports
*/
export type RowLevelSecurityPolicy = z.infer;
-export type RLSConfig = z.infer;
export type RLSUserContext = z.infer;
export type RLSEvaluationResult = z.infer;
diff --git a/packages/spec/src/system/compliance.test.ts b/packages/spec/src/system/compliance.test.ts
deleted file mode 100644
index b027a412a3..0000000000
--- a/packages/spec/src/system/compliance.test.ts
+++ /dev/null
@@ -1,407 +0,0 @@
-import { describe, it, expect } from 'vitest';
-import {
- GDPRConfigSchema,
- HIPAAConfigSchema,
- PCIDSSConfigSchema,
- AuditLogConfigSchema,
- ComplianceConfigSchema,
- AuditFindingSeveritySchema,
- AuditFindingStatusSchema,
- AuditFindingSchema,
- AuditScheduleSchema,
-} from './compliance.zod';
-
-describe('GDPRConfigSchema', () => {
- it('should accept valid GDPR config with defaults', () => {
- const config = GDPRConfigSchema.parse({
- enabled: true,
- dataSubjectRights: {},
- legalBasis: 'consent',
- });
-
- expect(config.enabled).toBe(true);
- expect(config.dataSubjectRights.rightToAccess).toBe(true);
- expect(config.dataSubjectRights.rightToRectification).toBe(true);
- expect(config.dataSubjectRights.rightToErasure).toBe(true);
- expect(config.dataSubjectRights.rightToRestriction).toBe(true);
- expect(config.dataSubjectRights.rightToPortability).toBe(true);
- expect(config.dataSubjectRights.rightToObjection).toBe(true);
- expect(config.consentTracking).toBe(true);
- });
-
- it('should accept all legal basis values', () => {
- const bases = [
- 'consent', 'contract', 'legal-obligation',
- 'vital-interests', 'public-task', 'legitimate-interests',
- ];
-
- bases.forEach((basis) => {
- expect(() => GDPRConfigSchema.parse({
- enabled: true,
- dataSubjectRights: {},
- legalBasis: basis,
- })).not.toThrow();
- });
- });
-
- it('should accept optional fields', () => {
- const config = GDPRConfigSchema.parse({
- enabled: true,
- dataSubjectRights: {},
- legalBasis: 'consent',
- dataRetentionDays: 365,
- dataProcessingAgreement: 'https://example.com/dpa',
- });
-
- expect(config.dataRetentionDays).toBe(365);
- expect(config.dataProcessingAgreement).toBe('https://example.com/dpa');
- });
-
- it('should reject invalid legal basis', () => {
- expect(() => GDPRConfigSchema.parse({
- enabled: true,
- dataSubjectRights: {},
- legalBasis: 'invalid',
- })).toThrow();
- });
-
- it('should reject missing required fields', () => {
- expect(() => GDPRConfigSchema.parse({})).toThrow();
- expect(() => GDPRConfigSchema.parse({ enabled: true })).toThrow();
- });
-});
-
-describe('HIPAAConfigSchema', () => {
- it('should accept valid HIPAA config with defaults', () => {
- const config = HIPAAConfigSchema.parse({
- enabled: true,
- phi: {},
- });
-
- expect(config.enabled).toBe(true);
- expect(config.phi.encryption).toBe(true);
- expect(config.phi.accessControl).toBe(true);
- expect(config.phi.auditTrail).toBe(true);
- expect(config.phi.backupAndRecovery).toBe(true);
- expect(config.businessAssociateAgreement).toBe(false);
- });
-
- it('should accept full configuration', () => {
- const config = HIPAAConfigSchema.parse({
- enabled: true,
- phi: {
- encryption: false,
- accessControl: true,
- auditTrail: true,
- backupAndRecovery: false,
- },
- businessAssociateAgreement: true,
- });
-
- expect(config.phi.encryption).toBe(false);
- expect(config.phi.backupAndRecovery).toBe(false);
- expect(config.businessAssociateAgreement).toBe(true);
- });
-
- it('should reject missing required fields', () => {
- expect(() => HIPAAConfigSchema.parse({})).toThrow();
- expect(() => HIPAAConfigSchema.parse({ enabled: true })).toThrow();
- });
-});
-
-describe('PCIDSSConfigSchema', () => {
- it('should accept valid PCI-DSS config with defaults', () => {
- const config = PCIDSSConfigSchema.parse({
- enabled: true,
- level: '1',
- cardDataFields: ['card_number', 'cvv'],
- });
-
- expect(config.enabled).toBe(true);
- expect(config.level).toBe('1');
- expect(config.cardDataFields).toEqual(['card_number', 'cvv']);
- expect(config.tokenization).toBe(true);
- expect(config.encryptionInTransit).toBe(true);
- expect(config.encryptionAtRest).toBe(true);
- });
-
- it('should accept all compliance levels', () => {
- const levels = ['1', '2', '3', '4'];
-
- levels.forEach((level) => {
- expect(() => PCIDSSConfigSchema.parse({
- enabled: true,
- level,
- cardDataFields: [],
- })).not.toThrow();
- });
- });
-
- it('should reject invalid level', () => {
- expect(() => PCIDSSConfigSchema.parse({
- enabled: true,
- level: '5',
- cardDataFields: [],
- })).toThrow();
- });
-
- it('should reject missing required fields', () => {
- expect(() => PCIDSSConfigSchema.parse({})).toThrow();
- expect(() => PCIDSSConfigSchema.parse({ enabled: true })).toThrow();
- expect(() => PCIDSSConfigSchema.parse({ enabled: true, level: '1' })).toThrow();
- });
-});
-
-describe('AuditLogConfigSchema', () => {
- it('should accept valid config with defaults', () => {
- const config = AuditLogConfigSchema.parse({
- events: ['create', 'update', 'delete'],
- });
-
- expect(config.enabled).toBe(true);
- expect(config.retentionDays).toBe(365);
- expect(config.immutable).toBe(true);
- expect(config.signLogs).toBe(false);
- expect(config.events).toEqual(['create', 'update', 'delete']);
- });
-
- it('should accept all event types', () => {
- const events = [
- 'create', 'read', 'update', 'delete', 'export',
- 'permission-change', 'login', 'logout', 'failed-login',
- ];
-
- expect(() => AuditLogConfigSchema.parse({ events })).not.toThrow();
- });
-
- it('should reject invalid event type', () => {
- expect(() => AuditLogConfigSchema.parse({
- events: ['invalid-event'],
- })).toThrow();
- });
-
- it('should reject missing events', () => {
- expect(() => AuditLogConfigSchema.parse({})).toThrow();
- });
-});
-
-describe('ComplianceConfigSchema', () => {
- it('should accept minimal configuration with required auditLog', () => {
- const config = ComplianceConfigSchema.parse({
- auditLog: {
- events: ['create', 'update'],
- },
- });
-
- expect(config.gdpr).toBeUndefined();
- expect(config.hipaa).toBeUndefined();
- expect(config.pciDss).toBeUndefined();
- expect(config.auditLog).toBeDefined();
- expect(config.auditLog.events).toEqual(['create', 'update']);
- });
-
- it('should accept full configuration', () => {
- const config = ComplianceConfigSchema.parse({
- gdpr: {
- enabled: true,
- dataSubjectRights: {},
- legalBasis: 'consent',
- },
- hipaa: {
- enabled: true,
- phi: {},
- },
- pciDss: {
- enabled: true,
- level: '1',
- cardDataFields: ['card_number'],
- },
- auditLog: {
- events: ['create', 'read', 'update', 'delete'],
- },
- });
-
- expect(config.gdpr?.enabled).toBe(true);
- expect(config.hipaa?.enabled).toBe(true);
- expect(config.pciDss?.enabled).toBe(true);
- expect(config.auditLog.events).toHaveLength(4);
- });
-
- it('should reject missing auditLog', () => {
- expect(() => ComplianceConfigSchema.parse({})).toThrow();
- });
-
- it('should accept configuration with audit schedules', () => {
- const config = ComplianceConfigSchema.parse({
- auditLog: {
- events: ['create', 'update'],
- },
- auditSchedules: [
- {
- id: 'AUDIT-2024-Q1',
- title: 'Q1 ISO 27001 Internal Audit',
- scope: ['access_control', 'encryption'],
- framework: 'iso27001',
- scheduledAt: 1711929600000,
- assessor: 'internal_audit_team',
- },
- ],
- });
-
- expect(config.auditSchedules).toHaveLength(1);
- expect(config.auditSchedules![0].framework).toBe('iso27001');
- });
-});
-
-describe('AuditFindingSeveritySchema', () => {
- it('should accept all valid severities', () => {
- const severities = ['critical', 'major', 'minor', 'observation'];
-
- severities.forEach((severity) => {
- expect(() => AuditFindingSeveritySchema.parse(severity)).not.toThrow();
- });
- });
-
- it('should reject invalid severity', () => {
- expect(() => AuditFindingSeveritySchema.parse('warning')).toThrow();
- });
-});
-
-describe('AuditFindingStatusSchema', () => {
- it('should accept all valid statuses', () => {
- const statuses = ['open', 'in_remediation', 'remediated', 'verified', 'accepted_risk', 'closed'];
-
- statuses.forEach((status) => {
- expect(() => AuditFindingStatusSchema.parse(status)).not.toThrow();
- });
- });
-
- it('should reject invalid status', () => {
- expect(() => AuditFindingStatusSchema.parse('pending')).toThrow();
- });
-});
-
-describe('AuditFindingSchema', () => {
- it('should accept valid finding', () => {
- const finding = AuditFindingSchema.parse({
- id: 'FIND-2024-001',
- title: 'Insufficient access logging',
- description: 'PHI access events are not being logged for HIPAA compliance',
- severity: 'major',
- status: 'in_remediation',
- controlReference: 'A.8.15',
- framework: 'iso27001',
- identifiedAt: 1704067200000,
- identifiedBy: 'external_auditor',
- remediationPlan: 'Implement audit logging for all PHI access events',
- remediationDeadline: 1706745600000,
- });
-
- expect(finding.severity).toBe('major');
- expect(finding.framework).toBe('iso27001');
- });
-
- it('should accept minimal finding', () => {
- const finding = AuditFindingSchema.parse({
- id: 'FIND-2024-002',
- title: 'Missing encryption',
- description: 'Field-level encryption not enabled',
- severity: 'minor',
- status: 'open',
- identifiedAt: Date.now(),
- identifiedBy: 'internal_audit',
- });
-
- expect(finding.controlReference).toBeUndefined();
- expect(finding.remediationPlan).toBeUndefined();
- });
-
- it('should accept verified finding', () => {
- const finding = AuditFindingSchema.parse({
- id: 'FIND-2024-003',
- title: 'Weak password policy',
- description: 'Password minimum length below 12 characters',
- severity: 'observation',
- status: 'verified',
- identifiedAt: 1704067200000,
- identifiedBy: 'auditor',
- verifiedAt: 1706745600000,
- verifiedBy: 'senior_auditor',
- notes: 'Password policy updated and verified',
- });
-
- expect(finding.verifiedAt).toBe(1706745600000);
- expect(finding.verifiedBy).toBe('senior_auditor');
- });
-
- it('should reject missing required fields', () => {
- expect(() => AuditFindingSchema.parse({})).toThrow();
- });
-});
-
-describe('AuditScheduleSchema', () => {
- it('should accept valid schedule with defaults', () => {
- const schedule = AuditScheduleSchema.parse({
- id: 'AUDIT-2024-Q1',
- title: 'Q1 ISO 27001 Internal Audit',
- scope: ['access_control', 'encryption', 'incident_response'],
- framework: 'iso27001',
- scheduledAt: 1711929600000,
- assessor: 'internal_audit_team',
- });
-
- expect(schedule.isExternal).toBe(false);
- expect(schedule.recurrenceMonths).toBe(0);
- expect(schedule.findings).toBeUndefined();
- });
-
- it('should accept full schedule with findings', () => {
- const schedule = AuditScheduleSchema.parse({
- id: 'AUDIT-2024-EXT',
- title: 'Annual External ISO 27001 Audit',
- scope: ['all_controls'],
- framework: 'iso27001',
- scheduledAt: 1711929600000,
- completedAt: 1712534400000,
- assessor: 'External Audit Firm LLC',
- isExternal: true,
- recurrenceMonths: 12,
- findings: [
- {
- id: 'FIND-001',
- title: 'Missing incident response plan',
- description: 'No documented incident response procedure',
- severity: 'major',
- status: 'open',
- controlReference: 'A.5.24',
- framework: 'iso27001',
- identifiedAt: 1712534400000,
- identifiedBy: 'External Audit Firm LLC',
- },
- ],
- });
-
- expect(schedule.isExternal).toBe(true);
- expect(schedule.recurrenceMonths).toBe(12);
- expect(schedule.findings).toHaveLength(1);
- });
-
- it('should accept all framework values', () => {
- const frameworks = ['gdpr', 'hipaa', 'sox', 'pci_dss', 'ccpa', 'iso27001'];
-
- frameworks.forEach((framework) => {
- expect(() => AuditScheduleSchema.parse({
- id: `AUDIT-${framework}`,
- title: `${framework} audit`,
- scope: ['general'],
- framework,
- scheduledAt: Date.now(),
- assessor: 'auditor',
- })).not.toThrow();
- });
- });
-
- it('should reject missing required fields', () => {
- expect(() => AuditScheduleSchema.parse({})).toThrow();
- });
-});
diff --git a/packages/spec/src/system/compliance.zod.ts b/packages/spec/src/system/compliance.zod.ts
deleted file mode 100644
index e186a1d371..0000000000
--- a/packages/spec/src/system/compliance.zod.ts
+++ /dev/null
@@ -1,297 +0,0 @@
-// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
-
-import { z } from 'zod';
-
-// ⚠️ EXPERIMENTAL — NOT ENFORCED (ADR-0056). The GDPR/HIPAA/PCI-DSS schemas below
-// are parsed but have NO runtime consumer — no data-subject-rights engine,
-// retention enforcer, BAA gate, or tokenizer reads them. Authoring them does NOT
-// change behaviour; recorded as roadmap intent (M2+). Do not rely on them for
-// compliance until a consumer lands (per ADR-0049 enforce-or-remove).
-import { ComplianceFrameworkSchema } from './security-context.zod';
-
-/**
- * Compliance protocol for GDPR, CCPA, HIPAA, SOX, PCI-DSS
- */
-import { lazySchema } from '../shared/lazy-schema';
-export const GDPRConfigSchema = lazySchema(() => z.object({
- enabled: z.boolean().describe('Enable GDPR compliance controls'),
- dataSubjectRights: z.object({
- rightToAccess: z.boolean().default(true).describe('Allow data subjects to access their data'),
- rightToRectification: z.boolean().default(true).describe('Allow data subjects to correct their data'),
- rightToErasure: z.boolean().default(true).describe('Allow data subjects to request deletion'),
- rightToRestriction: z.boolean().default(true).describe('Allow data subjects to restrict processing'),
- rightToPortability: z.boolean().default(true).describe('Allow data subjects to export their data'),
- rightToObjection: z.boolean().default(true).describe('Allow data subjects to object to processing'),
- }).describe('Data subject rights configuration per GDPR Articles 15-21'),
- legalBasis: z.enum([
- 'consent',
- 'contract',
- 'legal-obligation',
- 'vital-interests',
- 'public-task',
- 'legitimate-interests',
- ]).describe('Legal basis for data processing under GDPR Article 6'),
- consentTracking: z.boolean().default(true).describe('Track and record user consent'),
- dataRetentionDays: z.number().optional().describe('Maximum data retention period in days'),
- dataProcessingAgreement: z.string().optional().describe('URL or reference to the data processing agreement'),
-}).describe('GDPR (General Data Protection Regulation) compliance configuration'));
-
-export type GDPRConfig = z.infer;
-export type GDPRConfigInput = z.input;
-
-export const HIPAAConfigSchema = lazySchema(() => z.object({
- enabled: z.boolean().describe('Enable HIPAA compliance controls'),
- phi: z.object({
- encryption: z.boolean().default(true).describe('Encrypt Protected Health Information at rest'),
- accessControl: z.boolean().default(true).describe('Enforce role-based access to PHI'),
- auditTrail: z.boolean().default(true).describe('Log all PHI access events'),
- backupAndRecovery: z.boolean().default(true).describe('Enable PHI backup and disaster recovery'),
- }).describe('Protected Health Information safeguards'),
- businessAssociateAgreement: z.boolean().default(false).describe('BAA is in place with third-party processors'),
-}).describe('HIPAA (Health Insurance Portability and Accountability Act) compliance configuration'));
-
-export type HIPAAConfig = z.infer;
-export type HIPAAConfigInput = z.input;
-
-export const PCIDSSConfigSchema = lazySchema(() => z.object({
- enabled: z.boolean().describe('Enable PCI-DSS compliance controls'),
- level: z.enum(['1', '2', '3', '4']).describe('PCI-DSS compliance level (1 = highest)'),
- cardDataFields: z.array(z.string()).describe('Field names containing cardholder data'),
- tokenization: z.boolean().default(true).describe('Replace card data with secure tokens'),
- encryptionInTransit: z.boolean().default(true).describe('Encrypt cardholder data during transmission'),
- encryptionAtRest: z.boolean().default(true).describe('Encrypt stored cardholder data'),
-}).describe('PCI-DSS (Payment Card Industry Data Security Standard) compliance configuration'));
-
-export type PCIDSSConfig = z.infer;
-export type PCIDSSConfigInput = z.input;
-
-export const AuditLogConfigSchema = lazySchema(() => z.object({
- enabled: z.boolean().default(true).describe('Enable audit logging'),
- retentionDays: z.number().default(365).describe('Number of days to retain audit logs'),
- immutable: z.boolean().default(true).describe('Prevent modification or deletion of audit logs'),
- signLogs: z.boolean().default(false).describe('Cryptographically sign log entries for tamper detection'),
- events: z.array(z.enum([
- 'create',
- 'read',
- 'update',
- 'delete',
- 'export',
- 'permission-change',
- 'login',
- 'logout',
- 'failed-login',
- ])).describe('Event types to capture in the audit log'),
-}).describe('Audit log configuration for compliance and security monitoring'));
-
-export type AuditLogConfig = z.infer;
-export type AuditLogConfigInput = z.input;
-
-/**
- * Audit Finding Severity Schema
- *
- * Severity classification for audit findings.
- */
-export const AuditFindingSeveritySchema = lazySchema(() => z.enum([
- 'critical', // Immediate remediation required
- 'major', // Significant non-conformity
- 'minor', // Minor non-conformity
- 'observation', // Improvement opportunity
-]));
-
-/**
- * Audit Finding Status Schema
- *
- * Lifecycle status of an audit finding.
- */
-export const AuditFindingStatusSchema = lazySchema(() => z.enum([
- 'open', // Finding identified, not yet addressed
- 'in_remediation', // Remediation in progress
- 'remediated', // Remediation completed, pending verification
- 'verified', // Remediation verified and accepted
- 'accepted_risk', // Risk accepted by management
- 'closed', // Finding closed
-]));
-
-/**
- * Audit Finding Schema (A.5.35)
- *
- * Individual finding from a compliance or security audit.
- * Supports tracking from discovery through remediation and verification.
- *
- * @example
- * ```json
- * {
- * "id": "FIND-2024-001",
- * "title": "Insufficient access logging",
- * "description": "PHI access events are not being logged for HIPAA compliance",
- * "severity": "major",
- * "status": "in_remediation",
- * "controlReference": "A.8.15",
- * "framework": "iso27001",
- * "identifiedAt": 1704067200000,
- * "identifiedBy": "external_auditor",
- * "remediationPlan": "Implement audit logging for all PHI access events",
- * "remediationDeadline": 1706745600000
- * }
- * ```
- */
-export const AuditFindingSchema = lazySchema(() => z.object({
- /**
- * Unique finding identifier
- */
- id: z.string().describe('Unique finding identifier'),
-
- /**
- * Short descriptive title
- */
- title: z.string().describe('Finding title'),
-
- /**
- * Detailed description of the finding
- */
- description: z.string().describe('Finding description'),
-
- /**
- * Finding severity
- */
- severity: AuditFindingSeveritySchema.describe('Finding severity'),
-
- /**
- * Current status
- */
- status: AuditFindingStatusSchema.describe('Finding status'),
-
- /**
- * ISO 27001 control reference (e.g., "A.5.35", "A.8.15")
- */
- controlReference: z.string().optional().describe('ISO 27001 control reference'),
-
- /**
- * Compliance framework
- */
- framework: ComplianceFrameworkSchema.optional()
- .describe('Related compliance framework'),
-
- /**
- * Timestamp when finding was identified (Unix milliseconds)
- */
- identifiedAt: z.number().describe('Identification timestamp'),
-
- /**
- * User or entity who identified the finding
- */
- identifiedBy: z.string().describe('Identifier (auditor name or system)'),
-
- /**
- * Planned remediation actions
- */
- remediationPlan: z.string().optional().describe('Remediation plan'),
-
- /**
- * Remediation deadline (Unix milliseconds)
- */
- remediationDeadline: z.number().optional().describe('Remediation deadline timestamp'),
-
- /**
- * Timestamp when remediation was verified (Unix milliseconds)
- */
- verifiedAt: z.number().optional().describe('Verification timestamp'),
-
- /**
- * Verifier name or role
- */
- verifiedBy: z.string().optional().describe('Verifier name or role'),
-
- /**
- * Notes or comments
- */
- notes: z.string().optional().describe('Additional notes'),
-}).describe('Audit finding with remediation tracking per ISO 27001:2022 A.5.35'));
-
-export type AuditFinding = z.infer;
-
-/**
- * Audit Schedule Schema (A.5.35)
- *
- * Defines audit scheduling for independent information security reviews.
- * Supports recurring audits, scope definition, and assessor assignment.
- *
- * @example
- * ```json
- * {
- * "id": "AUDIT-2024-Q1",
- * "title": "Q1 ISO 27001 Internal Audit",
- * "scope": ["access_control", "encryption", "incident_response"],
- * "framework": "iso27001",
- * "scheduledAt": 1711929600000,
- * "assessor": "internal_audit_team",
- * "recurrenceMonths": 3
- * }
- * ```
- */
-export const AuditScheduleSchema = lazySchema(() => z.object({
- /**
- * Unique audit schedule identifier
- */
- id: z.string().describe('Unique audit schedule identifier'),
-
- /**
- * Audit title or name
- */
- title: z.string().describe('Audit title'),
-
- /**
- * Scope of areas to audit
- */
- scope: z.array(z.string()).describe('Audit scope areas'),
-
- /**
- * Target compliance framework
- */
- framework: ComplianceFrameworkSchema
- .describe('Target compliance framework'),
-
- /**
- * Scheduled audit date (Unix milliseconds)
- */
- scheduledAt: z.number().describe('Scheduled audit timestamp'),
-
- /**
- * Actual completion date (Unix milliseconds)
- */
- completedAt: z.number().optional().describe('Completion timestamp'),
-
- /**
- * Assessor name, team, or external firm
- */
- assessor: z.string().describe('Assessor or audit team'),
-
- /**
- * Whether this is an external (independent) audit
- */
- isExternal: z.boolean().default(false).describe('Whether this is an external audit'),
-
- /**
- * Recurrence interval in months (0 = one-time)
- */
- recurrenceMonths: z.number().default(0).describe('Recurrence interval in months (0 = one-time)'),
-
- /**
- * Findings from this audit
- */
- findings: z.array(AuditFindingSchema).optional().describe('Audit findings'),
-}).describe('Audit schedule for independent security reviews per ISO 27001:2022 A.5.35'));
-
-export type AuditSchedule = z.infer;
-
-export const ComplianceConfigSchema = lazySchema(() => z.object({
- gdpr: GDPRConfigSchema.optional().describe('GDPR compliance settings'),
- hipaa: HIPAAConfigSchema.optional().describe('HIPAA compliance settings'),
- pciDss: PCIDSSConfigSchema.optional().describe('PCI-DSS compliance settings'),
- auditLog: AuditLogConfigSchema.describe('Audit log configuration'),
- auditSchedules: z.array(AuditScheduleSchema).optional()
- .describe('Scheduled compliance audits (A.5.35)'),
-}).describe('Unified compliance configuration spanning GDPR, HIPAA, PCI-DSS, and audit governance'));
-
-export type ComplianceConfig = z.infer;
-export type ComplianceConfigInput = z.input;
diff --git a/packages/spec/src/system/index.ts b/packages/spec/src/system/index.ts
index 0ea9265a35..b8fdb07243 100644
--- a/packages/spec/src/system/index.ts
+++ b/packages/spec/src/system/index.ts
@@ -34,9 +34,13 @@ export * from './email-config.zod';
export * from './email-template.zod';
export * from './email-template.form';
export * from './metadata-form-registry';
-export * from './compliance.zod';
+// compliance.zod (GDPR/HIPAA/PCI configs) and masking.zod (role-based data
+// masking) were REMOVED per ADR-0056 D8 "design+enforce or remove": both were
+// declared-but-never-enforced (no runtime consumer), and compliance-grade
+// configuration must never merely LOOK live. FLS (plugin-security) is the
+// enforced field-visibility mechanism; a masking/deny layer arrives with
+// ADR-0066 ⑦/⑧ if needed. encryption.zod stays (EXPERIMENTAL — roadmap).
export * from './encryption.zod';
-export * from './masking.zod';
export * from './security-context.zod';
export * from './incident-response.zod';
export * from './supplier-security.zod';
diff --git a/packages/spec/src/system/masking.test.ts b/packages/spec/src/system/masking.test.ts
deleted file mode 100644
index 8832c10a42..0000000000
--- a/packages/spec/src/system/masking.test.ts
+++ /dev/null
@@ -1,96 +0,0 @@
-import { describe, it, expect } from 'vitest';
-import {
- MaskingStrategySchema,
- MaskingRuleSchema,
- MaskingConfigSchema,
-} from './masking.zod';
-
-describe('MaskingStrategySchema', () => {
- it('should accept valid strategies', () => {
- const strategies = ['redact', 'partial', 'hash', 'tokenize', 'randomize', 'nullify', 'substitute'];
-
- strategies.forEach((strategy) => {
- expect(() => MaskingStrategySchema.parse(strategy)).not.toThrow();
- });
- });
-
- it('should reject invalid strategies', () => {
- expect(() => MaskingStrategySchema.parse('invalid')).toThrow();
- expect(() => MaskingStrategySchema.parse('encrypt')).toThrow();
- });
-});
-
-describe('MaskingRuleSchema', () => {
- it('should accept valid rule with defaults', () => {
- const rule = MaskingRuleSchema.parse({
- field: 'ssn',
- strategy: 'redact',
- });
-
- expect(rule.field).toBe('ssn');
- expect(rule.strategy).toBe('redact');
- expect(rule.preserveFormat).toBe(true);
- expect(rule.preserveLength).toBe(true);
- });
-
- it('should accept full rule configuration', () => {
- const rule = MaskingRuleSchema.parse({
- field: 'phone_number',
- strategy: 'partial',
- pattern: '^(\\d{3}).*$',
- preserveFormat: false,
- preserveLength: false,
- roles: ['viewer', 'analyst'],
- exemptRoles: ['admin', 'compliance_officer'],
- });
-
- expect(rule.field).toBe('phone_number');
- expect(rule.strategy).toBe('partial');
- expect(rule.pattern).toBe('^(\\d{3}).*$');
- expect(rule.preserveFormat).toBe(false);
- expect(rule.preserveLength).toBe(false);
- expect(rule.roles).toEqual(['viewer', 'analyst']);
- expect(rule.exemptRoles).toEqual(['admin', 'compliance_officer']);
- });
-
- it('should reject missing required fields', () => {
- expect(() => MaskingRuleSchema.parse({})).toThrow();
- expect(() => MaskingRuleSchema.parse({ field: 'ssn' })).toThrow();
- });
-
- it('should reject invalid strategy', () => {
- expect(() => MaskingRuleSchema.parse({ field: 'ssn', strategy: 'invalid' })).toThrow();
- });
-});
-
-describe('MaskingConfigSchema', () => {
- it('should accept valid config with defaults', () => {
- const config = MaskingConfigSchema.parse({
- rules: [{ field: 'email', strategy: 'redact' }],
- });
-
- expect(config.enabled).toBe(false);
- expect(config.auditUnmasking).toBe(true);
- expect(config.rules).toHaveLength(1);
- });
-
- it('should accept full configuration', () => {
- const config = MaskingConfigSchema.parse({
- enabled: true,
- rules: [
- { field: 'ssn', strategy: 'redact' },
- { field: 'phone', strategy: 'partial', pattern: '^(\\d{3})' },
- { field: 'email', strategy: 'hash' },
- ],
- auditUnmasking: false,
- });
-
- expect(config.enabled).toBe(true);
- expect(config.rules).toHaveLength(3);
- expect(config.auditUnmasking).toBe(false);
- });
-
- it('should reject missing required rules', () => {
- expect(() => MaskingConfigSchema.parse({})).toThrow();
- });
-});
diff --git a/packages/spec/src/system/masking.zod.ts b/packages/spec/src/system/masking.zod.ts
deleted file mode 100644
index ef7cc124aa..0000000000
--- a/packages/spec/src/system/masking.zod.ts
+++ /dev/null
@@ -1,46 +0,0 @@
-// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
-
-import { z } from 'zod';
-
-// ⚠️ EXPERIMENTAL — NOT ENFORCED (ADR-0056). Data-masking rules are declared but no
-// redaction layer applies them — Field-Level Security (PermissionSet.fields) is the
-// enforced field-visibility mechanism today. Authoring masking does NOT change
-// behaviour (roadmap M2+; per ADR-0049).
-
-/**
- * Data masking protocol for PII protection
- */
-import { lazySchema } from '../shared/lazy-schema';
-export const MaskingStrategySchema = lazySchema(() => z.enum([
- 'redact', // Complete redaction: ****
- 'partial', // Partial masking: 138****5678
- 'hash', // Hash value: sha256(value)
- 'tokenize', // Tokenization: token-12345
- 'randomize', // Randomize: generate random value
- 'nullify', // Null value: null
- 'substitute', // Substitute with dummy data
-]).describe('Data masking strategy for PII protection'));
-
-export type MaskingStrategy = z.infer;
-
-export const MaskingRuleSchema = lazySchema(() => z.object({
- field: z.string().describe('Field name to apply masking to'),
- strategy: MaskingStrategySchema.describe('Masking strategy to use'),
- pattern: z.string().optional().describe('Regex pattern for partial masking'),
- preserveFormat: z.boolean().default(true).describe('Keep the original data format after masking'),
- preserveLength: z.boolean().default(true).describe('Keep the original data length after masking'),
- roles: z.array(z.string()).optional().describe('Roles that see masked data'),
- exemptRoles: z.array(z.string()).optional().describe('Roles that see unmasked data'),
-}).describe('Masking rule for a single field'));
-
-export type MaskingRule = z.infer;
-export type MaskingRuleInput = z.input;
-
-export const MaskingConfigSchema = lazySchema(() => z.object({
- enabled: z.boolean().default(false).describe('Enable data masking'),
- rules: z.array(MaskingRuleSchema).describe('List of field-level masking rules'),
- auditUnmasking: z.boolean().default(true).describe('Log when masked data is accessed unmasked'),
-}).describe('Top-level data masking configuration for PII protection'));
-
-export type MaskingConfig = z.infer;
-export type MaskingConfigInput = z.input;