|
| 1 | +# Dead-surface disposition plan (spec liveness) |
| 2 | + |
| 3 | +**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. |
| 4 | + |
| 5 | +> 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). |
| 6 | +
|
| 7 | +## Headline |
| 8 | + |
| 9 | +**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." |
| 10 | + |
| 11 | +| Disposition | Count | Meaning | |
| 12 | +|---|---|---| |
| 13 | +| **PRUNE** | ~64 | Remove from spec. No roadmap; aspirational / duplicate / display-only. Shrinks false surface. | |
| 14 | +| **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. | |
| 15 | +| **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. | |
| 16 | +| **OBJECTUI** | 1 | Fix lives in the separate `objectui` renderer repo (can't be done here). | |
| 17 | + |
| 18 | +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. |
| 19 | + |
| 20 | +--- |
| 21 | + |
| 22 | +## Priority-ordered actions |
| 23 | + |
| 24 | +### P0 — false-compliance / security-relevant dead → decide enforce-or-remove (don't leave) |
| 25 | +These imply a security/governance/isolation boundary that does not exist. |
| 26 | + |
| 27 | +| Property | Disposition | Why · cost · risk | |
| 28 | +|---|---|---| |
| 29 | +| `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. | |
| 30 | +| `field.maskingRule` | **PRUNE** | Implies field masking; no consumer. False data-protection promise. Remove. | |
| 31 | +| `field.writeRequiresMasterRead` | **PRUNE** | Governance flag, no consumer. Remove. | |
| 32 | +| `field.dataQuality` / `field.auditTrail` | **PRUNE** | Aspirational governance, no runtime. Remove (re-introduce with the feature). | |
| 33 | +| `permission.contextVariables` | **PRUNE** | Doc claims runtime RLS evaluation; RLS uses only `current_user.*` built-ins. Misleading — remove. | |
| 34 | +| `permission.isProfile` | **PRUNE** | profile-vs-permset never gates anything. Remove (the `profile` type alias already conveys intent). | |
| 35 | +| `agent.tenantId` | **ENFORCE-or-PRUNE** | Multi-tenant agent isolation — if agents must be tenant-scoped, wire it in the runtime; else remove. Product call. | |
| 36 | +| `skill.permissions` | **ENFORCE-or-PRUNE** | A skill should be restrictable; today unenforced + naming drift (`requiredPermissions`). Enforce at skill-activation, or remove + drop the drift. | |
| 37 | + |
| 38 | +### P1 — naming-drift / duplication *bugs* (authors set them, they silently no-op) → wire or rename |
| 39 | +User-visible: documented per the protocol, fails silently. |
| 40 | + |
| 41 | +| Property | Disposition | Why · cost · risk | |
| 42 | +|---|---|---| |
| 43 | +| `field.referenceFilters` | **WIRE-RENAME** | Lookup dialog reads `lookup_filters`; normalize the key so the documented camelCase works. Net user-facing fix. (renderer side = objectui) | |
| 44 | +| `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. | |
| 45 | +| `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. | |
| 46 | +| `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). | |
| 47 | +| `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. | |
| 48 | + |
| 49 | +### P2 — aspirational / duplicate / display surface → PRUNE (narrow-and-true) |
| 50 | +No security implication; no roadmap; removing shrinks false surface. Grouped for batch removal. |
| 51 | + |
| 52 | +**object (≈22)** — the largest prune target: |
| 53 | +`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). |
| 54 | + |
| 55 | +**field (≈24)** — enhanced-type display config + redundant flags: |
| 56 | +`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`. |
| 57 | + |
| 58 | +**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. |
| 59 | + |
| 60 | +**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.)* |
| 61 | + |
| 62 | +**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). |
| 63 | + |
| 64 | +**hook (1)**: `label` **or** `description` — keep one for docs, drop the redundant other. |
| 65 | + |
| 66 | +**role (1)**: `parent` — manager rollup unimplemented (org hierarchy uses `sys_department`). **PRUNE** now; re-introduce with the hierarchy feature, or **DEFER-M2** if roadmapped. |
| 67 | + |
| 68 | +**skill (1)**: `triggerPhrases` — no intent matcher exists. **PRUNE** (remove the misleading "activates this skill" framing) or **ENFORCE** if intent-routing is on the roadmap. |
| 69 | + |
| 70 | +--- |
| 71 | + |
| 72 | +## Execution guidance |
| 73 | + |
| 74 | +- **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. |
| 75 | +- **Wire-rename** items are bug-fixes; the renderer-side ones (`referenceFilters`, `action.disabled`) land in **objectui**. |
| 76 | +- **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. |
| 77 | +- After each prune/enforce, the liveness ledger updates and the gate stays green — the loop closes. |
| 78 | + |
| 79 | +## Sequencing recommendation |
| 80 | + |
| 81 | +1. **P0 first** (8 props) — false-compliance is the trust risk; mostly prunes, fast. |
| 82 | +2. **P2 object + field prune** (~46 props) — biggest surface reduction, low risk, high signal ("the spec got honest"). |
| 83 | +3. **P1 wire-rename** — schedule the framework-side ones; file the objectui ones in that repo. |
| 84 | +4. **Remaining P2** (flow/tool/action/hook/role/skill) — opportunistic. |
| 85 | + |
| 86 | +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`. |
0 commit comments