Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .changeset/dead-surface-plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
---

docs(audits): add the dead-surface disposition plan — turns the spec-liveness gate's 89 `dead` properties (10 governed types) into an executable prune / wire-rename / enforce-or-prune decision, priority-ordered. Decision artifact; no package impact.
86 changes: 86 additions & 0 deletions docs/audits/2026-06-dead-surface-disposition-plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# Dead-surface disposition plan (spec liveness)

**Date**: 2026-06-16 · **Input**: the spec-liveness gate (`packages/spec/liveness/`) over 10 governed metadata types. **Builds on**: [ADR-0049](../adr/0049-no-unenforced-security-properties.md) (security enforce-or-remove). **Purpose**: turn the gate's measurement into an executable decision — for each of the **89 `dead` properties**, recommend **enforce / wire-rename / prune / mark-experimental / defer / objectui** with rationale, cost and risk.

> The gate already proves these properties have no framework runtime consumer. This document decides what to *do* about each. It makes recommendations; the team owns execution (especially spec removals and runtime enforcement).

## Headline

**The dominant disposition is PRUNE (~64 of 89).** Most dead surface is aspirational enterprise config, duplicate flags, or display options that nobody reads and nobody has a committed roadmap for. For a platform whose value proposition is a *trustworthy* metadata contract (and whose positioning is an open Ontology protocol), **shedding false surface is the highest-leverage hygiene move** — under-promise in the spec, deliver what's declared. "Narrow and true" beats "broad and half-wired."

| Disposition | Count | Meaning |
|---|---|---|
| **PRUNE** | ~64 | Remove from spec. No roadmap; aspirational / duplicate / display-only. Shrinks false surface. |
| **WIRE-RENAME** | ~8 | A user-visible *bug*: the property is meant to work but reads under a wrong key, or duplicates a flat sibling. Fix the key / route the consumer. |
| **ENFORCE-or-PRUNE** | ~6 | Security/scoping-relevant — needs a product call: build the enforcement, or remove the false promise. Do **not** leave as silent dead. |
| **OBJECTUI** | 1 | Fix lives in the separate `objectui` renderer repo (can't be done here). |

Security note carried over from ADR-0049: a *dead security/governance property is worse than absent* — it implies a boundary that isn't there. The P0 cluster below is exactly those.

---

## Priority-ordered actions

### P0 — false-compliance / security-relevant dead → decide enforce-or-remove (don't leave)
These imply a security/governance/isolation boundary that does not exist.

| Property | Disposition | Why · cost · risk |
|---|---|---|
| `field.encryptionConfig` | **PRUNE** | Implies at-rest encryption; the only real channel is `type:'secret'` (engine `encryptSecretFields`). A field-level encryptionConfig is false assurance. Remove + point authors to `secret`. Low cost; risk: seeds using it. |
| `field.maskingRule` | **PRUNE** | Implies field masking; no consumer. False data-protection promise. Remove. |
| `field.writeRequiresMasterRead` | **PRUNE** | Governance flag, no consumer. Remove. |
| `field.dataQuality` / `field.auditTrail` | **PRUNE** | Aspirational governance, no runtime. Remove (re-introduce with the feature). |
| `permission.contextVariables` | **PRUNE** | Doc claims runtime RLS evaluation; RLS uses only `current_user.*` built-ins. Misleading — remove. |
| `permission.isProfile` | **PRUNE** | profile-vs-permset never gates anything. Remove (the `profile` type alias already conveys intent). |
| `agent.tenantId` | **ENFORCE-or-PRUNE** | Multi-tenant agent isolation — if agents must be tenant-scoped, wire it in the runtime; else remove. Product call. |
| `skill.permissions` | **ENFORCE-or-PRUNE** | A skill should be restrictable; today unenforced + naming drift (`requiredPermissions`). Enforce at skill-activation, or remove + drop the drift. |

### P1 — naming-drift / duplication *bugs* (authors set them, they silently no-op) → wire or rename
User-visible: documented per the protocol, fails silently.

| Property | Disposition | Why · cost · risk |
|---|---|---|
| `field.referenceFilters` | **WIRE-RENAME** | Lookup dialog reads `lookup_filters`; normalize the key so the documented camelCase works. Net user-facing fix. (renderer side = objectui) |
| `field.columnName` | **WIRE-RENAME → else PRUNE** | DANGEROUS: advertises custom physical column names the driver never honors (`resolveColumnName` has 0 call sites). Either wire it in driver-sql or remove. Removal is safer. |
| `field.currencyConfig` / `field.vectorConfig` / `field.fileAttachmentConfig` | **PRUNE (pick flat)** | Nested config is dead; renderers read flat siblings (`currency`/`precision`, `dimensions`, `multiple`/`accept`/`maxSize`). Remove the nested form — one shape per field type. |
| `agent.knowledge` | **WIRE-RENAME → else PRUNE** | Shape drift `{topics,indexes}` vs read `{sources}`; no runtime reader (RAG via `service-knowledge`). Reconcile the shape or remove (RAG is wired elsewhere). |
| `action.disabled` | **OBJECTUI** | CEL form ignored; primary renderers read non-spec `enabled` and invert. Fix is in the objectui renderers (`action-button.tsx`) + normalize the key. Cross-repo. |

### P2 — aspirational / duplicate / display surface → PRUNE (narrow-and-true)
No security implication; no roadmap; removing shrinks false surface. Grouped for batch removal.

**object (≈22)** — the largest prune target:
`enable.{trackHistory,searchable,files,feeds,activities,trash,mru,clone}` (capability flags, no readers — `apiEnabled`/`apiMethods` already enforced, #1937); `versioning` / `partitioning` / `cdc` (aspirational enterprise blocks); `softDelete` & `search` (duplicate the dead `enable.trash`/`enable.searchable`); `recordTypes` / `defaultDetailForm` (unimplemented, elaborate docstrings); `recordName` (superseded by field `autonumber`); `keyPrefix` / `tags` / `abstract` / `isSystem` / `active` (no reader); `tenancy.{strategy,tenantField,crossTenantAccess}` (only `tenancy.enabled` is live).

**field (≈24)** — enhanced-type display config + redundant flags:
`theme` / `lineNumbers` (code: only `language` live); `allowHalf` / `maxRating` (rating: `max` live); `displayMap` / `allowGeocoding` / `addressFormat`; `colorFormat` / `allowAlpha` / `presetColors`; `showValue` / `marks` (slider: min/max/step live); `barcodeFormat` / `qrErrorCorrection` / `displayValue` / `allowScanning`; `inlineTitle` / `inlineColumns` / `inlineAmountField` / `relatedList` / `relatedListTitle` / `relatedListColumns` (master-detail explicit overrides — auto-derivation works); `searchable` / `index` / `externalId` (field-level — superseded by object/dataset-level); `cached` / `dependencies` / `trackFeedHistory` / `caseSensitive`.

**flow (5)**: `description` / `template` (no reader); `active` (deprecated, redundant with `status`); `nodes.outputSchema` (never validated); `nodes.boundaryConfig` (drives the dead BPMN `boundary_event`); `errorHandling.fallbackNodeId` (engine uses per-node fault edges). *Special:* `flow.status` — currently doesn't gate (engine uses an in-memory `flowEnabled` map); **WIRE** `status` → the enable map, or accept it's display-only and prune. Product call.

**tool (5)**: `category` / `requiresConfirmation` / `permissions` / `active` / `builtIn` — cosmetic on a write-only projection. Prune. *(The deeper question — make `tool` metadata authoritative vs stop projecting a schema that implies authorability — is a design decision, not a prune; track separately.)*

**action (3)**: `execute` (deprecated, auto-migrated to `target`); `shortcut` (registered but no keydown listener); `bulkEnabled` (no spec-driven path calls `executeBulk`); `timeout` (server uses `body.timeoutMs`). Prune (or wire shortcut/bulk if wanted).

**hook (1)**: `label` **or** `description` — keep one for docs, drop the redundant other.

**role (1)**: `parent` — manager rollup unimplemented (org hierarchy uses `sys_department`). **PRUNE** now; re-introduce with the hierarchy feature, or **DEFER-M2** if roadmapped.

**skill (1)**: `triggerPhrases` — no intent matcher exists. **PRUNE** (remove the misleading "activates this skill" framing) or **ENFORCE** if intent-routing is on the roadmap.

---

## Execution guidance

- **Pruning is low-risk and mechanical**: each is a spec property removal + ledger entry removal; CI (`validate`/seed fixtures + the liveness gate) catches anything that authored the removed prop. Batch by type into a few PRs (one per type keeps review tight). Estimated: object + field prunes are ~2 PRs covering ~46 of the 64.
- **Wire-rename** items are bug-fixes; the renderer-side ones (`referenceFilters`, `action.disabled`) land in **objectui**.
- **ENFORCE-or-PRUNE (P0)** items need a one-line product decision each; default to **prune** (remove the false promise) unless there's a committed roadmap, per ADR-0049.
- After each prune/enforce, the liveness ledger updates and the gate stays green — the loop closes.

## Sequencing recommendation

1. **P0 first** (8 props) — false-compliance is the trust risk; mostly prunes, fast.
2. **P2 object + field prune** (~46 props) — biggest surface reduction, low risk, high signal ("the spec got honest").
3. **P1 wire-rename** — schedule the framework-side ones; file the objectui ones in that repo.
4. **Remaining P2** (flow/tool/action/hook/role/skill) — opportunistic.

The security cluster already handled by ADR-0049 (Policy tree, `allow{Transfer,Restore,Purge}`, SharingRule, flow `runAs`) is **not** re-listed here — those are `experimental`/M2, not `dead`.
3 changes: 3 additions & 0 deletions docs/audits/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@ dataset `filter`/`format`/`derived`/`dateGranularity`; skill `triggerConditions`
- **Healthiest** (near-total liveness, model schemas): **hook**, **validation**.
- **Worst**: **policy** (100% dead), **portal** (100% dead), **tool** (write-only).

## Disposition plan
- [`2026-06-dead-surface-disposition-plan.md`](./2026-06-dead-surface-disposition-plan.md) — turns the spec-liveness gate's 89 `dead` properties (10 governed types) into an executable decision: **prune / wire-rename / enforce-or-prune / objectui**, priority-ordered. Headline: ~64 are prune candidates (aspirational/duplicate/display with no roadmap).

## Suggested ADRs
1. **Security enforcement** (cluster #1) — highest priority; either enforce or remove every parsed-but-unenforced security prop.
2. **Finish ADR-0021** (cluster #2) — migrate chart/dashboard/report renderers + Studio off the legacy inline shape.
Expand Down