|
| 1 | +--- |
| 2 | +"@objectstack/spec": minor |
| 3 | +--- |
| 4 | + |
| 5 | +feat(spec): retire the fourteen inert deadline keys of the incident-response, training and change-management schemas (#14477, ADR-0049) |
| 6 | + |
| 7 | +<!-- adr-0087: registered incident-response-deadline-keys-retired, training-deadline-keys-retired, change-management-duration-keys-retired --> |
| 8 | + |
| 9 | +**BREAKING** accept-set narrowing, landing after the v17.0.0 cut (the lockstep |
| 10 | +launch-window convention ships it as `minor`; the migration prescriptions are |
| 11 | +registered under protocol major 18, where `os migrate meta` users will look). |
| 12 | +Maintainer ruling 2026-09-02 on the census card (ruled A: retire per family): |
| 13 | +ADR-0049 enforce-or-remove decides it — declared-but-unenforced deadline |
| 14 | +surface with zero measured readers comes off. |
| 15 | + |
| 16 | +Fourteen hour/minute/day-shaped deadline, SLA and duration key sites — twelve |
| 17 | +distinct names, because `durationMinutes` and `estimatedMinutes` each occur at |
| 18 | +two sites — sat on the exported incident-response, training and |
| 19 | +change-management schemas and in the generated reference docs, and **nothing |
| 20 | +read them**: the schemas are exported from `@objectstack/spec/system`, mounted |
| 21 | +by no stack key, registered as no metadata type, absent from the 2026-06 |
| 22 | +liveness ledgers, and the reader census over every package outside |
| 23 | +`packages/spec` (tests and changelogs excluded) and over objectui at the |
| 24 | +pinned sha returned zero hits for every key. An author could write |
| 25 | +`triageDeadlineHours: 4`, `validityDays: 365` or `regulatorDeadlineHours: 72` |
| 26 | +and reasonably expect the platform to escalate, expire or notify — it never |
| 27 | +did, and it never said so. Six of the keys carried defaults (30 minutes, |
| 28 | +1 hour, 2555 days; 365, 30 and 14 days) that were materialized into every |
| 29 | +parsed document without ever being consulted. A compliance-shaped deadline |
| 30 | +that fails silently is the worst form of the shape ADR-0049 names. |
| 31 | + |
| 32 | +**What is refused:** authoring any of the keys below, with any value, on the |
| 33 | +base schema and through every carrier that nests it (`Incident.responsePhases[]`, |
| 34 | +`IncidentResponsePolicy.notificationMatrix`, `TrainingPlan.courses[]`, |
| 35 | +`ChangeRequest.impact` / `.rollbackPlan` / `.implementation`). None of the |
| 36 | +schemas is `.strict()`, so each key is a `retiredKey()` tombstone rather than a |
| 37 | +bare deletion (a deletion would have stripped it in silence): authoring it is a |
| 38 | +`tsc` error (`never`) and a parse error carrying the prescription |
| 39 | +(`invalid_type` at the path of the key). |
| 40 | + |
| 41 | +| schema | retired keys | |
| 42 | +|:--|:--| |
| 43 | +| `IncidentResponsePhase` | `targetHours` | |
| 44 | +| `IncidentNotificationRule` | `withinMinutes`, `regulatorDeadlineHours` | |
| 45 | +| `IncidentNotificationMatrix` | `escalationTimeoutMinutes` (default 30) | |
| 46 | +| `IncidentResponsePolicy` | `triageDeadlineHours` (default 1), `retentionDays` (default 2555) | |
| 47 | +| `TrainingCourse` | `durationMinutes`, `validityDays` | |
| 48 | +| `TrainingPlan` | `recertificationIntervalDays` (default 365), `gracePeriodDays` (default 30), `reminderDaysBefore` (default 14) | |
| 49 | +| `ChangeImpact` | `downtime.durationMinutes` | |
| 50 | +| `RollbackPlan` | `steps[].estimatedMinutes` | |
| 51 | +| `ChangeRequest` | `implementation.steps[].estimatedMinutes` | |
| 52 | + |
| 53 | +**What stays, byte-identical:** every other key of the three families with its |
| 54 | +default and its (absent) readers, and every export — no def leaves the public |
| 55 | +surface. Parsed documents no longer carry the six former defaults. |
| 56 | + |
| 57 | +**Held, not touched:** the `ESignatureConfig` pair (`expirationDays`, |
| 58 | +`reminderDays` in `data/document.zod.ts`) — the ruling left that branch open |
| 59 | +pending the e-signature roadmap answer; it stays on the card. |
| 60 | + |
| 61 | +## FROM → TO |
| 62 | + |
| 63 | +```ts |
| 64 | +// before — parsed green; no engine ever read a single one of these numbers |
| 65 | +const policy: IncidentResponsePolicy = { |
| 66 | + notificationMatrix: { |
| 67 | + rules: [{ severity: 'critical', channels: ['pagerduty'], recipients: ['security_team'], |
| 68 | + withinMinutes: 15, notifyRegulators: true, regulatorDeadlineHours: 72 }], |
| 69 | + escalationTimeoutMinutes: 45, |
| 70 | + }, |
| 71 | + defaultResponseTeam: 'security_team', |
| 72 | + triageDeadlineHours: 2, |
| 73 | + retentionDays: 3650, |
| 74 | +}; |
| 75 | +const course: TrainingCourse = { |
| 76 | + id: 'COURSE-SEC-001', title: 'Security Fundamentals', description: '…', |
| 77 | + category: 'security_awareness', targetRoles: ['all_employees'], |
| 78 | + durationMinutes: 60, validityDays: 365, |
| 79 | +}; |
| 80 | +const rollback: RollbackPlan = { |
| 81 | + description: 'Restore from backup', |
| 82 | + steps: [{ order: 1, description: 'Restore backup', estimatedMinutes: 15 }], |
| 83 | +}; |
| 84 | + |
| 85 | +// after — delete the keys; there is no replacement because no incident-response, |
| 86 | +// training-management or change-management engine exists to keep a deadline. |
| 87 | +// Record retention is the object-level `lifecycle` block (ADR-0057), declared on |
| 88 | +// the object that stores the records. |
| 89 | +const policy: IncidentResponsePolicy = { |
| 90 | + notificationMatrix: { |
| 91 | + rules: [{ severity: 'critical', channels: ['pagerduty'], recipients: ['security_team'], |
| 92 | + notifyRegulators: true }], |
| 93 | + }, |
| 94 | + defaultResponseTeam: 'security_team', |
| 95 | +}; |
| 96 | +const course: TrainingCourse = { |
| 97 | + id: 'COURSE-SEC-001', title: 'Security Fundamentals', description: '…', |
| 98 | + category: 'security_awareness', targetRoles: ['all_employees'], |
| 99 | +}; |
| 100 | +const rollback: RollbackPlan = { |
| 101 | + description: 'Restore from backup', |
| 102 | + steps: [{ order: 1, description: 'Restore backup' }], |
| 103 | +}; |
| 104 | +``` |
| 105 | + |
| 106 | +One-line fix: delete the key wherever it is authored. There is no |
| 107 | +`os migrate meta` edit list for these keys — none of the schemas is a stack |
| 108 | +collection member, so the conversion chain has no seam to walk (the |
| 109 | +`MetadataPluginConfig.additionalTypes` precedent); the tombstone prescription |
| 110 | +and the protocol-18 upgrade guide are the channels. |
| 111 | + |
| 112 | +The retirement kit: |
| 113 | + |
| 114 | +- `retiredKey()` tombstones at all fourteen sites (`packages/spec/src/system/ |
| 115 | + incident-response.zod.ts`, `training.zod.ts`, `change-management.zod.ts`; |
| 116 | + each file's section comment records what the shape was and why no D2 |
| 117 | + conversion exists) |
| 118 | +- ADR-0087 registration: fourteen `RETIRED_KEYS_BY_MAJOR[18]` entries (the |
| 119 | + three nested change-management sites spelled `ChangeImpact:downtime.durationMinutes`, |
| 120 | + `RollbackPlan:steps.estimatedMinutes`, `ChangeRequest:implementation.steps.estimatedMinutes`) |
| 121 | + and three D3 semantic entries, one per family |
| 122 | +- no liveness-ledger row: none of the three families is an enrolled ledger |
| 123 | + type, so there is no row to keep or drop |
| 124 | +- pin tests (`deadline-keys-retirement.test.ts`): a refusal pin per site |
| 125 | + asserting the issue path, code and prescription on the base schema and |
| 126 | + through the nesting carriers; the tsc `never` channel; no-materialize pins |
| 127 | + for the six former defaults; the ADR-0087 registration; and a tree-scoped |
| 128 | + absence pin over every authored source in the repo |
| 129 | +- generated baselines and docs follow the schema: `authorable-surface/` gains |
| 130 | + eleven `[RETIRED]` rows, `authorable-defaults/` loses six rows, the three |
| 131 | + system reference pages are regenerated, and the gitignored `json-schema/` |
| 132 | + output is re-emitted on the next build |
| 133 | +- `json-schema.manifest/` is unchanged, and correctly so: it ratchets def |
| 134 | + *names*, and retiring keys removes no def from the published surface |
| 135 | +- `spec-changes.json` and the protocol upgrade guide are unchanged too: both |
| 136 | + project the migration chain at the current protocol major (17), so these |
| 137 | + protocol-18 registrations reach them at the 18 cut |
| 138 | +- zero authored occurrences in this repo's examples, skills and hand-written |
| 139 | + docs, and zero hits in objectui at the pinned sha, so no in-repo source |
| 140 | + changes ride along beyond the three families' own unit tests |
0 commit comments