From 31514ace2ae1e167a6129e54795aff1ecb7d8e23 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 5 Sep 2026 12:59:14 +0000 Subject: [PATCH 1/6] feat(spec)!: retire the incident-response, training and change-management families whole and the ESignatureConfig deadline pair (ADR-0049) Nineteen defs and forty-five exported names leave @objectstack/spec/system via RETIRED_DEFS_BY_MAJOR[18] with three D3 semantic entries; the fourteen deadline-key tombstones leave with their defs' source and their registry entries stay as history. ESignatureConfig.expirationDays / reminderDays become retiredKey() tombstones with two RETIRED_KEYS_BY_MAJOR[18] entries and one D3 entry. Generated artifacts follow in the next commit (build + check:generated --fix). Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01M59rPZZFzqhfMUPFqqZTkf --- .changeset/compliance-families-retired.md | 165 ++++ .../docs/getting-started/quick-reference.mdx | 3 +- .../spec/json-schema.manifest/system.json | 19 - packages/spec/src/data/document.test.ts | 63 +- packages/spec/src/data/document.zod.ts | 60 +- ...signature-deadline-keys-retirement.test.ts | 235 ++++++ .../retired-defs/18.system__ChangeImpact.ts | 28 + .../retired-defs/18.system__ChangePriority.ts | 26 + .../retired-defs/18.system__ChangeRequest.ts | 29 + .../retired-defs/18.system__ChangeStatus.ts | 26 + .../retired-defs/18.system__ChangeType.ts | 26 + .../retired-defs/18.system__Incident.ts | 27 + .../18.system__IncidentCategory.ts | 26 + .../18.system__IncidentNotificationMatrix.ts | 27 + .../18.system__IncidentNotificationRule.ts | 27 + .../18.system__IncidentResponsePhase.ts | 27 + .../18.system__IncidentResponsePolicy.ts | 28 + .../18.system__IncidentSeverity.ts | 26 + .../retired-defs/18.system__IncidentStatus.ts | 26 + .../retired-defs/18.system__RollbackPlan.ts | 28 + .../18.system__TrainingCategory.ts | 25 + .../18.system__TrainingCompletionStatus.ts | 25 + .../retired-defs/18.system__TrainingCourse.ts | 26 + .../retired-defs/18.system__TrainingPlan.ts | 27 + .../retired-defs/18.system__TrainingRecord.ts | 26 + ....data__ESignatureConfig__expirationDays.ts | 28 + ...18.data__ESignatureConfig__reminderDays.ts | 27 + .../18.change-management-family-retired.ts | 59 ++ ...esignature-config-deadline-keys-retired.ts | 42 + .../18.incident-response-family-retired.ts | 60 ++ .../semantic/18.training-family-retired.ts | 55 ++ packages/spec/src/migrations/registry.ts | 738 +++++++++++++++++- .../spec/src/system/change-management.test.ts | 736 ----------------- .../spec/src/system/change-management.zod.ts | 466 ----------- .../compliance-families-retirement.test.ts | 394 ++++++++++ .../system/deadline-keys-retirement.test.ts | 677 ---------------- .../spec/src/system/incident-response.test.ts | 391 ---------- .../spec/src/system/incident-response.zod.ts | 439 ----------- packages/spec/src/system/index.ts | 47 +- packages/spec/src/system/training.test.ts | 254 ------ packages/spec/src/system/training.zod.ts | 272 ------- .../src/type-alias-convention.pin.test.ts | 37 +- scripts/cross-package-test-inputs.mjs | 52 ++ turbo.json | 15 +- 44 files changed, 2515 insertions(+), 3325 deletions(-) create mode 100644 .changeset/compliance-families-retired.md create mode 100644 packages/spec/src/data/esignature-deadline-keys-retirement.test.ts create mode 100644 packages/spec/src/migrations/entries/retired-defs/18.system__ChangeImpact.ts create mode 100644 packages/spec/src/migrations/entries/retired-defs/18.system__ChangePriority.ts create mode 100644 packages/spec/src/migrations/entries/retired-defs/18.system__ChangeRequest.ts create mode 100644 packages/spec/src/migrations/entries/retired-defs/18.system__ChangeStatus.ts create mode 100644 packages/spec/src/migrations/entries/retired-defs/18.system__ChangeType.ts create mode 100644 packages/spec/src/migrations/entries/retired-defs/18.system__Incident.ts create mode 100644 packages/spec/src/migrations/entries/retired-defs/18.system__IncidentCategory.ts create mode 100644 packages/spec/src/migrations/entries/retired-defs/18.system__IncidentNotificationMatrix.ts create mode 100644 packages/spec/src/migrations/entries/retired-defs/18.system__IncidentNotificationRule.ts create mode 100644 packages/spec/src/migrations/entries/retired-defs/18.system__IncidentResponsePhase.ts create mode 100644 packages/spec/src/migrations/entries/retired-defs/18.system__IncidentResponsePolicy.ts create mode 100644 packages/spec/src/migrations/entries/retired-defs/18.system__IncidentSeverity.ts create mode 100644 packages/spec/src/migrations/entries/retired-defs/18.system__IncidentStatus.ts create mode 100644 packages/spec/src/migrations/entries/retired-defs/18.system__RollbackPlan.ts create mode 100644 packages/spec/src/migrations/entries/retired-defs/18.system__TrainingCategory.ts create mode 100644 packages/spec/src/migrations/entries/retired-defs/18.system__TrainingCompletionStatus.ts create mode 100644 packages/spec/src/migrations/entries/retired-defs/18.system__TrainingCourse.ts create mode 100644 packages/spec/src/migrations/entries/retired-defs/18.system__TrainingPlan.ts create mode 100644 packages/spec/src/migrations/entries/retired-defs/18.system__TrainingRecord.ts create mode 100644 packages/spec/src/migrations/entries/retired-keys/18.data__ESignatureConfig__expirationDays.ts create mode 100644 packages/spec/src/migrations/entries/retired-keys/18.data__ESignatureConfig__reminderDays.ts create mode 100644 packages/spec/src/migrations/entries/semantic/18.change-management-family-retired.ts create mode 100644 packages/spec/src/migrations/entries/semantic/18.esignature-config-deadline-keys-retired.ts create mode 100644 packages/spec/src/migrations/entries/semantic/18.incident-response-family-retired.ts create mode 100644 packages/spec/src/migrations/entries/semantic/18.training-family-retired.ts delete mode 100644 packages/spec/src/system/change-management.test.ts delete mode 100644 packages/spec/src/system/change-management.zod.ts create mode 100644 packages/spec/src/system/compliance-families-retirement.test.ts delete mode 100644 packages/spec/src/system/deadline-keys-retirement.test.ts delete mode 100644 packages/spec/src/system/incident-response.test.ts delete mode 100644 packages/spec/src/system/incident-response.zod.ts delete mode 100644 packages/spec/src/system/training.test.ts delete mode 100644 packages/spec/src/system/training.zod.ts diff --git a/.changeset/compliance-families-retired.md b/.changeset/compliance-families-retired.md new file mode 100644 index 0000000000..56bbfc3682 --- /dev/null +++ b/.changeset/compliance-families-retired.md @@ -0,0 +1,165 @@ +--- +"@objectstack/spec": minor +--- + +feat(spec)!: retire the incident-response, training and change-management families whole — nineteen defs and every name they exported — and the `ESignatureConfig` deadline pair (#15513, #14477, ADR-0049) + + + +**BREAKING** — published exported symbols leave `@objectstack/spec/system`, and +two authorable keys leave `data/ESignatureConfig` — landing after the v17.0.0 +cut (the lockstep launch-window convention ships it as `minor`; the +registrations live under protocol major 18, where `os migrate meta` users will +look). Maintainer ruling 2026-09-05 on #15513 (decision batch #40, ruled A: +retire the three compliance-shaped families whole via `RETIRED_DEFS_BY_MAJOR`, +the `integration/ErrorMappingConfig` precedent; none of the three is +roadmapped) and, in the same stroke, the answer the 2026-09-02 ruling on #14477 +had held open (no roadmapped e-signature consumer ⇒ the pair retires with the +rest). ADR-0049 enforce-or-remove decides it — declared-but-unenforced surface +with zero measured readers comes off. + +## What leaves the public surface — the three families, whole + +Nineteen defs (the card counted fifteen; the manifest counts nineteen — the +ruling names the families, the number is the files' reading), forty-five +exported names, roughly a hundred declared keys, and the generated reference +pages `references/system/incident-response`, `training` and +`change-management`: + +| file | defs (`json-schema.manifest/system.json` spelling) | +|:--|:--| +| `system/incident-response.zod.ts` | `system/Incident`, `system/IncidentCategory`, `system/IncidentNotificationMatrix`, `system/IncidentNotificationRule`, `system/IncidentResponsePhase`, `system/IncidentResponsePolicy`, `system/IncidentSeverity`, `system/IncidentStatus` | +| `system/training.zod.ts` | `system/TrainingCategory`, `system/TrainingCompletionStatus`, `system/TrainingCourse`, `system/TrainingPlan`, `system/TrainingRecord` | +| `system/change-management.zod.ts` | `system/ChangeImpact`, `system/ChangePriority`, `system/ChangeRequest`, `system/ChangeStatus`, `system/ChangeType`, `system/RollbackPlan` | + +With them: every `*Schema` const, every `z.input` alias (`Incident`, +`IncidentResponsePolicy`, `TrainingCourse`, `ChangeRequest`, …) and the six +`*Parsed` aliases (`IncidentNotificationRuleParsed`, +`IncidentNotificationMatrixParsed`, `IncidentResponsePolicyParsed`, +`TrainingCourseParsed`, `TrainingPlanParsed`, `ChangeRequestParsed`). + +**Why.** The schemas were exported from `@objectstack/spec/system`, mounted by +no `stack.zod.ts` key, registered as no metadata type, absent from the 2026-06 +liveness ledgers, and **read by nothing**: the reader census over every package +outside `packages/spec` (tests and changelogs excluded), over `examples/**` and +`skills/**`, and over objectui at the pinned sha (`a472b07`) returned zero hits +for every one of the forty-five names, with a lit control on the same pattern +(`ObjectSchema` / `FieldSchema`: 336, 200 and 342 hits per leg). Several keys +were boolean capability claims of exactly the shape ADR-0049 names — +`IncidentNotificationRule.notifyRegulators`, +`IncidentResponsePolicy.requirePostIncidentReview`, `TrainingCourse.mandatory`, +`TrainingPlan.trackCompletion` / `sendReminders`, +`ChangeRequest.approval.required`, +`ChangeRequest.securityImpact.requiresSecurityApproval` — so an author writing +`notifyRegulators: true` held a compliance promise the platform never kept, +with no error and no feedback, and the reference docs advertised a compliance +subsystem that does not exist. Tagging the families +`[EXPERIMENTAL — not enforced]` was the fallback the ruling did not take: it is +a human-only signal, and an AI generating from the schema still writes the key +and believes it. + +**What happened to the fourteen #14477 deadline-key tombstones** (PR #15514, +merged 2026-09-04): they leave with their defs' source. Their fourteen +`RETIRED_KEYS_BY_MAJOR[18]` entries and three D3 entries stay as history — gate +(b2) of `build-schemas.ts` accepts an entry naming a key the build no longer +emits, and the 17→18 upgrade guide still owes the reader those prescriptions. +`deadline-keys-retirement.test.ts`, whose every pin needed the schemas to exist, +is replaced by `compliance-families-retirement.test.ts`. + +## What is refused — the `ESignatureConfig` pair + +Authoring `expirationDays` or `reminderDays` on an `ESignatureConfig`, with any +value, on the base schema and through `Document.eSignature`. The schema is not +`.strict()`, so each key is a `retiredKey()` tombstone rather than a bare +deletion (a deletion would have stripped it in silence): authoring it is a `tsc` +error (`never`) and a parse error carrying the prescription (`invalid_type` at +the path of the key). Both carried defaults (30 days, 7 days) that were +materialized into every parsed configuration without ever being consulted; +parsed configurations no longer carry them. `provider`, `enabled` and `signers` +stay, byte-identical. Census for the pair: zero hits for `expirationDays`, +`reminderDays`, `eSignature` and the `ESignatureConfig` names on all three legs, +control lit inside `packages/spec` (`document.zod.ts` 9, `document.test.ts` 24). + +**Unmeasured, verbatim:** `cloud` and real customer configurations are +UNMEASURED for both the families and the pair — this census covers this repo +and objectui at the pin. + +## FROM → TO + +```ts +// before — imported and parsed green; no engine ever read a single key +import { IncidentResponsePolicySchema, type IncidentResponsePolicy } from '@objectstack/spec/system'; +const policy: IncidentResponsePolicy = { + notificationMatrix: { rules: [{ severity: 'critical', channels: ['pagerduty'], recipients: ['security_team'], notifyRegulators: true }] }, + defaultResponseTeam: 'security_team', + requirePostIncidentReview: true, +}; +IncidentResponsePolicySchema.parse(policy); + +const signing: ESignatureConfig = { + provider: 'docusign', + signers: [{ email: 'client@example.com', name: 'John Doe', role: 'Client', order: 1 }], + expirationDays: 30, + reminderDays: 7, +}; + +// after — the import is TS2305 and there is no replacement to point at, because +// no incident-response, training-management or change-management engine exists. +// A compliance record the organisation keeps is ordinary object data, declared +// as an object with its own fields and enforced by the object engine; an +// approval that must actually gate something is a flow (ADR-0018) with an +// approval node. +// +// The e-signature pair: delete the keys. `ESignatureConfig` itself stays. +const signing: ESignatureConfig = { + provider: 'docusign', + signers: [{ email: 'client@example.com', name: 'John Doe', role: 'Client', order: 1 }], +}; +``` + +One-line fix: delete the import (families) or the key (pair) wherever it is +authored. There is no `os migrate meta` edit list — none of the schemas is a +stack collection member and `document` is no metadata type, so the conversion +chain has no seam to walk (the `MetadataPluginConfig.additionalTypes` +precedent); the tombstone prescriptions, the `tsc` refusals and the protocol-18 +upgrade guide are the channels. + +The retirement kit: + +- the three schema files and their tests deleted whole; the survivor notes in + `packages/spec/src/system/index.ts` record what each module declared and why + nothing ever read it +- ADR-0087 registration: nineteen `RETIRED_DEFS_BY_MAJOR[18]` entries + (`entries/retired-defs/18.system__*.ts`) and three D3 semantic entries, one + per family; for the pair, `data/ESignatureConfig:expirationDays` and + `data/ESignatureConfig:reminderDays` in `RETIRED_KEYS_BY_MAJOR[18]` plus the + D3 entry `esignature-config-deadline-keys-retired`; the step-18 `rationale` + extended +- no liveness-ledger row: none of the families and neither `document` nor + `ESignatureConfig` is an enrolled ledger type, so there is no row to keep or + drop +- pin tests: `compliance-families-retirement.test.ts` (zero holders of the + forty-five names on every public entry via `export-origins/`, the deletion + probe, the in-package importer walk, the runtime namespace, the shards' + absence, the ADR-0087 registration, the #15514 history kept, and a + tree-scoped absence leg whose walk radius is DECLARED in + `scripts/cross-package-test-inputs.mjs` / `turbo.json` — the playbook rule + #15566 added after PR #15514); `esignature-deadline-keys-retirement.test.ts` + (refusal pins asserting issue path, code and prescription on the base schema + and through `Document.eSignature`; the tsc `never` channel; no-materialize + pins for the two former defaults; the ADR-0087 registration); the thirteen + isomorphism pins the three modules held leave `type-alias-convention.pin.test.ts` +- generated baselines and docs follow the schema: `json-schema.manifest/` + loses nineteen keys (the manifest-deletion gate adjudicates whole-def + removals against the merge base), `api-surface/`, `declaration-map/`, + `export-origins/`, `authorable-surface/` and `authorable-defaults/` lose the + families' rows, `authorable-surface/data.json` gains two `[RETIRED]` rows and + `authorable-defaults/data.json` loses two, the three system reference pages + are removed and `references/system/index.mdx`, `references/index.mdx` and + `references/data/document.mdx` regenerated, `spec-changes.json` and the + upgrade guide carry the four new registrations at the 18 cut +- hand-written docs: the `Change Management` row leaves + `getting-started/quick-reference.mdx` +- zero authored occurrences in this repo's examples, skills and hand-written + docs beyond that row, and zero hits in objectui at `a472b07`, so no sibling + change and no pin bump ride along diff --git a/content/docs/getting-started/quick-reference.mdx b/content/docs/getting-started/quick-reference.mdx index 2c6c34f213..d1e416898c 100644 --- a/content/docs/getting-started/quick-reference.mdx +++ b/content/docs/getting-started/quick-reference.mdx @@ -85,7 +85,7 @@ Plugin architecture, manifests, and kernel runtime. | **[Metadata Loader](/docs/references/kernel/metadata-loader)** | `metadata-loader.zod.ts` | MetadataLoaderContract | Metadata loading | | **[Package Registry](/docs/references/kernel/package-registry)** | `package-registry.zod.ts` | InstalledPackage, InstallPackageRequest | Package resolution | -## System Protocol (17 of 36 schemas) +## System Protocol (16 of 33 schemas) Runtime environment, logging, jobs, caching, and observability. @@ -93,7 +93,6 @@ Runtime environment, logging, jobs, caching, and observability. |:---------|:-----------|:------------|:--------| | **[Auth Config](/docs/references/system/auth-config)** | `auth-config.zod.ts` | AuthConfig | Authentication configuration | | **[Cache](/docs/references/system/cache)** | `cache.zod.ts` | CacheConfig | Caching layer | -| **[Change Management](/docs/references/system/change-management)** | `change-management.zod.ts` | ChangeRequest, RollbackPlan | Change tracking | | **[Collaboration](/docs/references/system/collaboration)** | `collaboration.zod.ts` | Collaboration | Real-time collab | | **[Encryption](/docs/references/system/encryption)** | `encryption.zod.ts` | Encryption | Encryption & keys | | **[HTTP Server](/docs/references/system/http-server)** | `http-server.zod.ts` | RouteHandlerMetadata, MiddlewareConfig | Route + middleware metadata | diff --git a/packages/spec/json-schema.manifest/system.json b/packages/spec/json-schema.manifest/system.json index ae479dc174..cca72fc486 100644 --- a/packages/spec/json-schema.manifest/system.json +++ b/packages/spec/json-schema.manifest/system.json @@ -39,12 +39,7 @@ "system/CacheStrategy", "system/CacheTier", "system/CacheWarmup", - "system/ChangeImpact", - "system/ChangePriority", - "system/ChangeRequest", "system/ChangeSet", - "system/ChangeStatus", - "system/ChangeType", "system/CollaborationMode", "system/CollaborationSession", "system/CollaborationSessionConfig", @@ -101,14 +96,6 @@ "system/GCounter", "system/HistogramBucketConfig", "system/HttpDestinationConfig", - "system/Incident", - "system/IncidentCategory", - "system/IncidentNotificationMatrix", - "system/IncidentNotificationRule", - "system/IncidentResponsePhase", - "system/IncidentResponsePolicy", - "system/IncidentSeverity", - "system/IncidentStatus", "system/IntervalSchedule", "system/Job", "system/JobExecution", @@ -205,7 +192,6 @@ "system/ResolvedGroup", "system/ResolvedSettingValue", "system/RetryPolicy", - "system/RollbackPlan", "system/RouteHandlerMetadata", "system/RowLevelIsolationStrategy", "system/SamplingDecision", @@ -278,11 +264,6 @@ "system/TraceSamplingConfig", "system/TraceState", "system/TracingConfig", - "system/TrainingCategory", - "system/TrainingCompletionStatus", - "system/TrainingCourse", - "system/TrainingPlan", - "system/TrainingRecord", "system/TranslationBundle", "system/TranslationConfig", "system/TranslationCoverageResult", diff --git a/packages/spec/src/data/document.test.ts b/packages/spec/src/data/document.test.ts index 893bdd4c7d..251640c7ae 100644 --- a/packages/spec/src/data/document.test.ts +++ b/packages/spec/src/data/document.test.ts @@ -190,8 +190,6 @@ describe('ESignatureConfigSchema', () => { order: 2, }, ], - expirationDays: 30, - reminderDays: 7, }; expect(() => ESignatureConfigSchema.parse(validConfig)).not.toThrow(); @@ -230,38 +228,43 @@ describe('ESignatureConfigSchema', () => { expect(parsed.enabled).toBe(false); }); - it('should default expirationDays to 30', () => { - const config = { + // `expirationDays` / `reminderDays` were retiredKey() tombstones since #14477 + // (ADR-0049): the two former default pins (30 / 7) pinned exactly the branch + // that left, so they are replaced, not adjusted. The full kit — refusal on + // the base schema and through `DocumentSchema.eSignature`, the no-materialize + // pin, the tsc `never` channel and the ADR-0087 registration — lives in + // `esignature-deadline-keys-retirement.test.ts`; these two are the family + // tests' one-line refusal witnesses (the #14477 house shape). + it('REFUSES an authored `expirationDays` — a retiredKey() tombstone since #14477 (ADR-0049)', () => { + const wellFormed = { provider: 'custom', - signers: [ - { - email: 'test@example.com', - name: 'Test', - role: 'Test', - order: 1, - }, - ], + signers: [{ email: 'test@example.com', name: 'Test', role: 'Test', order: 1 }], }; - - const parsed = ESignatureConfigSchema.parse(config); - expect(parsed.expirationDays).toBe(30); + const result = ESignatureConfigSchema.safeParse({ ...wellFormed, expirationDays: 30 }); + expect(result.success).toBe(false); + if (result.success) return; + const issue = result.error.issues.find((i) => i.path.join('.') === 'expirationDays'); + expect(issue?.code).toBe('invalid_type'); + expect(issue?.message).toMatch(/^`ESignatureConfig\.expirationDays` was removed in @objectstack\/spec 17 \(ADR-0049 enforce-or-remove\)/); + // Attribution control: the same config WITHOUT the key parses, and the + // parsed value carries neither the key nor its former default. + const parsed = ESignatureConfigSchema.parse(wellFormed); + expect(parsed).not.toHaveProperty('expirationDays'); }); - it('should default reminderDays to 7', () => { - const config = { + it('REFUSES an authored `reminderDays` — a retiredKey() tombstone since #14477 (ADR-0049)', () => { + const wellFormed = { provider: 'docusign', - signers: [ - { - email: 'test@example.com', - name: 'Test', - role: 'Test', - order: 1, - }, - ], + signers: [{ email: 'test@example.com', name: 'Test', role: 'Test', order: 1 }], }; - - const parsed = ESignatureConfigSchema.parse(config); - expect(parsed.reminderDays).toBe(7); + const result = ESignatureConfigSchema.safeParse({ ...wellFormed, reminderDays: 7 }); + expect(result.success).toBe(false); + if (result.success) return; + const issue = result.error.issues.find((i) => i.path.join('.') === 'reminderDays'); + expect(issue?.code).toBe('invalid_type'); + expect(issue?.message).toMatch(/^`ESignatureConfig\.reminderDays` was removed in @objectstack\/spec 17 \(ADR-0049 enforce-or-remove\)/); + const parsed = ESignatureConfigSchema.parse(wellFormed); + expect(parsed).not.toHaveProperty('reminderDays'); }); it('should accept all provider types', () => { @@ -422,8 +425,6 @@ describe('DocumentSchema', () => { order: 1, }, ], - expirationDays: 15, - reminderDays: 3, }, }; @@ -601,8 +602,6 @@ describe('DocumentSchema', () => { order: 3, }, ], - expirationDays: 45, - reminderDays: 5, }, access: { isPublic: false, diff --git a/packages/spec/src/data/document.zod.ts b/packages/spec/src/data/document.zod.ts index b1fe0ab42b..a4906b3261 100644 --- a/packages/spec/src/data/document.zod.ts +++ b/packages/spec/src/data/document.zod.ts @@ -1,6 +1,7 @@ // Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. import { z } from 'zod'; +import { retiredKey } from '../shared/retired-key'; /** * Document Version Schema @@ -144,6 +145,49 @@ export const DocumentTemplateSchema = lazySchema(() => z.object({ })).describe('Template placeholders'), })); +// ─── RETIRED deadline keys (ADR-0049 enforce-or-remove) ───────────────────── +// +// Two day-shaped keys were declared on `ESignatureConfigSchema` and read by +// NOTHING: no e-signature engine exists on the platform — no layer ever sent, +// expired or reminded a signature request — and the reader census over every +// package outside `packages/spec` (tests and changelogs excluded), over +// `examples/**` and `skills/**`, and over objectui at the pinned sha returned +// zero hits for `expirationDays`, `reminderDays`, `eSignature` and the +// `ESignatureConfig` names, with a lit control inside this package. An author +// could write `expirationDays: 30` and the platform would never act on it; the +// generated reference docs advertised an expiry nothing kept. The 2026-09-02 +// ruling on #14477 held the pair on one condition (a roadmapped consumer ⇒ +// `[EXPERIMENTAL — not enforced]`); the maintainer answered it on 2026-09-05 +// (no roadmap), so the ruling's own branch resolves to retirement. +// +// Route: `retiredKey()` tombstones, NOT plain deletion — the schema is not +// `.strict()`, so a bare deletion would make zod strip the key in silence, +// replacing an inert declaration with an invisible one (ADR-0104). The +// tombstone is audible in both channels: `tsc` (the input type is `never`) +// and the parse (the prescription is the message). No D2 conversion and no +// `os migrate meta` sentence: `DocumentSchema` is not a stack collection +// member and `document` is no metadata type, so a conversion would be a +// transform with no seam that ever runs (the +// `kernel/MetadataPluginConfig:additionalTypes` precedent). The retirement is +// registered as `RETIRED_KEYS_BY_MAJOR[18]` entries plus the D3 semantic entry +// `esignature-config-deadline-keys-retired`. `provider` / `enabled` / `signers` +// stay, byte-identical. + +const EXPIRATION_DAYS_RETIRED = + '`ESignatureConfig.expirationDays` was removed in @objectstack/spec 17 (ADR-0049 ' + + 'enforce-or-remove) — nothing ever read it: no e-signature engine exists on the platform, ' + + 'so no signature request was ever sent, expired or lapsed, and its default of 30 days was ' + + 'materialized into every parsed configuration without ever being consulted. Delete the ' + + 'key. There is no replacement, because no e-signature provider integration exists to keep ' + + 'an expiry window.'; + +const REMINDER_DAYS_RETIRED = + '`ESignatureConfig.reminderDays` was removed in @objectstack/spec 17 (ADR-0049 ' + + 'enforce-or-remove) — nothing ever read it: no e-signature engine exists on the platform, ' + + 'so no reminder email was ever sent, and its default of 7 days was materialized into ' + + 'every parsed configuration without ever being consulted. Delete the key. There is no ' + + 'replacement, because no e-signature provider integration exists to send reminders.'; + /** * E-Signature Configuration Schema * @@ -168,9 +212,7 @@ export const DocumentTemplateSchema = lazySchema(() => z.object({ * "role": "Manager", * "order": 2 * } - * ], - * "expirationDays": 30, - * "reminderDays": 7 + * ] * } * ``` */ @@ -212,16 +254,16 @@ export const ESignatureConfigSchema = lazySchema(() => z.object({ })).describe('Document signers'), /** - * Days until signature request expires - * @default 30 + * REMOVED (ADR-0049): `expirationDays` — see the RETIRED section above. + * Authoring it is a `tsc` error and a parse error carrying the prescription. */ - expirationDays: z.number().optional().default(30).describe('Expiration days'), + expirationDays: retiredKey(EXPIRATION_DAYS_RETIRED), /** - * Days between reminder emails - * @default 7 + * REMOVED (ADR-0049): `reminderDays` — see the RETIRED section above. + * Authoring it is a `tsc` error and a parse error carrying the prescription. */ - reminderDays: z.number().optional().default(7).describe('Reminder interval days'), + reminderDays: retiredKey(REMINDER_DAYS_RETIRED), })); /** diff --git a/packages/spec/src/data/esignature-deadline-keys-retirement.test.ts b/packages/spec/src/data/esignature-deadline-keys-retirement.test.ts new file mode 100644 index 0000000000..a4eaca1c11 --- /dev/null +++ b/packages/spec/src/data/esignature-deadline-keys-retirement.test.ts @@ -0,0 +1,235 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +import { describe, expect, it } from 'vitest'; +import type { ZodTypeAny } from 'zod'; + +import { DocumentSchema, ESignatureConfigSchema, type Document, type ESignatureConfig } from './document.zod'; +import { MIGRATIONS_BY_MAJOR, RETIRED_KEYS_BY_MAJOR } from '../migrations/registry'; + +// ─── [#14477] the `ESignatureConfig` deadline pair is REMOVED ──────────────── +// +// ADR-0049 enforce-or-remove. The 2026-09-02 ruling on #14477 (ruled A: retire +// per family) held `expirationDays` / `reminderDays` on one condition — a +// roadmapped e-signature consumer would have earned an +// `[EXPERIMENTAL — not enforced]` tag instead — and the maintainer answered it +// on 2026-09-05 (decision batch #40: no roadmap), so the ruling's own branch +// resolves to retirement. Two day-shaped keys on the published authorable +// surface (`data/ESignatureConfig`) and in the generated reference docs, read +// by NOTHING: no e-signature engine exists on the platform — no layer ever +// sent, expired or reminded a signature request — and the reader census over +// every package outside `packages/spec` (tests and changelogs excluded), over +// `examples/**` and `skills/**`, and over objectui at the pinned sha returned +// zero hits for `expirationDays`, `reminderDays`, `eSignature` and the +// `ESignatureConfig` names, with a lit control inside this package. Both +// carried defaults (30 / 7 days) that were materialized into every parsed +// configuration without ever being consulted. +// +// Route: `retiredKey()` tombstones, NOT plain deletion — `ESignatureConfigSchema` +// is not `.strict()`, so a bare deletion would make zod strip the key in +// silence (ADR-0104). Audible in two channels: `tsc` (the input type is +// `never`) and the parse (the prescription is the message). No D2 conversion: +// `DocumentSchema` is not a stack collection member and `document` is no +// metadata type, so the chain has no seam that ever runs (the +// `kernel/MetadataPluginConfig:additionalTypes` precedent) — the registration +// is two `RETIRED_KEYS_BY_MAJOR[18]` entries plus one D3 semantic entry. +// +// On the assertion set (the #8586 / #14676 / #14477 precedent): a schema +// refusal raises a `ZodError` whose issues carry `code` and `path` but no +// ADR-0112 `status` — that envelope belongs to the API error surface. So these +// pins assert the strongest set this surface really has: refusal, the issue +// `code`, the `path` naming WHICH site refused, and the prescription text +// (#5240: where the wording is the contract, pin the wording). + +// ── Well-formed fixtures: every required key, neither of the retired ones ─── + +const CONFIG: ESignatureConfig = { + provider: 'docusign', + enabled: true, + signers: [{ email: 'client@example.com', name: 'John Doe', role: 'Client', order: 1 }], +}; +const DOCUMENT: Document = { + id: 'doc_101', + name: 'Contract for Signature', + fileType: 'application/pdf', + fileSize: 1536, + eSignature: CONFIG, +}; + +interface RetiredSite { + /** The exact `RETIRED_KEYS_BY_MAJOR` spelling. */ + registered: string; + /** How the prescription opens (its backtick-wrapped qualified key). */ + qualified: string; + schema: ZodTypeAny; + wellFormed: unknown; + authored: unknown; + issuePath: (string | number)[]; + formerDefault: RegExp; +} + +const SITES: RetiredSite[] = [ + { + registered: 'data/ESignatureConfig:expirationDays', + qualified: 'ESignatureConfig.expirationDays', + schema: ESignatureConfigSchema, + wellFormed: CONFIG, + authored: { ...CONFIG, expirationDays: 30 }, + issuePath: ['expirationDays'], + formerDefault: /default of 30 days/, + }, + { + registered: 'data/ESignatureConfig:reminderDays', + qualified: 'ESignatureConfig.reminderDays', + schema: ESignatureConfigSchema, + wellFormed: CONFIG, + authored: { ...CONFIG, reminderDays: 7 }, + issuePath: ['reminderDays'], + formerDefault: /default of 7 days/, + }, +]; + +/** The same two keys through the one carrier that nests the config: `Document.eSignature`. */ +const CARRIERS: Array> = [ + { + qualified: 'ESignatureConfig.expirationDays', + schema: DocumentSchema, + wellFormed: DOCUMENT, + authored: { ...DOCUMENT, eSignature: { ...CONFIG, expirationDays: 15 } }, + issuePath: ['eSignature', 'expirationDays'], + }, + { + qualified: 'ESignatureConfig.reminderDays', + schema: DocumentSchema, + wellFormed: DOCUMENT, + authored: { ...DOCUMENT, eSignature: { ...CONFIG, reminderDays: 3 } }, + issuePath: ['eSignature', 'reminderDays'], + }, +]; + +const SEMANTIC_ID = 'esignature-config-deadline-keys-retired'; + +function escapeRegExp(s: string): string { + return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); +} + +function expectTombstoneRefusal(site: Pick) { + const result = site.schema.safeParse(site.authored); + expect(result.success, `${site.qualified} must be refused`).toBe(false); + if (result.success) return; // narrowing; the assertion above already failed + + const wanted = site.issuePath.join('.'); + const issue = result.error.issues.find((i) => i.path.join('.') === wanted); + expect(issue, `the refusal must surface at ${wanted}`).toBeDefined(); + // The machine-readable half of the envelope this surface actually has: a + // `retiredKey()` tombstone raises `invalid_type` from its `z.never()`. + expect(issue!.code).toBe('invalid_type'); + expect(issue!.path).toEqual(site.issuePath); + // The prescription IS the migration doc for whoever hits it — contract, not + // commentary: it opens with the qualified key, names the version and the + // ADR, says why the key was inert, and tells the author what to do. + expect(issue!.message).toMatch( + new RegExp('^`' + escapeRegExp(site.qualified) + '` was removed in @objectstack/spec 17 \\(ADR-0049 enforce-or-remove\\) — nothing ever read it'), + ); + expect(issue!.message).toMatch(/Delete the key/); + expect(issue!.message).toMatch(/no e-signature engine exists/); + // Customer-facing text carries the ADR, never an issue id. + expect(issue!.message).not.toMatch(/#\d{3,}/); + // Deliberately NO `os migrate meta` sentence: no conversion covers this + // schema (not a stack collection member), so the sentence would promise an + // edit list the tool cannot produce (`retired-key.ts`: the sentence must be + // TRUE of the tool). + expect(issue!.message).not.toMatch(/os migrate meta/); +} + +describe('[#14477] ESignatureConfig deadline pair retirement — refusal at every site', () => { + for (const site of SITES) { + it(`REJECTS an authored \`${site.qualified}\` at path \`${site.issuePath.join('.')}\`, carrying the prescription`, () => { + expectTombstoneRefusal(site); + // Attribution control: the same config WITHOUT the key is accepted, so + // the refusal above is attributable to the retired key and nothing else. + expect(site.schema.safeParse(site.wellFormed).success, `${site.qualified}: well-formed control must parse`).toBe(true); + }); + } + + for (const carrier of CARRIERS) { + it(`REJECTS \`${carrier.qualified}\` through \`Document.eSignature\`, at path \`${carrier.issuePath.join('.')}\``, () => { + expectTombstoneRefusal(carrier); + expect(carrier.schema.safeParse(carrier.wellFormed).success).toBe(true); + }); + } + + it('every prescription names the default it used to materialize', () => { + for (const site of SITES) { + const result = site.schema.safeParse(site.authored); + expect(result.success).toBe(false); + if (result.success) continue; + const issue = result.error.issues.find((i) => i.path.join('.') === site.issuePath.join('.'))!; + expect(issue.message, site.qualified).toMatch(site.formerDefault); + } + }); +}); + +describe('[#14477] no-materialize: parsed configurations carry neither key and neither former default', () => { + it('on the base schema', () => { + const parsed = ESignatureConfigSchema.parse(CONFIG); + expect(parsed).not.toHaveProperty('expirationDays'); + expect(parsed).not.toHaveProperty('reminderDays'); + // Attribution: the surviving default still materializes, so the absence + // above is the tombstone's doing and not a broken parse. + expect(ESignatureConfigSchema.parse({ provider: 'custom', signers: CONFIG.signers }).enabled).toBe(false); + }); + + it('through `Document.eSignature`', () => { + const parsed = DocumentSchema.parse(DOCUMENT); + expect(parsed.eSignature).toBeDefined(); + expect(parsed.eSignature).not.toHaveProperty('expirationDays'); + expect(parsed.eSignature).not.toHaveProperty('reminderDays'); + }); +}); + +describe('[#14477] the tsc channel: the input type of both retired keys is `never`', () => { + it('fails tsc at both authoring sites', () => { + const config: ESignatureConfig = { + ...CONFIG, + // @ts-expect-error — `expirationDays` is a retiredKey() tombstone: its input type is `never`. + expirationDays: 30, + // @ts-expect-error — `reminderDays` is a retiredKey() tombstone. + reminderDays: 7, + }; + const document: Document = { + ...DOCUMENT, + eSignature: { + ...CONFIG, + // @ts-expect-error — the tombstone reaches through the carrier. + expirationDays: 15, + }, + }; + // The literals above are typed, so tsc is the assertion; at runtime the + // same values are refused, which keeps this case from being vacuous. + for (const [schema, value] of [ + [ESignatureConfigSchema, config], + [DocumentSchema, document], + ] as Array<[ZodTypeAny, unknown]>) { + expect(schema.safeParse(value).success).toBe(false); + } + }); +}); + +describe('[#14477] ADR-0087 registration', () => { + it('declares both sites under major 18, with the D3 semantic entry wired and no D2 conversion', () => { + for (const site of SITES) { + expect(RETIRED_KEYS_BY_MAJOR[18], `${site.registered} must be declared`).toContain(site.registered); + } + const step = MIGRATIONS_BY_MAJOR[18]; + expect(step).toBeDefined(); + const entry = step!.semantic.find((s) => s.id === SEMANTIC_ID); + expect(entry, `${SEMANTIC_ID} must be wired into the step-18 chain`).toBeDefined(); + expect(entry!.reason.length).toBeGreaterThan(0); + expect(entry!.acceptanceCriteria.length).toBeGreaterThan(0); + // The route is stated where the next reader looks: why D3 semantic and + // not D2 — no stack seam (the additionalTypes precedent). + expect(entry!.reason).toMatch(/not a D2 conversion/); + // Deliberately no mechanical conversion. + expect(step!.conversionIds.filter((id) => /signature|document/.test(id))).toEqual([]); + }); +}); diff --git a/packages/spec/src/migrations/entries/retired-defs/18.system__ChangeImpact.ts b/packages/spec/src/migrations/entries/retired-defs/18.system__ChangeImpact.ts new file mode 100644 index 0000000000..1fbe13bdb0 --- /dev/null +++ b/packages/spec/src/migrations/entries/retired-defs/18.system__ChangeImpact.ts @@ -0,0 +1,28 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +// #15513 — `system/ChangeImpact` — the impact assessment (`level` / +// `affectedSystems` / `downtime` …, plus the #14477-retired nested +// `downtime.durationMinutes`) — leaves whole with the change-management family +// under ADR-0049 enforce-or-remove (maintainer ruling 2026-09-05 on #15513, +// ruled A: retire the three compliance-shaped families whole; not roadmapped). +// It was exported from `@objectstack/spec/system` +// (`system/change-management.zod.ts`), mounted by no `stack.zod.ts` key, +// registered as no metadata type, absent from the 2026-06 liveness ledgers, and +// read by NOTHING: the reader census over every package outside `packages/spec` +// (tests and changelogs excluded), over `examples/**` and `skills/**`, and over +// objectui at the pinned sha returned zero hits for every one of the family's +// exported names, with a lit control on the same pattern. No change-management +// engine exists on the platform: nothing routed a change request for approval, +// walked its implementation steps, honoured a rollback plan or gated on +// `securityImpact.requiresSecurityApproval` / `approval.required` — both of +// which read as gates the platform enforced, and neither ever did. An exported +// value schema with no consumer reads as a capability (#3950); the generated +// reference docs advertised a compliance subsystem that does not exist. No +// carrier key, so no `retiredKey()` tombstone and no D2 conversion (none of +// these schemas is a stack collection member — the +// `kernel/MetadataPluginConfig:additionalTypes` reasoning): +// RETIRED_DEFS_BY_MAJOR plus the D3 semantic entry +// `change-management-family-retired` ARE the declaration. The family's #14477 +// `RETIRED_KEYS_BY_MAJOR[18]` deadline-key entries stay as history — gate (b2) +// of build-schemas.ts accepts an entry naming a key the build no longer emits. +export const entry = 'system/ChangeImpact'; diff --git a/packages/spec/src/migrations/entries/retired-defs/18.system__ChangePriority.ts b/packages/spec/src/migrations/entries/retired-defs/18.system__ChangePriority.ts new file mode 100644 index 0000000000..50e71e1257 --- /dev/null +++ b/packages/spec/src/migrations/entries/retired-defs/18.system__ChangePriority.ts @@ -0,0 +1,26 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +// #15513 — `system/ChangePriority` — the 4-value priority enum — leaves whole +// with the change-management family under ADR-0049 enforce-or-remove (maintainer +// ruling 2026-09-05 on #15513, ruled A: retire the three compliance-shaped +// families whole; not roadmapped). It was exported from +// `@objectstack/spec/system` (`system/change-management.zod.ts`), mounted by no +// `stack.zod.ts` key, registered as no metadata type, absent from the 2026-06 +// liveness ledgers, and read by NOTHING: the reader census over every package +// outside `packages/spec` (tests and changelogs excluded), over `examples/**` +// and `skills/**`, and over objectui at the pinned sha returned zero hits for +// every one of the family's exported names, with a lit control on the same +// pattern. No change-management engine exists on the platform: nothing routed a +// change request for approval, walked its implementation steps, honoured a +// rollback plan or gated on `securityImpact.requiresSecurityApproval` / +// `approval.required` — both of which read as gates the platform enforced, and +// neither ever did. An exported value schema with no consumer reads as a +// capability (#3950); the generated reference docs advertised a compliance +// subsystem that does not exist. No carrier key, so no `retiredKey()` tombstone +// and no D2 conversion (none of these schemas is a stack collection member — the +// `kernel/MetadataPluginConfig:additionalTypes` reasoning): +// RETIRED_DEFS_BY_MAJOR plus the D3 semantic entry +// `change-management-family-retired` ARE the declaration. The family's #14477 +// `RETIRED_KEYS_BY_MAJOR[18]` deadline-key entries stay as history — gate (b2) +// of build-schemas.ts accepts an entry naming a key the build no longer emits. +export const entry = 'system/ChangePriority'; diff --git a/packages/spec/src/migrations/entries/retired-defs/18.system__ChangeRequest.ts b/packages/spec/src/migrations/entries/retired-defs/18.system__ChangeRequest.ts new file mode 100644 index 0000000000..cd0183e988 --- /dev/null +++ b/packages/spec/src/migrations/entries/retired-defs/18.system__ChangeRequest.ts @@ -0,0 +1,29 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +// #15513 — `system/ChangeRequest` — the change request itself (`id` / `title` / +// `type` / `priority` / `status` / `impact` / `approval` / `implementation` / +// `rollbackPlan` / `securityImpact` …, plus the #14477-retired nested +// `implementation.steps[].estimatedMinutes`) — leaves whole with the +// change-management family under ADR-0049 enforce-or-remove (maintainer ruling +// 2026-09-05 on #15513, ruled A: retire the three compliance-shaped families +// whole; not roadmapped). It was exported from `@objectstack/spec/system` +// (`system/change-management.zod.ts`), mounted by no `stack.zod.ts` key, +// registered as no metadata type, absent from the 2026-06 liveness ledgers, and +// read by NOTHING: the reader census over every package outside `packages/spec` +// (tests and changelogs excluded), over `examples/**` and `skills/**`, and over +// objectui at the pinned sha returned zero hits for every one of the family's +// exported names, with a lit control on the same pattern. No change-management +// engine exists on the platform: nothing routed a change request for approval, +// walked its implementation steps, honoured a rollback plan or gated on +// `securityImpact.requiresSecurityApproval` / `approval.required` — both of +// which read as gates the platform enforced, and neither ever did. An exported +// value schema with no consumer reads as a capability (#3950); the generated +// reference docs advertised a compliance subsystem that does not exist. No +// carrier key, so no `retiredKey()` tombstone and no D2 conversion (none of +// these schemas is a stack collection member — the +// `kernel/MetadataPluginConfig:additionalTypes` reasoning): +// RETIRED_DEFS_BY_MAJOR plus the D3 semantic entry +// `change-management-family-retired` ARE the declaration. The family's #14477 +// `RETIRED_KEYS_BY_MAJOR[18]` deadline-key entries stay as history — gate (b2) +// of build-schemas.ts accepts an entry naming a key the build no longer emits. +export const entry = 'system/ChangeRequest'; diff --git a/packages/spec/src/migrations/entries/retired-defs/18.system__ChangeStatus.ts b/packages/spec/src/migrations/entries/retired-defs/18.system__ChangeStatus.ts new file mode 100644 index 0000000000..844e111617 --- /dev/null +++ b/packages/spec/src/migrations/entries/retired-defs/18.system__ChangeStatus.ts @@ -0,0 +1,26 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +// #15513 — `system/ChangeStatus` — the 10-value change status enum — leaves +// whole with the change-management family under ADR-0049 enforce-or-remove +// (maintainer ruling 2026-09-05 on #15513, ruled A: retire the three +// compliance-shaped families whole; not roadmapped). It was exported from +// `@objectstack/spec/system` (`system/change-management.zod.ts`), mounted by no +// `stack.zod.ts` key, registered as no metadata type, absent from the 2026-06 +// liveness ledgers, and read by NOTHING: the reader census over every package +// outside `packages/spec` (tests and changelogs excluded), over `examples/**` +// and `skills/**`, and over objectui at the pinned sha returned zero hits for +// every one of the family's exported names, with a lit control on the same +// pattern. No change-management engine exists on the platform: nothing routed a +// change request for approval, walked its implementation steps, honoured a +// rollback plan or gated on `securityImpact.requiresSecurityApproval` / +// `approval.required` — both of which read as gates the platform enforced, and +// neither ever did. An exported value schema with no consumer reads as a +// capability (#3950); the generated reference docs advertised a compliance +// subsystem that does not exist. No carrier key, so no `retiredKey()` tombstone +// and no D2 conversion (none of these schemas is a stack collection member — the +// `kernel/MetadataPluginConfig:additionalTypes` reasoning): +// RETIRED_DEFS_BY_MAJOR plus the D3 semantic entry +// `change-management-family-retired` ARE the declaration. The family's #14477 +// `RETIRED_KEYS_BY_MAJOR[18]` deadline-key entries stay as history — gate (b2) +// of build-schemas.ts accepts an entry naming a key the build no longer emits. +export const entry = 'system/ChangeStatus'; diff --git a/packages/spec/src/migrations/entries/retired-defs/18.system__ChangeType.ts b/packages/spec/src/migrations/entries/retired-defs/18.system__ChangeType.ts new file mode 100644 index 0000000000..b2e35d4651 --- /dev/null +++ b/packages/spec/src/migrations/entries/retired-defs/18.system__ChangeType.ts @@ -0,0 +1,26 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +// #15513 — `system/ChangeType` — the 4-value ITIL change type enum — leaves +// whole with the change-management family under ADR-0049 enforce-or-remove +// (maintainer ruling 2026-09-05 on #15513, ruled A: retire the three +// compliance-shaped families whole; not roadmapped). It was exported from +// `@objectstack/spec/system` (`system/change-management.zod.ts`), mounted by no +// `stack.zod.ts` key, registered as no metadata type, absent from the 2026-06 +// liveness ledgers, and read by NOTHING: the reader census over every package +// outside `packages/spec` (tests and changelogs excluded), over `examples/**` +// and `skills/**`, and over objectui at the pinned sha returned zero hits for +// every one of the family's exported names, with a lit control on the same +// pattern. No change-management engine exists on the platform: nothing routed a +// change request for approval, walked its implementation steps, honoured a +// rollback plan or gated on `securityImpact.requiresSecurityApproval` / +// `approval.required` — both of which read as gates the platform enforced, and +// neither ever did. An exported value schema with no consumer reads as a +// capability (#3950); the generated reference docs advertised a compliance +// subsystem that does not exist. No carrier key, so no `retiredKey()` tombstone +// and no D2 conversion (none of these schemas is a stack collection member — the +// `kernel/MetadataPluginConfig:additionalTypes` reasoning): +// RETIRED_DEFS_BY_MAJOR plus the D3 semantic entry +// `change-management-family-retired` ARE the declaration. The family's #14477 +// `RETIRED_KEYS_BY_MAJOR[18]` deadline-key entries stay as history — gate (b2) +// of build-schemas.ts accepts an entry naming a key the build no longer emits. +export const entry = 'system/ChangeType'; diff --git a/packages/spec/src/migrations/entries/retired-defs/18.system__Incident.ts b/packages/spec/src/migrations/entries/retired-defs/18.system__Incident.ts new file mode 100644 index 0000000000..b47821cf6f --- /dev/null +++ b/packages/spec/src/migrations/entries/retired-defs/18.system__Incident.ts @@ -0,0 +1,27 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +// #15513 — `system/Incident` — the incident record itself (id / title / severity +// / category / status / reportedBy / affectedSystems / dataClassification / +// responsePhases / lessonsLearned …) — leaves whole with the incident-response +// family under ADR-0049 enforce-or-remove (maintainer ruling 2026-09-05 on +// #15513, ruled A: retire the three compliance-shaped families whole; not +// roadmapped). It was exported from `@objectstack/spec/system` +// (`system/incident-response.zod.ts`), mounted by no `stack.zod.ts` key, +// registered as no metadata type, absent from the 2026-06 liveness ledgers, and +// read by NOTHING: the reader census over every package outside `packages/spec` +// (tests and changelogs excluded), over `examples/**` and `skills/**`, and over +// objectui at the pinned sha returned zero hits for every one of the family's +// exported names, with a lit control on the same pattern. No incident-response +// engine exists on the platform: nothing classified, tracked, escalated or +// notified an incident, and nothing notified a regulator — an author writing +// `notifyRegulators: true` held a compliance promise the platform never kept, +// with no error and no feedback. An exported value schema with no consumer reads +// as a capability (#3950); the generated reference docs advertised a compliance +// subsystem that does not exist. No carrier key, so no `retiredKey()` tombstone +// and no D2 conversion (none of these schemas is a stack collection member — the +// `kernel/MetadataPluginConfig:additionalTypes` reasoning): +// RETIRED_DEFS_BY_MAJOR plus the D3 semantic entry +// `incident-response-family-retired` ARE the declaration. The family's #14477 +// `RETIRED_KEYS_BY_MAJOR[18]` deadline-key entries stay as history — gate (b2) +// of build-schemas.ts accepts an entry naming a key the build no longer emits. +export const entry = 'system/Incident'; diff --git a/packages/spec/src/migrations/entries/retired-defs/18.system__IncidentCategory.ts b/packages/spec/src/migrations/entries/retired-defs/18.system__IncidentCategory.ts new file mode 100644 index 0000000000..bf8af8616a --- /dev/null +++ b/packages/spec/src/migrations/entries/retired-defs/18.system__IncidentCategory.ts @@ -0,0 +1,26 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +// #15513 — `system/IncidentCategory` — the 10-value incident category enum — +// leaves whole with the incident-response family under ADR-0049 +// enforce-or-remove (maintainer ruling 2026-09-05 on #15513, ruled A: retire the +// three compliance-shaped families whole; not roadmapped). It was exported from +// `@objectstack/spec/system` (`system/incident-response.zod.ts`), mounted by no +// `stack.zod.ts` key, registered as no metadata type, absent from the 2026-06 +// liveness ledgers, and read by NOTHING: the reader census over every package +// outside `packages/spec` (tests and changelogs excluded), over `examples/**` +// and `skills/**`, and over objectui at the pinned sha returned zero hits for +// every one of the family's exported names, with a lit control on the same +// pattern. No incident-response engine exists on the platform: nothing +// classified, tracked, escalated or notified an incident, and nothing notified a +// regulator — an author writing `notifyRegulators: true` held a compliance +// promise the platform never kept, with no error and no feedback. An exported +// value schema with no consumer reads as a capability (#3950); the generated +// reference docs advertised a compliance subsystem that does not exist. No +// carrier key, so no `retiredKey()` tombstone and no D2 conversion (none of +// these schemas is a stack collection member — the +// `kernel/MetadataPluginConfig:additionalTypes` reasoning): +// RETIRED_DEFS_BY_MAJOR plus the D3 semantic entry +// `incident-response-family-retired` ARE the declaration. The family's #14477 +// `RETIRED_KEYS_BY_MAJOR[18]` deadline-key entries stay as history — gate (b2) +// of build-schemas.ts accepts an entry naming a key the build no longer emits. +export const entry = 'system/IncidentCategory'; diff --git a/packages/spec/src/migrations/entries/retired-defs/18.system__IncidentNotificationMatrix.ts b/packages/spec/src/migrations/entries/retired-defs/18.system__IncidentNotificationMatrix.ts new file mode 100644 index 0000000000..9075ce8f3a --- /dev/null +++ b/packages/spec/src/migrations/entries/retired-defs/18.system__IncidentNotificationMatrix.ts @@ -0,0 +1,27 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +// #15513 — `system/IncidentNotificationMatrix` — the notification-rule matrix +// (`rules[]`, plus the #14477-retired `escalationTimeoutMinutes`) — leaves whole +// with the incident-response family under ADR-0049 enforce-or-remove (maintainer +// ruling 2026-09-05 on #15513, ruled A: retire the three compliance-shaped +// families whole; not roadmapped). It was exported from +// `@objectstack/spec/system` (`system/incident-response.zod.ts`), mounted by no +// `stack.zod.ts` key, registered as no metadata type, absent from the 2026-06 +// liveness ledgers, and read by NOTHING: the reader census over every package +// outside `packages/spec` (tests and changelogs excluded), over `examples/**` +// and `skills/**`, and over objectui at the pinned sha returned zero hits for +// every one of the family's exported names, with a lit control on the same +// pattern. No incident-response engine exists on the platform: nothing +// classified, tracked, escalated or notified an incident, and nothing notified a +// regulator — an author writing `notifyRegulators: true` held a compliance +// promise the platform never kept, with no error and no feedback. An exported +// value schema with no consumer reads as a capability (#3950); the generated +// reference docs advertised a compliance subsystem that does not exist. No +// carrier key, so no `retiredKey()` tombstone and no D2 conversion (none of +// these schemas is a stack collection member — the +// `kernel/MetadataPluginConfig:additionalTypes` reasoning): +// RETIRED_DEFS_BY_MAJOR plus the D3 semantic entry +// `incident-response-family-retired` ARE the declaration. The family's #14477 +// `RETIRED_KEYS_BY_MAJOR[18]` deadline-key entries stay as history — gate (b2) +// of build-schemas.ts accepts an entry naming a key the build no longer emits. +export const entry = 'system/IncidentNotificationMatrix'; diff --git a/packages/spec/src/migrations/entries/retired-defs/18.system__IncidentNotificationRule.ts b/packages/spec/src/migrations/entries/retired-defs/18.system__IncidentNotificationRule.ts new file mode 100644 index 0000000000..0a7559f75a --- /dev/null +++ b/packages/spec/src/migrations/entries/retired-defs/18.system__IncidentNotificationRule.ts @@ -0,0 +1,27 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +// #15513 — `system/IncidentNotificationRule` — one severity → channels / +// recipients / `notifyRegulators` rule (plus the #14477-retired `withinMinutes` +// / `regulatorDeadlineHours`) — leaves whole with the incident-response family +// under ADR-0049 enforce-or-remove (maintainer ruling 2026-09-05 on #15513, +// ruled A: retire the three compliance-shaped families whole; not roadmapped). +// It was exported from `@objectstack/spec/system` +// (`system/incident-response.zod.ts`), mounted by no `stack.zod.ts` key, +// registered as no metadata type, absent from the 2026-06 liveness ledgers, and +// read by NOTHING: the reader census over every package outside `packages/spec` +// (tests and changelogs excluded), over `examples/**` and `skills/**`, and over +// objectui at the pinned sha returned zero hits for every one of the family's +// exported names, with a lit control on the same pattern. No incident-response +// engine exists on the platform: nothing classified, tracked, escalated or +// notified an incident, and nothing notified a regulator — an author writing +// `notifyRegulators: true` held a compliance promise the platform never kept, +// with no error and no feedback. An exported value schema with no consumer reads +// as a capability (#3950); the generated reference docs advertised a compliance +// subsystem that does not exist. No carrier key, so no `retiredKey()` tombstone +// and no D2 conversion (none of these schemas is a stack collection member — the +// `kernel/MetadataPluginConfig:additionalTypes` reasoning): +// RETIRED_DEFS_BY_MAJOR plus the D3 semantic entry +// `incident-response-family-retired` ARE the declaration. The family's #14477 +// `RETIRED_KEYS_BY_MAJOR[18]` deadline-key entries stay as history — gate (b2) +// of build-schemas.ts accepts an entry naming a key the build no longer emits. +export const entry = 'system/IncidentNotificationRule'; diff --git a/packages/spec/src/migrations/entries/retired-defs/18.system__IncidentResponsePhase.ts b/packages/spec/src/migrations/entries/retired-defs/18.system__IncidentResponsePhase.ts new file mode 100644 index 0000000000..d2f237993f --- /dev/null +++ b/packages/spec/src/migrations/entries/retired-defs/18.system__IncidentResponsePhase.ts @@ -0,0 +1,27 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +// #15513 — `system/IncidentResponsePhase` — one response phase (`phase` / +// `description` / `assignedTo`, plus the #14477-retired `targetHours`) — leaves +// whole with the incident-response family under ADR-0049 enforce-or-remove +// (maintainer ruling 2026-09-05 on #15513, ruled A: retire the three +// compliance-shaped families whole; not roadmapped). It was exported from +// `@objectstack/spec/system` (`system/incident-response.zod.ts`), mounted by no +// `stack.zod.ts` key, registered as no metadata type, absent from the 2026-06 +// liveness ledgers, and read by NOTHING: the reader census over every package +// outside `packages/spec` (tests and changelogs excluded), over `examples/**` +// and `skills/**`, and over objectui at the pinned sha returned zero hits for +// every one of the family's exported names, with a lit control on the same +// pattern. No incident-response engine exists on the platform: nothing +// classified, tracked, escalated or notified an incident, and nothing notified a +// regulator — an author writing `notifyRegulators: true` held a compliance +// promise the platform never kept, with no error and no feedback. An exported +// value schema with no consumer reads as a capability (#3950); the generated +// reference docs advertised a compliance subsystem that does not exist. No +// carrier key, so no `retiredKey()` tombstone and no D2 conversion (none of +// these schemas is a stack collection member — the +// `kernel/MetadataPluginConfig:additionalTypes` reasoning): +// RETIRED_DEFS_BY_MAJOR plus the D3 semantic entry +// `incident-response-family-retired` ARE the declaration. The family's #14477 +// `RETIRED_KEYS_BY_MAJOR[18]` deadline-key entries stay as history — gate (b2) +// of build-schemas.ts accepts an entry naming a key the build no longer emits. +export const entry = 'system/IncidentResponsePhase'; diff --git a/packages/spec/src/migrations/entries/retired-defs/18.system__IncidentResponsePolicy.ts b/packages/spec/src/migrations/entries/retired-defs/18.system__IncidentResponsePolicy.ts new file mode 100644 index 0000000000..13f47c9fe2 --- /dev/null +++ b/packages/spec/src/migrations/entries/retired-defs/18.system__IncidentResponsePolicy.ts @@ -0,0 +1,28 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +// #15513 — `system/IncidentResponsePolicy` — the policy document +// (`notificationMatrix` / `defaultResponseTeam` / `requirePostIncidentReview` …, +// plus the #14477-retired `triageDeadlineHours` / `retentionDays`) — leaves +// whole with the incident-response family under ADR-0049 enforce-or-remove +// (maintainer ruling 2026-09-05 on #15513, ruled A: retire the three +// compliance-shaped families whole; not roadmapped). It was exported from +// `@objectstack/spec/system` (`system/incident-response.zod.ts`), mounted by no +// `stack.zod.ts` key, registered as no metadata type, absent from the 2026-06 +// liveness ledgers, and read by NOTHING: the reader census over every package +// outside `packages/spec` (tests and changelogs excluded), over `examples/**` +// and `skills/**`, and over objectui at the pinned sha returned zero hits for +// every one of the family's exported names, with a lit control on the same +// pattern. No incident-response engine exists on the platform: nothing +// classified, tracked, escalated or notified an incident, and nothing notified a +// regulator — an author writing `notifyRegulators: true` held a compliance +// promise the platform never kept, with no error and no feedback. An exported +// value schema with no consumer reads as a capability (#3950); the generated +// reference docs advertised a compliance subsystem that does not exist. No +// carrier key, so no `retiredKey()` tombstone and no D2 conversion (none of +// these schemas is a stack collection member — the +// `kernel/MetadataPluginConfig:additionalTypes` reasoning): +// RETIRED_DEFS_BY_MAJOR plus the D3 semantic entry +// `incident-response-family-retired` ARE the declaration. The family's #14477 +// `RETIRED_KEYS_BY_MAJOR[18]` deadline-key entries stay as history — gate (b2) +// of build-schemas.ts accepts an entry naming a key the build no longer emits. +export const entry = 'system/IncidentResponsePolicy'; diff --git a/packages/spec/src/migrations/entries/retired-defs/18.system__IncidentSeverity.ts b/packages/spec/src/migrations/entries/retired-defs/18.system__IncidentSeverity.ts new file mode 100644 index 0000000000..3527ce0cb9 --- /dev/null +++ b/packages/spec/src/migrations/entries/retired-defs/18.system__IncidentSeverity.ts @@ -0,0 +1,26 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +// #15513 — `system/IncidentSeverity` — the 4-value severity enum — leaves whole +// with the incident-response family under ADR-0049 enforce-or-remove (maintainer +// ruling 2026-09-05 on #15513, ruled A: retire the three compliance-shaped +// families whole; not roadmapped). It was exported from +// `@objectstack/spec/system` (`system/incident-response.zod.ts`), mounted by no +// `stack.zod.ts` key, registered as no metadata type, absent from the 2026-06 +// liveness ledgers, and read by NOTHING: the reader census over every package +// outside `packages/spec` (tests and changelogs excluded), over `examples/**` +// and `skills/**`, and over objectui at the pinned sha returned zero hits for +// every one of the family's exported names, with a lit control on the same +// pattern. No incident-response engine exists on the platform: nothing +// classified, tracked, escalated or notified an incident, and nothing notified a +// regulator — an author writing `notifyRegulators: true` held a compliance +// promise the platform never kept, with no error and no feedback. An exported +// value schema with no consumer reads as a capability (#3950); the generated +// reference docs advertised a compliance subsystem that does not exist. No +// carrier key, so no `retiredKey()` tombstone and no D2 conversion (none of +// these schemas is a stack collection member — the +// `kernel/MetadataPluginConfig:additionalTypes` reasoning): +// RETIRED_DEFS_BY_MAJOR plus the D3 semantic entry +// `incident-response-family-retired` ARE the declaration. The family's #14477 +// `RETIRED_KEYS_BY_MAJOR[18]` deadline-key entries stay as history — gate (b2) +// of build-schemas.ts accepts an entry naming a key the build no longer emits. +export const entry = 'system/IncidentSeverity'; diff --git a/packages/spec/src/migrations/entries/retired-defs/18.system__IncidentStatus.ts b/packages/spec/src/migrations/entries/retired-defs/18.system__IncidentStatus.ts new file mode 100644 index 0000000000..eb9fd8a322 --- /dev/null +++ b/packages/spec/src/migrations/entries/retired-defs/18.system__IncidentStatus.ts @@ -0,0 +1,26 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +// #15513 — `system/IncidentStatus` — the 7-value incident status enum — leaves +// whole with the incident-response family under ADR-0049 enforce-or-remove +// (maintainer ruling 2026-09-05 on #15513, ruled A: retire the three +// compliance-shaped families whole; not roadmapped). It was exported from +// `@objectstack/spec/system` (`system/incident-response.zod.ts`), mounted by no +// `stack.zod.ts` key, registered as no metadata type, absent from the 2026-06 +// liveness ledgers, and read by NOTHING: the reader census over every package +// outside `packages/spec` (tests and changelogs excluded), over `examples/**` +// and `skills/**`, and over objectui at the pinned sha returned zero hits for +// every one of the family's exported names, with a lit control on the same +// pattern. No incident-response engine exists on the platform: nothing +// classified, tracked, escalated or notified an incident, and nothing notified a +// regulator — an author writing `notifyRegulators: true` held a compliance +// promise the platform never kept, with no error and no feedback. An exported +// value schema with no consumer reads as a capability (#3950); the generated +// reference docs advertised a compliance subsystem that does not exist. No +// carrier key, so no `retiredKey()` tombstone and no D2 conversion (none of +// these schemas is a stack collection member — the +// `kernel/MetadataPluginConfig:additionalTypes` reasoning): +// RETIRED_DEFS_BY_MAJOR plus the D3 semantic entry +// `incident-response-family-retired` ARE the declaration. The family's #14477 +// `RETIRED_KEYS_BY_MAJOR[18]` deadline-key entries stay as history — gate (b2) +// of build-schemas.ts accepts an entry naming a key the build no longer emits. +export const entry = 'system/IncidentStatus'; diff --git a/packages/spec/src/migrations/entries/retired-defs/18.system__RollbackPlan.ts b/packages/spec/src/migrations/entries/retired-defs/18.system__RollbackPlan.ts new file mode 100644 index 0000000000..a0ae14547a --- /dev/null +++ b/packages/spec/src/migrations/entries/retired-defs/18.system__RollbackPlan.ts @@ -0,0 +1,28 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +// #15513 — `system/RollbackPlan` — the rollback plan (`description` / `steps[]` +// / `testProcedure` …, plus the #14477-retired nested +// `steps[].estimatedMinutes`) — leaves whole with the change-management family +// under ADR-0049 enforce-or-remove (maintainer ruling 2026-09-05 on #15513, +// ruled A: retire the three compliance-shaped families whole; not roadmapped). +// It was exported from `@objectstack/spec/system` +// (`system/change-management.zod.ts`), mounted by no `stack.zod.ts` key, +// registered as no metadata type, absent from the 2026-06 liveness ledgers, and +// read by NOTHING: the reader census over every package outside `packages/spec` +// (tests and changelogs excluded), over `examples/**` and `skills/**`, and over +// objectui at the pinned sha returned zero hits for every one of the family's +// exported names, with a lit control on the same pattern. No change-management +// engine exists on the platform: nothing routed a change request for approval, +// walked its implementation steps, honoured a rollback plan or gated on +// `securityImpact.requiresSecurityApproval` / `approval.required` — both of +// which read as gates the platform enforced, and neither ever did. An exported +// value schema with no consumer reads as a capability (#3950); the generated +// reference docs advertised a compliance subsystem that does not exist. No +// carrier key, so no `retiredKey()` tombstone and no D2 conversion (none of +// these schemas is a stack collection member — the +// `kernel/MetadataPluginConfig:additionalTypes` reasoning): +// RETIRED_DEFS_BY_MAJOR plus the D3 semantic entry +// `change-management-family-retired` ARE the declaration. The family's #14477 +// `RETIRED_KEYS_BY_MAJOR[18]` deadline-key entries stay as history — gate (b2) +// of build-schemas.ts accepts an entry naming a key the build no longer emits. +export const entry = 'system/RollbackPlan'; diff --git a/packages/spec/src/migrations/entries/retired-defs/18.system__TrainingCategory.ts b/packages/spec/src/migrations/entries/retired-defs/18.system__TrainingCategory.ts new file mode 100644 index 0000000000..4becdd39be --- /dev/null +++ b/packages/spec/src/migrations/entries/retired-defs/18.system__TrainingCategory.ts @@ -0,0 +1,25 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +// #15513 — `system/TrainingCategory` — the 8-value training category enum — +// leaves whole with the training family under ADR-0049 enforce-or-remove +// (maintainer ruling 2026-09-05 on #15513, ruled A: retire the three +// compliance-shaped families whole; not roadmapped). It was exported from +// `@objectstack/spec/system` (`system/training.zod.ts`), mounted by no +// `stack.zod.ts` key, registered as no metadata type, absent from the 2026-06 +// liveness ledgers, and read by NOTHING: the reader census over every package +// outside `packages/spec` (tests and changelogs excluded), over `examples/**` +// and `skills/**`, and over objectui at the pinned sha returned zero hits for +// every one of the family's exported names, with a lit control on the same +// pattern. No training-management engine exists on the platform: nothing +// assigned a course, tracked a completion, sent a reminder or expired a +// certification — `mandatory: true`, `trackCompletion: true` and `sendReminders: +// true` were declarations nothing ever read. An exported value schema with no +// consumer reads as a capability (#3950); the generated reference docs +// advertised a compliance subsystem that does not exist. No carrier key, so no +// `retiredKey()` tombstone and no D2 conversion (none of these schemas is a +// stack collection member — the `kernel/MetadataPluginConfig:additionalTypes` +// reasoning): RETIRED_DEFS_BY_MAJOR plus the D3 semantic entry +// `training-family-retired` ARE the declaration. The family's #14477 +// `RETIRED_KEYS_BY_MAJOR[18]` deadline-key entries stay as history — gate (b2) +// of build-schemas.ts accepts an entry naming a key the build no longer emits. +export const entry = 'system/TrainingCategory'; diff --git a/packages/spec/src/migrations/entries/retired-defs/18.system__TrainingCompletionStatus.ts b/packages/spec/src/migrations/entries/retired-defs/18.system__TrainingCompletionStatus.ts new file mode 100644 index 0000000000..58376c02ff --- /dev/null +++ b/packages/spec/src/migrations/entries/retired-defs/18.system__TrainingCompletionStatus.ts @@ -0,0 +1,25 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +// #15513 — `system/TrainingCompletionStatus` — the 5-value completion status +// enum — leaves whole with the training family under ADR-0049 enforce-or-remove +// (maintainer ruling 2026-09-05 on #15513, ruled A: retire the three +// compliance-shaped families whole; not roadmapped). It was exported from +// `@objectstack/spec/system` (`system/training.zod.ts`), mounted by no +// `stack.zod.ts` key, registered as no metadata type, absent from the 2026-06 +// liveness ledgers, and read by NOTHING: the reader census over every package +// outside `packages/spec` (tests and changelogs excluded), over `examples/**` +// and `skills/**`, and over objectui at the pinned sha returned zero hits for +// every one of the family's exported names, with a lit control on the same +// pattern. No training-management engine exists on the platform: nothing +// assigned a course, tracked a completion, sent a reminder or expired a +// certification — `mandatory: true`, `trackCompletion: true` and `sendReminders: +// true` were declarations nothing ever read. An exported value schema with no +// consumer reads as a capability (#3950); the generated reference docs +// advertised a compliance subsystem that does not exist. No carrier key, so no +// `retiredKey()` tombstone and no D2 conversion (none of these schemas is a +// stack collection member — the `kernel/MetadataPluginConfig:additionalTypes` +// reasoning): RETIRED_DEFS_BY_MAJOR plus the D3 semantic entry +// `training-family-retired` ARE the declaration. The family's #14477 +// `RETIRED_KEYS_BY_MAJOR[18]` deadline-key entries stay as history — gate (b2) +// of build-schemas.ts accepts an entry naming a key the build no longer emits. +export const entry = 'system/TrainingCompletionStatus'; diff --git a/packages/spec/src/migrations/entries/retired-defs/18.system__TrainingCourse.ts b/packages/spec/src/migrations/entries/retired-defs/18.system__TrainingCourse.ts new file mode 100644 index 0000000000..0f235ecf93 --- /dev/null +++ b/packages/spec/src/migrations/entries/retired-defs/18.system__TrainingCourse.ts @@ -0,0 +1,26 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +// #15513 — `system/TrainingCourse` — a course (`id` / `title` / `category` / +// `mandatory` / `targetRoles` / `passingScore` …, plus the #14477-retired +// `durationMinutes` / `validityDays`) — leaves whole with the training family +// under ADR-0049 enforce-or-remove (maintainer ruling 2026-09-05 on #15513, +// ruled A: retire the three compliance-shaped families whole; not roadmapped). +// It was exported from `@objectstack/spec/system` (`system/training.zod.ts`), +// mounted by no `stack.zod.ts` key, registered as no metadata type, absent from +// the 2026-06 liveness ledgers, and read by NOTHING: the reader census over +// every package outside `packages/spec` (tests and changelogs excluded), over +// `examples/**` and `skills/**`, and over objectui at the pinned sha returned +// zero hits for every one of the family's exported names, with a lit control on +// the same pattern. No training-management engine exists on the platform: +// nothing assigned a course, tracked a completion, sent a reminder or expired a +// certification — `mandatory: true`, `trackCompletion: true` and `sendReminders: +// true` were declarations nothing ever read. An exported value schema with no +// consumer reads as a capability (#3950); the generated reference docs +// advertised a compliance subsystem that does not exist. No carrier key, so no +// `retiredKey()` tombstone and no D2 conversion (none of these schemas is a +// stack collection member — the `kernel/MetadataPluginConfig:additionalTypes` +// reasoning): RETIRED_DEFS_BY_MAJOR plus the D3 semantic entry +// `training-family-retired` ARE the declaration. The family's #14477 +// `RETIRED_KEYS_BY_MAJOR[18]` deadline-key entries stay as history — gate (b2) +// of build-schemas.ts accepts an entry naming a key the build no longer emits. +export const entry = 'system/TrainingCourse'; diff --git a/packages/spec/src/migrations/entries/retired-defs/18.system__TrainingPlan.ts b/packages/spec/src/migrations/entries/retired-defs/18.system__TrainingPlan.ts new file mode 100644 index 0000000000..ff1c8e8a74 --- /dev/null +++ b/packages/spec/src/migrations/entries/retired-defs/18.system__TrainingPlan.ts @@ -0,0 +1,27 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +// #15513 — `system/TrainingPlan` — the organisational plan (`courses[]` / +// `trackCompletion` / `sendReminders` …, plus the #14477-retired +// `recertificationIntervalDays` / `gracePeriodDays` / `reminderDaysBefore`) — +// leaves whole with the training family under ADR-0049 enforce-or-remove +// (maintainer ruling 2026-09-05 on #15513, ruled A: retire the three +// compliance-shaped families whole; not roadmapped). It was exported from +// `@objectstack/spec/system` (`system/training.zod.ts`), mounted by no +// `stack.zod.ts` key, registered as no metadata type, absent from the 2026-06 +// liveness ledgers, and read by NOTHING: the reader census over every package +// outside `packages/spec` (tests and changelogs excluded), over `examples/**` +// and `skills/**`, and over objectui at the pinned sha returned zero hits for +// every one of the family's exported names, with a lit control on the same +// pattern. No training-management engine exists on the platform: nothing +// assigned a course, tracked a completion, sent a reminder or expired a +// certification — `mandatory: true`, `trackCompletion: true` and `sendReminders: +// true` were declarations nothing ever read. An exported value schema with no +// consumer reads as a capability (#3950); the generated reference docs +// advertised a compliance subsystem that does not exist. No carrier key, so no +// `retiredKey()` tombstone and no D2 conversion (none of these schemas is a +// stack collection member — the `kernel/MetadataPluginConfig:additionalTypes` +// reasoning): RETIRED_DEFS_BY_MAJOR plus the D3 semantic entry +// `training-family-retired` ARE the declaration. The family's #14477 +// `RETIRED_KEYS_BY_MAJOR[18]` deadline-key entries stay as history — gate (b2) +// of build-schemas.ts accepts an entry naming a key the build no longer emits. +export const entry = 'system/TrainingPlan'; diff --git a/packages/spec/src/migrations/entries/retired-defs/18.system__TrainingRecord.ts b/packages/spec/src/migrations/entries/retired-defs/18.system__TrainingRecord.ts new file mode 100644 index 0000000000..9066e6026f --- /dev/null +++ b/packages/spec/src/migrations/entries/retired-defs/18.system__TrainingRecord.ts @@ -0,0 +1,26 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +// #15513 — `system/TrainingRecord` — one completion record (`courseId` / +// `userId` / `status` / `score` / `completedAt` …) — leaves whole with the +// training family under ADR-0049 enforce-or-remove (maintainer ruling 2026-09-05 +// on #15513, ruled A: retire the three compliance-shaped families whole; not +// roadmapped). It was exported from `@objectstack/spec/system` +// (`system/training.zod.ts`), mounted by no `stack.zod.ts` key, registered as no +// metadata type, absent from the 2026-06 liveness ledgers, and read by NOTHING: +// the reader census over every package outside `packages/spec` (tests and +// changelogs excluded), over `examples/**` and `skills/**`, and over objectui at +// the pinned sha returned zero hits for every one of the family's exported +// names, with a lit control on the same pattern. No training-management engine +// exists on the platform: nothing assigned a course, tracked a completion, sent +// a reminder or expired a certification — `mandatory: true`, `trackCompletion: +// true` and `sendReminders: true` were declarations nothing ever read. An +// exported value schema with no consumer reads as a capability (#3950); the +// generated reference docs advertised a compliance subsystem that does not +// exist. No carrier key, so no `retiredKey()` tombstone and no D2 conversion +// (none of these schemas is a stack collection member — the +// `kernel/MetadataPluginConfig:additionalTypes` reasoning): +// RETIRED_DEFS_BY_MAJOR plus the D3 semantic entry `training-family-retired` ARE +// the declaration. The family's #14477 `RETIRED_KEYS_BY_MAJOR[18]` deadline-key +// entries stay as history — gate (b2) of build-schemas.ts accepts an entry +// naming a key the build no longer emits. +export const entry = 'system/TrainingRecord'; diff --git a/packages/spec/src/migrations/entries/retired-keys/18.data__ESignatureConfig__expirationDays.ts b/packages/spec/src/migrations/entries/retired-keys/18.data__ESignatureConfig__expirationDays.ts new file mode 100644 index 0000000000..edfb67283e --- /dev/null +++ b/packages/spec/src/migrations/entries/retired-keys/18.data__ESignatureConfig__expirationDays.ts @@ -0,0 +1,28 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +// #14477 — ADR-0049 enforce-or-remove. The 2026-09-02 ruling (ruled A: retire +// per family) held the `ESignatureConfig` pair on one condition — a roadmapped +// e-signature consumer would have earned an `[EXPERIMENTAL — not enforced]` tag +// instead — and the maintainer answered it on 2026-09-05 (decision batch #40: +// no roadmap), so the ruling's own branch resolves to retirement. A day-shaped +// deadline key on the published authorable surface (`data/ESignatureConfig`), +// read by NOTHING: no e-signature engine exists on the platform, no layer ever +// sent, expired or reminded a signature request, and the reader census over +// every package outside `packages/spec` (tests and changelogs excluded), over +// `examples/**` and `skills/**`, and over objectui at the pinned sha returned +// zero hits, with a lit control inside `packages/spec`. Its default of 30 days +// was materialized into every parsed configuration without ever being +// consulted. +// +// Registered under 18, not 17: v17.0.0 was cut before this landed, so the +// tombstone ships on the 17.x line (launch-window convention) and the +// prescription lives at the major boundary where `migrate meta` users look. +// +// Registered here but NOT in `src/conversions/registry.ts`, for the reason +// `kernel/MetadataPluginConfig:additionalTypes` gives: `DocumentSchema` is not +// a stack collection member and `document` is no metadata type, so a +// MetadataConversion would be a transform with no seam that ever runs. The +// prescription reaches authors through the tombstone (`tsc` + the parse) and +// the D3 semantic entry named below. +// D3 semantic entry: `esignature-config-deadline-keys-retired`. +export const entry = 'data/ESignatureConfig:expirationDays'; diff --git a/packages/spec/src/migrations/entries/retired-keys/18.data__ESignatureConfig__reminderDays.ts b/packages/spec/src/migrations/entries/retired-keys/18.data__ESignatureConfig__reminderDays.ts new file mode 100644 index 0000000000..6e97e2dbec --- /dev/null +++ b/packages/spec/src/migrations/entries/retired-keys/18.data__ESignatureConfig__reminderDays.ts @@ -0,0 +1,27 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +// #14477 — ADR-0049 enforce-or-remove. The 2026-09-02 ruling (ruled A: retire +// per family) held the `ESignatureConfig` pair on one condition — a roadmapped +// e-signature consumer would have earned an `[EXPERIMENTAL — not enforced]` tag +// instead — and the maintainer answered it on 2026-09-05 (decision batch #40: +// no roadmap), so the ruling's own branch resolves to retirement. A day-shaped +// interval key on the published authorable surface (`data/ESignatureConfig`), +// read by NOTHING: no e-signature engine exists on the platform, no layer ever +// sent a reminder email, and the reader census over every package outside +// `packages/spec` (tests and changelogs excluded), over `examples/**` and +// `skills/**`, and over objectui at the pinned sha returned zero hits, with a +// lit control inside `packages/spec`. Its default of 7 days was materialized +// into every parsed configuration without ever being consulted. +// +// Registered under 18, not 17: v17.0.0 was cut before this landed, so the +// tombstone ships on the 17.x line (launch-window convention) and the +// prescription lives at the major boundary where `migrate meta` users look. +// +// Registered here but NOT in `src/conversions/registry.ts`, for the reason +// `kernel/MetadataPluginConfig:additionalTypes` gives: `DocumentSchema` is not +// a stack collection member and `document` is no metadata type, so a +// MetadataConversion would be a transform with no seam that ever runs. The +// prescription reaches authors through the tombstone (`tsc` + the parse) and +// the D3 semantic entry named below. +// D3 semantic entry: `esignature-config-deadline-keys-retired`. +export const entry = 'data/ESignatureConfig:reminderDays'; diff --git a/packages/spec/src/migrations/entries/semantic/18.change-management-family-retired.ts b/packages/spec/src/migrations/entries/semantic/18.change-management-family-retired.ts new file mode 100644 index 0000000000..fa409f515f --- /dev/null +++ b/packages/spec/src/migrations/entries/semantic/18.change-management-family-retired.ts @@ -0,0 +1,59 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +import type { SemanticMigration } from '../../types.js'; + +export const entry: SemanticMigration = { + id: 'change-management-family-retired', + // No backticks in `surface` — build-upgrade-guide.ts renders it inside a + // code span AND a table cell. + surface: + 'the change-management family, retired whole: the six defs system/ChangeImpact, ' + + 'system/ChangePriority, system/ChangeRequest, system/ChangeStatus, system/ChangeType ' + + 'and system/RollbackPlan, and every name system/change-management.zod.ts exported from ' + + '@objectstack/spec/system (the six *Schema consts, their z.input aliases and the ' + + 'ChangeRequestParsed alias)', + replacement: + 'nothing to re-declare — no change-management engine exists on the platform, so there is ' + + 'no working configuration to migrate to. Nothing routed a change request for approval, ' + + 'walked its implementation steps, honoured a rollback plan or gated on ' + + '`securityImpact.requiresSecurityApproval` / `approval.required`; a change record the ' + + 'organisation keeps is ordinary object data, declared as an object with its own fields, ' + + 'and an approval that must actually gate something is a flow (ADR-0018) with an approval ' + + 'node. Metadata change tracking on the platform is `sys_metadata` history and the ' + + 'package model (ADR-0126), unrelated to this vocabulary. If ITIL change management ' + + 'becomes a product capability it re-declares fresh, through the enforce route of ' + + 'ADR-0049 — the engine first, the vocabulary second', + reason: + 'ADR-0049 enforce-or-remove; maintainer ruling 2026-09-05 on #15513 (ruled A: retire the ' + + 'three compliance-shaped families whole via RETIRED_DEFS_BY_MAJOR, the ' + + 'integration/ErrorMappingConfig precedent; not roadmapped). Six defs and roughly fifty ' + + 'declared keys sat on the exported surface and in the generated reference docs, and were ' + + 'read by NOTHING: the schemas were exported from `@objectstack/spec/system`, mounted by ' + + 'no `stack.zod.ts` key, registered as no metadata type, absent from the 2026-06 liveness ' + + 'ledgers, and the reader census over every package outside `packages/spec` (tests and ' + + 'changelogs excluded), over `examples/**` and `skills/**`, and over objectui at the ' + + 'pinned sha returned zero hits for every exported name, with a lit control. ' + + '`ChangeRequest.approval.required` and `ChangeRequest.securityImpact.requiresSecurityApproval` ' + + 'read as gates the platform enforced, and neither ever did — the worst form of the ' + + 'declared-but-unenforced shape, on a security-adjacent surface. Tagging the family ' + + '`[EXPERIMENTAL — not enforced]` was the fallback the ruling did not take (a human-only ' + + 'signal). The #14477 duration-key tombstones (three nested sites, ' + + '`RETIRED_KEYS_BY_MAJOR[18]`, D3 `change-management-duration-keys-retired`) leave with ' + + 'their defs\' source; their registry entries stay as history. Why D3 semantic and not a ' + + 'D2 conversion: the chain walks a normalized STACK and `applyConversionsToStoredItem` ' + + 'maps a metadata type onto one of its collections; none of these schemas is either, so ' + + 'a conversion would be a transform with no seam that ever runs (the ' + + '`kernel/MetadataPluginConfig:additionalTypes` precedent), and with no carrier key there ' + + 'is no shape on which a tombstone could sit.', + acceptanceCriteria: + 'No code imports ChangeImpactSchema, ChangePrioritySchema, ChangeRequestSchema, ' + + 'ChangeStatusSchema, ChangeTypeSchema or RollbackPlanSchema — or any of their type ' + + 'aliases — from @objectstack/spec or @objectstack/spec/system: every such import is ' + + 'TS2305 after upgrade, and no working replacement exists to point at because the ' + + 'vocabulary described nothing real. `kernel/MetadataChangeType` (M92 of the type-alias ' + + 'pin, a different declaration with a live consumer) is unaffected. The six defs are ' + + 'absent from `json-schema.manifest/system.json`, the api-surface / declaration-map / ' + + 'export-origins shards and the generated reference docs. ⚠️ Runtime behaviour is ' + + 'deliberately UNCHANGED and must be verified as such: nothing ever parsed or read these ' + + 'shapes, so removing them removes no behaviour.', +}; diff --git a/packages/spec/src/migrations/entries/semantic/18.esignature-config-deadline-keys-retired.ts b/packages/spec/src/migrations/entries/semantic/18.esignature-config-deadline-keys-retired.ts new file mode 100644 index 0000000000..89c26fd8ef --- /dev/null +++ b/packages/spec/src/migrations/entries/semantic/18.esignature-config-deadline-keys-retired.ts @@ -0,0 +1,42 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +import type { SemanticMigration } from '../../types.js'; + +export const entry: SemanticMigration = { + id: 'esignature-config-deadline-keys-retired', + surface: + 'e-signature deadline keys: `ESignatureConfig.expirationDays` / `reminderDays` ' + + '(`document.eSignature.expirationDays` / `document.eSignature.reminderDays`)', + replacement: + 'nothing to re-declare — delete the keys. No e-signature engine exists on the platform: ' + + 'no signature request is sent, expired or reminded by any layer, so there is no live ' + + 'mechanism to declare an expiry window or a reminder interval to. `ESignatureConfig` ' + + 'itself stays (`provider` / `enabled` / `signers`), unchanged', + reason: + 'ADR-0049 enforce-or-remove; the 2026-09-02 ruling on #14477 held this pair on one ' + + 'condition — "no roadmap ⇒ they retire with the other three families" — and the ' + + 'maintainer answered it on 2026-09-05 (decision batch #40, no roadmapped e-signature ' + + 'consumer), so the ruling\'s own branch resolves to retirement. Two day-shaped keys sat ' + + 'on the published authorable surface (`authorable-surface/data.json`) and in the ' + + 'generated reference docs — an author could write `expirationDays: 30` and reasonably ' + + 'expect a signature request to lapse after thirty days — and were read by NOTHING: the ' + + 'reader census over every package outside `packages/spec` (tests and changelogs ' + + 'excluded), over `examples/**` and `skills/**`, and over objectui at the pinned sha ' + + 'returned zero hits for `expirationDays`, `reminderDays`, `eSignature` and the ' + + '`ESignatureConfig` names, with a lit control inside `packages/spec`. Both carried ' + + 'defaults (30 days, 7 days) that were materialized into every parsed configuration ' + + 'without ever being consulted. `cloud` and real customer configurations are UNMEASURED. ' + + 'Why D3 semantic and not a D2 conversion: `DocumentSchema` is not a stack collection ' + + 'member and `document` is no metadata type, so the chain has no seam that would ever ' + + 'see one (the `kernel/MetadataPluginConfig:additionalTypes` precedent); the ' + + 'prescription reaches authors through the `retiredKey()` tombstones (`tsc` + the parse) ' + + 'and this entry.', + acceptanceCriteria: + 'No `ESignatureConfig` literal — standalone or nested as `Document.eSignature` — carries ' + + '`expirationDays` or `reminderDays`. TypeScript authors get the refusal at compile time ' + + '(each key is typed `never`); a value reaching the parse is refused with the ' + + 'prescription (`invalid_type` at the path of the key, on the base schema and through ' + + 'the `DocumentSchema.eSignature` carrier). Parsed configurations no longer carry the ' + + 'two former defaults. ⚠️ Runtime behaviour is deliberately UNCHANGED and must be ' + + 'verified as such: nothing ever read the keys, so removing them removes no behaviour.', +}; diff --git a/packages/spec/src/migrations/entries/semantic/18.incident-response-family-retired.ts b/packages/spec/src/migrations/entries/semantic/18.incident-response-family-retired.ts new file mode 100644 index 0000000000..de70fa0efd --- /dev/null +++ b/packages/spec/src/migrations/entries/semantic/18.incident-response-family-retired.ts @@ -0,0 +1,60 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +import type { SemanticMigration } from '../../types.js'; + +export const entry: SemanticMigration = { + id: 'incident-response-family-retired', + // No backticks in `surface` — build-upgrade-guide.ts renders it inside a + // code span AND a table cell. + surface: + 'the incident-response family, retired whole: the eight defs system/Incident, ' + + 'system/IncidentCategory, system/IncidentNotificationMatrix, ' + + 'system/IncidentNotificationRule, system/IncidentResponsePhase, ' + + 'system/IncidentResponsePolicy, system/IncidentSeverity and system/IncidentStatus, ' + + 'and every name system/incident-response.zod.ts exported from @objectstack/spec/system ' + + '(the eight *Schema consts, their z.input aliases and the three *Parsed aliases)', + replacement: + 'nothing to re-declare — no incident-response engine exists on the platform, so there is ' + + 'no working configuration to migrate to. Nothing classified, tracked, escalated or ' + + 'notified an incident and nothing notified a regulator; a compliance record the ' + + 'organisation keeps is ordinary object data, declared as an object with its own ' + + 'fields and enforced by the object engine (validation, permissions, the ' + + 'object-level `lifecycle` block under ADR-0057). If incident response becomes a product ' + + 'capability it re-declares fresh, through the enforce route of ADR-0049 — the engine ' + + 'first, the vocabulary second', + reason: + 'ADR-0049 enforce-or-remove; maintainer ruling 2026-09-05 on #15513 (ruled A: retire the ' + + 'three compliance-shaped families whole via RETIRED_DEFS_BY_MAJOR, the ' + + 'integration/ErrorMappingConfig precedent; not roadmapped). Eight defs and roughly ' + + 'forty declared keys sat on the exported surface and in the generated reference docs, ' + + 'and were read by NOTHING: the schemas were exported from `@objectstack/spec/system`, ' + + 'mounted by no `stack.zod.ts` key, registered as no metadata type, absent from the ' + + '2026-06 liveness ledgers, and the reader census over every package outside ' + + '`packages/spec` (tests and changelogs excluded), over `examples/**` and `skills/**`, ' + + 'and over objectui at the pinned sha returned zero hits for every exported name, with ' + + 'a lit control. Several keys were boolean capability claims of exactly the shape ' + + 'ADR-0049 names — `IncidentNotificationRule.notifyRegulators`, ' + + '`IncidentResponsePolicy.requirePostIncidentReview` — so an author (very often an AI, ' + + 'ADR-0033) could write `notifyRegulators: true`, parse clean, and hold a compliance ' + + 'promise the platform never kept, with no error and no feedback. Tagging the family ' + + '`[EXPERIMENTAL — not enforced]` was the fallback the ruling did not take: it is a ' + + 'human-only signal, and an AI generating from the schema still writes the key and ' + + 'believes it. The #14477 deadline-key tombstones (six sites, `RETIRED_KEYS_BY_MAJOR[18]`, ' + + 'D3 `incident-response-deadline-keys-retired`) leave with their defs\' source; their ' + + 'registry entries stay as history. Why D3 semantic and not a D2 conversion: the chain ' + + 'walks a normalized STACK and `applyConversionsToStoredItem` maps a metadata type onto ' + + 'one of its collections; none of these schemas is either, so a conversion would be a ' + + 'transform with no seam that ever runs (the `kernel/MetadataPluginConfig:additionalTypes` ' + + 'precedent), and with no carrier key there is no shape on which a tombstone could sit.', + acceptanceCriteria: + 'No code imports IncidentSchema, IncidentCategorySchema, IncidentNotificationMatrixSchema, ' + + 'IncidentNotificationRuleSchema, IncidentResponsePhaseSchema, ' + + 'IncidentResponsePolicySchema, IncidentSeveritySchema or IncidentStatusSchema — or any ' + + 'of their type aliases — from @objectstack/spec or @objectstack/spec/system: every such ' + + 'import is TS2305 after upgrade, and no working replacement exists to point at because ' + + 'the vocabulary described nothing real. The eight defs are absent from ' + + '`json-schema.manifest/system.json`, the api-surface / declaration-map / export-origins ' + + 'shards and the generated reference docs. ⚠️ Runtime behaviour is deliberately UNCHANGED ' + + 'and must be verified as such: nothing ever parsed or read these shapes, so removing ' + + 'them removes no behaviour.', +}; diff --git a/packages/spec/src/migrations/entries/semantic/18.training-family-retired.ts b/packages/spec/src/migrations/entries/semantic/18.training-family-retired.ts new file mode 100644 index 0000000000..00ff1d9026 --- /dev/null +++ b/packages/spec/src/migrations/entries/semantic/18.training-family-retired.ts @@ -0,0 +1,55 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +import type { SemanticMigration } from '../../types.js'; + +export const entry: SemanticMigration = { + id: 'training-family-retired', + // No backticks in `surface` — build-upgrade-guide.ts renders it inside a + // code span AND a table cell. + surface: + 'the training family, retired whole: the five defs system/TrainingCategory, ' + + 'system/TrainingCompletionStatus, system/TrainingCourse, system/TrainingPlan and ' + + 'system/TrainingRecord, and every name system/training.zod.ts exported from ' + + '@objectstack/spec/system (the five *Schema consts, their z.input aliases and the two ' + + '*Parsed aliases)', + replacement: + 'nothing to re-declare — no training-management engine exists on the platform, so there ' + + 'is no working configuration to migrate to. Nothing assigned a course, tracked a ' + + 'completion, sent a reminder or expired a certification; a training record the ' + + 'organisation keeps is ordinary object data, declared as an object with its own fields ' + + 'and enforced by the object engine. If training management becomes a product capability ' + + 'it re-declares fresh, through the enforce route of ADR-0049 — the engine first, the ' + + 'vocabulary second', + reason: + 'ADR-0049 enforce-or-remove; maintainer ruling 2026-09-05 on #15513 (ruled A: retire the ' + + 'three compliance-shaped families whole via RETIRED_DEFS_BY_MAJOR, the ' + + 'integration/ErrorMappingConfig precedent; not roadmapped). Five defs and roughly ' + + 'twenty-five declared keys sat on the exported surface and in the generated reference ' + + 'docs, and were read by NOTHING: the schemas were exported from ' + + '`@objectstack/spec/system`, mounted by no `stack.zod.ts` key, registered as no metadata ' + + 'type, absent from the 2026-06 liveness ledgers, and the reader census over every ' + + 'package outside `packages/spec` (tests and changelogs excluded), over `examples/**` and ' + + '`skills/**`, and over objectui at the pinned sha returned zero hits for every exported ' + + 'name, with a lit control. `TrainingCourse.mandatory`, `TrainingPlan.trackCompletion` ' + + 'and `TrainingPlan.sendReminders` were boolean capability claims of exactly the shape ' + + 'ADR-0049 names: an author could write them, parse clean, and get no behaviour and no ' + + 'diagnostic. Tagging the family `[EXPERIMENTAL — not enforced]` was the fallback the ' + + 'ruling did not take (a human-only signal). The #14477 deadline-key tombstones (five ' + + 'sites, `RETIRED_KEYS_BY_MAJOR[18]`, D3 `training-deadline-keys-retired`) leave with ' + + 'their defs\' source; their registry entries stay as history. Why D3 semantic and not a ' + + 'D2 conversion: the chain walks a normalized STACK and `applyConversionsToStoredItem` ' + + 'maps a metadata type onto one of its collections; none of these schemas is either, so ' + + 'a conversion would be a transform with no seam that ever runs (the ' + + '`kernel/MetadataPluginConfig:additionalTypes` precedent), and with no carrier key there ' + + 'is no shape on which a tombstone could sit.', + acceptanceCriteria: + 'No code imports TrainingCategorySchema, TrainingCompletionStatusSchema, ' + + 'TrainingCourseSchema, TrainingPlanSchema or TrainingRecordSchema — or any of their type ' + + 'aliases — from @objectstack/spec or @objectstack/spec/system: every such import is ' + + 'TS2305 after upgrade, and no working replacement exists to point at because the ' + + 'vocabulary described nothing real. The five defs are absent from ' + + '`json-schema.manifest/system.json`, the api-surface / declaration-map / export-origins ' + + 'shards and the generated reference docs. ⚠️ Runtime behaviour is deliberately UNCHANGED ' + + 'and must be verified as such: nothing ever parsed or read these shapes, so removing ' + + 'them removes no behaviour.', +}; diff --git a/packages/spec/src/migrations/registry.ts b/packages/spec/src/migrations/registry.ts index d70701822d..04652d036b 100644 --- a/packages/spec/src/migrations/registry.ts +++ b/packages/spec/src/migrations/registry.ts @@ -5338,7 +5338,24 @@ const step18: MigrationStep = { 'who wrote `triageDeadlineHours: 4` held a deadline the platform never kept. All ' + 'fourteen are retiredKey tombstones (the schemas are not strict; a bare deletion ' + 'would be a silent strip) with no D2 conversion, for the additionalTypes reason: ' + - 'none of these schemas is a stack collection member, so the chain has no seam.', + 'none of these schemas is a stack collection member, so the chain has no seam. ' + + 'It then retires those three compliance-shaped families WHOLE (#15513, ADR-0049 ' + + 'enforce-or-remove; maintainer ruling 2026-09-05, ruled A, not roadmapped): the nineteen ' + + 'defs of `system/incident-response.zod.ts`, `system/training.zod.ts` and ' + + '`system/change-management.zod.ts` — roughly a hundred declared keys, exported from ' + + '`@objectstack/spec/system`, mounted by no stack key, registered as no metadata type, ' + + 'absent from the liveness ledgers, read by nothing repo-wide (examples, skills and ' + + 'objectui at the pinned sha included) — leave via RETIRED_DEFS_BY_MAJOR with one D3 ' + + 'semantic entry per family; the fourteen deadline-key tombstones leave with their ' + + 'defs\' source and their RETIRED_KEYS_BY_MAJOR[18] entries stay as history. Boolean ' + + 'capability claims such as `notifyRegulators`, `requirePostIncidentReview`, ' + + '`trackCompletion` and `approval.required` were the sharpest declared-≠-enforced shape ' + + 'left: an author writing `notifyRegulators: true` held a compliance promise the platform ' + + 'never kept. And it resolves the branch the #14477 ruling held open — no roadmapped ' + + 'e-signature consumer — so `ESignatureConfig.expirationDays` / `reminderDays` ' + + '(`data/document.zod.ts`, defaults 30 / 7 days, read by nothing) are retiredKey ' + + 'tombstones with no D2 conversion (`document` is no stack collection member), ' + + 'registered in RETIRED_KEYS_BY_MAJOR[18] with one D3 semantic entry.', conversionIds: [ 'field-malformed-scale-precision-removed', 'record-chatter-position-vocabulary', @@ -5768,6 +5785,61 @@ const step18: MigrationStep = { + '⚠️ Runtime behaviour is deliberately UNCHANGED and must be verified as such: nothing ' + 'ever read the keys, so removing them removes no behaviour.', }, + { + id: 'change-management-family-retired', + // No backticks in `surface` — build-upgrade-guide.ts renders it inside a + // code span AND a table cell. + surface: + 'the change-management family, retired whole: the six defs system/ChangeImpact, ' + + 'system/ChangePriority, system/ChangeRequest, system/ChangeStatus, system/ChangeType ' + + 'and system/RollbackPlan, and every name system/change-management.zod.ts exported from ' + + '@objectstack/spec/system (the six *Schema consts, their z.input aliases and the ' + + 'ChangeRequestParsed alias)', + replacement: + 'nothing to re-declare — no change-management engine exists on the platform, so there is ' + + 'no working configuration to migrate to. Nothing routed a change request for approval, ' + + 'walked its implementation steps, honoured a rollback plan or gated on ' + + '`securityImpact.requiresSecurityApproval` / `approval.required`; a change record the ' + + 'organisation keeps is ordinary object data, declared as an object with its own fields, ' + + 'and an approval that must actually gate something is a flow (ADR-0018) with an approval ' + + 'node. Metadata change tracking on the platform is `sys_metadata` history and the ' + + 'package model (ADR-0126), unrelated to this vocabulary. If ITIL change management ' + + 'becomes a product capability it re-declares fresh, through the enforce route of ' + + 'ADR-0049 — the engine first, the vocabulary second', + reason: + 'ADR-0049 enforce-or-remove; maintainer ruling 2026-09-05 on #15513 (ruled A: retire the ' + + 'three compliance-shaped families whole via RETIRED_DEFS_BY_MAJOR, the ' + + 'integration/ErrorMappingConfig precedent; not roadmapped). Six defs and roughly fifty ' + + 'declared keys sat on the exported surface and in the generated reference docs, and were ' + + 'read by NOTHING: the schemas were exported from `@objectstack/spec/system`, mounted by ' + + 'no `stack.zod.ts` key, registered as no metadata type, absent from the 2026-06 liveness ' + + 'ledgers, and the reader census over every package outside `packages/spec` (tests and ' + + 'changelogs excluded), over `examples/**` and `skills/**`, and over objectui at the ' + + 'pinned sha returned zero hits for every exported name, with a lit control. ' + + '`ChangeRequest.approval.required` and `ChangeRequest.securityImpact.requiresSecurityApproval` ' + + 'read as gates the platform enforced, and neither ever did — the worst form of the ' + + 'declared-but-unenforced shape, on a security-adjacent surface. Tagging the family ' + + '`[EXPERIMENTAL — not enforced]` was the fallback the ruling did not take (a human-only ' + + 'signal). The #14477 duration-key tombstones (three nested sites, ' + + '`RETIRED_KEYS_BY_MAJOR[18]`, D3 `change-management-duration-keys-retired`) leave with ' + + 'their defs\' source; their registry entries stay as history. Why D3 semantic and not a ' + + 'D2 conversion: the chain walks a normalized STACK and `applyConversionsToStoredItem` ' + + 'maps a metadata type onto one of its collections; none of these schemas is either, so ' + + 'a conversion would be a transform with no seam that ever runs (the ' + + '`kernel/MetadataPluginConfig:additionalTypes` precedent), and with no carrier key there ' + + 'is no shape on which a tombstone could sit.', + acceptanceCriteria: + 'No code imports ChangeImpactSchema, ChangePrioritySchema, ChangeRequestSchema, ' + + 'ChangeStatusSchema, ChangeTypeSchema or RollbackPlanSchema — or any of their type ' + + 'aliases — from @objectstack/spec or @objectstack/spec/system: every such import is ' + + 'TS2305 after upgrade, and no working replacement exists to point at because the ' + + 'vocabulary described nothing real. `kernel/MetadataChangeType` (M92 of the type-alias ' + + 'pin, a different declaration with a live consumer) is unaffected. The six defs are ' + + 'absent from `json-schema.manifest/system.json`, the api-surface / declaration-map / ' + + 'export-origins shards and the generated reference docs. ⚠️ Runtime behaviour is ' + + 'deliberately UNCHANGED and must be verified as such: nothing ever parsed or read these ' + + 'shapes, so removing them removes no behaviour.', + }, { id: 'cli-command-contribution-retired', surface: @@ -6586,6 +6658,44 @@ const step18: MigrationStep = { + '(`{"address.city": …}`) needs NO action — it is deliberately not judged. Reads complete ' + 'with no `INVALID_FIELD` naming a dotted filter key, at either door.', }, + { + id: 'esignature-config-deadline-keys-retired', + surface: + 'e-signature deadline keys: `ESignatureConfig.expirationDays` / `reminderDays` ' + + '(`document.eSignature.expirationDays` / `document.eSignature.reminderDays`)', + replacement: + 'nothing to re-declare — delete the keys. No e-signature engine exists on the platform: ' + + 'no signature request is sent, expired or reminded by any layer, so there is no live ' + + 'mechanism to declare an expiry window or a reminder interval to. `ESignatureConfig` ' + + 'itself stays (`provider` / `enabled` / `signers`), unchanged', + reason: + 'ADR-0049 enforce-or-remove; the 2026-09-02 ruling on #14477 held this pair on one ' + + 'condition — "no roadmap ⇒ they retire with the other three families" — and the ' + + 'maintainer answered it on 2026-09-05 (decision batch #40, no roadmapped e-signature ' + + 'consumer), so the ruling\'s own branch resolves to retirement. Two day-shaped keys sat ' + + 'on the published authorable surface (`authorable-surface/data.json`) and in the ' + + 'generated reference docs — an author could write `expirationDays: 30` and reasonably ' + + 'expect a signature request to lapse after thirty days — and were read by NOTHING: the ' + + 'reader census over every package outside `packages/spec` (tests and changelogs ' + + 'excluded), over `examples/**` and `skills/**`, and over objectui at the pinned sha ' + + 'returned zero hits for `expirationDays`, `reminderDays`, `eSignature` and the ' + + '`ESignatureConfig` names, with a lit control inside `packages/spec`. Both carried ' + + 'defaults (30 days, 7 days) that were materialized into every parsed configuration ' + + 'without ever being consulted. `cloud` and real customer configurations are UNMEASURED. ' + + 'Why D3 semantic and not a D2 conversion: `DocumentSchema` is not a stack collection ' + + 'member and `document` is no metadata type, so the chain has no seam that would ever ' + + 'see one (the `kernel/MetadataPluginConfig:additionalTypes` precedent); the ' + + 'prescription reaches authors through the `retiredKey()` tombstones (`tsc` + the parse) ' + + 'and this entry.', + acceptanceCriteria: + 'No `ESignatureConfig` literal — standalone or nested as `Document.eSignature` — carries ' + + '`expirationDays` or `reminderDays`. TypeScript authors get the refusal at compile time ' + + '(each key is typed `never`); a value reaching the parse is refused with the ' + + 'prescription (`invalid_type` at the path of the key, on the base schema and through ' + + 'the `DocumentSchema.eSignature` carrier). Parsed configurations no longer carry the ' + + 'two former defaults. ⚠️ Runtime behaviour is deliberately UNCHANGED and must be ' + + 'verified as such: nothing ever read the keys, so removing them removes no behaviour.', + }, { id: 'event-name-schema-retired', surface: @@ -7142,6 +7252,62 @@ const step18: MigrationStep = { + 'the three former defaults. ⚠️ Runtime behaviour is deliberately UNCHANGED and must be ' + 'verified as such: nothing ever read the keys, so removing them removes no behaviour.', }, + { + id: 'incident-response-family-retired', + // No backticks in `surface` — build-upgrade-guide.ts renders it inside a + // code span AND a table cell. + surface: + 'the incident-response family, retired whole: the eight defs system/Incident, ' + + 'system/IncidentCategory, system/IncidentNotificationMatrix, ' + + 'system/IncidentNotificationRule, system/IncidentResponsePhase, ' + + 'system/IncidentResponsePolicy, system/IncidentSeverity and system/IncidentStatus, ' + + 'and every name system/incident-response.zod.ts exported from @objectstack/spec/system ' + + '(the eight *Schema consts, their z.input aliases and the three *Parsed aliases)', + replacement: + 'nothing to re-declare — no incident-response engine exists on the platform, so there is ' + + 'no working configuration to migrate to. Nothing classified, tracked, escalated or ' + + 'notified an incident and nothing notified a regulator; a compliance record the ' + + 'organisation keeps is ordinary object data, declared as an object with its own ' + + 'fields and enforced by the object engine (validation, permissions, the ' + + 'object-level `lifecycle` block under ADR-0057). If incident response becomes a product ' + + 'capability it re-declares fresh, through the enforce route of ADR-0049 — the engine ' + + 'first, the vocabulary second', + reason: + 'ADR-0049 enforce-or-remove; maintainer ruling 2026-09-05 on #15513 (ruled A: retire the ' + + 'three compliance-shaped families whole via RETIRED_DEFS_BY_MAJOR, the ' + + 'integration/ErrorMappingConfig precedent; not roadmapped). Eight defs and roughly ' + + 'forty declared keys sat on the exported surface and in the generated reference docs, ' + + 'and were read by NOTHING: the schemas were exported from `@objectstack/spec/system`, ' + + 'mounted by no `stack.zod.ts` key, registered as no metadata type, absent from the ' + + '2026-06 liveness ledgers, and the reader census over every package outside ' + + '`packages/spec` (tests and changelogs excluded), over `examples/**` and `skills/**`, ' + + 'and over objectui at the pinned sha returned zero hits for every exported name, with ' + + 'a lit control. Several keys were boolean capability claims of exactly the shape ' + + 'ADR-0049 names — `IncidentNotificationRule.notifyRegulators`, ' + + '`IncidentResponsePolicy.requirePostIncidentReview` — so an author (very often an AI, ' + + 'ADR-0033) could write `notifyRegulators: true`, parse clean, and hold a compliance ' + + 'promise the platform never kept, with no error and no feedback. Tagging the family ' + + '`[EXPERIMENTAL — not enforced]` was the fallback the ruling did not take: it is a ' + + 'human-only signal, and an AI generating from the schema still writes the key and ' + + 'believes it. The #14477 deadline-key tombstones (six sites, `RETIRED_KEYS_BY_MAJOR[18]`, ' + + 'D3 `incident-response-deadline-keys-retired`) leave with their defs\' source; their ' + + 'registry entries stay as history. Why D3 semantic and not a D2 conversion: the chain ' + + 'walks a normalized STACK and `applyConversionsToStoredItem` maps a metadata type onto ' + + 'one of its collections; none of these schemas is either, so a conversion would be a ' + + 'transform with no seam that ever runs (the `kernel/MetadataPluginConfig:additionalTypes` ' + + 'precedent), and with no carrier key there is no shape on which a tombstone could sit.', + acceptanceCriteria: + 'No code imports IncidentSchema, IncidentCategorySchema, IncidentNotificationMatrixSchema, ' + + 'IncidentNotificationRuleSchema, IncidentResponsePhaseSchema, ' + + 'IncidentResponsePolicySchema, IncidentSeveritySchema or IncidentStatusSchema — or any ' + + 'of their type aliases — from @objectstack/spec or @objectstack/spec/system: every such ' + + 'import is TS2305 after upgrade, and no working replacement exists to point at because ' + + 'the vocabulary described nothing real. The eight defs are absent from ' + + '`json-schema.manifest/system.json`, the api-surface / declaration-map / export-origins ' + + 'shards and the generated reference docs. ⚠️ Runtime behaviour is deliberately UNCHANGED ' + + 'and must be verified as such: nothing ever parsed or read these shapes, so removing ' + + 'them removes no behaviour.', + }, { id: 'kernel-context-preview-mode-retired', // No backticks in `surface` — build-upgrade-guide.ts renders it inside a @@ -8296,6 +8462,57 @@ const step18: MigrationStep = { + 'three former defaults. ⚠️ Runtime behaviour is deliberately UNCHANGED and must be ' + 'verified as such: nothing ever read the keys, so removing them removes no behaviour.', }, + { + id: 'training-family-retired', + // No backticks in `surface` — build-upgrade-guide.ts renders it inside a + // code span AND a table cell. + surface: + 'the training family, retired whole: the five defs system/TrainingCategory, ' + + 'system/TrainingCompletionStatus, system/TrainingCourse, system/TrainingPlan and ' + + 'system/TrainingRecord, and every name system/training.zod.ts exported from ' + + '@objectstack/spec/system (the five *Schema consts, their z.input aliases and the two ' + + '*Parsed aliases)', + replacement: + 'nothing to re-declare — no training-management engine exists on the platform, so there ' + + 'is no working configuration to migrate to. Nothing assigned a course, tracked a ' + + 'completion, sent a reminder or expired a certification; a training record the ' + + 'organisation keeps is ordinary object data, declared as an object with its own fields ' + + 'and enforced by the object engine. If training management becomes a product capability ' + + 'it re-declares fresh, through the enforce route of ADR-0049 — the engine first, the ' + + 'vocabulary second', + reason: + 'ADR-0049 enforce-or-remove; maintainer ruling 2026-09-05 on #15513 (ruled A: retire the ' + + 'three compliance-shaped families whole via RETIRED_DEFS_BY_MAJOR, the ' + + 'integration/ErrorMappingConfig precedent; not roadmapped). Five defs and roughly ' + + 'twenty-five declared keys sat on the exported surface and in the generated reference ' + + 'docs, and were read by NOTHING: the schemas were exported from ' + + '`@objectstack/spec/system`, mounted by no `stack.zod.ts` key, registered as no metadata ' + + 'type, absent from the 2026-06 liveness ledgers, and the reader census over every ' + + 'package outside `packages/spec` (tests and changelogs excluded), over `examples/**` and ' + + '`skills/**`, and over objectui at the pinned sha returned zero hits for every exported ' + + 'name, with a lit control. `TrainingCourse.mandatory`, `TrainingPlan.trackCompletion` ' + + 'and `TrainingPlan.sendReminders` were boolean capability claims of exactly the shape ' + + 'ADR-0049 names: an author could write them, parse clean, and get no behaviour and no ' + + 'diagnostic. Tagging the family `[EXPERIMENTAL — not enforced]` was the fallback the ' + + 'ruling did not take (a human-only signal). The #14477 deadline-key tombstones (five ' + + 'sites, `RETIRED_KEYS_BY_MAJOR[18]`, D3 `training-deadline-keys-retired`) leave with ' + + 'their defs\' source; their registry entries stay as history. Why D3 semantic and not a ' + + 'D2 conversion: the chain walks a normalized STACK and `applyConversionsToStoredItem` ' + + 'maps a metadata type onto one of its collections; none of these schemas is either, so ' + + 'a conversion would be a transform with no seam that ever runs (the ' + + '`kernel/MetadataPluginConfig:additionalTypes` precedent), and with no carrier key there ' + + 'is no shape on which a tombstone could sit.', + acceptanceCriteria: + 'No code imports TrainingCategorySchema, TrainingCompletionStatusSchema, ' + + 'TrainingCourseSchema, TrainingPlanSchema or TrainingRecordSchema — or any of their type ' + + 'aliases — from @objectstack/spec or @objectstack/spec/system: every such import is ' + + 'TS2305 after upgrade, and no working replacement exists to point at because the ' + + 'vocabulary described nothing real. The five defs are absent from ' + + '`json-schema.manifest/system.json`, the api-surface / declaration-map / export-origins ' + + 'shards and the generated reference docs. ⚠️ Runtime behaviour is deliberately UNCHANGED ' + + 'and must be verified as such: nothing ever parsed or read these shapes, so removing ' + + 'them removes no behaviour.', + }, { id: 'ui-cloud-connection-widgets-unknown-keys-refused', surface: 'page `cloud-connection:panel` / `marketplace:installed-list` components — ' @@ -9184,6 +9401,57 @@ export const RETIRED_KEYS_BY_MAJOR: Readonly> // consumers through this tombstone plus the D3 semantic entry // `session-user-language-retired`. 'api/SessionUser:language', + // #14477 — ADR-0049 enforce-or-remove. The 2026-09-02 ruling (ruled A: retire + // per family) held the `ESignatureConfig` pair on one condition — a roadmapped + // e-signature consumer would have earned an `[EXPERIMENTAL — not enforced]` tag + // instead — and the maintainer answered it on 2026-09-05 (decision batch #40: + // no roadmap), so the ruling's own branch resolves to retirement. A day-shaped + // deadline key on the published authorable surface (`data/ESignatureConfig`), + // read by NOTHING: no e-signature engine exists on the platform, no layer ever + // sent, expired or reminded a signature request, and the reader census over + // every package outside `packages/spec` (tests and changelogs excluded), over + // `examples/**` and `skills/**`, and over objectui at the pinned sha returned + // zero hits, with a lit control inside `packages/spec`. Its default of 30 days + // was materialized into every parsed configuration without ever being + // consulted. + // + // Registered under 18, not 17: v17.0.0 was cut before this landed, so the + // tombstone ships on the 17.x line (launch-window convention) and the + // prescription lives at the major boundary where `migrate meta` users look. + // + // Registered here but NOT in `src/conversions/registry.ts`, for the reason + // `kernel/MetadataPluginConfig:additionalTypes` gives: `DocumentSchema` is not + // a stack collection member and `document` is no metadata type, so a + // MetadataConversion would be a transform with no seam that ever runs. The + // prescription reaches authors through the tombstone (`tsc` + the parse) and + // the D3 semantic entry named below. + // D3 semantic entry: `esignature-config-deadline-keys-retired`. + 'data/ESignatureConfig:expirationDays', + // #14477 — ADR-0049 enforce-or-remove. The 2026-09-02 ruling (ruled A: retire + // per family) held the `ESignatureConfig` pair on one condition — a roadmapped + // e-signature consumer would have earned an `[EXPERIMENTAL — not enforced]` tag + // instead — and the maintainer answered it on 2026-09-05 (decision batch #40: + // no roadmap), so the ruling's own branch resolves to retirement. A day-shaped + // interval key on the published authorable surface (`data/ESignatureConfig`), + // read by NOTHING: no e-signature engine exists on the platform, no layer ever + // sent a reminder email, and the reader census over every package outside + // `packages/spec` (tests and changelogs excluded), over `examples/**` and + // `skills/**`, and over objectui at the pinned sha returned zero hits, with a + // lit control inside `packages/spec`. Its default of 7 days was materialized + // into every parsed configuration without ever being consulted. + // + // Registered under 18, not 17: v17.0.0 was cut before this landed, so the + // tombstone ships on the 17.x line (launch-window convention) and the + // prescription lives at the major boundary where `migrate meta` users look. + // + // Registered here but NOT in `src/conversions/registry.ts`, for the reason + // `kernel/MetadataPluginConfig:additionalTypes` gives: `DocumentSchema` is not + // a stack collection member and `document` is no metadata type, so a + // MetadataConversion would be a transform with no seam that ever runs. The + // prescription reaches authors through the tombstone (`tsc` + the parse) and + // the D3 semantic entry named below. + // D3 semantic entry: `esignature-config-deadline-keys-retired`. + 'data/ESignatureConfig:reminderDays', // #10414 — ADR-0049 enforce-or-remove (triage routed REMOVE; the #10298 shape // one level up). `filters` was a declared, authorable per-metric raw-SQL // filter (`filters: [{ sql: string }]`) with ZERO consumers, measured with a @@ -11482,6 +11750,474 @@ export const RETIRED_DEFS_BY_MAJOR: Readonly> // plus the D3 semantic entry `branded-identifier-schemas-retired` are the // declaration. 'shared/ViewName', + // #15513 — `system/ChangeImpact` — the impact assessment (`level` / + // `affectedSystems` / `downtime` …, plus the #14477-retired nested + // `downtime.durationMinutes`) — leaves whole with the change-management family + // under ADR-0049 enforce-or-remove (maintainer ruling 2026-09-05 on #15513, + // ruled A: retire the three compliance-shaped families whole; not roadmapped). + // It was exported from `@objectstack/spec/system` + // (`system/change-management.zod.ts`), mounted by no `stack.zod.ts` key, + // registered as no metadata type, absent from the 2026-06 liveness ledgers, and + // read by NOTHING: the reader census over every package outside `packages/spec` + // (tests and changelogs excluded), over `examples/**` and `skills/**`, and over + // objectui at the pinned sha returned zero hits for every one of the family's + // exported names, with a lit control on the same pattern. No change-management + // engine exists on the platform: nothing routed a change request for approval, + // walked its implementation steps, honoured a rollback plan or gated on + // `securityImpact.requiresSecurityApproval` / `approval.required` — both of + // which read as gates the platform enforced, and neither ever did. An exported + // value schema with no consumer reads as a capability (#3950); the generated + // reference docs advertised a compliance subsystem that does not exist. No + // carrier key, so no `retiredKey()` tombstone and no D2 conversion (none of + // these schemas is a stack collection member — the + // `kernel/MetadataPluginConfig:additionalTypes` reasoning): + // RETIRED_DEFS_BY_MAJOR plus the D3 semantic entry + // `change-management-family-retired` ARE the declaration. The family's #14477 + // `RETIRED_KEYS_BY_MAJOR[18]` deadline-key entries stay as history — gate (b2) + // of build-schemas.ts accepts an entry naming a key the build no longer emits. + 'system/ChangeImpact', + // #15513 — `system/ChangePriority` — the 4-value priority enum — leaves whole + // with the change-management family under ADR-0049 enforce-or-remove (maintainer + // ruling 2026-09-05 on #15513, ruled A: retire the three compliance-shaped + // families whole; not roadmapped). It was exported from + // `@objectstack/spec/system` (`system/change-management.zod.ts`), mounted by no + // `stack.zod.ts` key, registered as no metadata type, absent from the 2026-06 + // liveness ledgers, and read by NOTHING: the reader census over every package + // outside `packages/spec` (tests and changelogs excluded), over `examples/**` + // and `skills/**`, and over objectui at the pinned sha returned zero hits for + // every one of the family's exported names, with a lit control on the same + // pattern. No change-management engine exists on the platform: nothing routed a + // change request for approval, walked its implementation steps, honoured a + // rollback plan or gated on `securityImpact.requiresSecurityApproval` / + // `approval.required` — both of which read as gates the platform enforced, and + // neither ever did. An exported value schema with no consumer reads as a + // capability (#3950); the generated reference docs advertised a compliance + // subsystem that does not exist. No carrier key, so no `retiredKey()` tombstone + // and no D2 conversion (none of these schemas is a stack collection member — the + // `kernel/MetadataPluginConfig:additionalTypes` reasoning): + // RETIRED_DEFS_BY_MAJOR plus the D3 semantic entry + // `change-management-family-retired` ARE the declaration. The family's #14477 + // `RETIRED_KEYS_BY_MAJOR[18]` deadline-key entries stay as history — gate (b2) + // of build-schemas.ts accepts an entry naming a key the build no longer emits. + 'system/ChangePriority', + // #15513 — `system/ChangeRequest` — the change request itself (`id` / `title` / + // `type` / `priority` / `status` / `impact` / `approval` / `implementation` / + // `rollbackPlan` / `securityImpact` …, plus the #14477-retired nested + // `implementation.steps[].estimatedMinutes`) — leaves whole with the + // change-management family under ADR-0049 enforce-or-remove (maintainer ruling + // 2026-09-05 on #15513, ruled A: retire the three compliance-shaped families + // whole; not roadmapped). It was exported from `@objectstack/spec/system` + // (`system/change-management.zod.ts`), mounted by no `stack.zod.ts` key, + // registered as no metadata type, absent from the 2026-06 liveness ledgers, and + // read by NOTHING: the reader census over every package outside `packages/spec` + // (tests and changelogs excluded), over `examples/**` and `skills/**`, and over + // objectui at the pinned sha returned zero hits for every one of the family's + // exported names, with a lit control on the same pattern. No change-management + // engine exists on the platform: nothing routed a change request for approval, + // walked its implementation steps, honoured a rollback plan or gated on + // `securityImpact.requiresSecurityApproval` / `approval.required` — both of + // which read as gates the platform enforced, and neither ever did. An exported + // value schema with no consumer reads as a capability (#3950); the generated + // reference docs advertised a compliance subsystem that does not exist. No + // carrier key, so no `retiredKey()` tombstone and no D2 conversion (none of + // these schemas is a stack collection member — the + // `kernel/MetadataPluginConfig:additionalTypes` reasoning): + // RETIRED_DEFS_BY_MAJOR plus the D3 semantic entry + // `change-management-family-retired` ARE the declaration. The family's #14477 + // `RETIRED_KEYS_BY_MAJOR[18]` deadline-key entries stay as history — gate (b2) + // of build-schemas.ts accepts an entry naming a key the build no longer emits. + 'system/ChangeRequest', + // #15513 — `system/ChangeStatus` — the 10-value change status enum — leaves + // whole with the change-management family under ADR-0049 enforce-or-remove + // (maintainer ruling 2026-09-05 on #15513, ruled A: retire the three + // compliance-shaped families whole; not roadmapped). It was exported from + // `@objectstack/spec/system` (`system/change-management.zod.ts`), mounted by no + // `stack.zod.ts` key, registered as no metadata type, absent from the 2026-06 + // liveness ledgers, and read by NOTHING: the reader census over every package + // outside `packages/spec` (tests and changelogs excluded), over `examples/**` + // and `skills/**`, and over objectui at the pinned sha returned zero hits for + // every one of the family's exported names, with a lit control on the same + // pattern. No change-management engine exists on the platform: nothing routed a + // change request for approval, walked its implementation steps, honoured a + // rollback plan or gated on `securityImpact.requiresSecurityApproval` / + // `approval.required` — both of which read as gates the platform enforced, and + // neither ever did. An exported value schema with no consumer reads as a + // capability (#3950); the generated reference docs advertised a compliance + // subsystem that does not exist. No carrier key, so no `retiredKey()` tombstone + // and no D2 conversion (none of these schemas is a stack collection member — the + // `kernel/MetadataPluginConfig:additionalTypes` reasoning): + // RETIRED_DEFS_BY_MAJOR plus the D3 semantic entry + // `change-management-family-retired` ARE the declaration. The family's #14477 + // `RETIRED_KEYS_BY_MAJOR[18]` deadline-key entries stay as history — gate (b2) + // of build-schemas.ts accepts an entry naming a key the build no longer emits. + 'system/ChangeStatus', + // #15513 — `system/ChangeType` — the 4-value ITIL change type enum — leaves + // whole with the change-management family under ADR-0049 enforce-or-remove + // (maintainer ruling 2026-09-05 on #15513, ruled A: retire the three + // compliance-shaped families whole; not roadmapped). It was exported from + // `@objectstack/spec/system` (`system/change-management.zod.ts`), mounted by no + // `stack.zod.ts` key, registered as no metadata type, absent from the 2026-06 + // liveness ledgers, and read by NOTHING: the reader census over every package + // outside `packages/spec` (tests and changelogs excluded), over `examples/**` + // and `skills/**`, and over objectui at the pinned sha returned zero hits for + // every one of the family's exported names, with a lit control on the same + // pattern. No change-management engine exists on the platform: nothing routed a + // change request for approval, walked its implementation steps, honoured a + // rollback plan or gated on `securityImpact.requiresSecurityApproval` / + // `approval.required` — both of which read as gates the platform enforced, and + // neither ever did. An exported value schema with no consumer reads as a + // capability (#3950); the generated reference docs advertised a compliance + // subsystem that does not exist. No carrier key, so no `retiredKey()` tombstone + // and no D2 conversion (none of these schemas is a stack collection member — the + // `kernel/MetadataPluginConfig:additionalTypes` reasoning): + // RETIRED_DEFS_BY_MAJOR plus the D3 semantic entry + // `change-management-family-retired` ARE the declaration. The family's #14477 + // `RETIRED_KEYS_BY_MAJOR[18]` deadline-key entries stay as history — gate (b2) + // of build-schemas.ts accepts an entry naming a key the build no longer emits. + 'system/ChangeType', + // #15513 — `system/Incident` — the incident record itself (id / title / severity + // / category / status / reportedBy / affectedSystems / dataClassification / + // responsePhases / lessonsLearned …) — leaves whole with the incident-response + // family under ADR-0049 enforce-or-remove (maintainer ruling 2026-09-05 on + // #15513, ruled A: retire the three compliance-shaped families whole; not + // roadmapped). It was exported from `@objectstack/spec/system` + // (`system/incident-response.zod.ts`), mounted by no `stack.zod.ts` key, + // registered as no metadata type, absent from the 2026-06 liveness ledgers, and + // read by NOTHING: the reader census over every package outside `packages/spec` + // (tests and changelogs excluded), over `examples/**` and `skills/**`, and over + // objectui at the pinned sha returned zero hits for every one of the family's + // exported names, with a lit control on the same pattern. No incident-response + // engine exists on the platform: nothing classified, tracked, escalated or + // notified an incident, and nothing notified a regulator — an author writing + // `notifyRegulators: true` held a compliance promise the platform never kept, + // with no error and no feedback. An exported value schema with no consumer reads + // as a capability (#3950); the generated reference docs advertised a compliance + // subsystem that does not exist. No carrier key, so no `retiredKey()` tombstone + // and no D2 conversion (none of these schemas is a stack collection member — the + // `kernel/MetadataPluginConfig:additionalTypes` reasoning): + // RETIRED_DEFS_BY_MAJOR plus the D3 semantic entry + // `incident-response-family-retired` ARE the declaration. The family's #14477 + // `RETIRED_KEYS_BY_MAJOR[18]` deadline-key entries stay as history — gate (b2) + // of build-schemas.ts accepts an entry naming a key the build no longer emits. + 'system/Incident', + // #15513 — `system/IncidentCategory` — the 10-value incident category enum — + // leaves whole with the incident-response family under ADR-0049 + // enforce-or-remove (maintainer ruling 2026-09-05 on #15513, ruled A: retire the + // three compliance-shaped families whole; not roadmapped). It was exported from + // `@objectstack/spec/system` (`system/incident-response.zod.ts`), mounted by no + // `stack.zod.ts` key, registered as no metadata type, absent from the 2026-06 + // liveness ledgers, and read by NOTHING: the reader census over every package + // outside `packages/spec` (tests and changelogs excluded), over `examples/**` + // and `skills/**`, and over objectui at the pinned sha returned zero hits for + // every one of the family's exported names, with a lit control on the same + // pattern. No incident-response engine exists on the platform: nothing + // classified, tracked, escalated or notified an incident, and nothing notified a + // regulator — an author writing `notifyRegulators: true` held a compliance + // promise the platform never kept, with no error and no feedback. An exported + // value schema with no consumer reads as a capability (#3950); the generated + // reference docs advertised a compliance subsystem that does not exist. No + // carrier key, so no `retiredKey()` tombstone and no D2 conversion (none of + // these schemas is a stack collection member — the + // `kernel/MetadataPluginConfig:additionalTypes` reasoning): + // RETIRED_DEFS_BY_MAJOR plus the D3 semantic entry + // `incident-response-family-retired` ARE the declaration. The family's #14477 + // `RETIRED_KEYS_BY_MAJOR[18]` deadline-key entries stay as history — gate (b2) + // of build-schemas.ts accepts an entry naming a key the build no longer emits. + 'system/IncidentCategory', + // #15513 — `system/IncidentNotificationMatrix` — the notification-rule matrix + // (`rules[]`, plus the #14477-retired `escalationTimeoutMinutes`) — leaves whole + // with the incident-response family under ADR-0049 enforce-or-remove (maintainer + // ruling 2026-09-05 on #15513, ruled A: retire the three compliance-shaped + // families whole; not roadmapped). It was exported from + // `@objectstack/spec/system` (`system/incident-response.zod.ts`), mounted by no + // `stack.zod.ts` key, registered as no metadata type, absent from the 2026-06 + // liveness ledgers, and read by NOTHING: the reader census over every package + // outside `packages/spec` (tests and changelogs excluded), over `examples/**` + // and `skills/**`, and over objectui at the pinned sha returned zero hits for + // every one of the family's exported names, with a lit control on the same + // pattern. No incident-response engine exists on the platform: nothing + // classified, tracked, escalated or notified an incident, and nothing notified a + // regulator — an author writing `notifyRegulators: true` held a compliance + // promise the platform never kept, with no error and no feedback. An exported + // value schema with no consumer reads as a capability (#3950); the generated + // reference docs advertised a compliance subsystem that does not exist. No + // carrier key, so no `retiredKey()` tombstone and no D2 conversion (none of + // these schemas is a stack collection member — the + // `kernel/MetadataPluginConfig:additionalTypes` reasoning): + // RETIRED_DEFS_BY_MAJOR plus the D3 semantic entry + // `incident-response-family-retired` ARE the declaration. The family's #14477 + // `RETIRED_KEYS_BY_MAJOR[18]` deadline-key entries stay as history — gate (b2) + // of build-schemas.ts accepts an entry naming a key the build no longer emits. + 'system/IncidentNotificationMatrix', + // #15513 — `system/IncidentNotificationRule` — one severity → channels / + // recipients / `notifyRegulators` rule (plus the #14477-retired `withinMinutes` + // / `regulatorDeadlineHours`) — leaves whole with the incident-response family + // under ADR-0049 enforce-or-remove (maintainer ruling 2026-09-05 on #15513, + // ruled A: retire the three compliance-shaped families whole; not roadmapped). + // It was exported from `@objectstack/spec/system` + // (`system/incident-response.zod.ts`), mounted by no `stack.zod.ts` key, + // registered as no metadata type, absent from the 2026-06 liveness ledgers, and + // read by NOTHING: the reader census over every package outside `packages/spec` + // (tests and changelogs excluded), over `examples/**` and `skills/**`, and over + // objectui at the pinned sha returned zero hits for every one of the family's + // exported names, with a lit control on the same pattern. No incident-response + // engine exists on the platform: nothing classified, tracked, escalated or + // notified an incident, and nothing notified a regulator — an author writing + // `notifyRegulators: true` held a compliance promise the platform never kept, + // with no error and no feedback. An exported value schema with no consumer reads + // as a capability (#3950); the generated reference docs advertised a compliance + // subsystem that does not exist. No carrier key, so no `retiredKey()` tombstone + // and no D2 conversion (none of these schemas is a stack collection member — the + // `kernel/MetadataPluginConfig:additionalTypes` reasoning): + // RETIRED_DEFS_BY_MAJOR plus the D3 semantic entry + // `incident-response-family-retired` ARE the declaration. The family's #14477 + // `RETIRED_KEYS_BY_MAJOR[18]` deadline-key entries stay as history — gate (b2) + // of build-schemas.ts accepts an entry naming a key the build no longer emits. + 'system/IncidentNotificationRule', + // #15513 — `system/IncidentResponsePhase` — one response phase (`phase` / + // `description` / `assignedTo`, plus the #14477-retired `targetHours`) — leaves + // whole with the incident-response family under ADR-0049 enforce-or-remove + // (maintainer ruling 2026-09-05 on #15513, ruled A: retire the three + // compliance-shaped families whole; not roadmapped). It was exported from + // `@objectstack/spec/system` (`system/incident-response.zod.ts`), mounted by no + // `stack.zod.ts` key, registered as no metadata type, absent from the 2026-06 + // liveness ledgers, and read by NOTHING: the reader census over every package + // outside `packages/spec` (tests and changelogs excluded), over `examples/**` + // and `skills/**`, and over objectui at the pinned sha returned zero hits for + // every one of the family's exported names, with a lit control on the same + // pattern. No incident-response engine exists on the platform: nothing + // classified, tracked, escalated or notified an incident, and nothing notified a + // regulator — an author writing `notifyRegulators: true` held a compliance + // promise the platform never kept, with no error and no feedback. An exported + // value schema with no consumer reads as a capability (#3950); the generated + // reference docs advertised a compliance subsystem that does not exist. No + // carrier key, so no `retiredKey()` tombstone and no D2 conversion (none of + // these schemas is a stack collection member — the + // `kernel/MetadataPluginConfig:additionalTypes` reasoning): + // RETIRED_DEFS_BY_MAJOR plus the D3 semantic entry + // `incident-response-family-retired` ARE the declaration. The family's #14477 + // `RETIRED_KEYS_BY_MAJOR[18]` deadline-key entries stay as history — gate (b2) + // of build-schemas.ts accepts an entry naming a key the build no longer emits. + 'system/IncidentResponsePhase', + // #15513 — `system/IncidentResponsePolicy` — the policy document + // (`notificationMatrix` / `defaultResponseTeam` / `requirePostIncidentReview` …, + // plus the #14477-retired `triageDeadlineHours` / `retentionDays`) — leaves + // whole with the incident-response family under ADR-0049 enforce-or-remove + // (maintainer ruling 2026-09-05 on #15513, ruled A: retire the three + // compliance-shaped families whole; not roadmapped). It was exported from + // `@objectstack/spec/system` (`system/incident-response.zod.ts`), mounted by no + // `stack.zod.ts` key, registered as no metadata type, absent from the 2026-06 + // liveness ledgers, and read by NOTHING: the reader census over every package + // outside `packages/spec` (tests and changelogs excluded), over `examples/**` + // and `skills/**`, and over objectui at the pinned sha returned zero hits for + // every one of the family's exported names, with a lit control on the same + // pattern. No incident-response engine exists on the platform: nothing + // classified, tracked, escalated or notified an incident, and nothing notified a + // regulator — an author writing `notifyRegulators: true` held a compliance + // promise the platform never kept, with no error and no feedback. An exported + // value schema with no consumer reads as a capability (#3950); the generated + // reference docs advertised a compliance subsystem that does not exist. No + // carrier key, so no `retiredKey()` tombstone and no D2 conversion (none of + // these schemas is a stack collection member — the + // `kernel/MetadataPluginConfig:additionalTypes` reasoning): + // RETIRED_DEFS_BY_MAJOR plus the D3 semantic entry + // `incident-response-family-retired` ARE the declaration. The family's #14477 + // `RETIRED_KEYS_BY_MAJOR[18]` deadline-key entries stay as history — gate (b2) + // of build-schemas.ts accepts an entry naming a key the build no longer emits. + 'system/IncidentResponsePolicy', + // #15513 — `system/IncidentSeverity` — the 4-value severity enum — leaves whole + // with the incident-response family under ADR-0049 enforce-or-remove (maintainer + // ruling 2026-09-05 on #15513, ruled A: retire the three compliance-shaped + // families whole; not roadmapped). It was exported from + // `@objectstack/spec/system` (`system/incident-response.zod.ts`), mounted by no + // `stack.zod.ts` key, registered as no metadata type, absent from the 2026-06 + // liveness ledgers, and read by NOTHING: the reader census over every package + // outside `packages/spec` (tests and changelogs excluded), over `examples/**` + // and `skills/**`, and over objectui at the pinned sha returned zero hits for + // every one of the family's exported names, with a lit control on the same + // pattern. No incident-response engine exists on the platform: nothing + // classified, tracked, escalated or notified an incident, and nothing notified a + // regulator — an author writing `notifyRegulators: true` held a compliance + // promise the platform never kept, with no error and no feedback. An exported + // value schema with no consumer reads as a capability (#3950); the generated + // reference docs advertised a compliance subsystem that does not exist. No + // carrier key, so no `retiredKey()` tombstone and no D2 conversion (none of + // these schemas is a stack collection member — the + // `kernel/MetadataPluginConfig:additionalTypes` reasoning): + // RETIRED_DEFS_BY_MAJOR plus the D3 semantic entry + // `incident-response-family-retired` ARE the declaration. The family's #14477 + // `RETIRED_KEYS_BY_MAJOR[18]` deadline-key entries stay as history — gate (b2) + // of build-schemas.ts accepts an entry naming a key the build no longer emits. + 'system/IncidentSeverity', + // #15513 — `system/IncidentStatus` — the 7-value incident status enum — leaves + // whole with the incident-response family under ADR-0049 enforce-or-remove + // (maintainer ruling 2026-09-05 on #15513, ruled A: retire the three + // compliance-shaped families whole; not roadmapped). It was exported from + // `@objectstack/spec/system` (`system/incident-response.zod.ts`), mounted by no + // `stack.zod.ts` key, registered as no metadata type, absent from the 2026-06 + // liveness ledgers, and read by NOTHING: the reader census over every package + // outside `packages/spec` (tests and changelogs excluded), over `examples/**` + // and `skills/**`, and over objectui at the pinned sha returned zero hits for + // every one of the family's exported names, with a lit control on the same + // pattern. No incident-response engine exists on the platform: nothing + // classified, tracked, escalated or notified an incident, and nothing notified a + // regulator — an author writing `notifyRegulators: true` held a compliance + // promise the platform never kept, with no error and no feedback. An exported + // value schema with no consumer reads as a capability (#3950); the generated + // reference docs advertised a compliance subsystem that does not exist. No + // carrier key, so no `retiredKey()` tombstone and no D2 conversion (none of + // these schemas is a stack collection member — the + // `kernel/MetadataPluginConfig:additionalTypes` reasoning): + // RETIRED_DEFS_BY_MAJOR plus the D3 semantic entry + // `incident-response-family-retired` ARE the declaration. The family's #14477 + // `RETIRED_KEYS_BY_MAJOR[18]` deadline-key entries stay as history — gate (b2) + // of build-schemas.ts accepts an entry naming a key the build no longer emits. + 'system/IncidentStatus', + // #15513 — `system/RollbackPlan` — the rollback plan (`description` / `steps[]` + // / `testProcedure` …, plus the #14477-retired nested + // `steps[].estimatedMinutes`) — leaves whole with the change-management family + // under ADR-0049 enforce-or-remove (maintainer ruling 2026-09-05 on #15513, + // ruled A: retire the three compliance-shaped families whole; not roadmapped). + // It was exported from `@objectstack/spec/system` + // (`system/change-management.zod.ts`), mounted by no `stack.zod.ts` key, + // registered as no metadata type, absent from the 2026-06 liveness ledgers, and + // read by NOTHING: the reader census over every package outside `packages/spec` + // (tests and changelogs excluded), over `examples/**` and `skills/**`, and over + // objectui at the pinned sha returned zero hits for every one of the family's + // exported names, with a lit control on the same pattern. No change-management + // engine exists on the platform: nothing routed a change request for approval, + // walked its implementation steps, honoured a rollback plan or gated on + // `securityImpact.requiresSecurityApproval` / `approval.required` — both of + // which read as gates the platform enforced, and neither ever did. An exported + // value schema with no consumer reads as a capability (#3950); the generated + // reference docs advertised a compliance subsystem that does not exist. No + // carrier key, so no `retiredKey()` tombstone and no D2 conversion (none of + // these schemas is a stack collection member — the + // `kernel/MetadataPluginConfig:additionalTypes` reasoning): + // RETIRED_DEFS_BY_MAJOR plus the D3 semantic entry + // `change-management-family-retired` ARE the declaration. The family's #14477 + // `RETIRED_KEYS_BY_MAJOR[18]` deadline-key entries stay as history — gate (b2) + // of build-schemas.ts accepts an entry naming a key the build no longer emits. + 'system/RollbackPlan', + // #15513 — `system/TrainingCategory` — the 8-value training category enum — + // leaves whole with the training family under ADR-0049 enforce-or-remove + // (maintainer ruling 2026-09-05 on #15513, ruled A: retire the three + // compliance-shaped families whole; not roadmapped). It was exported from + // `@objectstack/spec/system` (`system/training.zod.ts`), mounted by no + // `stack.zod.ts` key, registered as no metadata type, absent from the 2026-06 + // liveness ledgers, and read by NOTHING: the reader census over every package + // outside `packages/spec` (tests and changelogs excluded), over `examples/**` + // and `skills/**`, and over objectui at the pinned sha returned zero hits for + // every one of the family's exported names, with a lit control on the same + // pattern. No training-management engine exists on the platform: nothing + // assigned a course, tracked a completion, sent a reminder or expired a + // certification — `mandatory: true`, `trackCompletion: true` and `sendReminders: + // true` were declarations nothing ever read. An exported value schema with no + // consumer reads as a capability (#3950); the generated reference docs + // advertised a compliance subsystem that does not exist. No carrier key, so no + // `retiredKey()` tombstone and no D2 conversion (none of these schemas is a + // stack collection member — the `kernel/MetadataPluginConfig:additionalTypes` + // reasoning): RETIRED_DEFS_BY_MAJOR plus the D3 semantic entry + // `training-family-retired` ARE the declaration. The family's #14477 + // `RETIRED_KEYS_BY_MAJOR[18]` deadline-key entries stay as history — gate (b2) + // of build-schemas.ts accepts an entry naming a key the build no longer emits. + 'system/TrainingCategory', + // #15513 — `system/TrainingCompletionStatus` — the 5-value completion status + // enum — leaves whole with the training family under ADR-0049 enforce-or-remove + // (maintainer ruling 2026-09-05 on #15513, ruled A: retire the three + // compliance-shaped families whole; not roadmapped). It was exported from + // `@objectstack/spec/system` (`system/training.zod.ts`), mounted by no + // `stack.zod.ts` key, registered as no metadata type, absent from the 2026-06 + // liveness ledgers, and read by NOTHING: the reader census over every package + // outside `packages/spec` (tests and changelogs excluded), over `examples/**` + // and `skills/**`, and over objectui at the pinned sha returned zero hits for + // every one of the family's exported names, with a lit control on the same + // pattern. No training-management engine exists on the platform: nothing + // assigned a course, tracked a completion, sent a reminder or expired a + // certification — `mandatory: true`, `trackCompletion: true` and `sendReminders: + // true` were declarations nothing ever read. An exported value schema with no + // consumer reads as a capability (#3950); the generated reference docs + // advertised a compliance subsystem that does not exist. No carrier key, so no + // `retiredKey()` tombstone and no D2 conversion (none of these schemas is a + // stack collection member — the `kernel/MetadataPluginConfig:additionalTypes` + // reasoning): RETIRED_DEFS_BY_MAJOR plus the D3 semantic entry + // `training-family-retired` ARE the declaration. The family's #14477 + // `RETIRED_KEYS_BY_MAJOR[18]` deadline-key entries stay as history — gate (b2) + // of build-schemas.ts accepts an entry naming a key the build no longer emits. + 'system/TrainingCompletionStatus', + // #15513 — `system/TrainingCourse` — a course (`id` / `title` / `category` / + // `mandatory` / `targetRoles` / `passingScore` …, plus the #14477-retired + // `durationMinutes` / `validityDays`) — leaves whole with the training family + // under ADR-0049 enforce-or-remove (maintainer ruling 2026-09-05 on #15513, + // ruled A: retire the three compliance-shaped families whole; not roadmapped). + // It was exported from `@objectstack/spec/system` (`system/training.zod.ts`), + // mounted by no `stack.zod.ts` key, registered as no metadata type, absent from + // the 2026-06 liveness ledgers, and read by NOTHING: the reader census over + // every package outside `packages/spec` (tests and changelogs excluded), over + // `examples/**` and `skills/**`, and over objectui at the pinned sha returned + // zero hits for every one of the family's exported names, with a lit control on + // the same pattern. No training-management engine exists on the platform: + // nothing assigned a course, tracked a completion, sent a reminder or expired a + // certification — `mandatory: true`, `trackCompletion: true` and `sendReminders: + // true` were declarations nothing ever read. An exported value schema with no + // consumer reads as a capability (#3950); the generated reference docs + // advertised a compliance subsystem that does not exist. No carrier key, so no + // `retiredKey()` tombstone and no D2 conversion (none of these schemas is a + // stack collection member — the `kernel/MetadataPluginConfig:additionalTypes` + // reasoning): RETIRED_DEFS_BY_MAJOR plus the D3 semantic entry + // `training-family-retired` ARE the declaration. The family's #14477 + // `RETIRED_KEYS_BY_MAJOR[18]` deadline-key entries stay as history — gate (b2) + // of build-schemas.ts accepts an entry naming a key the build no longer emits. + 'system/TrainingCourse', + // #15513 — `system/TrainingPlan` — the organisational plan (`courses[]` / + // `trackCompletion` / `sendReminders` …, plus the #14477-retired + // `recertificationIntervalDays` / `gracePeriodDays` / `reminderDaysBefore`) — + // leaves whole with the training family under ADR-0049 enforce-or-remove + // (maintainer ruling 2026-09-05 on #15513, ruled A: retire the three + // compliance-shaped families whole; not roadmapped). It was exported from + // `@objectstack/spec/system` (`system/training.zod.ts`), mounted by no + // `stack.zod.ts` key, registered as no metadata type, absent from the 2026-06 + // liveness ledgers, and read by NOTHING: the reader census over every package + // outside `packages/spec` (tests and changelogs excluded), over `examples/**` + // and `skills/**`, and over objectui at the pinned sha returned zero hits for + // every one of the family's exported names, with a lit control on the same + // pattern. No training-management engine exists on the platform: nothing + // assigned a course, tracked a completion, sent a reminder or expired a + // certification — `mandatory: true`, `trackCompletion: true` and `sendReminders: + // true` were declarations nothing ever read. An exported value schema with no + // consumer reads as a capability (#3950); the generated reference docs + // advertised a compliance subsystem that does not exist. No carrier key, so no + // `retiredKey()` tombstone and no D2 conversion (none of these schemas is a + // stack collection member — the `kernel/MetadataPluginConfig:additionalTypes` + // reasoning): RETIRED_DEFS_BY_MAJOR plus the D3 semantic entry + // `training-family-retired` ARE the declaration. The family's #14477 + // `RETIRED_KEYS_BY_MAJOR[18]` deadline-key entries stay as history — gate (b2) + // of build-schemas.ts accepts an entry naming a key the build no longer emits. + 'system/TrainingPlan', + // #15513 — `system/TrainingRecord` — one completion record (`courseId` / + // `userId` / `status` / `score` / `completedAt` …) — leaves whole with the + // training family under ADR-0049 enforce-or-remove (maintainer ruling 2026-09-05 + // on #15513, ruled A: retire the three compliance-shaped families whole; not + // roadmapped). It was exported from `@objectstack/spec/system` + // (`system/training.zod.ts`), mounted by no `stack.zod.ts` key, registered as no + // metadata type, absent from the 2026-06 liveness ledgers, and read by NOTHING: + // the reader census over every package outside `packages/spec` (tests and + // changelogs excluded), over `examples/**` and `skills/**`, and over objectui at + // the pinned sha returned zero hits for every one of the family's exported + // names, with a lit control on the same pattern. No training-management engine + // exists on the platform: nothing assigned a course, tracked a completion, sent + // a reminder or expired a certification — `mandatory: true`, `trackCompletion: + // true` and `sendReminders: true` were declarations nothing ever read. An + // exported value schema with no consumer reads as a capability (#3950); the + // generated reference docs advertised a compliance subsystem that does not + // exist. No carrier key, so no `retiredKey()` tombstone and no D2 conversion + // (none of these schemas is a stack collection member — the + // `kernel/MetadataPluginConfig:additionalTypes` reasoning): + // RETIRED_DEFS_BY_MAJOR plus the D3 semantic entry `training-family-retired` ARE + // the declaration. The family's #14477 `RETIRED_KEYS_BY_MAJOR[18]` deadline-key + // entries stay as history — gate (b2) of build-schemas.ts accepts an entry + // naming a key the build no longer emits. + 'system/TrainingRecord', // #10485 — `ui/BorderRadius` (the border-radius scale sub-block) left with `ui/Theme`: // its ONLY consumer was the retired `ThemeSchema` (the #3950 rule — an // exported value schema with no consumer reads as a capability). See diff --git a/packages/spec/src/system/change-management.test.ts b/packages/spec/src/system/change-management.test.ts deleted file mode 100644 index 638a476868..0000000000 --- a/packages/spec/src/system/change-management.test.ts +++ /dev/null @@ -1,736 +0,0 @@ -import { describe, it, expect } from 'vitest'; -import { - ChangeTypeSchema, - ChangePrioritySchema, - ChangeStatusSchema, - ChangeImpactSchema, - RollbackPlanSchema, - ChangeRequestSchema, - type ChangeRequest, - type ChangeImpact, - type RollbackPlan, -} from './change-management.zod'; - -describe('ChangeTypeSchema', () => { - it('should accept all valid change types', () => { - const validTypes = ['standard', 'normal', 'emergency', 'major']; - - validTypes.forEach((type) => { - expect(() => ChangeTypeSchema.parse(type)).not.toThrow(); - }); - }); - - it('should reject invalid change type', () => { - expect(() => ChangeTypeSchema.parse('invalid')).toThrow(); - }); -}); - -describe('ChangePrioritySchema', () => { - it('should accept all valid priorities', () => { - const validPriorities = ['critical', 'high', 'medium', 'low']; - - validPriorities.forEach((priority) => { - expect(() => ChangePrioritySchema.parse(priority)).not.toThrow(); - }); - }); - - it('should reject invalid priority', () => { - expect(() => ChangePrioritySchema.parse('urgent')).toThrow(); - }); -}); - -describe('ChangeStatusSchema', () => { - it('should accept all valid statuses', () => { - const validStatuses = [ - 'draft', - 'submitted', - 'in-review', - 'approved', - 'scheduled', - 'in-progress', - 'completed', - 'failed', - 'rolled-back', - 'cancelled', - ]; - - validStatuses.forEach((status) => { - expect(() => ChangeStatusSchema.parse(status)).not.toThrow(); - }); - }); - - it('should reject invalid status', () => { - expect(() => ChangeStatusSchema.parse('pending')).toThrow(); - }); -}); - -describe('ChangeImpactSchema', () => { - it('should validate complete impact assessment', () => { - const validImpact: ChangeImpact = { - level: 'high', - affectedSystems: ['crm-api', 'customer-portal'], - affectedUsers: 5000, - downtime: { - required: true, - }, - }; - - expect(() => ChangeImpactSchema.parse(validImpact)).not.toThrow(); - }); - - it('should accept minimal impact assessment', () => { - const minimalImpact = { - level: 'low', - affectedSystems: ['test-system'], - }; - - expect(() => ChangeImpactSchema.parse(minimalImpact)).not.toThrow(); - }); - - it('should accept all impact levels', () => { - const levels = ['low', 'medium', 'high', 'critical'] as const; - - levels.forEach((level) => { - const impact = { - level, - affectedSystems: ['system-1'], - }; - - expect(() => ChangeImpactSchema.parse(impact)).not.toThrow(); - }); - }); - - it('should validate downtime configuration', () => { - const impact = { - level: 'medium', - affectedSystems: ['api-gateway'], - downtime: { - required: false, - }, - }; - - expect(() => ChangeImpactSchema.parse(impact)).not.toThrow(); - }); - - it('should reject invalid impact level', () => { - const invalidImpact = { - level: 'severe', - affectedSystems: ['system'], - }; - - expect(() => ChangeImpactSchema.parse(invalidImpact)).toThrow(); - }); -}); - -describe('RollbackPlanSchema', () => { - it('should validate complete rollback plan', () => { - const validPlan: RollbackPlan = { - description: 'Revert database schema to previous version', - steps: [ - { - order: 1, - description: 'Stop application servers', - }, - { - order: 2, - description: 'Restore database backup', - }, - { - order: 3, - description: 'Restart application servers', - }, - ], - testProcedure: 'Verify application login and basic functionality', - }; - - expect(() => RollbackPlanSchema.parse(validPlan)).not.toThrow(); - }); - - it('should accept rollback plan without test procedure', () => { - const planWithoutTest = { - description: 'Simple rollback', - steps: [ - { - order: 1, - description: 'Revert changes', - }, - ], - }; - - expect(() => RollbackPlanSchema.parse(planWithoutTest)).not.toThrow(); - }); - - it('should validate multiple rollback steps', () => { - const plan = { - description: 'Multi-step rollback', - steps: [ - { - order: 1, - description: 'Step 1', - }, - { - order: 2, - description: 'Step 2', - }, - { - order: 3, - description: 'Step 3', - }, - { - order: 4, - description: 'Step 4', - }, - ], - }; - - expect(() => RollbackPlanSchema.parse(plan)).not.toThrow(); - }); -}); - -describe('ChangeRequestSchema', () => { - it('should validate complete change request', () => { - const validRequest: ChangeRequest = { - id: 'CHG-2024-001', - title: 'Upgrade CRM Database Schema', - description: 'Migrate customer database to new schema version 2.0', - type: 'normal', - priority: 'high', - status: 'approved', - requestedBy: 'user_123', - requestedAt: 1704067200000, - impact: { - level: 'high', - affectedSystems: ['crm-api', 'customer-portal'], - affectedUsers: 5000, - downtime: { - required: true, - }, - }, - implementation: { - description: 'Execute database migration scripts', - steps: [ - { - order: 1, - description: 'Backup current database', - }, - { - order: 2, - description: 'Run migration scripts', - }, - ], - testing: 'Run integration test suite', - }, - rollbackPlan: { - description: 'Restore from backup', - steps: [ - { - order: 1, - description: 'Restore backup', - }, - ], - }, - schedule: { - plannedStart: 1704153600000, - plannedEnd: 1704155400000, - }, - }; - - expect(() => ChangeRequestSchema.parse(validRequest)).not.toThrow(); - }); - - it('should accept minimal change request', () => { - const minimalRequest = { - id: 'CHG-2024-002', - title: 'Simple Change', - description: 'A simple change', - type: 'standard', - priority: 'low', - status: 'draft', - requestedBy: 'user_456', - requestedAt: Date.now(), - impact: { - level: 'low', - affectedSystems: ['test-system'], - }, - implementation: { - description: 'Make the change', - steps: [ - { - order: 1, - description: 'Execute change', - }, - ], - }, - rollbackPlan: { - description: 'Undo the change', - steps: [ - { - order: 1, - description: 'Revert', - }, - ], - }, - }; - - expect(() => ChangeRequestSchema.parse(minimalRequest)).not.toThrow(); - }); - - it('should validate standard change type', () => { - const standardChange = { - id: 'CHG-STD-001', - title: 'Standard Change', - description: 'Pre-approved standard change', - type: 'standard', - priority: 'low', - status: 'approved', - requestedBy: 'user_789', - requestedAt: Date.now(), - impact: { - level: 'low', - affectedSystems: ['component-a'], - }, - implementation: { - description: 'Standard procedure', - steps: [ - { - order: 1, - description: 'Execute', - }, - ], - }, - rollbackPlan: { - description: 'Standard rollback', - steps: [ - { - order: 1, - description: 'Revert', - }, - ], - }, - }; - - expect(() => ChangeRequestSchema.parse(standardChange)).not.toThrow(); - }); - - it('should validate emergency change type', () => { - const emergencyChange = { - id: 'CHG-EMG-001', - title: 'Emergency Security Patch', - description: 'Critical security vulnerability fix', - type: 'emergency', - priority: 'critical', - status: 'in-progress', - requestedBy: 'security_team', - requestedAt: Date.now(), - impact: { - level: 'critical', - affectedSystems: ['all-systems'], - affectedUsers: 50000, - downtime: { - required: true, - }, - }, - implementation: { - description: 'Apply security patch', - steps: [ - { - order: 1, - description: 'Deploy patch', - }, - ], - }, - rollbackPlan: { - description: 'Remove patch', - steps: [ - { - order: 1, - description: 'Rollback', - }, - ], - }, - }; - - expect(() => ChangeRequestSchema.parse(emergencyChange)).not.toThrow(); - }); - - it('should validate major change requiring CAB approval', () => { - const majorChange = { - id: 'CHG-MAJ-001', - title: 'Major Infrastructure Upgrade', - description: 'Upgrade core infrastructure', - type: 'major', - priority: 'high', - status: 'in-review', - requestedBy: 'infrastructure_team', - requestedAt: Date.now(), - impact: { - level: 'critical', - affectedSystems: ['core-infrastructure', 'all-applications'], - affectedUsers: 100000, - downtime: { - required: true, - }, - }, - implementation: { - description: 'Multi-phase infrastructure upgrade', - steps: [ - { - order: 1, - description: 'Phase 1: Database upgrade', - }, - { - order: 2, - description: 'Phase 2: Application upgrade', - }, - ], - testing: 'Comprehensive integration testing', - }, - rollbackPlan: { - description: 'Restore from snapshots', - steps: [ - { - order: 1, - description: 'Restore infrastructure snapshot', - }, - ], - }, - approval: { - required: true, - approvers: [ - { - userId: 'cab_member_1', - approvedAt: 1704067200000, - comments: 'Approved with conditions', - }, - { - userId: 'cab_member_2', - }, - ], - }, - }; - - expect(() => ChangeRequestSchema.parse(majorChange)).not.toThrow(); - }); - - it('should validate schedule with actual times', () => { - const scheduledChange = { - id: 'CHG-2024-003', - title: 'Scheduled Maintenance', - description: 'Routine maintenance', - type: 'normal', - priority: 'medium', - status: 'completed', - requestedBy: 'ops_team', - requestedAt: Date.now(), - impact: { - level: 'medium', - affectedSystems: ['web-servers'], - }, - implementation: { - description: 'Update web servers', - steps: [ - { - order: 1, - description: 'Update', - }, - ], - }, - rollbackPlan: { - description: 'Rollback update', - steps: [ - { - order: 1, - description: 'Revert', - }, - ], - }, - schedule: { - plannedStart: 1704153600000, - plannedEnd: 1704155400000, - actualStart: 1704153650000, - actualEnd: 1704155350000, - }, - }; - - expect(() => ChangeRequestSchema.parse(scheduledChange)).not.toThrow(); - }); - - it('should validate attachments', () => { - const changeWithAttachments = { - id: 'CHG-2024-004', - title: 'Change with Documentation', - description: 'Well-documented change', - type: 'normal', - priority: 'medium', - status: 'submitted', - requestedBy: 'user_123', - requestedAt: Date.now(), - impact: { - level: 'medium', - affectedSystems: ['api'], - }, - implementation: { - description: 'API update', - steps: [ - { - order: 1, - description: 'Deploy', - }, - ], - }, - rollbackPlan: { - description: 'Rollback', - steps: [ - { - order: 1, - description: 'Revert', - }, - ], - }, - attachments: [ - { - name: 'implementation-plan.pdf', - url: 'https://example.com/docs/plan.pdf', - }, - { - name: 'architecture-diagram.png', - url: 'https://example.com/diagrams/arch.png', - }, - ], - }; - - expect(() => ChangeRequestSchema.parse(changeWithAttachments)).not.toThrow(); - }); - - it('should validate attachment URLs', () => { - const invalidChange = { - id: 'CHG-2024-005', - title: 'Invalid Attachment', - description: 'Change with invalid attachment URL', - type: 'normal', - priority: 'low', - status: 'draft', - requestedBy: 'user_123', - requestedAt: Date.now(), - impact: { - level: 'low', - affectedSystems: ['test'], - }, - implementation: { - description: 'Test', - steps: [ - { - order: 1, - description: 'Test', - }, - ], - }, - rollbackPlan: { - description: 'Test', - steps: [ - { - order: 1, - description: 'Test', - }, - ], - }, - attachments: [ - { - name: 'document.pdf', - url: 'not-a-valid-url', - }, - ], - }; - - expect(() => ChangeRequestSchema.parse(invalidChange)).toThrow(); - }); - - it('should validate failed change status', () => { - const failedChange = { - id: 'CHG-2024-006', - title: 'Failed Change', - description: 'Change that failed', - type: 'normal', - priority: 'high', - status: 'failed', - requestedBy: 'user_123', - requestedAt: Date.now(), - impact: { - level: 'high', - affectedSystems: ['database'], - }, - implementation: { - description: 'Database update', - steps: [ - { - order: 1, - description: 'Update schema', - }, - ], - }, - rollbackPlan: { - description: 'Restore backup', - steps: [ - { - order: 1, - description: 'Restore', - }, - ], - }, - }; - - expect(() => ChangeRequestSchema.parse(failedChange)).not.toThrow(); - }); - - it('should validate rolled-back change status', () => { - const rolledBackChange = { - id: 'CHG-2024-007', - title: 'Rolled Back Change', - description: 'Change that was rolled back', - type: 'normal', - priority: 'high', - status: 'rolled-back', - requestedBy: 'user_123', - requestedAt: Date.now(), - impact: { - level: 'high', - affectedSystems: ['application'], - }, - implementation: { - description: 'App update', - steps: [ - { - order: 1, - description: 'Deploy', - }, - ], - }, - rollbackPlan: { - description: 'Revert deployment', - steps: [ - { - order: 1, - description: 'Rollback', - }, - ], - testProcedure: 'Verify app functionality', - }, - }; - - expect(() => ChangeRequestSchema.parse(rolledBackChange)).not.toThrow(); - }); - - it('should accept change with security impact assessment (A.8.32)', () => { - const changeWithSecurityImpact = { - id: 'CHG-2024-SEC-001', - title: 'API Gateway Configuration Change', - description: 'Update API gateway security headers', - type: 'normal', - priority: 'high', - status: 'approved', - requestedBy: 'security_team', - requestedAt: Date.now(), - impact: { - level: 'high', - affectedSystems: ['api-gateway'], - }, - implementation: { - description: 'Update security headers', - steps: [ - { - order: 1, - description: 'Deploy configuration', - }, - ], - }, - rollbackPlan: { - description: 'Revert configuration', - steps: [ - { - order: 1, - description: 'Restore previous config', - }, - ], - }, - securityImpact: { - assessed: true, - riskLevel: 'high', - affectedDataClassifications: ['pii', 'confidential'], - requiresSecurityApproval: true, - reviewedBy: 'ciso', - reviewedAt: Date.now(), - reviewNotes: 'Approved with monitoring requirement', - }, - }; - - const parsed = ChangeRequestSchema.parse(changeWithSecurityImpact); - expect(parsed.securityImpact?.assessed).toBe(true); - expect(parsed.securityImpact?.riskLevel).toBe('high'); - expect(parsed.securityImpact?.requiresSecurityApproval).toBe(true); - }); - - it('should accept change with minimal security impact', () => { - const change = { - id: 'CHG-2024-SEC-002', - title: 'Minor UI Change', - description: 'Update button color', - type: 'standard', - priority: 'low', - status: 'draft', - requestedBy: 'user_123', - requestedAt: Date.now(), - impact: { - level: 'low', - affectedSystems: ['ui'], - }, - implementation: { - description: 'Update CSS', - steps: [{ order: 1, description: 'Deploy' }], - }, - rollbackPlan: { - description: 'Revert CSS', - steps: [{ order: 1, description: 'Revert' }], - }, - securityImpact: { - assessed: true, - riskLevel: 'none', - }, - }; - - const parsed = ChangeRequestSchema.parse(change); - expect(parsed.securityImpact?.riskLevel).toBe('none'); - expect(parsed.securityImpact?.requiresSecurityApproval).toBe(false); - }); - - it('should accept all security risk levels', () => { - const levels = ['none', 'low', 'medium', 'high', 'critical'] as const; - - levels.forEach((riskLevel) => { - const change = { - id: `CHG-${riskLevel}`, - title: 'Test', - description: 'Test', - type: 'standard', - priority: 'low', - status: 'draft', - requestedBy: 'user', - requestedAt: Date.now(), - impact: { level: 'low', affectedSystems: ['test'] }, - implementation: { - description: 'Test', - steps: [{ order: 1, description: 'Test' }], - }, - rollbackPlan: { - description: 'Test', - steps: [{ order: 1, description: 'Test' }], - }, - securityImpact: { - assessed: true, - riskLevel, - }, - }; - - expect(() => ChangeRequestSchema.parse(change)).not.toThrow(); - }); - }); -}); diff --git a/packages/spec/src/system/change-management.zod.ts b/packages/spec/src/system/change-management.zod.ts deleted file mode 100644 index 751fe8218d..0000000000 --- a/packages/spec/src/system/change-management.zod.ts +++ /dev/null @@ -1,466 +0,0 @@ -// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. - -import { z } from 'zod'; -import { DataClassificationSchema } from './security-context.zod'; -import { retiredKey } from '../shared/retired-key'; - -/** - * Change Type Enum - * - * Classification of change requests based on risk and approval requirements. - * Follows ITIL change management best practices. - */ -import { lazySchema } from '../shared/lazy-schema'; -export const ChangeTypeSchema = lazySchema(() => z.enum([ - 'standard', // Pre-approved, low-risk changes - 'normal', // Requires standard approval process - 'emergency', // Fast-track approval for critical issues - 'major', // Requires CAB (Change Advisory Board) approval -])); - -// ─── RETIRED duration keys (ADR-0049 enforce-or-remove) ───────────────────── -// -// Three minute-shaped duration keys were declared on the change-management -// schemas and read by NOTHING: no change-management engine scheduled a -// maintenance window, executed or timed an implementation or rollback step, -// or compared an estimate with what happened — the schemas are exported, -// mounted by no stack key and registered as no metadata type, and the reader -// census over every package outside `packages/spec` (and over objectui at the -// pinned sha) returned zero hits for every key. Maintainer ruling 2026-09-02 -// (recorded on #14477): retire the family under enforce-or-remove. -// -// Route: `retiredKey()` tombstones, NOT plain deletion (the schemas are not -// `.strict()`; a bare deletion would be a silent strip, ADR-0104). The three -// sites are nested (`downtime.durationMinutes`, `steps[].estimatedMinutes` -// twice), so the authorable-surface ratchet — which walks top-level def -// properties — never listed them; they are registered by their nested -// spelling in `RETIRED_KEYS_BY_MAJOR[18]` for the spec-changes / upgrade-guide -// projection, the `kernel/Manifest:contributes.actions` precedent. No D2 -// conversion and no `os migrate meta` sentence: none of these schemas is a -// stack collection member (the `kernel/MetadataPluginConfig:additionalTypes` -// precedent); the D3 semantic entry is `change-management-duration-keys-retired`. - -const DOWNTIME_DURATION_MINUTES_RETIRED = - '`ChangeImpact.downtime.durationMinutes` was removed in @objectstack/spec 17 (ADR-0049 ' - + 'enforce-or-remove) — nothing ever read it: no engine scheduled a maintenance window or ' - + 'measured an outage against it, so the declared downtime was never enforced, announced or ' - + 'compared with what happened. Delete the key. There is no replacement, because no ' - + 'change-management engine exists.'; - -const ROLLBACK_STEP_ESTIMATED_MINUTES_RETIRED = - '`RollbackPlan.steps[].estimatedMinutes` was removed in @objectstack/spec 17 (ADR-0049 ' - + 'enforce-or-remove) — nothing ever read it: no engine executed, timed or summed rollback ' - + 'steps, so the estimate was never compared with anything. Delete the key from every step. ' - + 'There is no replacement, because no change-management engine exists.'; - -const IMPLEMENTATION_STEP_ESTIMATED_MINUTES_RETIRED = - '`ChangeRequest.implementation.steps[].estimatedMinutes` was removed in @objectstack/spec 17 ' - + '(ADR-0049 enforce-or-remove) — nothing ever read it: no engine executed, timed or summed ' - + 'implementation steps, so the estimate never fed a schedule and was never compared with ' - + '`schedule.plannedStart` / `plannedEnd`. Delete the key from every step. There is no ' - + 'replacement, because no change-management engine exists.'; - -/** - * Change Priority Enum - * - * Priority level for change request processing. - */ -export const ChangePrioritySchema = lazySchema(() => z.enum([ - 'critical', - 'high', - 'medium', - 'low', -])); - -/** - * Change Status Enum - * - * Current status of a change request in its lifecycle. - */ -export const ChangeStatusSchema = lazySchema(() => z.enum([ - 'draft', - 'submitted', - 'in-review', - 'approved', - 'scheduled', - 'in-progress', - 'completed', - 'failed', - 'rolled-back', - 'cancelled', -])); - -/** - * Change Impact Schema - * - * Assessment of the impact and scope of a change request. - * Used for risk evaluation and approval routing. - * - * @example - * ```json - * { - * "level": "high", - * "affectedSystems": ["crm-api", "customer-portal"], - * "affectedUsers": 5000, - * "downtime": { - * "required": true - * } - * } - * ``` - */ -export const ChangeImpactSchema = lazySchema(() => z.object({ - /** - * Overall impact level of the change - */ - level: z.enum(['low', 'medium', 'high', 'critical']).describe('Impact level'), - - /** - * List of systems affected by this change - */ - affectedSystems: z.array(z.string()).describe('Affected systems'), - - /** - * Estimated number of users affected - */ - affectedUsers: z.number().optional().describe('Affected user count'), - - /** - * Downtime requirements - */ - downtime: z.object({ - /** - * Whether downtime is required - */ - required: z.boolean().describe('Downtime required'), - - /** - * REMOVED (ADR-0049 enforce-or-remove) — see `DOWNTIME_DURATION_MINUTES_RETIRED` above. - */ - durationMinutes: retiredKey(DOWNTIME_DURATION_MINUTES_RETIRED), - }).optional().describe('Downtime information'), -})); - -/** - * Rollback Plan Schema - * - * Detailed procedure for reverting changes if implementation fails. - * Required for all non-standard changes. - * - * @example - * ```json - * { - * "description": "Revert database schema to previous version", - * "steps": [ - * { - * "order": 1, - * "description": "Stop application servers" - * }, - * { - * "order": 2, - * "description": "Restore database backup" - * } - * ], - * "testProcedure": "Verify application login and basic functionality" - * } - * ``` - */ -export const RollbackPlanSchema = lazySchema(() => z.object({ - /** - * High-level description of the rollback approach - */ - description: z.string().describe('Rollback description'), - - /** - * Sequential steps to execute rollback - */ - steps: z.array(z.object({ - /** - * Step execution order - */ - order: z.number().describe('Step order'), - - /** - * Detailed description of this step - */ - description: z.string().describe('Step description'), - - /** - * REMOVED (ADR-0049 enforce-or-remove) — see `ROLLBACK_STEP_ESTIMATED_MINUTES_RETIRED` above. - */ - estimatedMinutes: retiredKey(ROLLBACK_STEP_ESTIMATED_MINUTES_RETIRED), - })).describe('Rollback steps'), - - /** - * Testing procedure to verify successful rollback - */ - testProcedure: z.string().optional().describe('Test procedure'), -})); - -/** - * Change Request Schema - * - * Comprehensive change management protocol for IT governance. - * Supports change requests, deployment tracking, and ITIL compliance. - * - * @example - * ```json - * { - * "id": "CHG-2024-001", - * "title": "Upgrade CRM Database Schema", - * "description": "Migrate customer database to new schema version 2.0", - * "type": "normal", - * "priority": "high", - * "status": "approved", - * "requestedBy": "user_123", - * "requestedAt": 1704067200000, - * "impact": { - * "level": "high", - * "affectedSystems": ["crm-api", "customer-portal"], - * "affectedUsers": 5000, - * "downtime": { - * "required": true - * } - * }, - * "implementation": { - * "description": "Execute database migration scripts", - * "steps": [ - * { - * "order": 1, - * "description": "Backup current database" - * } - * ], - * "testing": "Run integration test suite" - * }, - * "rollbackPlan": { - * "description": "Restore from backup", - * "steps": [ - * { - * "order": 1, - * "description": "Restore backup" - * } - * ] - * }, - * "schedule": { - * "plannedStart": 1704153600000, - * "plannedEnd": 1704155400000 - * } - * } - * ``` - */ -export const ChangeRequestSchema = lazySchema(() => z.object({ - /** - * Unique change request identifier - */ - id: z.string().describe('Change request ID'), - - /** - * Short descriptive title of the change - */ - title: z.string().describe('Change title'), - - /** - * Detailed description of the change and its purpose - */ - description: z.string().describe('Change description'), - - /** - * Change classification type - */ - type: ChangeTypeSchema.describe('Change type'), - - /** - * Priority level for processing - */ - priority: ChangePrioritySchema.describe('Change priority'), - - /** - * Current status in the change lifecycle - */ - status: ChangeStatusSchema.describe('Change status'), - - /** - * User ID of the change requester - */ - requestedBy: z.string().describe('Requester user ID'), - - /** - * Timestamp when change was requested (Unix milliseconds) - */ - requestedAt: z.number().describe('Request timestamp'), - - /** - * Impact assessment of the change - */ - impact: ChangeImpactSchema.describe('Impact assessment'), - - /** - * Implementation plan and procedures - */ - implementation: z.object({ - /** - * High-level implementation description - */ - description: z.string().describe('Implementation description'), - - /** - * Sequential implementation steps - */ - steps: z.array(z.object({ - /** - * Step execution order - */ - order: z.number().describe('Step order'), - - /** - * Detailed description of this step - */ - description: z.string().describe('Step description'), - - /** - * REMOVED (ADR-0049 enforce-or-remove) — see `IMPLEMENTATION_STEP_ESTIMATED_MINUTES_RETIRED` above. - */ - estimatedMinutes: retiredKey(IMPLEMENTATION_STEP_ESTIMATED_MINUTES_RETIRED), - })).describe('Implementation steps'), - - /** - * Testing procedures to verify successful implementation - */ - testing: z.string().optional().describe('Testing procedure'), - }).describe('Implementation plan'), - - /** - * Rollback plan in case of failure - */ - rollbackPlan: RollbackPlanSchema.describe('Rollback plan'), - - /** - * Change schedule and timing - */ - schedule: z.object({ - /** - * Planned start time (Unix milliseconds) - */ - plannedStart: z.number().describe('Planned start time'), - - /** - * Planned end time (Unix milliseconds) - */ - plannedEnd: z.number().describe('Planned end time'), - - /** - * Actual start time (Unix milliseconds) - */ - actualStart: z.number().optional().describe('Actual start time'), - - /** - * Actual end time (Unix milliseconds) - */ - actualEnd: z.number().optional().describe('Actual end time'), - }).optional().describe('Schedule'), - - /** - * Security impact assessment for the change (A.8.32) - */ - securityImpact: z.object({ - /** - * Whether a security impact assessment has been performed - */ - assessed: z.boolean().describe('Whether security impact has been assessed'), - - /** - * Security risk level of this change - */ - riskLevel: z.enum(['none', 'low', 'medium', 'high', 'critical']).optional() - .describe('Security risk level'), - - /** - * Data classifications affected by this change - */ - affectedDataClassifications: z.array(DataClassificationSchema) - .optional().describe('Affected data classifications'), - - /** - * Whether the change requires security team approval - */ - requiresSecurityApproval: z.boolean().default(false) - .describe('Whether security team approval is required'), - - /** - * Security reviewer user ID - */ - reviewedBy: z.string().optional() - .describe('Security reviewer user ID'), - - /** - * Security review completion timestamp (Unix milliseconds) - */ - reviewedAt: z.number().optional() - .describe('Security review timestamp'), - - /** - * Security review notes or conditions - */ - reviewNotes: z.string().optional() - .describe('Security review notes or conditions'), - }).optional().describe('Security impact assessment per ISO 27001:2022 A.8.32'), - - /** - * Approval workflow configuration - */ - approval: z.object({ - /** - * Whether approval is required for this change - */ - required: z.boolean().describe('Approval required'), - - /** - * List of approvers and their approval status - */ - approvers: z.array(z.object({ - /** - * Approver user ID - */ - userId: z.string().describe('Approver user ID'), - - /** - * Timestamp when approval was granted (Unix milliseconds) - */ - approvedAt: z.number().optional().describe('Approval timestamp'), - - /** - * Comments from the approver - */ - comments: z.string().optional().describe('Approver comments'), - })).describe('Approvers'), - }).optional().describe('Approval workflow'), - - /** - * Supporting documentation and files - */ - attachments: z.array(z.object({ - /** - * Attachment file name - */ - name: z.string().describe('Attachment name'), - - /** - * URL to download the attachment - */ - url: z.string().url().describe('Attachment URL'), - })).optional().describe('Attachments'), - - /** - * Custom metadata key-value pairs for extensibility - */ - metadata: z.record(z.string(), z.unknown()).optional().describe('Custom metadata key-value pairs for extensibility'), -})); - -// Type exports -export type ChangeRequest = z.input; -/** Post-parse shape of {@link ChangeRequest} — defaults applied, transforms run (ADR-0122). */ -export type ChangeRequestParsed = z.infer; -export type ChangeType = z.input; -export type ChangeStatus = z.input; -export type ChangePriority = z.input; -export type ChangeImpact = z.input; -export type RollbackPlan = z.input; diff --git a/packages/spec/src/system/compliance-families-retirement.test.ts b/packages/spec/src/system/compliance-families-retirement.test.ts new file mode 100644 index 0000000000..31b4440290 --- /dev/null +++ b/packages/spec/src/system/compliance-families-retirement.test.ts @@ -0,0 +1,394 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +import fs from 'node:fs'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +import { describe, expect, it } from 'vitest'; + +import { + EXPORT_ENTRY_POINTS, + exportNamesOf, + holdersOf, +} from '../../scripts/lib/export-origins-testkit'; +import { MIGRATIONS_BY_MAJOR, RETIRED_DEFS_BY_MAJOR, RETIRED_KEYS_BY_MAJOR } from '../migrations/registry'; + +// ─── [#15513] the incident-response, training and change-management families are RETIRED WHOLE ── +// +// ADR-0049 enforce-or-remove; maintainer ruling 2026-09-05 (ruled A: retire the +// three compliance-shaped families whole via RETIRED_DEFS_BY_MAJOR, the +// `integration/ErrorMappingConfig` precedent; none of the three is roadmapped). +// `system/incident-response.zod.ts`, `system/training.zod.ts` and +// `system/change-management.zod.ts` are deleted whole — nineteen emitted defs, +// forty-five exported names, reference docs with them. +// +// The measurement that decided it (the #15513 card at a06faebbe / 83a3353e3, +// triage at f1d7872, re-taken on this branch's base with a lit control in the +// same run): +// +// 1. STATIC — zero readers of any of the forty-five exported names in +// `packages/**` outside `packages/spec` (tests and changelogs excluded), +// in `examples/**` and `skills/**`, and in objectui at the pinned sha, +// while the corpus-reach control (`ObjectSchema` / `FieldSchema` under +// identical exclusions) returns hundreds of hits. The one word-bounded hit +// for the bare `Incident` is the English word inside a showcase flow's +// notify-message string, not a reader of the type. +// 2. DOORS — no `stack.zod.ts` key mounts any of the schemas and +// `DEFAULT_METADATA_TYPE_REGISTRY` has no type for any of them, so no +// authoring path — file, REST or stored row — ever reached a parse. +// 3. The compliance face: `IncidentNotificationRule.notifyRegulators`, +// `IncidentResponsePolicy.requirePostIncidentReview`, +// `TrainingCourse.mandatory`, `TrainingPlan.trackCompletion` / +// `sendReminders`, `ChangeRequest.approval.required` and +// `ChangeRequest.securityImpact.requiresSecurityApproval` were boolean +// capability claims of exactly the shape ADR-0049 names — an author (very +// often an AI, ADR-0033) writing `notifyRegulators: true` held a compliance +// promise the platform never kept, with no error and no feedback. +// +// ## Why route 3, and why there is nothing to tombstone +// +// With no carrier key there is no shape on which a `retiredKey()` tombstone +// could sit, and no author document for an ADR-0087 D2 conversion to rewrite — +// a prescription nobody can receive is noise. The declared record is the three +// D3 `SemanticMigration`s (one per family) plus the nineteen +// `RETIRED_DEFS_BY_MAJOR[18]` entries the manifest-deletion gate reads. +// +// ## What happened to the #14477 deadline-key retirement (PR #15514) +// +// The fourteen `retiredKey()` tombstones that PR planted inside these defs +// leave with their defs' source, and `deadline-keys-retirement.test.ts` — whose +// every pin needed the schemas to exist — is replaced by this file. Its +// fourteen `RETIRED_KEYS_BY_MAJOR[18]` entries and three D3 entries are +// HISTORY and stay: gate (b2) of `scripts/build-schemas.ts` accepts an entry +// naming a key the build no longer emits, and the upgrade guide still owes the +// 17→18 reader the deadline-key prescriptions. Pinned below so nobody "cleans +// them up" as dead registrations. +// +// Form follows #8075 (`message-queue-retirement.test.ts`): resolved symbol +// identity over every public entry via the build-time `export-origins/` +// artifact, the file-deletion probe, the in-package importer walk and the +// runtime-namespace cross-check — plus the tree-scoped absence leg the ruling +// asks for, with its walk radius DECLARED (the playbook rule that landed after +// PR #15514: `scripts/cross-package-test-inputs.mjs`, `turbo.json`). + +/** The 45 names the nineteen retired defs exported (19 schema consts + 26 type aliases). */ +const RETIRED_NAMES = [ + // incident-response.zod.ts — 8 defs, 19 names + 'IncidentSeveritySchema', 'IncidentSeverity', + 'IncidentCategorySchema', 'IncidentCategory', + 'IncidentStatusSchema', 'IncidentStatus', + 'IncidentResponsePhaseSchema', 'IncidentResponsePhase', + 'IncidentNotificationRuleSchema', 'IncidentNotificationRule', 'IncidentNotificationRuleParsed', + 'IncidentNotificationMatrixSchema', 'IncidentNotificationMatrix', 'IncidentNotificationMatrixParsed', + 'IncidentSchema', 'Incident', + 'IncidentResponsePolicySchema', 'IncidentResponsePolicy', 'IncidentResponsePolicyParsed', + // training.zod.ts — 5 defs, 12 names + 'TrainingCategorySchema', 'TrainingCategory', + 'TrainingCompletionStatusSchema', 'TrainingCompletionStatus', + 'TrainingCourseSchema', 'TrainingCourse', 'TrainingCourseParsed', + 'TrainingRecordSchema', 'TrainingRecord', + 'TrainingPlanSchema', 'TrainingPlan', 'TrainingPlanParsed', + // change-management.zod.ts — 6 defs, 14 names + 'ChangeTypeSchema', 'ChangeType', + 'ChangePrioritySchema', 'ChangePriority', + 'ChangeStatusSchema', 'ChangeStatus', + 'ChangeImpactSchema', 'ChangeImpact', + 'RollbackPlanSchema', 'RollbackPlan', + 'ChangeRequestSchema', 'ChangeRequest', 'ChangeRequestParsed', +] as const; + +/** The nineteen def keys, spelled as `json-schema.manifest/system.json` and the registry spell them. */ +const RETIRED_DEFS = [ + 'system/Incident', 'system/IncidentCategory', 'system/IncidentNotificationMatrix', + 'system/IncidentNotificationRule', 'system/IncidentResponsePhase', 'system/IncidentResponsePolicy', + 'system/IncidentSeverity', 'system/IncidentStatus', + 'system/TrainingCategory', 'system/TrainingCompletionStatus', 'system/TrainingCourse', + 'system/TrainingPlan', 'system/TrainingRecord', + 'system/ChangeImpact', 'system/ChangePriority', 'system/ChangeRequest', 'system/ChangeStatus', + 'system/ChangeType', 'system/RollbackPlan', +] as const; + +const SEMANTIC_IDS = [ + 'incident-response-family-retired', + 'training-family-retired', + 'change-management-family-retired', +] as const; + +/** PR #15514's registrations — history, deliberately kept (see the header). */ +const HISTORY_KEYS = [ + 'system/IncidentResponsePhase:targetHours', + 'system/IncidentNotificationRule:withinMinutes', + 'system/IncidentNotificationRule:regulatorDeadlineHours', + 'system/IncidentNotificationMatrix:escalationTimeoutMinutes', + 'system/IncidentResponsePolicy:triageDeadlineHours', + 'system/IncidentResponsePolicy:retentionDays', + 'system/TrainingCourse:durationMinutes', + 'system/TrainingCourse:validityDays', + 'system/TrainingPlan:recertificationIntervalDays', + 'system/TrainingPlan:gracePeriodDays', + 'system/TrainingPlan:reminderDaysBefore', + 'system/ChangeImpact:downtime.durationMinutes', + 'system/RollbackPlan:steps.estimatedMinutes', + 'system/ChangeRequest:implementation.steps.estimatedMinutes', +] as const; +const HISTORY_SEMANTIC_IDS = [ + 'incident-response-deadline-keys-retired', + 'training-deadline-keys-retired', + 'change-management-duration-keys-retired', +] as const; + +/** + * The near-namesakes a "finish everything incident / change" sweep would + * plausibly take, each a DIFFERENT declaration with a live consumer. + */ +const MUST_SURVIVE_KERNEL = ['MetadataChangeTypeSchema', 'MetadataChangeType'] as const; +/** System-entry neighbours that stay — the ones the retired files imported from. */ +const MUST_SURVIVE_SYSTEM = ['DataClassificationSchema', 'SecurityContextSchema', 'ChangeSetSchema'] as const; + +const SPEC_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..'); +const SRC_ROOT = path.join(SPEC_ROOT, 'src'); + +describe('[#15513] system/ compliance families retirement — the public surface', () => { + it('every retired name has ZERO holders on any public entry; the survivors still stand', () => { + for (const needed of ['.', './system', './kernel']) { + expect(EXPORT_ENTRY_POINTS, `exports map must include ${needed}`).toContain(needed); + } + expect(exportNamesOf('./system').length, './system must export a non-trivial surface').toBeGreaterThan(100); + + for (const name of RETIRED_NAMES) { + expect(holdersOf(name), `${name} must have zero holders after #15513`).toEqual([]); + } + const systemNames = exportNamesOf('./system'); + for (const name of MUST_SURVIVE_SYSTEM) { + expect(systemNames, `${name} must SURVIVE this retirement`).toContain(name); + } + const kernelNames = exportNamesOf('./kernel'); + for (const name of MUST_SURVIVE_KERNEL) { + expect(kernelNames, `${name} must SURVIVE this retirement`).toContain(name); + } + }); + + it('runtime namespace agrees with the compiler view', async () => { + const system = await import('./index'); + for (const name of RETIRED_NAMES.filter((n) => n.endsWith('Schema'))) { + expect(name in system, `system must not export ${name}`).toBe(false); + } + for (const name of MUST_SURVIVE_SYSTEM) { + expect(name in system, `${name} must SURVIVE at runtime`).toBe(true); + } + }); + + it('the three modules are gone from disk, and nothing in the package imports them any more', () => { + for (const f of [ + 'incident-response.zod.ts', 'incident-response.test.ts', + 'training.zod.ts', 'training.test.ts', + 'change-management.zod.ts', 'change-management.test.ts', + 'deadline-keys-retirement.test.ts', + ]) { + expect(fs.existsSync(path.join(SRC_ROOT, 'system', f)), `system/${f} must be deleted`).toBe(false); + } + // Anti-vacuity: a kept sibling proves the probe looks in the right place. + expect(fs.existsSync(path.join(SRC_ROOT, 'system', 'security-context.zod.ts'))).toBe(true); + + const importers: string[] = []; + const walk = (dir: string) => { + for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { + const full = path.join(dir, entry.name); + if (entry.isDirectory()) walk(full); + else if (entry.name.endsWith('.ts')) { + const src = fs.readFileSync(full, 'utf-8'); + if (/(?:import|export)[^;]*['"][^'"]*\/(?:incident-response|training|change-management)\.zod(?:\.js)?['"]/.test(src)) { + importers.push(path.relative(SRC_ROOT, full)); + } + } + } + }; + walk(SRC_ROOT); + expect(importers, 'a resurrected import means the retirement is being undone — re-read #15513').toEqual([]); + }); + + it('the generated shards no longer list any of the nineteen defs or forty-five names', () => { + const shard = (dir: string) => fs.readFileSync(path.join(SPEC_ROOT, dir, 'system.json'), 'utf-8'); + const manifest = JSON.parse(shard('json-schema.manifest')) as { schemas: string[] }; + for (const def of RETIRED_DEFS) { + expect(manifest.schemas, `${def} must have left json-schema.manifest/`).not.toContain(def); + } + // Anti-vacuity: a surviving neighbour is still there. + expect(manifest.schemas).toContain('system/ChangeSet'); + const exact = (name: string) => new RegExp(`"${name}"`); + for (const dir of ['api-surface', 'declaration-map', 'export-origins']) { + const text = shard(dir); + for (const name of RETIRED_NAMES) { + expect(text, `${dir}/system.json must not list ${name}`).not.toMatch(exact(name)); + } + expect(text, `${dir}/system.json anti-vacuity`).toMatch(exact('DataClassificationSchema')); + } + for (const dir of ['authorable-surface', 'authorable-defaults']) { + const text = shard(dir); + for (const def of RETIRED_DEFS) { + expect(text, `${dir}/system.json must carry no row under ${def}`).not.toMatch(new RegExp(`"${def}:`)); + } + } + }); +}); + +describe('[#15513] ADR-0087 registration', () => { + it('declares all nineteen defs under major 18, with the three D3 semantic entries wired and no D2 conversion', () => { + for (const def of RETIRED_DEFS) { + expect(RETIRED_DEFS_BY_MAJOR[18], `${def} must be declared`).toContain(def); + } + const step = MIGRATIONS_BY_MAJOR[18]; + expect(step).toBeDefined(); + const ids = step!.semantic.map((s) => s.id); + for (const id of SEMANTIC_IDS) { + expect(ids, `${id} must be wired into the step-18 chain`).toContain(id); + const entry = step!.semantic.find((s) => s.id === id)!; + expect(entry.reason.length).toBeGreaterThan(0); + expect(entry.acceptanceCriteria.length).toBeGreaterThan(0); + // The route is stated where the next reader looks: why D3 semantic and + // not D2 — no stack seam (the additionalTypes precedent). + expect(entry.reason).toMatch(/not a D2 conversion/); + // And no backticks in `surface`: the upgrade guide renders it inside a + // code span AND a table cell. + expect(entry.surface).not.toMatch(/`/); + } + // Deliberately no mechanical conversion: a transform over a stack that + // never carries these documents would be a seam that never runs. + expect(step!.conversionIds.filter((id) => /incident|training|change-management|deadline/.test(id))).toEqual([]); + // The step's own rationale records the family retirement. + expect(step!.rationale).toMatch(/retires those three compliance-shaped families WHOLE/); + }); + + it("keeps PR #15514's fourteen deadline-key registrations and three D3 entries as history", () => { + for (const key of HISTORY_KEYS) { + expect(RETIRED_KEYS_BY_MAJOR[18], `${key} is history — keep it`).toContain(key); + } + const ids = MIGRATIONS_BY_MAJOR[18]!.semantic.map((s) => s.id); + for (const id of HISTORY_SEMANTIC_IDS) { + expect(ids, `${id} is history — keep it`).toContain(id); + } + }); +}); + +// ─── Tree-scoped absence, with a DECLARED radius ───────────────────────────── +// +// What this leg guarantees. The walk below reads six repo roots — `packages`, +// `examples`, `skills`, `content`, `scripts`, `docs` is NOT among them — and +// every root it reads is declared for `@objectstack/spec#test` in +// `scripts/cross-package-test-inputs.mjs` (with this file as the `heldBy` +// witness for the globs no literal path holds) and mirrored in `turbo.json`, +// so a resurrection inside the radius puts this suite into +// `turbo ls --affected` and moves the `test` task's cache key. That is the +// declaration half PR #15514's pin lacked (#15528, closed by the playbook rule +// #15566 added). +// +// The bound, stated: `docs/**` (ADRs, audits), `.claude/**`, `.github/**` and +// the repo-root files are outside the walk. Each is prose-only for these names +// — an ADR that mentions `IncidentResponsePolicySchema` is a mention, not an +// authoring or an import — and `docs/adr/**` and `.claude/**` are governed, +// human-merged surfaces; declaring them would add new top-level roots to +// ci.yml's `crosspkg:` filter for no reachable resurrection. `apps/` holds only +// the Fumadocs site (its content lives under `content/`). +// +// Extensions: `.tsx` is deliberately NOT scanned, and the declared globs under +// `packages/` are spelled per extension so none covers a `.tsx` file — the +// dispatch-gates self-test pins that no cross-package hint reaches +// `packages/client-react`'s `realtime-hooks.test.tsx` (the `@objectstack/core` +// entry records the measurement). A `.tsx` import of a retired name fails +// `tsc` in its own package, which is the enforced channel for typed sources. +// The residue this leg covers is everything `tsc` does not compile: JSON, +// YAML, MD, MDX, and untyped `.js` / `.mjs` / `.cjs`. +describe('[#15513] tree-scoped absence: nothing inside the declared radius references a retired name', () => { + const REPO_ROOT = path.resolve(SPEC_ROOT, '../..'); + const THIS_FILE = path.relative(REPO_ROOT, fileURLToPath(import.meta.url)).split(path.sep).join('/'); + + /** The walked roots — declared in `scripts/cross-package-test-inputs.mjs` under `@objectstack/spec`. */ + const WALK_ROOTS = ['packages', 'examples', 'skills', 'content', 'scripts']; + /** Per-extension under `packages/` (never `.tsx`, see above); the other roots are declared whole. */ + const SCANNED_EXT = new Set(['.ts', '.mts', '.cts', '.js', '.mjs', '.cjs', '.json', '.md', '.mdx', '.yaml', '.yml']); + /** Build, SCM and cache state — not authored sources. */ + const SKIPPED_DIRS = new Set(['node_modules', 'dist', '.git', '.turbo', '.cache', '.objectstack', 'coverage', '.next', '.source']); + + /** + * The distinctive names: every `*Schema` const and every multi-word or + * CamelCase alias. The bare `Incident` is excluded from THIS leg (it is an + * English word — the showcase's `'Incident push failed'` string is the + * measured false positive) and judged by the export-origins leg above + * instead, where the match is an exact symbol. + */ + const NAME = new RegExp( + '\\b(' + RETIRED_NAMES.filter((n) => n !== 'Incident').join('|') + ')\\b', + ); + /** A reference, never a prose mention: an import/export specifier, a member access, a type position, a JSON def key. */ + const REFERENCE = new RegExp( + '(?:' + + // `import { X }` / `export { X }` / `, X,` inside a specifier list + '[{,]\\s*(?:type\\s+)?' + NAME.source + '\\s*[,}]' + + '|' + + // `typeof X` / `: X` / `` — a type position + '(?:typeof\\s+|:\\s*|<)' + NAME.source + '\\b' + + '|' + + // `X.parse(` / `X.safeParse(` — a method call on the schema + NAME.source + '\\.\\w+\\(' + + '|' + + // the manifest / registry spelling of the def + '"system/(?:' + RETIRED_DEFS.map((d) => d.slice('system/'.length)).join('|') + ')"' + + ')', + ); + + /** Structural exclusions, each with its reason. NOT an allowlist file: these are the retirement kit and its projections. */ + const EXCLUDED = new Set([ + // The survivor note in the barrel names what left. + 'packages/spec/src/system/index.ts', + // This pin names them to assert their absence. + THIS_FILE, + ]); + const EXCLUDED_PREFIXES = [ + // Registers the retirement by def / key (entries + the generated registry). + 'packages/spec/src/migrations/', + // Generated projections of the registry. + 'packages/spec/spec-changes.json', + // Release prose records the removal (release-owned; never edited by a code PR). + 'content/docs/releases/', + '.changeset/', + ]; + + it('the matcher recognises a reference and ignores a prose mention (anti-vacuity)', () => { + expect(REFERENCE.test("import { IncidentResponsePolicySchema } from './incident-response.zod';")).toBe(true); + expect(REFERENCE.test('const x: TrainingCourse = {};')).toBe(true); + expect(REFERENCE.test('ChangeRequestSchema.parse(value)')).toBe(true); + expect(REFERENCE.test('typeof ChangeRequestSchema')).toBe(true); + expect(REFERENCE.test('"system/RollbackPlan",')).toBe(true); + expect(REFERENCE.test('the `IncidentResponsePolicySchema` export was removed')).toBe(false); + expect(REFERENCE.test("title: 'Incident push failed: {record.name}'")).toBe(false); + expect(REFERENCE.test('MetadataChangeTypeSchema')).toBe(false); // the live near-namesake + }); + + it('no reference survives inside the declared radius outside the retirement kit', () => { + const offenders: string[] = []; + let visited = 0; + const walk = (dir: string) => { + for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { + const full = path.join(dir, entry.name); + const rel = path.relative(REPO_ROOT, full).split(path.sep).join('/'); + if (entry.isDirectory()) { + if (SKIPPED_DIRS.has(entry.name) || entry.name.startsWith('.')) continue; + walk(full); + continue; + } + if (!entry.isFile()) continue; + if (!SCANNED_EXT.has(path.extname(entry.name))) continue; + if (entry.name === 'CHANGELOG.md') continue; // release prose records the removal + if (EXCLUDED.has(rel) || EXCLUDED_PREFIXES.some((p) => rel.startsWith(p))) continue; + visited += 1; + const text = fs.readFileSync(full, 'utf-8'); + const m = REFERENCE.exec(text); + if (m) offenders.push(`${rel} references \`${m[0].trim()}\``); + } + }; + for (const root of WALK_ROOTS) walk(path.join(REPO_ROOT, root)); + // Anti-vacuity: the walk really covered the tree. + expect(visited).toBeGreaterThan(1000); + expect(offenders, 'a reference to a retired name means the retirement is being undone — re-read #15513').toEqual([]); + }); +}); diff --git a/packages/spec/src/system/deadline-keys-retirement.test.ts b/packages/spec/src/system/deadline-keys-retirement.test.ts deleted file mode 100644 index 970f997046..0000000000 --- a/packages/spec/src/system/deadline-keys-retirement.test.ts +++ /dev/null @@ -1,677 +0,0 @@ -// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. - -import fs from 'node:fs'; -import path from 'node:path'; -import { fileURLToPath } from 'node:url'; - -import { describe, expect, it } from 'vitest'; -import type { ZodTypeAny } from 'zod'; - -import { - IncidentNotificationMatrixSchema, - IncidentNotificationRuleSchema, - IncidentResponsePhaseSchema, - IncidentResponsePolicySchema, - IncidentSchema, - type IncidentNotificationRule, - type IncidentResponsePhase, - type IncidentResponsePolicy, -} from './incident-response.zod'; -import { - TrainingCourseSchema, - TrainingPlanSchema, - type TrainingCourse, - type TrainingPlan, -} from './training.zod'; -import { - ChangeImpactSchema, - ChangeRequestSchema, - RollbackPlanSchema, - type ChangeImpact, - type ChangeRequest, - type RollbackPlan, -} from './change-management.zod'; -import { MIGRATIONS_BY_MAJOR, RETIRED_KEYS_BY_MAJOR } from '../migrations/registry'; - -// ─── [#14477] the fourteen inert deadline keys are REMOVED ────────────────── -// -// ADR-0049 enforce-or-remove; maintainer ruling 2026-09-02, ruled A (retire -// per family). Fourteen hour/minute/day-shaped deadline, SLA and duration keys -// on the incident-response, training and change-management schemas sat on the -// published authorable surface and in the generated reference docs, and were -// read by NOTHING: the schemas are exported, mounted by no stack key, -// registered as no metadata type, absent from the 2026-06 liveness ledgers, -// and the reader census over every package outside `packages/spec` (tests and -// changelogs excluded) and over objectui at the pinned sha returned zero hits -// for every key. Six carried defaults that were materialized into every parsed -// document without ever being consulted. -// -// Route: `retiredKey()` tombstones, NOT plain deletion — none of the schemas -// is `.strict()`, so a bare deletion would make zod strip the key in silence, -// replacing an inert declaration with an invisible one (ADR-0104). Audible in -// two channels: `tsc` (the input type is `never`) and the parse (the -// prescription is the message). No D2 conversion: none of the schemas is a -// stack collection member, so the chain has no seam that ever runs (the -// `kernel/MetadataPluginConfig:additionalTypes` precedent) — the registration -// is fourteen `RETIRED_KEYS_BY_MAJOR[18]` entries plus three D3 semantic -// entries, one per family. -// -// On the assertion set (the #8586 / #14676 precedent): a schema refusal raises -// a `ZodError` whose issues carry `code` and `path` but no ADR-0112 `status` — -// that envelope belongs to the API error surface. So these pins assert the -// strongest set this surface really has: refusal, the issue `code`, the `path` -// naming WHICH site refused, and the prescription text (#5240: where the -// wording is the contract, pin the wording). - -// ── Well-formed fixtures: every required key, none of the retired ones ────── - -const PHASE: IncidentResponsePhase = { - phase: 'containment', - description: 'Isolate affected systems', - assignedTo: 'security_team', -}; -const RULE: IncidentNotificationRule = { - severity: 'critical', - channels: ['pagerduty'], - recipients: ['security_team'], -}; -const MATRIX = { rules: [RULE] }; -const POLICY: IncidentResponsePolicy = { - notificationMatrix: MATRIX, - defaultResponseTeam: 'security_team', -}; -const INCIDENT = { - id: 'INC-2024-001', - title: 'Unauthorized API Access Detected', - description: 'Multiple failed authentication attempts from an unknown IP range', - severity: 'high', - category: 'unauthorized_access', - status: 'investigating', - reportedBy: 'monitoring_system', - reportedAt: 1704067200000, - affectedSystems: ['api-gateway'], -}; -const COURSE: TrainingCourse = { - id: 'COURSE-SEC-001', - title: 'Information Security Fundamentals', - description: 'Annual security awareness training for all employees', - category: 'security_awareness', - targetRoles: ['all_employees'], -}; -const PLAN: TrainingPlan = { courses: [COURSE] }; -const STEP = { order: 1, description: 'Restore database backup' }; -const IMPACT: ChangeImpact = { - level: 'high', - affectedSystems: ['crm-api'], - downtime: { required: true }, -}; -const ROLLBACK: RollbackPlan = { description: 'Restore from backup', steps: [STEP] }; -const CHANGE: ChangeRequest = { - id: 'CHG-2024-001', - title: 'Upgrade CRM Database Schema', - description: 'Migrate the customer database to schema version 2.0', - type: 'normal', - priority: 'high', - status: 'approved', - requestedBy: 'user_123', - requestedAt: 1704067200000, - impact: IMPACT, - implementation: { description: 'Execute the migration scripts', steps: [STEP] }, - rollbackPlan: ROLLBACK, -}; - -/** One retired declaration site, as the schema, the registry and the prescription each spell it. */ -interface RetiredSite { - /** The exact `RETIRED_KEYS_BY_MAJOR` spelling. */ - registered: string; - /** How the prescription opens (its backtick-wrapped qualified key). */ - qualified: string; - schema: ZodTypeAny; - /** Parses green as-is. */ - wellFormed: unknown; - /** The same document with the retired key authored. */ - authored: unknown; - /** Where the refusal must surface. */ - issuePath: (string | number)[]; -} - -const SITES: RetiredSite[] = [ - { - registered: 'system/IncidentResponsePhase:targetHours', - qualified: 'IncidentResponsePhase.targetHours', - schema: IncidentResponsePhaseSchema, - wellFormed: PHASE, - authored: { ...PHASE, targetHours: 2 }, - issuePath: ['targetHours'], - }, - { - registered: 'system/IncidentNotificationRule:withinMinutes', - qualified: 'IncidentNotificationRule.withinMinutes', - schema: IncidentNotificationRuleSchema, - wellFormed: RULE, - authored: { ...RULE, withinMinutes: 15 }, - issuePath: ['withinMinutes'], - }, - { - registered: 'system/IncidentNotificationRule:regulatorDeadlineHours', - qualified: 'IncidentNotificationRule.regulatorDeadlineHours', - schema: IncidentNotificationRuleSchema, - wellFormed: RULE, - authored: { ...RULE, notifyRegulators: true, regulatorDeadlineHours: 72 }, - issuePath: ['regulatorDeadlineHours'], - }, - { - registered: 'system/IncidentNotificationMatrix:escalationTimeoutMinutes', - qualified: 'IncidentNotificationMatrix.escalationTimeoutMinutes', - schema: IncidentNotificationMatrixSchema, - wellFormed: MATRIX, - authored: { ...MATRIX, escalationTimeoutMinutes: 30 }, - issuePath: ['escalationTimeoutMinutes'], - }, - { - registered: 'system/IncidentResponsePolicy:triageDeadlineHours', - qualified: 'IncidentResponsePolicy.triageDeadlineHours', - schema: IncidentResponsePolicySchema, - wellFormed: POLICY, - authored: { ...POLICY, triageDeadlineHours: 1 }, - issuePath: ['triageDeadlineHours'], - }, - { - registered: 'system/IncidentResponsePolicy:retentionDays', - qualified: 'IncidentResponsePolicy.retentionDays', - schema: IncidentResponsePolicySchema, - wellFormed: POLICY, - authored: { ...POLICY, retentionDays: 2555 }, - issuePath: ['retentionDays'], - }, - { - registered: 'system/TrainingCourse:durationMinutes', - qualified: 'TrainingCourse.durationMinutes', - schema: TrainingCourseSchema, - wellFormed: COURSE, - authored: { ...COURSE, durationMinutes: 60 }, - issuePath: ['durationMinutes'], - }, - { - registered: 'system/TrainingCourse:validityDays', - qualified: 'TrainingCourse.validityDays', - schema: TrainingCourseSchema, - wellFormed: COURSE, - authored: { ...COURSE, validityDays: 365 }, - issuePath: ['validityDays'], - }, - { - registered: 'system/TrainingPlan:recertificationIntervalDays', - qualified: 'TrainingPlan.recertificationIntervalDays', - schema: TrainingPlanSchema, - wellFormed: PLAN, - authored: { ...PLAN, recertificationIntervalDays: 365 }, - issuePath: ['recertificationIntervalDays'], - }, - { - registered: 'system/TrainingPlan:gracePeriodDays', - qualified: 'TrainingPlan.gracePeriodDays', - schema: TrainingPlanSchema, - wellFormed: PLAN, - authored: { ...PLAN, gracePeriodDays: 30 }, - issuePath: ['gracePeriodDays'], - }, - { - registered: 'system/TrainingPlan:reminderDaysBefore', - qualified: 'TrainingPlan.reminderDaysBefore', - schema: TrainingPlanSchema, - wellFormed: PLAN, - authored: { ...PLAN, reminderDaysBefore: 14 }, - issuePath: ['reminderDaysBefore'], - }, - { - registered: 'system/ChangeImpact:downtime.durationMinutes', - qualified: 'ChangeImpact.downtime.durationMinutes', - schema: ChangeImpactSchema, - wellFormed: IMPACT, - authored: { ...IMPACT, downtime: { required: true, durationMinutes: 30 } }, - issuePath: ['downtime', 'durationMinutes'], - }, - { - registered: 'system/RollbackPlan:steps.estimatedMinutes', - qualified: 'RollbackPlan.steps[].estimatedMinutes', - schema: RollbackPlanSchema, - wellFormed: ROLLBACK, - authored: { ...ROLLBACK, steps: [{ ...STEP, estimatedMinutes: 15 }] }, - issuePath: ['steps', 0, 'estimatedMinutes'], - }, - { - registered: 'system/ChangeRequest:implementation.steps.estimatedMinutes', - qualified: 'ChangeRequest.implementation.steps[].estimatedMinutes', - schema: ChangeRequestSchema, - wellFormed: CHANGE, - authored: { - ...CHANGE, - implementation: { description: 'Execute the migration scripts', steps: [{ ...STEP, estimatedMinutes: 10 }] }, - }, - issuePath: ['implementation', 'steps', 0, 'estimatedMinutes'], - }, -]; - -/** The carriers that nest a retired site: the refusal must travel through them. */ -const CARRIERS: Array> = [ - { - qualified: 'IncidentResponsePhase.targetHours', - schema: IncidentSchema, - wellFormed: { ...INCIDENT, responsePhases: [PHASE] }, - authored: { ...INCIDENT, responsePhases: [{ ...PHASE, targetHours: 2 }] }, - issuePath: ['responsePhases', 0, 'targetHours'], - }, - { - qualified: 'IncidentNotificationRule.withinMinutes', - schema: IncidentResponsePolicySchema, - wellFormed: POLICY, - authored: { ...POLICY, notificationMatrix: { rules: [{ ...RULE, withinMinutes: 15 }] } }, - issuePath: ['notificationMatrix', 'rules', 0, 'withinMinutes'], - }, - { - qualified: 'IncidentNotificationMatrix.escalationTimeoutMinutes', - schema: IncidentResponsePolicySchema, - wellFormed: POLICY, - authored: { ...POLICY, notificationMatrix: { ...MATRIX, escalationTimeoutMinutes: 45 } }, - issuePath: ['notificationMatrix', 'escalationTimeoutMinutes'], - }, - { - qualified: 'TrainingCourse.validityDays', - schema: TrainingPlanSchema, - wellFormed: PLAN, - authored: { courses: [{ ...COURSE, validityDays: 365 }] }, - issuePath: ['courses', 0, 'validityDays'], - }, - { - qualified: 'ChangeImpact.downtime.durationMinutes', - schema: ChangeRequestSchema, - wellFormed: CHANGE, - authored: { ...CHANGE, impact: { ...IMPACT, downtime: { required: true, durationMinutes: 30 } } }, - issuePath: ['impact', 'downtime', 'durationMinutes'], - }, - { - qualified: 'RollbackPlan.steps[].estimatedMinutes', - schema: ChangeRequestSchema, - wellFormed: CHANGE, - authored: { ...CHANGE, rollbackPlan: { ...ROLLBACK, steps: [{ ...STEP, estimatedMinutes: 15 }] } }, - issuePath: ['rollbackPlan', 'steps', 0, 'estimatedMinutes'], - }, -]; - -const SEMANTIC_IDS = [ - 'incident-response-deadline-keys-retired', - 'training-deadline-keys-retired', - 'change-management-duration-keys-retired', -] as const; - -function escapeRegExp(s: string): string { - return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); -} - -function expectTombstoneRefusal(site: Pick) { - const result = site.schema.safeParse(site.authored); - expect(result.success, `${site.qualified} must be refused`).toBe(false); - if (result.success) return; // narrowing; the assertion above already failed - - const wanted = site.issuePath.join('.'); - const issue = result.error.issues.find((i) => i.path.join('.') === wanted); - expect(issue, `the refusal must surface at ${wanted}`).toBeDefined(); - // The machine-readable half of the envelope this surface actually has: a - // `retiredKey()` tombstone raises `invalid_type` from its `z.never()`. - expect(issue!.code).toBe('invalid_type'); - expect(issue!.path).toEqual(site.issuePath); - // The prescription IS the migration doc for whoever hits it — contract, not - // commentary: it opens with the qualified key, names the version and the - // ADR, says why the key was inert, and tells the author what to do. - expect(issue!.message).toMatch( - new RegExp('^`' + escapeRegExp(site.qualified) + '` was removed in @objectstack/spec 17 \\(ADR-0049 enforce-or-remove\\) — nothing ever read it'), - ); - expect(issue!.message).toMatch(/Delete the key/); - // Customer-facing text carries the ADR, never an issue id — a `#NNNN` - // token resolves to nothing for the reader who meets this refusal. - expect(issue!.message).not.toMatch(/#\d{3,}/); - // Deliberately NO `os migrate meta` sentence: no conversion covers these - // schemas (not stack collection members), so the sentence would promise an - // edit list the tool cannot produce (`retired-key.ts`: the sentence must be - // TRUE of the tool). - expect(issue!.message).not.toMatch(/os migrate meta/); -} - -describe('[#14477] inert deadline keys retirement — refusal at every site', () => { - for (const site of SITES) { - it(`REJECTS an authored \`${site.qualified}\` at path \`${site.issuePath.join('.')}\`, carrying the prescription`, () => { - expectTombstoneRefusal(site); - // Attribution control: the same document WITHOUT the key is accepted, so - // the refusal above is attributable to the retired key and nothing else. - expect(site.schema.safeParse(site.wellFormed).success, `${site.qualified}: well-formed control must parse`).toBe(true); - }); - } - - for (const carrier of CARRIERS) { - it(`REJECTS \`${carrier.qualified}\` through its carrier, at path \`${carrier.issuePath.join('.')}\``, () => { - expectTombstoneRefusal(carrier); - expect(carrier.schema.safeParse(carrier.wellFormed).success).toBe(true); - }); - } - - it('the `retentionDays` prescription names the live retention mechanism, so nobody re-declares a number here as a repair', () => { - const result = IncidentResponsePolicySchema.safeParse({ ...POLICY, retentionDays: 3650 }); - expect(result.success).toBe(false); - if (result.success) return; - const issue = result.error.issues.find((i) => i.path[0] === 'retentionDays'); - expect(issue!.message).toMatch(/object-level `lifecycle` block \(ADR-0057\)/); - expect(issue!.message).toMatch(/LifecycleService/); - }); - - it('every prescription with a former default names the default it used to materialize', () => { - const expected: Array<[string, RegExp]> = [ - ['IncidentNotificationMatrix.escalationTimeoutMinutes', /default of 30 minutes/], - ['IncidentResponsePolicy.triageDeadlineHours', /default of 1 hour/], - ['IncidentResponsePolicy.retentionDays', /default of 2555 days/], - ['TrainingPlan.recertificationIntervalDays', /default of 365 days/], - ['TrainingPlan.gracePeriodDays', /default of 30 days/], - ['TrainingPlan.reminderDaysBefore', /default of 14 days/], - ]; - for (const [qualified, pattern] of expected) { - const site = SITES.find((s) => s.qualified === qualified)!; - const result = site.schema.safeParse(site.authored); - expect(result.success).toBe(false); - if (result.success) continue; - const issue = result.error.issues.find((i) => i.path.join('.') === site.issuePath.join('.'))!; - expect(issue.message, qualified).toMatch(pattern); - } - }); -}); - -describe('[#14477] no-materialize: parsed documents carry none of the keys and none of the six former defaults', () => { - it('incident-response', () => { - const rule = IncidentNotificationRuleSchema.parse(RULE); - expect(rule.notifyRegulators).toBe(false); // control: a live default still applies - expect(rule).not.toHaveProperty('withinMinutes'); - expect(rule).not.toHaveProperty('regulatorDeadlineHours'); - - const matrix = IncidentNotificationMatrixSchema.parse(MATRIX); - expect(matrix.escalationChain).toEqual([]); // control - // Used to materialize `30` into every parsed matrix. - expect(matrix).not.toHaveProperty('escalationTimeoutMinutes'); - - const policy = IncidentResponsePolicySchema.parse(POLICY); - expect(policy.enabled).toBe(true); // control - expect(policy.requirePostIncidentReview).toBe(true); // control - // Used to materialize `1` and `2555`. - expect(policy).not.toHaveProperty('triageDeadlineHours'); - expect(policy).not.toHaveProperty('retentionDays'); - - const phase = IncidentResponsePhaseSchema.parse(PHASE); - expect(phase.phase).toBe('containment'); - expect(phase).not.toHaveProperty('targetHours'); - }); - - it('training', () => { - const course = TrainingCourseSchema.parse(COURSE); - expect(course.mandatory).toBe(false); // control - expect(course).not.toHaveProperty('durationMinutes'); - expect(course).not.toHaveProperty('validityDays'); - - const plan = TrainingPlanSchema.parse(PLAN); - expect(plan.enabled).toBe(true); // control - expect(plan.sendReminders).toBe(true); // control - // Used to materialize `365`, `30` and `14`. - expect(plan).not.toHaveProperty('recertificationIntervalDays'); - expect(plan).not.toHaveProperty('gracePeriodDays'); - expect(plan).not.toHaveProperty('reminderDaysBefore'); - }); - - it('change-management', () => { - const impact = ChangeImpactSchema.parse(IMPACT); - expect(impact.downtime?.required).toBe(true); // control - expect(impact.downtime).not.toHaveProperty('durationMinutes'); - - const rollback = RollbackPlanSchema.parse(ROLLBACK); - expect(rollback.steps[0]?.order).toBe(1); // control - expect(rollback.steps[0]).not.toHaveProperty('estimatedMinutes'); - - const change = ChangeRequestSchema.parse(CHANGE); - expect(change.implementation.steps[0]?.description).toBe(STEP.description); // control - expect(change.implementation.steps[0]).not.toHaveProperty('estimatedMinutes'); - expect(change.rollbackPlan.steps[0]).not.toHaveProperty('estimatedMinutes'); - expect(change.impact.downtime).not.toHaveProperty('durationMinutes'); - }); -}); - -describe('[#14477] the tsc channel: the input type of every retired key is `never`', () => { - it('fails tsc at every authoring site', () => { - const phase: IncidentResponsePhase = { - ...PHASE, - // @ts-expect-error — `targetHours` is a retiredKey() tombstone: its input type is `never`. - targetHours: 2, - }; - const rule: IncidentNotificationRule = { - ...RULE, - // @ts-expect-error — `withinMinutes` is a retiredKey() tombstone. - withinMinutes: 15, - // @ts-expect-error — `regulatorDeadlineHours` is a retiredKey() tombstone. - regulatorDeadlineHours: 72, - }; - const policy: IncidentResponsePolicy = { - ...POLICY, - notificationMatrix: { - ...MATRIX, - // @ts-expect-error — `escalationTimeoutMinutes` is a retiredKey() tombstone. - escalationTimeoutMinutes: 30, - }, - // @ts-expect-error — `triageDeadlineHours` is a retiredKey() tombstone. - triageDeadlineHours: 1, - // @ts-expect-error — `retentionDays` is a retiredKey() tombstone. - retentionDays: 2555, - }; - const course: TrainingCourse = { - ...COURSE, - // @ts-expect-error — `durationMinutes` is a retiredKey() tombstone. - durationMinutes: 60, - // @ts-expect-error — `validityDays` is a retiredKey() tombstone. - validityDays: 365, - }; - const plan: TrainingPlan = { - ...PLAN, - // @ts-expect-error — `recertificationIntervalDays` is a retiredKey() tombstone. - recertificationIntervalDays: 365, - // @ts-expect-error — `gracePeriodDays` is a retiredKey() tombstone. - gracePeriodDays: 30, - // @ts-expect-error — `reminderDaysBefore` is a retiredKey() tombstone. - reminderDaysBefore: 14, - }; - const impact: ChangeImpact = { - ...IMPACT, - downtime: { - required: true, - // @ts-expect-error — the nested `downtime.durationMinutes` is a retiredKey() tombstone. - durationMinutes: 30, - }, - }; - const rollback: RollbackPlan = { - ...ROLLBACK, - steps: [{ - ...STEP, - // @ts-expect-error — `steps[].estimatedMinutes` is a retiredKey() tombstone. - estimatedMinutes: 15, - }], - }; - const change: ChangeRequest = { - ...CHANGE, - implementation: { - description: 'Execute the migration scripts', - steps: [{ - ...STEP, - // @ts-expect-error — `implementation.steps[].estimatedMinutes` is a retiredKey() tombstone. - estimatedMinutes: 10, - }], - }, - }; - // The parse channel agrees with the type channel on the same literals. - for (const [schema, value] of [ - [IncidentResponsePhaseSchema, phase], - [IncidentNotificationRuleSchema, rule], - [IncidentResponsePolicySchema, policy], - [TrainingCourseSchema, course], - [TrainingPlanSchema, plan], - [ChangeImpactSchema, impact], - [RollbackPlanSchema, rollback], - [ChangeRequestSchema, change], - ] as Array<[ZodTypeAny, unknown]>) { - expect(schema.safeParse(value).success).toBe(false); - } - }); -}); - -describe('[#14477] ADR-0087 registration', () => { - it('declares all fourteen sites under major 18, with the three D3 semantic entries wired and no D2 conversion', () => { - for (const site of SITES) { - expect(RETIRED_KEYS_BY_MAJOR[18], `${site.registered} must be declared`).toContain(site.registered); - } - const step = MIGRATIONS_BY_MAJOR[18]; - expect(step).toBeDefined(); - const ids = step!.semantic.map((s) => s.id); - for (const id of SEMANTIC_IDS) { - expect(ids, `${id} must be wired into the step-18 chain`).toContain(id); - const entry = step!.semantic.find((s) => s.id === id)!; - expect(entry.reason.length).toBeGreaterThan(0); - expect(entry.acceptanceCriteria.length).toBeGreaterThan(0); - // The route is stated where the next reader looks: why D3 semantic and - // not D2 — no stack seam (the additionalTypes precedent). - expect(entry.reason).toMatch(/not a D2 conversion/); - } - // Deliberately no mechanical conversion: a transform over a stack that - // never carries these documents would be a seam that never runs. - expect(step!.conversionIds.filter((id) => /incident|training|change-management|deadline/.test(id))).toEqual([]); - }); -}); - -// What this leg guarantees, and what it does not. The walk below reads the -// whole repo tree from REPO_ROOT, but the inputs it reaches outside -// `@objectstack/spec`'s declared cross-package globs in `turbo.json` are not -// hashed by turbo. So a resurrection authored in `examples/**`, `apps/**`, -// hand-written `content/docs/**` or most `packages/*/src/**` does not put this -// suite into `turbo ls --affected`, and the PR that authors it can replay a -// cached green. Read this leg as a FULL-RUN guarantee — the merge queue and a -// plain `pnpm test` — rather than an affected-path one. -// -// `check:cross-package-test-inputs` is green here as designed: every literal -// path this file names is declared, and the gate's declaration file states the -// trade for a walk that descends on a loop variable (the escape verdict -// resolves and the name does not). Widening spec's declared inputs to the -// walk's real radius would put this suite on every docs PR, so nothing is -// widened here; the playbook-vs-gate question is filed as #15528 and is not -// this PR's to answer. -// -// Independently of turbo, every *typed* TypeScript resurrection is caught at -// author time by the `never` channel pinned above, which needs no tree walk. -// The residue this leg covers is the untyped rest: JSON, YAML, MDX and -// unannotated literals. -describe('[#14477] tree-scoped absence: nothing in the repo authors a retired key any more', () => { - const REPO_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../../..'); - const THIS_DIR = path.dirname(fileURLToPath(import.meta.url)); - - /** Names unique to the three families — an authored occurrence anywhere is a resurrection. */ - const UNIQUE_KEYS = [ - 'targetHours', - 'withinMinutes', - 'regulatorDeadlineHours', - 'escalationTimeoutMinutes', - 'triageDeadlineHours', - 'validityDays', - 'recertificationIntervalDays', - 'gracePeriodDays', - 'reminderDaysBefore', - ]; - /** - * Names other, LIVE schemas also declare (`AuditPolicy.retentionDays`, - * `TenantBackup.retentionDays`, …): judged only in a file that also names one - * of the three families, so a live key on an unrelated schema is never - * misread as a resurrection. The bound is deliberate and stated: a document - * authored for these families in a file that names none of them is - * invisible to this leg — no such file exists, because none of the families - * is a metadata type with a file-based authoring path. - */ - const SHARED_KEYS = ['retentionDays', 'durationMinutes', 'estimatedMinutes']; - const FAMILY = /\b(IncidentResponsePhase|IncidentNotificationRule|IncidentNotificationMatrix|IncidentResponsePolicy|IncidentSchema|TrainingCourse|TrainingPlan|ChangeImpact|RollbackPlan|ChangeRequest)\b/; - /** An AUTHORING (`key:` / `"key":` / `key?:`), never a prose mention. */ - const authoring = (keys: readonly string[]) => new RegExp(`["']?\\b(${keys.join('|')})\\b["']?\\s*\\??\\s*:`); - - const SCANNED_EXT = new Set(['.ts', '.tsx', '.js', '.mjs', '.cjs', '.mts', '.cts', '.json', '.md', '.mdx', '.yaml', '.yml']); - /** Build, SCM and cache state — not authored sources. */ - const SKIPPED_DIRS = new Set(['node_modules', 'dist', '.git', '.turbo', '.cache', '.objectstack', 'coverage']); - /** Structural exclusions, each with its reason. NOT an allowlist file: these are the retirement kit and its projections. */ - const EXCLUDED = [ - // The tombstones declare the key they refuse. - 'packages/spec/src/system/incident-response.zod.ts', - 'packages/spec/src/system/training.zod.ts', - 'packages/spec/src/system/change-management.zod.ts', - // Refusal fixtures author the key to prove the refusal — this pin included. - 'packages/spec/src/system/incident-response.test.ts', - 'packages/spec/src/system/training.test.ts', - 'packages/spec/src/system/change-management.test.ts', - path.relative(REPO_ROOT, fileURLToPath(import.meta.url)), - ]; - const EXCLUDED_PREFIXES = [ - // Registers the retirement by key (entries + the generated registry). - 'packages/spec/src/migrations/', - // Generated projections of the tombstone: the `[RETIRED]` / `[REMOVED]` rows. - 'packages/spec/authorable-surface/', - 'packages/spec/json-schema/', - 'packages/spec/json-schema.manifest/', - 'packages/spec/spec-changes.json', - 'docs/protocol-upgrade-guide.md', - 'content/docs/references/', - // Release prose records the removal (release-owned; never edited by a code PR). - 'content/docs/releases/', - '.changeset/', - ]; - - it('the matcher recognises the authored shape (anti-vacuity, on a file the scan deliberately excludes)', () => { - const tombstoned = fs.readFileSync(path.join(THIS_DIR, 'incident-response.zod.ts'), 'utf-8'); - expect(authoring(UNIQUE_KEYS).test(tombstoned)).toBe(true); - expect(FAMILY.test(tombstoned) && authoring(SHARED_KEYS).test(tombstoned)).toBe(true); - // And a bare prose mention is NOT an authoring. - expect(authoring(UNIQUE_KEYS).test('the `targetHours` key was removed')).toBe(false); - expect(authoring(UNIQUE_KEYS).test('`IncidentResponsePhase.targetHours` was removed')).toBe(false); - }); - - it('no authored occurrence survives outside the retirement kit and its generated projections', () => { - const offenders: string[] = []; - let visited = 0; - const unique = authoring(UNIQUE_KEYS); - const shared = authoring(SHARED_KEYS); - const walk = (dir: string) => { - for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { - const full = path.join(dir, entry.name); - const rel = path.relative(REPO_ROOT, full).split(path.sep).join('/'); - if (entry.isDirectory()) { - if (SKIPPED_DIRS.has(entry.name)) continue; - // Other dot-directories are generated caches (`.source`, `.next`, …); - // the two hand-authored ones are scanned. - if (entry.name.startsWith('.') && entry.name !== '.claude' && entry.name !== '.github') continue; - walk(full); - continue; - } - if (!entry.isFile()) continue; - if (!SCANNED_EXT.has(path.extname(entry.name))) continue; - if (entry.name === 'CHANGELOG.md') continue; // release prose records the removal - if (EXCLUDED.includes(rel) || EXCLUDED_PREFIXES.some((p) => rel.startsWith(p))) continue; - visited += 1; - const text = fs.readFileSync(full, 'utf-8'); - const u = unique.exec(text); - if (u) offenders.push(`${rel} authors \`${u[1]}\``); - if (FAMILY.test(text)) { - const s = shared.exec(text); - if (s) offenders.push(`${rel} names a family and authors \`${s[1]}\``); - } - } - }; - walk(REPO_ROOT); - // Anti-vacuity: the walk really covered the tree. - expect(visited).toBeGreaterThan(1000); - expect(offenders, 'an authored retired key means the retirement is being undone — re-read #14477').toEqual([]); - }); -}); diff --git a/packages/spec/src/system/incident-response.test.ts b/packages/spec/src/system/incident-response.test.ts deleted file mode 100644 index fa03608740..0000000000 --- a/packages/spec/src/system/incident-response.test.ts +++ /dev/null @@ -1,391 +0,0 @@ -import { describe, it, expect } from 'vitest'; -import { - IncidentSeveritySchema, - IncidentCategorySchema, - IncidentStatusSchema, - IncidentResponsePhaseSchema, - IncidentNotificationRuleSchema, - IncidentNotificationMatrixSchema, - IncidentSchema, - IncidentResponsePolicySchema, - type Incident, - type IncidentResponsePhase, - type IncidentNotificationRule, -} from './incident-response.zod'; - -describe('IncidentSeveritySchema', () => { - it('should accept all valid severity levels', () => { - const validLevels = ['critical', 'high', 'medium', 'low']; - - validLevels.forEach((level) => { - expect(() => IncidentSeveritySchema.parse(level)).not.toThrow(); - }); - }); - - it('should reject invalid severity level', () => { - expect(() => IncidentSeveritySchema.parse('extreme')).toThrow(); - }); -}); - -describe('IncidentCategorySchema', () => { - it('should accept all valid categories', () => { - const validCategories = [ - 'data_breach', 'malware', 'unauthorized_access', 'denial_of_service', - 'social_engineering', 'insider_threat', 'physical_security', - 'configuration_error', 'vulnerability_exploit', 'policy_violation', 'other', - ]; - - validCategories.forEach((category) => { - expect(() => IncidentCategorySchema.parse(category)).not.toThrow(); - }); - }); - - it('should reject invalid category', () => { - expect(() => IncidentCategorySchema.parse('unknown_type')).toThrow(); - }); -}); - -describe('IncidentStatusSchema', () => { - it('should accept all valid statuses', () => { - const validStatuses = [ - 'reported', 'triaged', 'investigating', 'containing', - 'eradicating', 'recovering', 'resolved', 'closed', - ]; - - validStatuses.forEach((status) => { - expect(() => IncidentStatusSchema.parse(status)).not.toThrow(); - }); - }); - - it('should reject invalid status', () => { - expect(() => IncidentStatusSchema.parse('pending')).toThrow(); - }); -}); - -describe('IncidentResponsePhaseSchema', () => { - it('should accept valid response phase', () => { - const phase: IncidentResponsePhase = { - phase: 'containment', - description: 'Isolate affected systems', - assignedTo: 'security_team', - }; - - expect(() => IncidentResponsePhaseSchema.parse(phase)).not.toThrow(); - }); - - it('should accept all phase types', () => { - const phases = ['identification', 'containment', 'eradication', 'recovery', 'lessons_learned']; - - phases.forEach((phase) => { - expect(() => IncidentResponsePhaseSchema.parse({ - phase, - description: `${phase} phase`, - assignedTo: 'team', - })).not.toThrow(); - }); - }); - - it('should accept optional fields', () => { - const phase = IncidentResponsePhaseSchema.parse({ - phase: 'recovery', - description: 'Restore services', - assignedTo: 'ops_team', - completedAt: 1704067200000, - notes: 'All systems restored successfully', - }); - - expect(phase.completedAt).toBe(1704067200000); - expect(phase.notes).toBe('All systems restored successfully'); - }); - - it('REFUSES an authored `targetHours` — a retiredKey() tombstone since #14477 (ADR-0049)', () => { - // The full refusal envelope (path, code, prescription) is pinned in - // `deadline-keys-retirement.test.ts`; this keeps the family suite honest - // about the shape it parses: any value, not only a negative one, is refused. - expect(() => IncidentResponsePhaseSchema.parse({ - phase: 'identification', - description: 'Identify', - assignedTo: 'team', - targetHours: 2, - })).toThrow(/`IncidentResponsePhase\.targetHours` was removed/s); - }); -}); - -describe('IncidentNotificationRuleSchema', () => { - it('should accept valid notification rule', () => { - const rule: IncidentNotificationRule = { - severity: 'critical', - channels: ['email', 'pagerduty'], - recipients: ['ciso', 'security_team'], - notifyRegulators: true, - }; - - expect(() => IncidentNotificationRuleSchema.parse(rule)).not.toThrow(); - }); - - it('should apply defaults', () => { - const rule = IncidentNotificationRuleSchema.parse({ - severity: 'low', - channels: ['email'], - recipients: ['security_team'], - }); - - expect(rule.notifyRegulators).toBe(false); - }); - - it('should accept all channel types', () => { - const channels = ['email', 'sms', 'slack', 'pagerduty', 'webhook']; - - expect(() => IncidentNotificationRuleSchema.parse({ - severity: 'high', - channels, - recipients: ['all'], - })).not.toThrow(); - }); - - it('should reject invalid channel', () => { - expect(() => IncidentNotificationRuleSchema.parse({ - severity: 'high', - channels: ['carrier_pigeon'], - recipients: ['team'], - })).toThrow(); - }); -}); - -describe('IncidentNotificationMatrixSchema', () => { - it('should accept valid notification matrix with defaults', () => { - const matrix = IncidentNotificationMatrixSchema.parse({ - rules: [ - { - severity: 'critical', - channels: ['pagerduty', 'sms'], - recipients: ['ciso', 'security_team'], - }, - ], - }); - - expect(matrix).not.toHaveProperty('escalationTimeoutMinutes'); - expect(matrix.escalationChain).toEqual([]); - expect(matrix.rules).toHaveLength(1); - }); - - it('should accept full matrix configuration', () => { - const matrix = IncidentNotificationMatrixSchema.parse({ - rules: [ - { - severity: 'critical', - channels: ['pagerduty', 'sms', 'email'], - recipients: ['ciso', 'executive_team'], - notifyRegulators: true, - }, - { - severity: 'high', - channels: ['slack', 'email'], - recipients: ['security_team'], - }, - { - severity: 'low', - channels: ['email'], - recipients: ['security_team'], - }, - ], - escalationChain: ['security_lead', 'ciso', 'ceo'], - }); - - expect(matrix.rules).toHaveLength(3); - expect(matrix).not.toHaveProperty('escalationTimeoutMinutes'); - expect(matrix.escalationChain).toHaveLength(3); - }); -}); - -describe('IncidentSchema', () => { - it('should accept complete incident', () => { - const incident: Incident = { - id: 'INC-2024-001', - title: 'Unauthorized API Access Detected', - description: 'Multiple failed authentication attempts from unknown IP range', - severity: 'high', - category: 'unauthorized_access', - status: 'investigating', - reportedBy: 'monitoring_system', - reportedAt: 1704067200000, - detectedAt: 1704067100000, - affectedSystems: ['api-gateway', 'auth-service'], - affectedDataClassifications: ['pii', 'confidential'], - responsePhases: [ - { - phase: 'identification', - description: 'Identify scope of unauthorized access', - assignedTo: 'security_team', - }, - { - phase: 'containment', - description: 'Block suspicious IP range', - assignedTo: 'network_team', - }, - ], - rootCause: 'Compromised API key', - correctiveActions: ['Rotate all API keys', 'Implement IP allowlisting'], - lessonsLearned: 'Need to implement API key rotation policy', - relatedChangeRequestIds: ['CHG-2024-001'], - metadata: { sourceIp: '10.0.0.1' }, - }; - - expect(() => IncidentSchema.parse(incident)).not.toThrow(); - }); - - it('should accept minimal incident', () => { - const minimal = { - id: 'INC-2024-002', - title: 'Policy Violation', - description: 'Employee accessed restricted data', - severity: 'low', - category: 'policy_violation', - status: 'reported', - reportedBy: 'user_123', - reportedAt: Date.now(), - affectedSystems: ['hr-system'], - }; - - expect(() => IncidentSchema.parse(minimal)).not.toThrow(); - }); - - it('should accept resolved incident with full lifecycle', () => { - const resolved = { - id: 'INC-2024-003', - title: 'Malware Detection', - description: 'Ransomware detected on workstation', - severity: 'critical', - category: 'malware', - status: 'closed', - reportedBy: 'endpoint_detection', - reportedAt: 1704067200000, - detectedAt: 1704067100000, - resolvedAt: 1704153600000, - affectedSystems: ['workstation-42'], - responsePhases: [ - { - phase: 'identification', - description: 'Identify malware type', - assignedTo: 'security_team', - completedAt: 1704070800000, - notes: 'Identified as known ransomware variant', - }, - { - phase: 'containment', - description: 'Isolate affected workstation', - assignedTo: 'it_support', - completedAt: 1704072600000, - }, - { - phase: 'eradication', - description: 'Remove malware and reimage', - assignedTo: 'it_support', - completedAt: 1704086400000, - }, - { - phase: 'recovery', - description: 'Restore from backup', - assignedTo: 'it_support', - completedAt: 1704115200000, - }, - { - phase: 'lessons_learned', - description: 'Post-incident review', - assignedTo: 'security_team', - completedAt: 1704153600000, - }, - ], - rootCause: 'Phishing email with malicious attachment', - correctiveActions: [ - 'Block malicious email domain', - 'Update email filtering rules', - 'Deploy additional endpoint protection', - ], - lessonsLearned: 'Need enhanced phishing detection and user training', - }; - - expect(() => IncidentSchema.parse(resolved)).not.toThrow(); - }); - - it('should accept all data classification values', () => { - const classifications = ['pii', 'phi', 'pci', 'financial', 'confidential', 'internal', 'public']; - - const incident = { - id: 'INC-2024-004', - title: 'Data Breach', - description: 'Comprehensive data breach', - severity: 'critical', - category: 'data_breach', - status: 'reported', - reportedBy: 'system', - reportedAt: Date.now(), - affectedSystems: ['database'], - affectedDataClassifications: classifications, - }; - - expect(() => IncidentSchema.parse(incident)).not.toThrow(); - }); - - it('should reject missing required fields', () => { - expect(() => IncidentSchema.parse({})).toThrow(); - expect(() => IncidentSchema.parse({ id: 'INC-001' })).toThrow(); - }); -}); - -describe('IncidentResponsePolicySchema', () => { - it('should accept valid policy with defaults', () => { - const policy = IncidentResponsePolicySchema.parse({ - notificationMatrix: { - rules: [ - { - severity: 'critical', - channels: ['pagerduty'], - recipients: ['security_team'], - }, - ], - }, - defaultResponseTeam: 'security_team', - }); - - expect(policy.enabled).toBe(true); - expect(policy).not.toHaveProperty('triageDeadlineHours'); - expect(policy.requirePostIncidentReview).toBe(true); - expect(policy.regulatoryNotificationThreshold).toBe('high'); - expect(policy).not.toHaveProperty('retentionDays'); - }); - - it('should accept full policy configuration', () => { - const policy = IncidentResponsePolicySchema.parse({ - enabled: true, - notificationMatrix: { - rules: [ - { - severity: 'critical', - channels: ['pagerduty', 'sms', 'email'], - recipients: ['ciso', 'executive_team'], - notifyRegulators: true, - }, - { - severity: 'high', - channels: ['slack', 'email'], - recipients: ['security_team'], - }, - ], - escalationChain: ['security_lead', 'ciso'], - }, - defaultResponseTeam: 'incident_response_team', - requirePostIncidentReview: true, - regulatoryNotificationThreshold: 'critical', - }); - - expect(policy).not.toHaveProperty('triageDeadlineHours'); - expect(policy.regulatoryNotificationThreshold).toBe('critical'); - expect(policy).not.toHaveProperty('retentionDays'); - }); - - it('should reject missing required fields', () => { - expect(() => IncidentResponsePolicySchema.parse({})).toThrow(); - expect(() => IncidentResponsePolicySchema.parse({ enabled: true })).toThrow(); - }); -}); diff --git a/packages/spec/src/system/incident-response.zod.ts b/packages/spec/src/system/incident-response.zod.ts deleted file mode 100644 index 40a929a2af..0000000000 --- a/packages/spec/src/system/incident-response.zod.ts +++ /dev/null @@ -1,439 +0,0 @@ -// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. - -import { z } from 'zod'; -import { DataClassificationSchema } from './security-context.zod'; -import { retiredKey } from '../shared/retired-key'; - -/** - * Incident Response Protocol — ISO 27001:2022 (A.5.24–A.5.28) - * - * Defines schemas for information security event management including - * incident classification, severity grading, response procedures, - * and notification matrices. - * - * @see https://www.iso.org/standard/27001 - * @category Security - */ - -/** - * Incident Severity Schema - * - * Severity grading for security incidents following ISO 27001 guidelines. - * Determines response urgency and escalation requirements. - */ -import { lazySchema } from '../shared/lazy-schema'; -export const IncidentSeveritySchema = lazySchema(() => z.enum([ - 'critical', // Immediate threat to business operations or data integrity - 'high', // Significant impact requiring urgent response - 'medium', // Moderate impact with controlled response timeline - 'low', // Minor impact with standard response procedures -])); - -// ─── RETIRED deadline keys (ADR-0049 enforce-or-remove) ───────────────────── -// -// Six hour/minute/day-shaped deadline and SLA keys were declared on the -// incident-response schemas and read by NOTHING: no scheduler, escalation -// engine, regulator notifier, SLA clock or retention sweeper exists on the -// platform for this family — the schemas are exported, mounted by no stack -// key and registered as no metadata type, and the reader census over every -// package outside `packages/spec` (and over objectui at the pinned sha) -// returned zero hits for every key. An author could write -// `triageDeadlineHours: 4` and the platform would never act on it; the -// generated reference docs advertised a deadline nothing kept. Maintainer -// ruling 2026-09-02 (recorded on #14477): retire the family under -// enforce-or-remove. -// -// Route: `retiredKey()` tombstones, NOT plain deletion — none of these -// schemas is `.strict()`, so a bare deletion would make zod strip the key in -// silence, replacing an inert declaration with an invisible one (ADR-0104). -// The tombstone is audible in both channels: `tsc` (the input type is -// `never`) and the parse (the prescription is the message). No D2 conversion -// and no `os migrate meta` sentence: the conversion chain walks a normalized -// STACK and none of these schemas is a stack collection member, so a -// conversion would be a transform with no seam that ever runs (the -// `kernel/MetadataPluginConfig:additionalTypes` precedent). The retirement is -// registered as `RETIRED_KEYS_BY_MAJOR[18]` entries plus the D3 semantic entry -// `incident-response-deadline-keys-retired`. - -const TARGET_HOURS_RETIRED = - '`IncidentResponsePhase.targetHours` was removed in @objectstack/spec 17 (ADR-0049 ' - + 'enforce-or-remove) — nothing ever read it: no engine tracked a response phase against a ' - + 'clock, so the target was never checked, never escalated and never reported. Delete the ' - + 'key. There is no replacement, because no incident-response engine exists to keep a phase ' - + 'deadline.'; - -const WITHIN_MINUTES_RETIRED = - '`IncidentNotificationRule.withinMinutes` was removed in @objectstack/spec 17 (ADR-0049 ' - + 'enforce-or-remove) — nothing ever read it: no dispatcher sent an incident notification, ' - + 'so no deadline for one was ever measured. Delete the key. There is no replacement, ' - + 'because no incident-notification engine exists to keep the deadline.'; - -const REGULATOR_DEADLINE_HOURS_RETIRED = - '`IncidentNotificationRule.regulatorDeadlineHours` was removed in @objectstack/spec 17 ' - + '(ADR-0049 enforce-or-remove) — nothing ever read it: no engine notified a regulator, so a ' - + 'regulatory deadline declared here (a GDPR 72-hour window, for example) was never tracked, ' - + 'and a compliance author who wrote it held a promise the platform did not keep. Delete the ' - + 'key. There is no replacement, because no regulatory-notification engine exists.'; - -const ESCALATION_TIMEOUT_MINUTES_RETIRED = - '`IncidentNotificationMatrix.escalationTimeoutMinutes` was removed in @objectstack/spec 17 ' - + '(ADR-0049 enforce-or-remove) — nothing ever read it: no engine walked `escalationChain` ' - + 'on a timer, so the timeout never fired, and its default of 30 minutes was materialized ' - + 'into every parsed matrix without ever being consulted. Delete the key. There is no ' - + 'replacement, because no escalation engine exists.'; - -const TRIAGE_DEADLINE_HOURS_RETIRED = - '`IncidentResponsePolicy.triageDeadlineHours` was removed in @objectstack/spec 17 (ADR-0049 ' - + 'enforce-or-remove) — nothing ever read it: no engine timed the interval between detection ' - + 'and triage, so the deadline was never kept, and its default of 1 hour was materialized ' - + 'into every parsed policy without ever being consulted. Delete the key. There is no ' - + 'replacement, because no incident-response engine exists to keep a triage window.'; - -const RETENTION_DAYS_RETIRED = - '`IncidentResponsePolicy.retentionDays` was removed in @objectstack/spec 17 (ADR-0049 ' - + 'enforce-or-remove) — nothing ever read it: no sweeper deleted incident records on a ' - + 'schedule, so the retention period was never applied, and its default of 2555 days was ' - + 'materialized into every parsed policy without ever being consulted. Delete the key. ' - + 'Retention on this platform is the object-level `lifecycle` block (ADR-0057), enforced by ' - + 'the LifecycleService over the records of an object — declare it on the object that stores ' - + 'incident records, not on this policy document.'; - -/** - * Incident Category Schema - * - * Classification of security incidents by type (A.5.25). - * Used for routing, reporting, and trend analysis. - */ -export const IncidentCategorySchema = lazySchema(() => z.enum([ - 'data_breach', // Unauthorized access or disclosure of data - 'malware', // Malicious software detection - 'unauthorized_access', // Unauthorized system or data access - 'denial_of_service', // Service availability attack - 'social_engineering', // Phishing, pretexting, or manipulation - 'insider_threat', // Threat originating from internal actors - 'physical_security', // Physical security breach - 'configuration_error', // Security misconfiguration - 'vulnerability_exploit', // Exploitation of known vulnerability - 'policy_violation', // Violation of security policies - 'other', // Other security incidents -])); - -/** - * Incident Status Schema - * - * Current status of a security incident in its lifecycle. - */ -export const IncidentStatusSchema = lazySchema(() => z.enum([ - 'reported', // Initial report received - 'triaged', // Severity and category assessed - 'investigating', // Active investigation in progress - 'containing', // Containment measures being applied - 'eradicating', // Root cause being removed - 'recovering', // Systems being restored to normal - 'resolved', // Incident resolved - 'closed', // Post-incident review complete -])); - -/** - * Incident Response Phase Schema - * - * Defines structured response phases per NIST SP 800-61 / ISO 27001 (A.5.26). - */ -export const IncidentResponsePhaseSchema = lazySchema(() => z.object({ - /** - * Phase name identifier - */ - phase: z.enum([ - 'identification', - 'containment', - 'eradication', - 'recovery', - 'lessons_learned', - ]).describe('Response phase name'), - - /** - * Phase description and objectives - */ - description: z.string().describe('Phase description and objectives'), - - /** - * Responsible team or role for this phase - */ - assignedTo: z.string().describe('Responsible team or role'), - - /** - * REMOVED (ADR-0049 enforce-or-remove) — see `TARGET_HOURS_RETIRED` above. - */ - targetHours: retiredKey(TARGET_HOURS_RETIRED), - - /** - * Actual completion timestamp (Unix milliseconds) - */ - completedAt: z.number().optional().describe('Actual completion timestamp'), - - /** - * Notes and findings during this phase - */ - notes: z.string().optional().describe('Phase notes and findings'), -}).describe('Incident response phase with timing and assignment')); - -export type IncidentResponsePhase = z.input; - -/** - * Notification Rule Schema - * - * Defines who must be notified and when, based on severity (A.5.27). - */ -export const IncidentNotificationRuleSchema = lazySchema(() => z.object({ - /** - * Minimum severity level that triggers this notification - */ - severity: IncidentSeveritySchema.describe('Minimum severity to trigger notification'), - - /** - * Notification channels to use - */ - channels: z.array(z.enum([ - 'email', - 'sms', - 'slack', - 'pagerduty', - 'webhook', - ])).describe('Notification channels'), - - /** - * Roles or teams to notify - */ - recipients: z.array(z.string()).describe('Roles or teams to notify'), - - /** - * REMOVED (ADR-0049 enforce-or-remove) — see `WITHIN_MINUTES_RETIRED` above. - */ - withinMinutes: retiredKey(WITHIN_MINUTES_RETIRED), - - /** - * Whether to notify external regulators (for data breaches) - */ - notifyRegulators: z.boolean().default(false) - .describe('Whether to notify regulatory authorities'), - - /** - * REMOVED (ADR-0049 enforce-or-remove) — see `REGULATOR_DEADLINE_HOURS_RETIRED` above. - */ - regulatorDeadlineHours: retiredKey(REGULATOR_DEADLINE_HOURS_RETIRED), -}).describe('Incident notification rule per severity level')); - -export type IncidentNotificationRule = z.input; -/** Post-parse shape of {@link IncidentNotificationRule} — defaults applied, transforms run (ADR-0122). */ -export type IncidentNotificationRuleParsed = z.infer; - -/** - * Notification Matrix Schema - * - * Complete notification matrix mapping severity levels to stakeholder groups (A.5.27). - */ -export const IncidentNotificationMatrixSchema = lazySchema(() => z.object({ - /** - * Notification rules ordered by severity - */ - rules: z.array(IncidentNotificationRuleSchema) - .describe('Notification rules by severity level'), - - /** - * REMOVED (ADR-0049 enforce-or-remove) — see `ESCALATION_TIMEOUT_MINUTES_RETIRED` above. - */ - escalationTimeoutMinutes: retiredKey(ESCALATION_TIMEOUT_MINUTES_RETIRED), - - /** - * Escalation chain: ordered list of roles to escalate to - */ - escalationChain: z.array(z.string()).default([]) - .describe('Ordered escalation chain of roles'), -}).describe('Incident notification matrix with escalation policies')); - -export type IncidentNotificationMatrix = z.input; -/** Post-parse shape of {@link IncidentNotificationMatrix} — defaults applied, transforms run (ADR-0122). */ -export type IncidentNotificationMatrixParsed = z.infer; - -/** - * Incident Schema - * - * Comprehensive security incident record following ISO 27001:2022 (A.5.24–A.5.28). - * Tracks the full incident lifecycle from detection through post-incident review. - * - * @example - * ```json - * { - * "id": "INC-2024-001", - * "title": "Unauthorized API Access Detected", - * "description": "Multiple failed authentication attempts from unknown IP range", - * "severity": "high", - * "category": "unauthorized_access", - * "status": "investigating", - * "reportedBy": "monitoring_system", - * "reportedAt": 1704067200000, - * "affectedSystems": ["api-gateway", "auth-service"], - * "affectedDataClassifications": ["pii", "confidential"], - * "responsePhases": [ - * { - * "phase": "identification", - * "description": "Identify scope of unauthorized access", - * "assignedTo": "security_team" - * } - * ] - * } - * ``` - */ -export const IncidentSchema = lazySchema(() => z.object({ - /** - * Unique incident identifier - */ - id: z.string().describe('Unique incident identifier'), - - /** - * Short descriptive title of the incident - */ - title: z.string().describe('Incident title'), - - /** - * Detailed description of the security event - */ - description: z.string().describe('Detailed incident description'), - - /** - * Severity classification - */ - severity: IncidentSeveritySchema.describe('Incident severity level'), - - /** - * Incident category / type - */ - category: IncidentCategorySchema.describe('Incident category'), - - /** - * Current status in the incident lifecycle - */ - status: IncidentStatusSchema.describe('Current incident status'), - - /** - * User or system that reported the incident - */ - reportedBy: z.string().describe('Reporter user ID or system name'), - - /** - * Timestamp when the incident was reported (Unix milliseconds) - */ - reportedAt: z.number().describe('Report timestamp'), - - /** - * Timestamp when the incident was detected (may differ from reported) - */ - detectedAt: z.number().optional().describe('Detection timestamp'), - - /** - * Timestamp when the incident was resolved - */ - resolvedAt: z.number().optional().describe('Resolution timestamp'), - - /** - * Systems affected by the incident - */ - affectedSystems: z.array(z.string()).describe('Affected systems'), - - /** - * Data classifications affected (for data breach assessment) - */ - affectedDataClassifications: z.array(DataClassificationSchema) - .optional().describe('Affected data classifications'), - - /** - * Structured response phases tracking - */ - responsePhases: z.array(IncidentResponsePhaseSchema).optional() - .describe('Incident response phases'), - - /** - * Root cause analysis (completed post-incident) - */ - rootCause: z.string().optional().describe('Root cause analysis'), - - /** - * Corrective actions taken or planned - */ - correctiveActions: z.array(z.string()).optional() - .describe('Corrective actions taken or planned'), - - /** - * Lessons learned from the incident (A.5.28) - */ - lessonsLearned: z.string().optional() - .describe('Lessons learned from the incident'), - - /** - * Related change request IDs (if changes resulted from incident) - */ - relatedChangeRequestIds: z.array(z.string()).optional() - .describe('Related change request IDs'), - - /** - * Custom metadata for extensibility - */ - metadata: z.record(z.string(), z.unknown()).optional() - .describe('Custom metadata key-value pairs'), -}).describe('Security incident record per ISO 27001:2022 A.5.24–A.5.28')); - -/** - * Incident Response Policy Schema - * - * Organization-level incident response policy configuration (A.5.24). - */ -export const IncidentResponsePolicySchema = lazySchema(() => z.object({ - /** - * Whether incident response is enabled - */ - enabled: z.boolean().default(true) - .describe('Enable incident response management'), - - /** - * Notification matrix configuration - */ - notificationMatrix: IncidentNotificationMatrixSchema - .describe('Notification and escalation matrix'), - - /** - * Default response team or role - */ - defaultResponseTeam: z.string() - .describe('Default incident response team or role'), - - /** - * REMOVED (ADR-0049 enforce-or-remove) — see `TRIAGE_DEADLINE_HOURS_RETIRED` above. - */ - triageDeadlineHours: retiredKey(TRIAGE_DEADLINE_HOURS_RETIRED), - - /** - * Whether to require post-incident review for all incidents - */ - requirePostIncidentReview: z.boolean().default(true) - .describe('Require post-incident review for all incidents'), - - /** - * Minimum severity level that requires regulatory notification - */ - regulatoryNotificationThreshold: IncidentSeveritySchema.default('high') - .describe('Minimum severity requiring regulatory notification'), - - /** - * REMOVED (ADR-0049 enforce-or-remove) — see `RETENTION_DAYS_RETIRED` above. - */ - retentionDays: retiredKey(RETENTION_DAYS_RETIRED), -}).describe('Organization-level incident response policy per ISO 27001:2022')); - -// Type exports -export type IncidentSeverity = z.input; -export type IncidentCategory = z.input; -export type IncidentStatus = z.input; -export type Incident = z.input; -export type IncidentResponsePolicy = z.input; -/** Post-parse shape of {@link IncidentResponsePolicy} — defaults applied, transforms run (ADR-0122). */ -export type IncidentResponsePolicyParsed = z.infer; diff --git a/packages/spec/src/system/index.ts b/packages/spec/src/system/index.ts index f6d9f1ff95..2fd3fa3dd9 100644 --- a/packages/spec/src/system/index.ts +++ b/packages/spec/src/system/index.ts @@ -5,7 +5,7 @@ * * Runtime Services & Infrastructure Configuration * - Infrastructure: Cache, Queue, Storage, Search, HTTP - * - Observability: Audit, Logging, Metrics, Tracing, Change Management + * - Observability: Audit, Logging, Metrics, Tracing * - Security: Compliance, Encryption, Masking, Auth Config * - Services: Job, Worker, Notification, Translation */ @@ -46,7 +46,22 @@ export * from './stack-server.zod'; export * from './logging.zod'; export * from './metrics.zod'; export * from './tracing.zod'; -export * from './change-management.zod'; +// change-management.zod (ChangeTypeSchema / ChangePrioritySchema / +// ChangeStatusSchema / ChangeImpactSchema / RollbackPlanSchema / +// ChangeRequestSchema + every type alias) was REMOVED per ADR-0049 +// enforce-or-remove (#15513, maintainer ruling 2026-09-05, ruled A). The module +// declared an ITIL change-request vocabulary — impact assessment, CAB approval, +// implementation steps, rollback plans, a `securityImpact.requiresSecurityApproval` +// gate — and no runtime ever parsed or read any of it: zero consumers outside +// this package repo-wide (examples, skills and objectui at the pinned sha +// included), no stack key, no metadata type, absent from the liveness ledgers. +// `approval.required` and `requiresSecurityApproval` read as gates the platform +// enforced, and neither ever did. The fourteen #14477 deadline-key tombstones +// the three compliance families carried leave with their defs' source; their +// `RETIRED_KEYS_BY_MAJOR[18]` entries stay as history. An approval that must +// actually gate something is a flow (ADR-0018) with an approval node; metadata +// change tracking is `sys_metadata` history and the package model (ADR-0126). +// See the D3 record `change-management-family-retired`. export * from './migration.zod'; // Security & Compliance @@ -65,9 +80,33 @@ export * from './metadata-form-registry'; // ADR-0066 ⑦/⑧ if needed. encryption.zod stays (EXPERIMENTAL — roadmap). export * from './encryption.zod'; export * from './security-context.zod'; -export * from './incident-response.zod'; +// incident-response.zod (IncidentSeveritySchema / IncidentCategorySchema / +// IncidentStatusSchema / IncidentResponsePhaseSchema / +// IncidentNotificationRuleSchema / IncidentNotificationMatrixSchema / +// IncidentSchema / IncidentResponsePolicySchema + every type alias) was REMOVED +// per ADR-0049 enforce-or-remove (#15513, maintainer ruling 2026-09-05, ruled +// A; not roadmapped). The module declared an ISO 27001 A.5.24–A.5.28 +// incident-management vocabulary — severity grading, response phases, a +// notification matrix with `notifyRegulators`, a policy with +// `requirePostIncidentReview` — and no runtime ever parsed or read any of it: +// zero consumers outside this package repo-wide (examples, skills and objectui +// at the pinned sha included), no stack key, no metadata type, absent from the +// liveness ledgers. An author writing `notifyRegulators: true` held a +// compliance promise the platform never kept, with no error and no feedback — +// the exact declared-≠-enforced shape ADR-0049 exists for, on a compliance +// surface where "believed it notified the regulator" and "knew it did not" are +// not the same risk. Incident response as authorable protocol metadata returns +// via the enforce route of ADR-0049 through a new ADR — the engine first, the +// vocabulary second. See the D3 record `incident-response-family-retired`. export * from './supplier-security.zod'; -export * from './training.zod'; +// training.zod (TrainingCategorySchema / TrainingCompletionStatusSchema / +// TrainingCourseSchema / TrainingRecordSchema / TrainingPlanSchema + every type +// alias) was REMOVED per ADR-0049 enforce-or-remove (#15513, same ruling). The +// module declared an ISO 27001 A.6.3 training-management vocabulary — courses +// with `mandatory`, plans with `trackCompletion` / `sendReminders`, completion +// records — and no runtime ever parsed or read any of it (same census, same +// zero). Nothing assigned a course, tracked a completion, sent a reminder or +// expired a certification. See the D3 record `training-family-retired`. // Settings (ADR-0007: Manifest + K/V Store + Resolver) export * from './settings-manifest.zod'; diff --git a/packages/spec/src/system/training.test.ts b/packages/spec/src/system/training.test.ts deleted file mode 100644 index 7730728e36..0000000000 --- a/packages/spec/src/system/training.test.ts +++ /dev/null @@ -1,254 +0,0 @@ -import { describe, it, expect } from 'vitest'; -import { - TrainingCategorySchema, - TrainingCompletionStatusSchema, - TrainingCourseSchema, - TrainingRecordSchema, - TrainingPlanSchema, - type TrainingCourse, - type TrainingRecord, -} from './training.zod'; - -describe('TrainingCategorySchema', () => { - it('should accept all valid categories', () => { - const validCategories = [ - 'security_awareness', 'data_protection', 'incident_response', - 'access_control', 'phishing_awareness', 'compliance', - 'secure_development', 'physical_security', 'business_continuity', 'other', - ]; - - validCategories.forEach((category) => { - expect(() => TrainingCategorySchema.parse(category)).not.toThrow(); - }); - }); - - it('should reject invalid category', () => { - expect(() => TrainingCategorySchema.parse('yoga')).toThrow(); - }); -}); - -describe('TrainingCompletionStatusSchema', () => { - it('should accept all valid statuses', () => { - const statuses = ['not_started', 'in_progress', 'completed', 'failed', 'expired']; - - statuses.forEach((status) => { - expect(() => TrainingCompletionStatusSchema.parse(status)).not.toThrow(); - }); - }); - - it('should reject invalid status', () => { - expect(() => TrainingCompletionStatusSchema.parse('skipped')).toThrow(); - }); -}); - -describe('TrainingCourseSchema', () => { - it('should accept valid course with defaults', () => { - const course = TrainingCourseSchema.parse({ - id: 'COURSE-SEC-001', - title: 'Information Security Fundamentals', - description: 'Annual security awareness training for all employees', - category: 'security_awareness', - targetRoles: ['all_employees'], - }); - - expect(course.mandatory).toBe(false); - expect(course.passingScore).toBeUndefined(); - expect(course.validityDays).toBeUndefined(); - }); - - it('should accept full course configuration', () => { - const course: TrainingCourse = { - id: 'COURSE-SEC-002', - title: 'Phishing Awareness Training', - description: 'Recognize and report phishing attempts', - category: 'phishing_awareness', - mandatory: true, - targetRoles: ['all_employees', 'contractors'], - passingScore: 80, - version: '2.0', - }; - - expect(() => TrainingCourseSchema.parse(course)).not.toThrow(); - }); - - it('should accept all category types', () => { - const categories = [ - 'security_awareness', 'data_protection', 'incident_response', - 'access_control', 'phishing_awareness', 'compliance', - 'secure_development', 'physical_security', 'business_continuity', 'other', - ]; - - categories.forEach((category) => { - expect(() => TrainingCourseSchema.parse({ - id: `COURSE-${category}`, - title: `${category} Training`, - description: `Training for ${category}`, - category, - targetRoles: ['all'], - })).not.toThrow(); - }); - }); - - it('REFUSES an authored `durationMinutes` — a retiredKey() tombstone since #14477 (ADR-0049)', () => { - // The full refusal envelope (path, code, prescription) is pinned in - // `deadline-keys-retirement.test.ts`; this keeps the family suite honest - // about the shape it parses: any value, not only `0`, is refused. - expect(() => TrainingCourseSchema.parse({ - id: 'COURSE-001', - title: 'Test', - description: 'Test', - category: 'other', - durationMinutes: 30, - targetRoles: ['all'], - })).toThrow(/`TrainingCourse\.durationMinutes` was removed/s); - }); - - it('should reject passing score out of range', () => { - expect(() => TrainingCourseSchema.parse({ - id: 'COURSE-001', - title: 'Test', - description: 'Test', - category: 'other', - targetRoles: ['all'], - passingScore: 101, - })).toThrow(); - - expect(() => TrainingCourseSchema.parse({ - id: 'COURSE-001', - title: 'Test', - description: 'Test', - category: 'other', - targetRoles: ['all'], - passingScore: -1, - })).toThrow(); - }); - - it('should reject missing required fields', () => { - expect(() => TrainingCourseSchema.parse({})).toThrow(); - expect(() => TrainingCourseSchema.parse({ id: 'COURSE-001' })).toThrow(); - }); -}); - -describe('TrainingRecordSchema', () => { - it('should accept valid completed training record', () => { - const record: TrainingRecord = { - courseId: 'COURSE-SEC-001', - userId: 'user_123', - status: 'completed', - assignedAt: 1704067200000, - completedAt: 1704153600000, - score: 95, - expiresAt: 1735689600000, - }; - - expect(() => TrainingRecordSchema.parse(record)).not.toThrow(); - }); - - it('should accept minimal not-started record', () => { - const record = { - courseId: 'COURSE-SEC-002', - userId: 'user_456', - status: 'not_started', - assignedAt: Date.now(), - }; - - expect(() => TrainingRecordSchema.parse(record)).not.toThrow(); - }); - - it('should accept failed record', () => { - const record = { - courseId: 'COURSE-SEC-003', - userId: 'user_789', - status: 'failed', - assignedAt: 1704067200000, - completedAt: 1704153600000, - score: 45, - notes: 'Did not meet passing score of 80%', - }; - - expect(() => TrainingRecordSchema.parse(record)).not.toThrow(); - }); - - it('should reject score out of range', () => { - expect(() => TrainingRecordSchema.parse({ - courseId: 'COURSE-001', - userId: 'user_123', - status: 'completed', - assignedAt: Date.now(), - score: 150, - })).toThrow(); - }); - - it('should reject missing required fields', () => { - expect(() => TrainingRecordSchema.parse({})).toThrow(); - expect(() => TrainingRecordSchema.parse({ courseId: 'COURSE-001' })).toThrow(); - }); -}); - -describe('TrainingPlanSchema', () => { - it('should accept plan with defaults', () => { - const plan = TrainingPlanSchema.parse({ - courses: [ - { - id: 'COURSE-SEC-001', - title: 'Security Awareness', - description: 'Annual security training', - category: 'security_awareness', - targetRoles: ['all_employees'], - }, - ], - }); - - expect(plan.enabled).toBe(true); - expect(plan).not.toHaveProperty('recertificationIntervalDays'); - expect(plan.trackCompletion).toBe(true); - expect(plan).not.toHaveProperty('gracePeriodDays'); - expect(plan.sendReminders).toBe(true); - expect(plan).not.toHaveProperty('reminderDaysBefore'); - }); - - it('should accept full plan configuration', () => { - const plan = TrainingPlanSchema.parse({ - enabled: true, - courses: [ - { - id: 'COURSE-SEC-001', - title: 'Security Fundamentals', - description: 'Core security training', - category: 'security_awareness', - mandatory: true, - targetRoles: ['all_employees'], - passingScore: 80, - }, - { - id: 'COURSE-SEC-002', - title: 'Secure Development', - description: 'Secure coding practices', - category: 'secure_development', - mandatory: true, - targetRoles: ['developers', 'devops'], - passingScore: 85, - }, - ], - trackCompletion: true, - sendReminders: true, - }); - - expect(plan.courses).toHaveLength(2); - expect(plan).not.toHaveProperty('recertificationIntervalDays'); - expect(plan).not.toHaveProperty('gracePeriodDays'); - expect(plan).not.toHaveProperty('reminderDaysBefore'); - }); - - it('should accept plan with empty courses', () => { - const plan = TrainingPlanSchema.parse({ - courses: [], - }); - - expect(plan.courses).toHaveLength(0); - }); - - it('should reject missing courses', () => { - expect(() => TrainingPlanSchema.parse({})).toThrow(); - }); -}); diff --git a/packages/spec/src/system/training.zod.ts b/packages/spec/src/system/training.zod.ts deleted file mode 100644 index 27d58383be..0000000000 --- a/packages/spec/src/system/training.zod.ts +++ /dev/null @@ -1,272 +0,0 @@ -// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. - -import { z } from 'zod'; -import { retiredKey } from '../shared/retired-key'; - -/** - * Information Security Training Protocol — ISO 27001:2022 (A.6.3) - * - * Defines schemas for security awareness and training management including - * course definitions, completion tracking, and organizational training plans. - * - * @see https://www.iso.org/standard/27001 - * @category Security - */ - -/** - * Training Category Schema - * - * Classification of training content by domain. - */ -import { lazySchema } from '../shared/lazy-schema'; -export const TrainingCategorySchema = lazySchema(() => z.enum([ - 'security_awareness', // General security awareness - 'data_protection', // Data handling and privacy - 'incident_response', // Incident reporting and response - 'access_control', // Access management best practices - 'phishing_awareness', // Phishing and social engineering - 'compliance', // Regulatory compliance (GDPR, HIPAA, etc.) - 'secure_development', // Secure coding and development practices - 'physical_security', // Physical security awareness - 'business_continuity', // Business continuity and disaster recovery - 'other', // Other training categories -])); - -// ─── RETIRED deadline keys (ADR-0049 enforce-or-remove) ───────────────────── -// -// Five minute/day-shaped duration and deadline keys were declared on the -// training schemas and read by NOTHING: no training engine scheduled a -// course, computed a certification expiry, re-assigned training on an -// interval, escalated an expired certification or sent a reminder — the -// schemas are exported, mounted by no stack key and registered as no metadata -// type, and the reader census over every package outside `packages/spec` (and -// over objectui at the pinned sha) returned zero hits for every key. Maintainer -// ruling 2026-09-02 (recorded on #14477): retire the family under -// enforce-or-remove. -// -// Route: `retiredKey()` tombstones, NOT plain deletion (the schemas are not -// `.strict()`; a bare deletion would be a silent strip, ADR-0104). No D2 -// conversion and no `os migrate meta` sentence: none of these schemas is a -// stack collection member, so a conversion would have no seam that ever runs -// (the `kernel/MetadataPluginConfig:additionalTypes` precedent). Registered -// as `RETIRED_KEYS_BY_MAJOR[18]` entries plus the D3 semantic entry -// `training-deadline-keys-retired`. - -const DURATION_MINUTES_RETIRED = - '`TrainingCourse.durationMinutes` was removed in @objectstack/spec 17 (ADR-0049 ' - + 'enforce-or-remove) — nothing ever read it: no training engine scheduled, timed or ' - + 'reported a course, so the duration was a number the platform displayed nowhere and acted ' - + 'on never. Delete the key. There is no replacement, because no training-management engine ' - + 'exists.'; - -const VALIDITY_DAYS_RETIRED = - '`TrainingCourse.validityDays` was removed in @objectstack/spec 17 (ADR-0049 ' - + 'enforce-or-remove) — nothing ever read it: no engine computed a certification expiry from ' - + 'it, so a certificate declared valid for 365 days never expired on the platform and never ' - + 'triggered recertification. Delete the key. There is no replacement, because no ' - + 'training-management engine exists to keep a validity window.'; - -const RECERTIFICATION_INTERVAL_DAYS_RETIRED = - '`TrainingPlan.recertificationIntervalDays` was removed in @objectstack/spec 17 (ADR-0049 ' - + 'enforce-or-remove) — nothing ever read it: no engine re-assigned training on an interval, ' - + 'so the interval never elapsed into anything, and its default of 365 days was materialized ' - + 'into every parsed plan without ever being consulted. Delete the key. There is no ' - + 'replacement, because no training-management engine exists.'; - -const GRACE_PERIOD_DAYS_RETIRED = - '`TrainingPlan.gracePeriodDays` was removed in @objectstack/spec 17 (ADR-0049 ' - + 'enforce-or-remove) — nothing ever read it: no engine escalated an expired certification, ' - + 'so a grace period before that escalation had nothing to delay, and its default of 30 days ' - + 'was materialized into every parsed plan without ever being consulted. Delete the key. ' - + 'There is no replacement, because no training-management engine exists.'; - -const REMINDER_DAYS_BEFORE_RETIRED = - '`TrainingPlan.reminderDaysBefore` was removed in @objectstack/spec 17 (ADR-0049 ' - + 'enforce-or-remove) — nothing ever read it: no engine sent a training reminder, so the lead ' - + 'time was never counted down, and its default of 14 days was materialized into every parsed ' - + 'plan without ever being consulted. Delete the key. There is no replacement, because no ' - + 'training-reminder engine exists.'; - -/** - * Training Completion Status Schema - */ -export const TrainingCompletionStatusSchema = lazySchema(() => z.enum([ - 'not_started', // Training not yet begun - 'in_progress', // Training currently underway - 'completed', // Training completed successfully - 'failed', // Training assessment not passed - 'expired', // Training certification has expired -])); - -/** - * Training Course Schema - * - * Definition of a security training course or module. - * - * @example - * ```json - * { - * "id": "COURSE-SEC-001", - * "title": "Information Security Fundamentals", - * "description": "Annual security awareness training for all employees", - * "category": "security_awareness", - * "mandatory": true, - * "targetRoles": ["all_employees"], - * "passingScore": 80 - * } - * ``` - */ -export const TrainingCourseSchema = lazySchema(() => z.object({ - /** - * Unique course identifier - */ - id: z.string().describe('Unique course identifier'), - - /** - * Course title - */ - title: z.string().describe('Course title'), - - /** - * Course description and objectives - */ - description: z.string().describe('Course description and learning objectives'), - - /** - * Training category - */ - category: TrainingCategorySchema.describe('Training category'), - - /** - * REMOVED (ADR-0049 enforce-or-remove) — see `DURATION_MINUTES_RETIRED` above. - */ - durationMinutes: retiredKey(DURATION_MINUTES_RETIRED), - - /** - * Whether this training is mandatory - */ - mandatory: z.boolean().default(false).describe('Whether training is mandatory'), - - /** - * Target roles or groups for this training - */ - targetRoles: z.array(z.string()).describe('Target roles or groups'), - - /** - * REMOVED (ADR-0049 enforce-or-remove) — see `VALIDITY_DAYS_RETIRED` above. - */ - validityDays: retiredKey(VALIDITY_DAYS_RETIRED), - - /** - * Minimum passing score (percentage) for assessment - */ - passingScore: z.number().min(0).max(100).optional() - .describe('Minimum passing score percentage'), - - /** - * Course version for tracking content updates - */ - version: z.string().optional().describe('Course content version'), -}).describe('Security training course definition')); - -/** - * Training Record Schema - * - * Individual employee training completion record. - */ -export const TrainingRecordSchema = lazySchema(() => z.object({ - /** - * Reference to the course ID - */ - courseId: z.string().describe('Training course identifier'), - - /** - * User who completed (or is assigned) the training - */ - userId: z.string().describe('User identifier'), - - /** - * Completion status - */ - status: TrainingCompletionStatusSchema.describe('Training completion status'), - - /** - * Training assignment date (Unix milliseconds) - */ - assignedAt: z.number().describe('Assignment timestamp'), - - /** - * Training completion date (Unix milliseconds) - */ - completedAt: z.number().optional().describe('Completion timestamp'), - - /** - * Assessment score (percentage) - */ - score: z.number().min(0).max(100).optional().describe('Assessment score percentage'), - - /** - * Certification expiry date (Unix milliseconds) - */ - expiresAt: z.number().optional().describe('Certification expiry timestamp'), - - /** - * Notes or comments from instructor or system - */ - notes: z.string().optional().describe('Training notes or comments'), -}).describe('Individual training completion record')); - -/** - * Training Plan Schema - * - * Organizational training plan defining schedule and requirements (A.6.3). - */ -export const TrainingPlanSchema = lazySchema(() => z.object({ - /** - * Whether training management is enabled - */ - enabled: z.boolean().default(true).describe('Enable training management'), - - /** - * Training courses in the plan - */ - courses: z.array(TrainingCourseSchema).describe('Training courses'), - - /** - * REMOVED (ADR-0049 enforce-or-remove) — see `RECERTIFICATION_INTERVAL_DAYS_RETIRED` above. - */ - recertificationIntervalDays: retiredKey(RECERTIFICATION_INTERVAL_DAYS_RETIRED), - - /** - * Whether to track training completion for compliance reporting - */ - trackCompletion: z.boolean().default(true) - .describe('Track training completion for compliance'), - - /** - * REMOVED (ADR-0049 enforce-or-remove) — see `GRACE_PERIOD_DAYS_RETIRED` above. - */ - gracePeriodDays: retiredKey(GRACE_PERIOD_DAYS_RETIRED), - - /** - * Whether to send reminders for upcoming training deadlines - */ - sendReminders: z.boolean().default(true) - .describe('Send reminders for upcoming training deadlines'), - - /** - * REMOVED (ADR-0049 enforce-or-remove) — see `REMINDER_DAYS_BEFORE_RETIRED` above. - */ - reminderDaysBefore: retiredKey(REMINDER_DAYS_BEFORE_RETIRED), -}).describe('Organizational training plan per ISO 27001:2022 A.6.3')); - -// Type exports -export type TrainingCategory = z.input; -export type TrainingCompletionStatus = z.input; -export type TrainingCourse = z.input; -/** Post-parse shape of {@link TrainingCourse} — defaults applied, transforms run (ADR-0122). */ -export type TrainingCourseParsed = z.infer; -export type TrainingRecord = z.input; -export type TrainingPlan = z.input; -/** Post-parse shape of {@link TrainingPlan} — defaults applied, transforms run (ADR-0122). */ -export type TrainingPlanParsed = z.infer; diff --git a/packages/spec/src/type-alias-convention.pin.test.ts b/packages/spec/src/type-alias-convention.pin.test.ts index f8c8df2da3..f127aa9442 100644 --- a/packages/spec/src/type-alias-convention.pin.test.ts +++ b/packages/spec/src/type-alias-convention.pin.test.ts @@ -213,7 +213,8 @@ import type * as M118 from './studio/object-designer.zod.js'; import type * as M119 from './studio/plugin.zod.js'; import type * as M120 from './system/auth-config.zod.js'; import type * as M121 from './system/cache.zod.js'; -import type * as M122 from './system/change-management.zod.js'; +// M122 was './system/change-management.zod.js' — the change-management family retired whole at +// #15513 (ADR-0049); the M-indices are positional, so the slot stays vacant. import type * as M123 from './system/collaboration.zod.js'; import type * as M124 from './system/core-services.zod.js'; import type * as M125 from './system/deploy-bundle.zod.js'; @@ -224,7 +225,8 @@ import type * as M129 from './system/email-template.zod.js'; import type * as M130 from './system/encryption.zod.js'; import type * as M131 from './system/environment-artifact.zod.js'; import type * as M132 from './system/http-server.zod.js'; -import type * as M133 from './system/incident-response.zod.js'; +// M133 was './system/incident-response.zod.js' — the incident-response family retired whole at +// #15513 (ADR-0049); the M-indices are positional, so the slot stays vacant. import type * as M134 from './system/job.zod.js'; import type * as M135 from './system/license.zod.js'; import type * as M136 from './system/logging.zod.js'; @@ -241,7 +243,8 @@ import type * as M147 from './system/settings-manifest.zod.js'; import type * as M148 from './system/supplier-security.zod.js'; import type * as M149 from './system/tenant.zod.js'; import type * as M150 from './system/tracing.zod.js'; -import type * as M151 from './system/training.zod.js'; +// M151 was './system/training.zod.js' — the training family retired whole at +// #15513 (ADR-0049); the M-indices are positional, so the slot stays vacant. import type * as M152 from './system/translation.zod.js'; import type * as M153 from './system/worker.zod.js'; import type * as M154 from './ui/action-params.zod.js'; @@ -270,7 +273,7 @@ import type * as M183 from './api/sortability.zod.js'; import type * as M184 from './shared/value-domain.zod.js'; // --------------------------------------------------------------------------- -// 825 isomorphic aliases: `z.input` === `z.infer`, so no `XParsed` is declared. +// 812 isomorphic aliases: `z.input` === `z.infer`, so no `XParsed` is declared. // // That number is machine-checked, not hand-kept. The runtime companion at the // bottom of this file recomputes the pin count from the source and asserts that @@ -1083,12 +1086,6 @@ export type Iso522 = Assert, z.in export type Iso523 = Assert, z.infer< typeof M121.CacheInvalidationSchema > >>; export type Iso524 = Assert, z.infer< typeof M121.CacheConsistencySchema > >>; -// system/change-management.zod.ts -export type Iso525 = Assert, z.infer< typeof M122.ChangeTypeSchema > >>; -export type Iso526 = Assert, z.infer< typeof M122.ChangeStatusSchema > >>; -export type Iso527 = Assert, z.infer< typeof M122.ChangePrioritySchema > >>; -export type Iso528 = Assert, z.infer< typeof M122.ChangeImpactSchema > >>; -export type Iso529 = Assert, z.infer< typeof M122.RollbackPlanSchema > >>; // system/collaboration.zod.ts export type Iso530 = Assert, z.infer< typeof M123.OTOperationType > >>; @@ -1145,12 +1142,6 @@ export type Iso563 = Assert, z.inf // system/http-server.zod.ts export type Iso564 = Assert, z.infer< typeof M132.MiddlewareType > >>; -// system/incident-response.zod.ts -export type Iso568 = Assert, z.infer< typeof M133.IncidentResponsePhaseSchema > >>; -export type Iso569 = Assert, z.infer< typeof M133.IncidentSeveritySchema > >>; -export type Iso570 = Assert, z.infer< typeof M133.IncidentCategorySchema > >>; -export type Iso571 = Assert, z.infer< typeof M133.IncidentStatusSchema > >>; -export type Iso572 = Assert, z.infer< typeof M133.IncidentSchema > >>; // system/job.zod.ts export type Iso573 = Assert, z.infer< typeof M134.IntervalScheduleSchema > >>; @@ -1260,10 +1251,6 @@ export type Iso644 = Assert, z.i export type Iso645 = Assert, z.infer< typeof M150.TracePropagationFormat > >>; export type Iso646 = Assert, z.infer< typeof M150.OtelExporterType > >>; -// system/training.zod.ts -export type Iso647 = Assert, z.infer< typeof M151.TrainingCategorySchema > >>; -export type Iso648 = Assert, z.infer< typeof M151.TrainingCompletionStatusSchema > >>; -export type Iso649 = Assert, z.infer< typeof M151.TrainingRecordSchema > >>; // system/translation.zod.ts export type Iso650 = Assert, z.infer< typeof M152.FieldTranslationSchema > >>; @@ -1685,7 +1672,7 @@ describe('ADR-0122 type-alias convention', () => { // this title and the section header above the pin list — are now asserted // against the recomputed count below, so neither can go stale without a red // test naming it. - it('still declares all 825 isomorphic pins', () => { + it('still declares all 812 isomorphic pins', () => { // The truth of each pin is proved by tsc, not here — an `Assert>` // that stops holding is a compile error with the alias named. What tsc // cannot notice is a pin that was DELETED: removing the assertion removes @@ -2146,7 +2133,13 @@ describe('ADR-0122 type-alias convention', () => { // earlier: `ElementRecordPickerPropsParsed` declared, the Iso819 pin // deleted. -1 converted to an `XParsed` pair; the Iso number stays vacant // (ids are claims about pins, not positions). - expect(pins).toHaveLength(825); + // 825 -> 812 is #15513's ADR-0049 whole-family retirement of the + // incident-response, training and change-management schemas: the thirteen + // isomorphic enums and objects those three modules declared (Iso525–Iso529, + // Iso568–Iso572, Iso647–Iso649) left with their defs (whole-def removal, + // `RETIRED_DEFS_BY_MAJOR[18]`), so the pins that named them leave with the + // schemas; the M122 / M133 / M151 import slots stay vacant. + expect(pins).toHaveLength(812); // The count is stated in PROSE twice as well — this case's title and the // section header above the pin list — and until #6605 nothing read either diff --git a/scripts/cross-package-test-inputs.mjs b/scripts/cross-package-test-inputs.mjs index 065e771576..f6823cebb4 100644 --- a/scripts/cross-package-test-inputs.mjs +++ b/scripts/cross-package-test-inputs.mjs @@ -172,6 +172,45 @@ export const CROSS_PACKAGE_TEST_INPUTS = { // `coversDirectory` is the check, and it is why the narrower spelling was // tried first and rejected by this gate. 'skills/**', + // src/system/compliance-families-retirement.test.ts is the #15513 + // tree-scoped absence pin (ADR-0049 whole-family retirement). It walks + // FIVE repo roots -- `packages`, `examples`, `skills`, `content`, + // `scripts` -- seeded off `import.meta.url` and descending with + // `readdirSync(dir)` on a LOOP VARIABLE, so the escape verdict resolves + // and no name does; every glob below that no roster path holds names + // the pin in `heldBy`. The declaration is what puts the pin into + // `turbo ls --affected` and the `test` task's cache key for a + // resurrection anywhere inside the radius -- the half the retirement + // playbook mandates (its ⭐ 半径按包申报一次 rule, #15566) and the + // #14477 pin lacked (#15528). + // + // ⛔ Per EXTENSION under `packages/`, never `packages/**`: the + // dispatch-gates self-test pins that no cross-package hint reaches + // `packages/client-react`'s `realtime-hooks.test.tsx`, and a bare + // `packages/**` was the one entry that covered a `.tsx` file (the + // `@objectstack/core` entry above records the measurement). The pin's + // scanner skips `.tsx` for the same reason -- extensions and globs widen + // together or not at all; a typed `.tsx` import of a retired name fails + // `tsc` in its own package, which is the enforced channel there. + // `packages/**/*.ts` and `*.mts` subsume this entry's narrower `.ts` + // globs above, which are left as the tests that declared them spelled + // them. `examples/**` and `content/**` are declared whole (the pin scans + // their JSON / MD / MDX / YAML too; `content/**` subsumes the two + // `content/docs/...` rows above). `skills/**` and `scripts/**` were + // already declared by the tests named at their rows. + 'packages/**/*.ts', + 'packages/**/*.mts', + 'packages/**/*.cts', + 'packages/**/*.js', + 'packages/**/*.mjs', + 'packages/**/*.cjs', + 'packages/**/*.json', + 'packages/**/*.md', + 'packages/**/*.mdx', + 'packages/**/*.yaml', + 'packages/**/*.yml', + 'examples/**', + 'content/**', ], heldBy: { // The two repo-wide `*.object.ts` walkers. Each seeds a recognised @@ -183,6 +222,19 @@ export const CROSS_PACKAGE_TEST_INPUTS = { 'packages/spec/src/data/api-methods-batch-conformance.test.ts', 'packages/spec/src/system/constants/platform-object-names.test.ts', ], + // The #15513 absence pin's walk radius (see the globs' comment above): + // one witness per glob no literal path on this package's roster holds. + 'packages/**/*.mts': ['packages/spec/src/system/compliance-families-retirement.test.ts'], + 'packages/**/*.cts': ['packages/spec/src/system/compliance-families-retirement.test.ts'], + 'packages/**/*.js': ['packages/spec/src/system/compliance-families-retirement.test.ts'], + 'packages/**/*.mjs': ['packages/spec/src/system/compliance-families-retirement.test.ts'], + 'packages/**/*.cjs': ['packages/spec/src/system/compliance-families-retirement.test.ts'], + 'packages/**/*.json': ['packages/spec/src/system/compliance-families-retirement.test.ts'], + 'packages/**/*.md': ['packages/spec/src/system/compliance-families-retirement.test.ts'], + 'packages/**/*.mdx': ['packages/spec/src/system/compliance-families-retirement.test.ts'], + 'packages/**/*.yaml': ['packages/spec/src/system/compliance-families-retirement.test.ts'], + 'packages/**/*.yml': ['packages/spec/src/system/compliance-families-retirement.test.ts'], + 'examples/**': ['packages/spec/src/system/compliance-families-retirement.test.ts'], }, }, '@objectstack/core': { diff --git a/turbo.json b/turbo.json index a89f287450..bc9d72c496 100644 --- a/turbo.json +++ b/turbo.json @@ -67,7 +67,20 @@ "$TURBO_ROOT$/content/docs/api/error-catalog.mdx", "$TURBO_ROOT$/docs/audits/2026-07-unknown-key-strictness-ledger.md", "$TURBO_ROOT$/.claude/skills/spec-property-retirement/SKILL.md", - "$TURBO_ROOT$/skills/**" + "$TURBO_ROOT$/skills/**", + "$TURBO_ROOT$/packages/**/*.ts", + "$TURBO_ROOT$/packages/**/*.mts", + "$TURBO_ROOT$/packages/**/*.cts", + "$TURBO_ROOT$/packages/**/*.js", + "$TURBO_ROOT$/packages/**/*.mjs", + "$TURBO_ROOT$/packages/**/*.cjs", + "$TURBO_ROOT$/packages/**/*.json", + "$TURBO_ROOT$/packages/**/*.md", + "$TURBO_ROOT$/packages/**/*.mdx", + "$TURBO_ROOT$/packages/**/*.yaml", + "$TURBO_ROOT$/packages/**/*.yml", + "$TURBO_ROOT$/examples/**", + "$TURBO_ROOT$/content/**" ] }, "@objectstack/core#test": { From 77324c411e1b0a9fc6f56a0c1dd3a0e193402830 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 5 Sep 2026 15:35:11 +0000 Subject: [PATCH 2/6] chore(spec): regenerate the surface, docs and ledger artifacts for the compliance-families retirement authorable-surface/system.json -88 rows and authorable-defaults/system.json -9 (the deliberate hand-deletions gate (a) asks for on a whole-def retirement), data.json +2 [RETIRED] rows and -2 defaults for the ESignatureConfig pair; api-surface -44, declaration-map -38, export-origins -44; the three reference pages removed and the system nav / index regenerated; strictness-ledger counts follow the schema files. All by the repo tooling (build + check:generated --fix). Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01M59rPZZFzqhfMUPFqqZTkf --- content/docs/references/data/document.mdx | 10 +- content/docs/references/index.mdx | 11 +- .../references/system/change-management.mdx | 193 --------------- .../references/system/incident-response.mdx | 226 ------------------ content/docs/references/system/index.mdx | 3 - content/docs/references/system/meta.json | 3 - content/docs/references/system/training.mdx | 152 ------------ ...07-unknown-key-strictness-ledger.counts.md | 2 +- packages/spec/api-surface/system.json | 44 ---- packages/spec/authorable-defaults/data.json | 2 - packages/spec/authorable-defaults/system.json | 11 +- packages/spec/authorable-surface/data.json | 4 +- packages/spec/authorable-surface/system.json | 88 ------- packages/spec/declaration-map/system.json | 38 --- packages/spec/export-origins/system.json | 44 ---- 15 files changed, 13 insertions(+), 818 deletions(-) delete mode 100644 content/docs/references/system/change-management.mdx delete mode 100644 content/docs/references/system/incident-response.mdx delete mode 100644 content/docs/references/system/training.mdx diff --git a/content/docs/references/data/document.mdx b/content/docs/references/data/document.mdx index 41572995b2..91642cef46 100644 --- a/content/docs/references/data/document.mdx +++ b/content/docs/references/data/document.mdx @@ -36,7 +36,7 @@ const result = DocumentSchema.parse(data); | **tags** | `string[]` | optional | Document tags | | **versioning** | `{ enabled: boolean; versions: object[]; majorVersion: number; minorVersion: number }` | optional | Version control | | **template** | `{ id: string; name: string; description?: string; fileUrl: string; … }` | optional | Document template | -| **eSignature** | `{ provider: Enum<'docusign' \| 'adobe-sign' \| 'hellosign' \| 'custom'>; enabled: boolean; signers: object[]; expirationDays: number; … }` | optional | E-signature config | +| **eSignature** | `{ provider: Enum<'docusign' \| 'adobe-sign' \| 'hellosign' \| 'custom'>; enabled: boolean; signers: object[] }` | optional | E-signature config | | **access** | `{ isPublic: boolean; sharedWith?: string[]; expiresAt?: number }` | optional | Access control | | **metadata** | `Record` | optional | Custom metadata | @@ -67,8 +67,8 @@ const result = DocumentSchema.parse(data); | **provider** | `Enum<'docusign' \| 'adobe-sign' \| 'hellosign' \| 'custom'>` | ✅ | E-signature provider | | **enabled** | `boolean` | optional (default: `false`) | E-signature enabled | | **signers** | `{ email: string; name: string; role: string; order: number }[]` | ✅ | Document signers | -| **expirationDays** | `number` | optional (default: `30`) | Expiration days | -| **reminderDays** | `number` | optional (default: `7`) | Reminder interval days | +| **expirationDays** | `never` | optional | [REMOVED] `ESignatureConfig.expirationDays` was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — nothing ever read it: no e-signature engine exists on the platform, so no signature request was ever sent, expired or lapsed, and its default of 30 days was materialized into every parsed configuration without ever being consulted. Delete the key. There is no replacement, because no e-signature provider integration exists to keep an expiry window. | +| **reminderDays** | `never` | optional | [REMOVED] `ESignatureConfig.reminderDays` was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — nothing ever read it: no e-signature engine exists on the platform, so no reminder email was ever sent, and its default of 7 days was materialized into every parsed configuration without ever being consulted. Delete the key. There is no replacement, because no e-signature provider integration exists to send reminders. | ### Nested Shape: `Document.access` @@ -132,8 +132,8 @@ const result = DocumentSchema.parse(data); | **provider** | `Enum<'docusign' \| 'adobe-sign' \| 'hellosign' \| 'custom'>` | ✅ | E-signature provider | | **enabled** | `boolean` | optional (default: `false`) | E-signature enabled | | **signers** | `{ email: string; name: string; role: string; order: number }[]` | ✅ | Document signers | -| **expirationDays** | `number` | optional (default: `30`) | Expiration days | -| **reminderDays** | `number` | optional (default: `7`) | Reminder interval days | +| **expirationDays** | `never` | optional | [REMOVED] `ESignatureConfig.expirationDays` was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — nothing ever read it: no e-signature engine exists on the platform, so no signature request was ever sent, expired or lapsed, and its default of 30 days was materialized into every parsed configuration without ever being consulted. Delete the key. There is no replacement, because no e-signature provider integration exists to keep an expiry window. | +| **reminderDays** | `never` | optional | [REMOVED] `ESignatureConfig.reminderDays` was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — nothing ever read it: no e-signature engine exists on the platform, so no reminder email was ever sent, and its default of 7 days was materialized into every parsed configuration without ever being consulted. Delete the key. There is no replacement, because no e-signature provider integration exists to send reminders. | ### Nested Shape: `ESignatureConfig.signers[number]` diff --git a/content/docs/references/index.mdx b/content/docs/references/index.mdx index 0311171044..79be81be47 100644 --- a/content/docs/references/index.mdx +++ b/content/docs/references/index.mdx @@ -1,6 +1,6 @@ --- title: Protocol Reference -description: Every schema published by @objectstack/spec — 1591 schemas across 14 protocol modules +description: Every schema published by @objectstack/spec — 1572 schemas across 14 protocol modules --- {/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs live in the module folders under content/docs/. */} @@ -31,9 +31,9 @@ counts are sums of the rows they head. Regenerate with | [Security Protocol](/docs/references/security) | 5 | 30 | Permission sets, row-level security, sharing rules, tenancy posture. | | [Shared Protocol](/docs/references/shared) | 8 | 27 | Primitives used across every protocol — identifiers, HTTP, expressions, error maps, enums. | | [Studio Protocol](/docs/references/studio) | 3 | 35 | Studio designer metadata — the authoring surfaces for the protocols above. | -| [System Protocol](/docs/references/system) | 36 | 291 | The runtime environment — logging, jobs, cache, metrics, notifications, i18n and compliance. | +| [System Protocol](/docs/references/system) | 33 | 272 | The runtime environment — logging, jobs, cache, metrics, notifications, i18n and compliance. | | [UI Protocol](/docs/references/ui) | 16 | 153 | Apps, pages, views, dashboards, reports, actions and themes — the ObjectUI layer. | -| **Total** | **200** | **1591** | 14 protocol modules | +| **Total** | **197** | **1572** | 14 protocol modules | --- @@ -319,7 +319,7 @@ Studio designer metadata — the authoring surfaces for the protocols above. ## System Protocol -**Source:** `packages/spec/src/system/` · **Import:** `@objectstack/spec/system` · **36 pages, 291 schemas** +**Source:** `packages/spec/src/system/` · **Import:** `@objectstack/spec/system` · **33 pages, 272 schemas** The runtime environment — logging, jobs, cache, metrics, notifications, i18n and compliance. @@ -329,7 +329,6 @@ The runtime environment — logging, jobs, cache, metrics, notifications, i18n a | [`auth-config.zod.ts`](/docs/references/system/auth-config) | `AdvancedAuthConfig`, `AudienceConfig`, `AuthConfig`, `AuthPluginConfig`, `AuthProviderConfig`, `EmailAndPasswordConfig`, `EmailVerificationConfig`, `MutualTLSConfig`, `OidcProviderConfig`, `OidcProvidersConfig`, `SocialProviderConfig` | | [`book.zod.ts`](/docs/references/system/book) | `Book`, `BookAudience`, `BookGroup`, `BookInclude`, `BookNode`, `ResolvedBook`, `ResolvedEntry`, `ResolvedGroup` | | [`cache.zod.ts`](/docs/references/system/cache) | `CacheAvalanchePrevention`, `CacheConfig`, `CacheConsistency`, `CacheInvalidation`, `CacheStrategy`, `CacheTier`, `CacheWarmup`, `DistributedCacheConfig` | -| [`change-management.zod.ts`](/docs/references/system/change-management) | `ChangeImpact`, `ChangePriority`, `ChangeRequest`, `ChangeStatus`, `ChangeType`, `RollbackPlan` | | [`collaboration.zod.ts`](/docs/references/system/collaboration) | `AwarenessEvent`, `AwarenessSession`, `AwarenessUpdate`, `AwarenessUserState`, `CRDTMergeResult`, `CRDTState`, `CRDTType`, `CollaborationMode`, `CollaborationSession`, `CollaborationSessionConfig`, `CollaborativeCursor`, `CounterOperation`, `CursorColorPreset`, `CursorSelection`, `CursorStyle`, `CursorUpdate`, `GCounter`, `LWWRegister`, `ORSet`, `ORSetElement`, `OTComponent`, `OTOperation`, `OTOperationType`, `OTTransformResult`, `PNCounter`, `TextCRDTOperation`, `TextCRDTState`, `UserActivityStatus`, `VectorClock` | | [`core-services.zod.ts`](/docs/references/system/core-services) | `CoreServiceName`, `KernelServiceMap`, `KernelServiceStatus`, `ServiceConfig`, `ServiceCriticality` | | [`deploy-bundle.zod.ts`](/docs/references/system/deploy-bundle) | `DeployBundle`, `DeployDiff`, `DeployManifest`, `DeployStatusEnum`, `DeployValidationIssue`, `DeployValidationResult`, `MigrationPlan`, `MigrationStatement`, `SchemaChange` | @@ -340,7 +339,6 @@ The runtime environment — logging, jobs, cache, metrics, notifications, i18n a | [`encryption.zod.ts`](/docs/references/system/encryption) | `EncryptionAlgorithm`, `EncryptionConfig`, `FieldEncryption`, `KeyManagementProvider`, `KeyRotationPolicy` | | [`environment-artifact.zod.ts`](/docs/references/system/environment-artifact) | `Sha256Digest` | | [`http-server.zod.ts`](/docs/references/system/http-server) | `MiddlewareConfig`, `MiddlewareType`, `RouteHandlerMetadata` | -| [`incident-response.zod.ts`](/docs/references/system/incident-response) | `Incident`, `IncidentCategory`, `IncidentNotificationMatrix`, `IncidentNotificationRule`, `IncidentResponsePhase`, `IncidentResponsePolicy`, `IncidentSeverity`, `IncidentStatus` | | [`job.zod.ts`](/docs/references/system/job) | `CronSchedule`, `IntervalSchedule`, `Job`, `JobExecution`, `JobExecutionStatus`, `OnceSchedule`, `RetryPolicy`, `Schedule` | | [`license.zod.ts`](/docs/references/system/license) | `Feature`, `License`, `LicenseMetricType`, `Plan` | | [`logging.zod.ts`](/docs/references/system/logging) | `ConsoleDestinationConfig`, `ExtendedLogLevel`, `ExternalServiceDestinationConfig`, `FileDestinationConfig`, `HttpDestinationConfig`, `LogDestination`, `LogDestinationType`, `LogEnrichmentConfig`, `LogEntry`, `LogFormat`, `LogLevel`, `LoggerConfig`, `LoggingConfig`, `StructuredLogEntry` | @@ -358,7 +356,6 @@ The runtime environment — logging, jobs, cache, metrics, notifications, i18n a | [`supplier-security.zod.ts`](/docs/references/system/supplier-security) | `SupplierAssessmentStatus`, `SupplierRiskLevel`, `SupplierSecurityAssessment`, `SupplierSecurityPolicy`, `SupplierSecurityRequirement` | | [`tenant.zod.ts`](/docs/references/system/tenant) | `DatabaseLevelIsolationStrategy`, `DatabaseProvider`, `QuotaEnforcementResult`, `RowLevelIsolationStrategy`, `SchemaLevelIsolationStrategy`, `Tenant`, `TenantConnectionConfig`, `TenantIsolationConfig`, `TenantIsolationLevel`, `TenantQuota`, `TenantSecurityPolicy`, `TenantUsage` | | [`tracing.zod.ts`](/docs/references/system/tracing) | `OpenTelemetryCompatibility`, `OtelExporterType`, `SamplingDecision`, `SamplingStrategyType`, `Span`, `SpanAttributeValue`, `SpanAttributes`, `SpanEvent`, `SpanKind`, `SpanLink`, `SpanStatus`, `TraceContext`, `TraceContextPropagation`, `TraceFlags`, `TracePropagationFormat`, `TraceSamplingConfig`, `TraceState`, `TracingConfig` | -| [`training.zod.ts`](/docs/references/system/training) | `TrainingCategory`, `TrainingCompletionStatus`, `TrainingCourse`, `TrainingPlan`, `TrainingRecord` | | [`translation.zod.ts`](/docs/references/system/translation) | `ActionResultDialogTranslation`, `CoverageBreakdownEntry`, `FieldTranslation`, `Locale`, `ObjectTranslationData`, `TranslationBundle`, `TranslationConfig`, `TranslationCoverageResult`, `TranslationData`, `TranslationDiffItem`, `TranslationDiffStatus`, `TranslationItem` | | [`worker.zod.ts`](/docs/references/system/worker) | `BatchProgress`, `QueueConfig`, `Task`, `TaskExecutionResult`, `TaskPriority`, `TaskRetryPolicy`, `TaskStatus`, `WorkerStats` | diff --git a/content/docs/references/system/change-management.mdx b/content/docs/references/system/change-management.mdx deleted file mode 100644 index 8dc1add70a..0000000000 --- a/content/docs/references/system/change-management.mdx +++ /dev/null @@ -1,193 +0,0 @@ ---- -title: Change Management -description: Change Management protocol schemas ---- - -{/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs live in the module folders under content/docs/. */} - - -**Source:** `packages/spec/src/system/change-management.zod.ts` - - -## TypeScript Usage - -```typescript -import { ChangeImpactSchema, ChangePrioritySchema, ChangeRequestSchema, ChangeStatusSchema, ChangeTypeSchema, RollbackPlanSchema } from '@objectstack/spec/system'; -import type { ChangeImpact, ChangePriority, ChangeRequest, ChangeStatus, ChangeType, RollbackPlan } from '@objectstack/spec/system'; - -// Validate data -const result = ChangeImpactSchema.parse(data); -``` - ---- - -## ChangeImpact - -### Properties - -| Property | Type | Required | Description | -| :--- | :--- | :--- | :--- | -| **level** | `Enum<'low' \| 'medium' \| 'high' \| 'critical'>` | ✅ | Impact level | -| **affectedSystems** | `string[]` | ✅ | Affected systems | -| **affectedUsers** | `number` | optional | Affected user count | -| **downtime** | `{ required: boolean }` | optional | Downtime information | - -### Nested Shape: `ChangeImpact.downtime` - -| Property | Type | Required | Description | -| :--- | :--- | :--- | :--- | -| **required** | `boolean` | ✅ | Downtime required | -| **durationMinutes** | `never` | optional | [REMOVED] `ChangeImpact.downtime.durationMinutes` was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — nothing ever read it: no engine scheduled a maintenance window or measured an outage against it, so the declared downtime was never enforced, announced or compared with what happened. Delete the key. There is no replacement, because no change-management engine exists. | - - ---- - -## ChangePriority - -### Allowed Values - -* `critical` -* `high` -* `medium` -* `low` - - ---- - -## ChangeRequest - -### Properties - -| Property | Type | Required | Description | -| :--- | :--- | :--- | :--- | -| **id** | `string` | ✅ | Change request ID | -| **title** | `string` | ✅ | Change title | -| **description** | `string` | ✅ | Change description | -| **type** | `Enum<'standard' \| 'normal' \| 'emergency' \| 'major'>` | ✅ | Change type | -| **priority** | `Enum<'critical' \| 'high' \| 'medium' \| 'low'>` | ✅ | Change priority | -| **status** | `Enum<'draft' \| 'submitted' \| 'in-review' \| 'approved' \| 'scheduled' \| 'in-progress' \| 'completed' \| 'failed' \| 'rolled-back' \| 'cancelled'>` | ✅ | Change status | -| **requestedBy** | `string` | ✅ | Requester user ID | -| **requestedAt** | `number` | ✅ | Request timestamp | -| **impact** | `{ level: Enum<'low' \| 'medium' \| 'high' \| 'critical'>; affectedSystems: string[]; affectedUsers?: number; downtime?: object }` | ✅ | Impact assessment | -| **implementation** | `{ description: string; steps: object[]; testing?: string }` | ✅ | Implementation plan | -| **rollbackPlan** | `{ description: string; steps: object[]; testProcedure?: string }` | ✅ | Rollback plan | -| **schedule** | `{ plannedStart: number; plannedEnd: number; actualStart?: number; actualEnd?: number }` | optional | Schedule | -| **securityImpact** | `{ assessed: boolean; riskLevel?: Enum<'none' \| 'low' \| 'medium' \| 'high' \| 'critical'>; affectedDataClassifications?: Enum<'pii' \| 'phi' \| 'pci' \| 'financial' \| 'confidential' \| 'internal' \| 'public'>[]; requiresSecurityApproval: boolean; … }` | optional | Security impact assessment per ISO 27001:2022 A.8.32 | -| **approval** | `{ required: boolean; approvers: object[] }` | optional | Approval workflow | -| **attachments** | `{ name: string; url: string }[]` | optional | Attachments | -| **metadata** | `Record` | optional | Custom metadata key-value pairs for extensibility | - -### Nested Shape: `ChangeRequest.impact` - -| Property | Type | Required | Description | -| :--- | :--- | :--- | :--- | -| **level** | `Enum<'low' \| 'medium' \| 'high' \| 'critical'>` | ✅ | Impact level | -| **affectedSystems** | `string[]` | ✅ | Affected systems | -| **affectedUsers** | `number` | optional | Affected user count | -| **downtime** | `{ required: boolean }` | optional | Downtime information | - -### Nested Shape: `ChangeRequest.implementation` - -| Property | Type | Required | Description | -| :--- | :--- | :--- | :--- | -| **description** | `string` | ✅ | Implementation description | -| **steps** | `{ order: number; description: string }[]` | ✅ | Implementation steps | -| **testing** | `string` | optional | Testing procedure | - -### Nested Shape: `ChangeRequest.rollbackPlan` - -| Property | Type | Required | Description | -| :--- | :--- | :--- | :--- | -| **description** | `string` | ✅ | Rollback description | -| **steps** | `{ order: number; description: string }[]` | ✅ | Rollback steps | -| **testProcedure** | `string` | optional | Test procedure | - -### Nested Shape: `ChangeRequest.schedule` - -| Property | Type | Required | Description | -| :--- | :--- | :--- | :--- | -| **plannedStart** | `number` | ✅ | Planned start time | -| **plannedEnd** | `number` | ✅ | Planned end time | -| **actualStart** | `number` | optional | Actual start time | -| **actualEnd** | `number` | optional | Actual end time | - -### Nested Shape: `ChangeRequest.securityImpact` - -| Property | Type | Required | Description | -| :--- | :--- | :--- | :--- | -| **assessed** | `boolean` | ✅ | Whether security impact has been assessed | -| **riskLevel** | `Enum<'none' \| 'low' \| 'medium' \| 'high' \| 'critical'>` | optional | Security risk level | -| **affectedDataClassifications** | `Enum<'pii' \| 'phi' \| 'pci' \| 'financial' \| 'confidential' \| 'internal' \| 'public'>[]` | optional | Affected data classifications | -| **requiresSecurityApproval** | `boolean` | optional (default: `false`) | Whether security team approval is required | -| **reviewedBy** | `string` | optional | Security reviewer user ID | -| **reviewedAt** | `number` | optional | Security review timestamp | -| **reviewNotes** | `string` | optional | Security review notes or conditions | - -### Nested Shape: `ChangeRequest.approval` - -| Property | Type | Required | Description | -| :--- | :--- | :--- | :--- | -| **required** | `boolean` | ✅ | Approval required | -| **approvers** | `{ userId: string; approvedAt?: number; comments?: string }[]` | ✅ | Approvers | - -### Nested Shape: `ChangeRequest.attachments[number]` - -| Property | Type | Required | Description | -| :--- | :--- | :--- | :--- | -| **name** | `string` | ✅ | Attachment name | -| **url** | `string` | ✅ | Attachment URL | - - ---- - -## ChangeStatus - -### Allowed Values - -* `draft` -* `submitted` -* `in-review` -* `approved` -* `scheduled` -* `in-progress` -* `completed` -* `failed` -* `rolled-back` -* `cancelled` - - ---- - -## ChangeType - -### Allowed Values - -* `standard` -* `normal` -* `emergency` -* `major` - - ---- - -## RollbackPlan - -### Properties - -| Property | Type | Required | Description | -| :--- | :--- | :--- | :--- | -| **description** | `string` | ✅ | Rollback description | -| **steps** | `{ order: number; description: string }[]` | ✅ | Rollback steps | -| **testProcedure** | `string` | optional | Test procedure | - -### Nested Shape: `RollbackPlan.steps[number]` - -| Property | Type | Required | Description | -| :--- | :--- | :--- | :--- | -| **order** | `number` | ✅ | Step order | -| **description** | `string` | ✅ | Step description | -| **estimatedMinutes** | `never` | optional | [REMOVED] `RollbackPlan.steps[].estimatedMinutes` was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — nothing ever read it: no engine executed, timed or summed rollback steps, so the estimate was never compared with anything. Delete the key from every step. There is no replacement, because no change-management engine exists. | - - ---- - diff --git a/content/docs/references/system/incident-response.mdx b/content/docs/references/system/incident-response.mdx deleted file mode 100644 index 3636251453..0000000000 --- a/content/docs/references/system/incident-response.mdx +++ /dev/null @@ -1,226 +0,0 @@ ---- -title: Incident Response -description: Incident Response protocol schemas ---- - -{/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs live in the module folders under content/docs/. */} - -Incident Response Protocol — ISO 27001:2022 (A.5.24–A.5.28) - -Defines schemas for information security event management including -incident classification, severity grading, response procedures, -and notification matrices. - -See also: https://www.iso.org/standard/27001 - - -**Source:** `packages/spec/src/system/incident-response.zod.ts` - - -## TypeScript Usage - -```typescript -import { IncidentSchema, IncidentCategorySchema, IncidentNotificationMatrixSchema, IncidentNotificationRuleSchema, IncidentResponsePhaseSchema, IncidentResponsePolicySchema, IncidentSeveritySchema, IncidentStatusSchema } from '@objectstack/spec/system'; -import type { Incident, IncidentCategory, IncidentNotificationMatrix, IncidentNotificationRule, IncidentResponsePhase, IncidentResponsePolicy, IncidentSeverity, IncidentStatus } from '@objectstack/spec/system'; - -// Validate data -const result = IncidentSchema.parse(data); -``` - ---- - -## Incident - -Security incident record per ISO 27001:2022 A.5.24–A.5.28 - -### Properties - -| Property | Type | Required | Description | -| :--- | :--- | :--- | :--- | -| **id** | `string` | ✅ | Unique incident identifier | -| **title** | `string` | ✅ | Incident title | -| **description** | `string` | ✅ | Detailed incident description | -| **severity** | `Enum<'critical' \| 'high' \| 'medium' \| 'low'>` | ✅ | Incident severity level | -| **category** | `Enum<'data_breach' \| 'malware' \| 'unauthorized_access' \| 'denial_of_service' \| 'social_engineering' \| 'insider_threat' \| 'physical_security' \| 'configuration_error' \| … +3 more>` | ✅ | Incident category | -| **status** | `Enum<'reported' \| 'triaged' \| 'investigating' \| 'containing' \| 'eradicating' \| 'recovering' \| 'resolved' \| 'closed'>` | ✅ | Current incident status | -| **reportedBy** | `string` | ✅ | Reporter user ID or system name | -| **reportedAt** | `number` | ✅ | Report timestamp | -| **detectedAt** | `number` | optional | Detection timestamp | -| **resolvedAt** | `number` | optional | Resolution timestamp | -| **affectedSystems** | `string[]` | ✅ | Affected systems | -| **affectedDataClassifications** | `Enum<'pii' \| 'phi' \| 'pci' \| 'financial' \| 'confidential' \| 'internal' \| 'public'>[]` | optional | Affected data classifications | -| **responsePhases** | `{ phase: Enum<'identification' \| 'containment' \| 'eradication' \| 'recovery' \| 'lessons_learned'>; description: string; assignedTo: string; completedAt?: number; … }[]` | optional | Incident response phases | -| **rootCause** | `string` | optional | Root cause analysis | -| **correctiveActions** | `string[]` | optional | Corrective actions taken or planned | -| **lessonsLearned** | `string` | optional | Lessons learned from the incident | -| **relatedChangeRequestIds** | `string[]` | optional | Related change request IDs | -| **metadata** | `Record` | optional | Custom metadata key-value pairs | - -### Allowed Values: `Incident.category` - -* `data_breach` -* `malware` -* `unauthorized_access` -* `denial_of_service` -* `social_engineering` -* `insider_threat` -* `physical_security` -* `configuration_error` -* `vulnerability_exploit` -* `policy_violation` -* `other` - -### Nested Shape: `Incident.responsePhases[number]` - -Incident response phase with timing and assignment - -| Property | Type | Required | Description | -| :--- | :--- | :--- | :--- | -| **phase** | `Enum<'identification' \| 'containment' \| 'eradication' \| 'recovery' \| 'lessons_learned'>` | ✅ | Response phase name | -| **description** | `string` | ✅ | Phase description and objectives | -| **assignedTo** | `string` | ✅ | Responsible team or role | -| **targetHours** | `never` | optional | [REMOVED] `IncidentResponsePhase.targetHours` was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — nothing ever read it: no engine tracked a response phase against a clock, so the target was never checked, never escalated and never reported. Delete the key. There is no replacement, because no incident-response engine exists to keep a phase deadline. | -| **completedAt** | `number` | optional | Actual completion timestamp | -| **notes** | `string` | optional | Phase notes and findings | - - ---- - -## IncidentCategory - -### Allowed Values - -* `data_breach` -* `malware` -* `unauthorized_access` -* `denial_of_service` -* `social_engineering` -* `insider_threat` -* `physical_security` -* `configuration_error` -* `vulnerability_exploit` -* `policy_violation` -* `other` - - ---- - -## IncidentNotificationMatrix - -Incident notification matrix with escalation policies - -### Properties - -| Property | Type | Required | Description | -| :--- | :--- | :--- | :--- | -| **rules** | `{ severity: Enum<'critical' \| 'high' \| 'medium' \| 'low'>; channels: Enum<'email' \| 'sms' \| 'slack' \| 'pagerduty' \| 'webhook'>[]; recipients: string[]; notifyRegulators: boolean }[]` | ✅ | Notification rules by severity level | -| **escalationTimeoutMinutes** | `never` | optional | [REMOVED] `IncidentNotificationMatrix.escalationTimeoutMinutes` was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — nothing ever read it: no engine walked `escalationChain` on a timer, so the timeout never fired, and its default of 30 minutes was materialized into every parsed matrix without ever being consulted. Delete the key. There is no replacement, because no escalation engine exists. | -| **escalationChain** | `string[]` | optional (default: `[]`) | Ordered escalation chain of roles | - -### Nested Shape: `IncidentNotificationMatrix.rules[number]` - -Incident notification rule per severity level - -| Property | Type | Required | Description | -| :--- | :--- | :--- | :--- | -| **severity** | `Enum<'critical' \| 'high' \| 'medium' \| 'low'>` | ✅ | Minimum severity to trigger notification | -| **channels** | `Enum<'email' \| 'sms' \| 'slack' \| 'pagerduty' \| 'webhook'>[]` | ✅ | Notification channels | -| **recipients** | `string[]` | ✅ | Roles or teams to notify | -| **withinMinutes** | `never` | optional | [REMOVED] `IncidentNotificationRule.withinMinutes` was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — nothing ever read it: no dispatcher sent an incident notification, so no deadline for one was ever measured. Delete the key. There is no replacement, because no incident-notification engine exists to keep the deadline. | -| **notifyRegulators** | `boolean` | optional (default: `false`) | Whether to notify regulatory authorities | -| **regulatorDeadlineHours** | `never` | optional | [REMOVED] `IncidentNotificationRule.regulatorDeadlineHours` was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — nothing ever read it: no engine notified a regulator, so a regulatory deadline declared here (a GDPR 72-hour window, for example) was never tracked, and a compliance author who wrote it held a promise the platform did not keep. Delete the key. There is no replacement, because no regulatory-notification engine exists. | - - ---- - -## IncidentNotificationRule - -Incident notification rule per severity level - -### Properties - -| Property | Type | Required | Description | -| :--- | :--- | :--- | :--- | -| **severity** | `Enum<'critical' \| 'high' \| 'medium' \| 'low'>` | ✅ | Minimum severity to trigger notification | -| **channels** | `Enum<'email' \| 'sms' \| 'slack' \| 'pagerduty' \| 'webhook'>[]` | ✅ | Notification channels | -| **recipients** | `string[]` | ✅ | Roles or teams to notify | -| **withinMinutes** | `never` | optional | [REMOVED] `IncidentNotificationRule.withinMinutes` was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — nothing ever read it: no dispatcher sent an incident notification, so no deadline for one was ever measured. Delete the key. There is no replacement, because no incident-notification engine exists to keep the deadline. | -| **notifyRegulators** | `boolean` | optional (default: `false`) | Whether to notify regulatory authorities | -| **regulatorDeadlineHours** | `never` | optional | [REMOVED] `IncidentNotificationRule.regulatorDeadlineHours` was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — nothing ever read it: no engine notified a regulator, so a regulatory deadline declared here (a GDPR 72-hour window, for example) was never tracked, and a compliance author who wrote it held a promise the platform did not keep. Delete the key. There is no replacement, because no regulatory-notification engine exists. | - - ---- - -## IncidentResponsePhase - -Incident response phase with timing and assignment - -### Properties - -| Property | Type | Required | Description | -| :--- | :--- | :--- | :--- | -| **phase** | `Enum<'identification' \| 'containment' \| 'eradication' \| 'recovery' \| 'lessons_learned'>` | ✅ | Response phase name | -| **description** | `string` | ✅ | Phase description and objectives | -| **assignedTo** | `string` | ✅ | Responsible team or role | -| **targetHours** | `never` | optional | [REMOVED] `IncidentResponsePhase.targetHours` was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — nothing ever read it: no engine tracked a response phase against a clock, so the target was never checked, never escalated and never reported. Delete the key. There is no replacement, because no incident-response engine exists to keep a phase deadline. | -| **completedAt** | `number` | optional | Actual completion timestamp | -| **notes** | `string` | optional | Phase notes and findings | - - ---- - -## IncidentResponsePolicy - -Organization-level incident response policy per ISO 27001:2022 - -### Properties - -| Property | Type | Required | Description | -| :--- | :--- | :--- | :--- | -| **enabled** | `boolean` | optional (default: `true`) | Enable incident response management | -| **notificationMatrix** | `{ rules: object[]; escalationChain: string[] }` | ✅ | Notification and escalation matrix | -| **defaultResponseTeam** | `string` | ✅ | Default incident response team or role | -| **triageDeadlineHours** | `never` | optional | [REMOVED] `IncidentResponsePolicy.triageDeadlineHours` was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — nothing ever read it: no engine timed the interval between detection and triage, so the deadline was never kept, and its default of 1 hour was materialized into every parsed policy without ever being consulted. Delete the key. There is no replacement, because no incident-response engine exists to keep a triage window. | -| **requirePostIncidentReview** | `boolean` | optional (default: `true`) | Require post-incident review for all incidents | -| **regulatoryNotificationThreshold** | `Enum<'critical' \| 'high' \| 'medium' \| 'low'>` | optional (default: `"high"`) | Minimum severity requiring regulatory notification | -| **retentionDays** | `never` | optional | [REMOVED] `IncidentResponsePolicy.retentionDays` was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — nothing ever read it: no sweeper deleted incident records on a schedule, so the retention period was never applied, and its default of 2555 days was materialized into every parsed policy without ever being consulted. Delete the key. Retention on this platform is the object-level `lifecycle` block (ADR-0057), enforced by the LifecycleService over the records of an object — declare it on the object that stores incident records, not on this policy document. | - -### Nested Shape: `IncidentResponsePolicy.notificationMatrix` - -| Property | Type | Required | Description | -| :--- | :--- | :--- | :--- | -| **rules** | `{ severity: Enum<'critical' \| 'high' \| 'medium' \| 'low'>; channels: Enum<'email' \| 'sms' \| 'slack' \| 'pagerduty' \| 'webhook'>[]; recipients: string[]; notifyRegulators: boolean }[]` | ✅ | Notification rules by severity level | -| **escalationTimeoutMinutes** | `never` | optional | [REMOVED] `IncidentNotificationMatrix.escalationTimeoutMinutes` was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — nothing ever read it: no engine walked `escalationChain` on a timer, so the timeout never fired, and its default of 30 minutes was materialized into every parsed matrix without ever being consulted. Delete the key. There is no replacement, because no escalation engine exists. | -| **escalationChain** | `string[]` | optional (default: `[]`) | Ordered escalation chain of roles | - - ---- - -## IncidentSeverity - -### Allowed Values - -* `critical` -* `high` -* `medium` -* `low` - - ---- - -## IncidentStatus - -### Allowed Values - -* `reported` -* `triaged` -* `investigating` -* `containing` -* `eradicating` -* `recovering` -* `resolved` -* `closed` - - ---- - diff --git a/content/docs/references/system/index.mdx b/content/docs/references/system/index.mdx index 58954c3400..6a0036db80 100644 --- a/content/docs/references/system/index.mdx +++ b/content/docs/references/system/index.mdx @@ -12,7 +12,6 @@ This section contains all protocol schemas for the system layer of ObjectStack. - @@ -23,7 +22,6 @@ This section contains all protocol schemas for the system layer of ObjectStack. - @@ -41,7 +39,6 @@ This section contains all protocol schemas for the system layer of ObjectStack. - diff --git a/content/docs/references/system/meta.json b/content/docs/references/system/meta.json index d2bd8a631d..d61a873e56 100644 --- a/content/docs/references/system/meta.json +++ b/content/docs/references/system/meta.json @@ -30,13 +30,10 @@ "metrics", "tracing", "---Security & Compliance---", - "change-management", "disaster-recovery", "encryption", - "incident-response", "security-context", "supplier-security", - "training", "---Content & Collaboration---", "book", "collaboration", diff --git a/content/docs/references/system/training.mdx b/content/docs/references/system/training.mdx deleted file mode 100644 index 2e2091f89c..0000000000 --- a/content/docs/references/system/training.mdx +++ /dev/null @@ -1,152 +0,0 @@ ---- -title: Training -description: Training protocol schemas ---- - -{/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs live in the module folders under content/docs/. */} - -Information Security Training Protocol — ISO 27001:2022 (A.6.3) - -Defines schemas for security awareness and training management including -course definitions, completion tracking, and organizational training plans. - -See also: https://www.iso.org/standard/27001 - - -**Source:** `packages/spec/src/system/training.zod.ts` - - -## TypeScript Usage - -```typescript -import { TrainingCategorySchema, TrainingCompletionStatusSchema, TrainingCourseSchema, TrainingPlanSchema, TrainingRecordSchema } from '@objectstack/spec/system'; -import type { TrainingCategory, TrainingCompletionStatus, TrainingCourse, TrainingPlan, TrainingRecord } from '@objectstack/spec/system'; - -// Validate data -const result = TrainingCategorySchema.parse(data); -``` - ---- - -## TrainingCategory - -### Allowed Values - -* `security_awareness` -* `data_protection` -* `incident_response` -* `access_control` -* `phishing_awareness` -* `compliance` -* `secure_development` -* `physical_security` -* `business_continuity` -* `other` - - ---- - -## TrainingCompletionStatus - -### Allowed Values - -* `not_started` -* `in_progress` -* `completed` -* `failed` -* `expired` - - ---- - -## TrainingCourse - -Security training course definition - -### Properties - -| Property | Type | Required | Description | -| :--- | :--- | :--- | :--- | -| **id** | `string` | ✅ | Unique course identifier | -| **title** | `string` | ✅ | Course title | -| **description** | `string` | ✅ | Course description and learning objectives | -| **category** | `Enum<'security_awareness' \| 'data_protection' \| 'incident_response' \| 'access_control' \| 'phishing_awareness' \| 'compliance' \| 'secure_development' \| … +3 more>` | ✅ | Training category | -| **durationMinutes** | `never` | optional | [REMOVED] `TrainingCourse.durationMinutes` was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — nothing ever read it: no training engine scheduled, timed or reported a course, so the duration was a number the platform displayed nowhere and acted on never. Delete the key. There is no replacement, because no training-management engine exists. | -| **mandatory** | `boolean` | optional (default: `false`) | Whether training is mandatory | -| **targetRoles** | `string[]` | ✅ | Target roles or groups | -| **validityDays** | `never` | optional | [REMOVED] `TrainingCourse.validityDays` was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — nothing ever read it: no engine computed a certification expiry from it, so a certificate declared valid for 365 days never expired on the platform and never triggered recertification. Delete the key. There is no replacement, because no training-management engine exists to keep a validity window. | -| **passingScore** | `number` | optional | Minimum passing score percentage | -| **version** | `string` | optional | Course content version | - -### Allowed Values: `TrainingCourse.category` - -* `security_awareness` -* `data_protection` -* `incident_response` -* `access_control` -* `phishing_awareness` -* `compliance` -* `secure_development` -* `physical_security` -* `business_continuity` -* `other` - - ---- - -## TrainingPlan - -Organizational training plan per ISO 27001:2022 A.6.3 - -### Properties - -| Property | Type | Required | Description | -| :--- | :--- | :--- | :--- | -| **enabled** | `boolean` | optional (default: `true`) | Enable training management | -| **courses** | `{ id: string; title: string; description: string; category: Enum<'security_awareness' \| 'data_protection' \| 'incident_response' \| …>; … }[]` | ✅ | Training courses | -| **recertificationIntervalDays** | `never` | optional | [REMOVED] `TrainingPlan.recertificationIntervalDays` was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — nothing ever read it: no engine re-assigned training on an interval, so the interval never elapsed into anything, and its default of 365 days was materialized into every parsed plan without ever being consulted. Delete the key. There is no replacement, because no training-management engine exists. | -| **trackCompletion** | `boolean` | optional (default: `true`) | Track training completion for compliance | -| **gracePeriodDays** | `never` | optional | [REMOVED] `TrainingPlan.gracePeriodDays` was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — nothing ever read it: no engine escalated an expired certification, so a grace period before that escalation had nothing to delay, and its default of 30 days was materialized into every parsed plan without ever being consulted. Delete the key. There is no replacement, because no training-management engine exists. | -| **sendReminders** | `boolean` | optional (default: `true`) | Send reminders for upcoming training deadlines | -| **reminderDaysBefore** | `never` | optional | [REMOVED] `TrainingPlan.reminderDaysBefore` was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — nothing ever read it: no engine sent a training reminder, so the lead time was never counted down, and its default of 14 days was materialized into every parsed plan without ever being consulted. Delete the key. There is no replacement, because no training-reminder engine exists. | - -### Nested Shape: `TrainingPlan.courses[number]` - -Security training course definition - -| Property | Type | Required | Description | -| :--- | :--- | :--- | :--- | -| **id** | `string` | ✅ | Unique course identifier | -| **title** | `string` | ✅ | Course title | -| **description** | `string` | ✅ | Course description and learning objectives | -| **category** | `Enum<'security_awareness' \| 'data_protection' \| 'incident_response' \| …>` | ✅ | Training category | -| **durationMinutes** | `never` | optional | [REMOVED] `TrainingCourse.durationMinutes` was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — nothing ever read it: no training engine scheduled, timed or reported a course, so the duration was a number the platform displayed nowhere and acted on never. Delete the key. There is no replacement, because no training-management engine exists. | -| **mandatory** | `boolean` | optional (default: `false`) | Whether training is mandatory | -| **targetRoles** | `string[]` | ✅ | Target roles or groups | -| **validityDays** | `never` | optional | [REMOVED] `TrainingCourse.validityDays` was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — nothing ever read it: no engine computed a certification expiry from it, so a certificate declared valid for 365 days never expired on the platform and never triggered recertification. Delete the key. There is no replacement, because no training-management engine exists to keep a validity window. | -| **passingScore** | `number` | optional | Minimum passing score percentage | -| **version** | `string` | optional | Course content version | - - ---- - -## TrainingRecord - -Individual training completion record - -### Properties - -| Property | Type | Required | Description | -| :--- | :--- | :--- | :--- | -| **courseId** | `string` | ✅ | Training course identifier | -| **userId** | `string` | ✅ | User identifier | -| **status** | `Enum<'not_started' \| 'in_progress' \| 'completed' \| 'failed' \| 'expired'>` | ✅ | Training completion status | -| **assignedAt** | `number` | ✅ | Assignment timestamp | -| **completedAt** | `number` | optional | Completion timestamp | -| **score** | `number` | optional | Assessment score percentage | -| **expiresAt** | `number` | optional | Certification expiry timestamp | -| **notes** | `string` | optional | Training notes or comments | - - ---- - diff --git a/docs/audits/2026-07-unknown-key-strictness-ledger.counts.md b/docs/audits/2026-07-unknown-key-strictness-ledger.counts.md index 5bae84621a..c0ebcf5cec 100644 --- a/docs/audits/2026-07-unknown-key-strictness-ledger.counts.md +++ b/docs/audits/2026-07-unknown-key-strictness-ledger.counts.md @@ -264,4 +264,4 @@ directory rather than per file. | `kernel/` | 260 | | `qa/` | 6 | | `shared/` | 20 | -| `system/` | 370 | +| `system/` | 350 | diff --git a/packages/spec/api-surface/system.json b/packages/spec/api-surface/system.json index 39ed8694f4..d1b46c83aa 100644 --- a/packages/spec/api-surface/system.json +++ b/packages/spec/api-surface/system.json @@ -110,20 +110,9 @@ "CacheWarmup (type)", "CacheWarmupParsed (type)", "CacheWarmupSchema (const)", - "ChangeImpact (type)", - "ChangeImpactSchema (const)", - "ChangePriority (type)", - "ChangePrioritySchema (const)", - "ChangeRequest (type)", - "ChangeRequestParsed (type)", - "ChangeRequestSchema (const)", "ChangeSet (type)", "ChangeSetParsed (type)", "ChangeSetSchema (const)", - "ChangeStatus (type)", - "ChangeStatusSchema (const)", - "ChangeType (type)", - "ChangeTypeSchema (const)", "CollaborationMode (type)", "CollaborationSession (type)", "CollaborationSessionConfig (type)", @@ -276,25 +265,6 @@ "HttpDestinationConfigSchema (const)", "ISettingsCapability (interface)", "ISettingsClient (interface)", - "Incident (type)", - "IncidentCategory (type)", - "IncidentCategorySchema (const)", - "IncidentNotificationMatrix (type)", - "IncidentNotificationMatrixParsed (type)", - "IncidentNotificationMatrixSchema (const)", - "IncidentNotificationRule (type)", - "IncidentNotificationRuleParsed (type)", - "IncidentNotificationRuleSchema (const)", - "IncidentResponsePhase (type)", - "IncidentResponsePhaseSchema (const)", - "IncidentResponsePolicy (type)", - "IncidentResponsePolicyParsed (type)", - "IncidentResponsePolicySchema (const)", - "IncidentSchema (const)", - "IncidentSeverity (type)", - "IncidentSeveritySchema (const)", - "IncidentStatus (type)", - "IncidentStatusSchema (const)", "IntervalSchedule (type)", "IntervalScheduleSchema (const)", "Job (type)", @@ -545,8 +515,6 @@ "RetryPolicy (type)", "RetryPolicyParsed (type)", "RetryPolicySchema (const)", - "RollbackPlan (type)", - "RollbackPlanSchema (const)", "RouteHandlerMetadata (type)", "RouteHandlerMetadataParsed (type)", "RouteHandlerMetadataSchema (const)", @@ -728,18 +696,6 @@ "TracingConfig (type)", "TracingConfigParsed (type)", "TracingConfigSchema (const)", - "TrainingCategory (type)", - "TrainingCategorySchema (const)", - "TrainingCompletionStatus (type)", - "TrainingCompletionStatusSchema (const)", - "TrainingCourse (type)", - "TrainingCourseParsed (type)", - "TrainingCourseSchema (const)", - "TrainingPlan (type)", - "TrainingPlanParsed (type)", - "TrainingPlanSchema (const)", - "TrainingRecord (type)", - "TrainingRecordSchema (const)", "TranslateDocumentOptions (interface)", "TranslationBundle (type)", "TranslationBundleSchema (const)", diff --git a/packages/spec/authorable-defaults/data.json b/packages/spec/authorable-defaults/data.json index 21ccd54454..d49786dcd1 100644 --- a/packages/spec/authorable-defaults/data.json +++ b/packages/spec/authorable-defaults/data.json @@ -27,8 +27,6 @@ "data/DocumentSchemaValidation:enabled = false", "data/DocumentVersion:isLatest = false", "data/ESignatureConfig:enabled = false", - "data/ESignatureConfig:expirationDays = 30", - "data/ESignatureConfig:reminderDays = 7", "data/EngineDeleteOptions:multi = false", "data/EngineUpdateOptions:multi = false", "data/EngineUpdateOptions:returning = false", diff --git a/packages/spec/authorable-defaults/system.json b/packages/spec/authorable-defaults/system.json index 5ab5ff1c11..197f904959 100644 --- a/packages/spec/authorable-defaults/system.json +++ b/packages/spec/authorable-defaults/system.json @@ -108,11 +108,6 @@ "system/FileDestinationConfig:encoding = \"utf8\"", "system/HttpDestinationConfig:method = \"POST\"", "system/HttpDestinationConfig:timeout = 30000", - "system/IncidentNotificationMatrix:escalationChain = []", - "system/IncidentNotificationRule:notifyRegulators = false", - "system/IncidentResponsePolicy:enabled = true", - "system/IncidentResponsePolicy:regulatoryNotificationThreshold = \"high\"", - "system/IncidentResponsePolicy:requirePostIncidentReview = true", "system/Job:enabled = true", "system/KeyRotationPolicy:autoRotate = true", "system/KeyRotationPolicy:enabled = false", @@ -253,10 +248,6 @@ "system/TraceSamplingConfig:rules = []", "system/TracingConfig:enabled = true", "system/TracingConfig:propagation = {\"extract\":true,\"formats\":[\"w3c\"],\"inject\":true}", - "system/TracingConfig:traceIdGenerator = \"random\"", - "system/TrainingCourse:mandatory = false", - "system/TrainingPlan:enabled = true", - "system/TrainingPlan:sendReminders = true", - "system/TrainingPlan:trackCompletion = true" + "system/TracingConfig:traceIdGenerator = \"random\"" ] } diff --git a/packages/spec/authorable-surface/data.json b/packages/spec/authorable-surface/data.json index 7718349fee..e9c59aa7d9 100644 --- a/packages/spec/authorable-surface/data.json +++ b/packages/spec/authorable-surface/data.json @@ -281,9 +281,9 @@ "data/DroppedFieldsEvent:object", "data/DroppedFieldsEvent:reason", "data/ESignatureConfig:enabled", - "data/ESignatureConfig:expirationDays", + "data/ESignatureConfig:expirationDays [RETIRED]", "data/ESignatureConfig:provider", - "data/ESignatureConfig:reminderDays", + "data/ESignatureConfig:reminderDays [RETIRED]", "data/ESignatureConfig:signers", "data/EngineAggregateOptions:aggregations", "data/EngineAggregateOptions:context", diff --git a/packages/spec/authorable-surface/system.json b/packages/spec/authorable-surface/system.json index a223202d98..fb3ead9999 100644 --- a/packages/spec/authorable-surface/system.json +++ b/packages/spec/authorable-surface/system.json @@ -198,26 +198,6 @@ "system/CacheWarmup:patterns", "system/CacheWarmup:schedule", "system/CacheWarmup:strategy", - "system/ChangeImpact:affectedSystems", - "system/ChangeImpact:affectedUsers", - "system/ChangeImpact:downtime", - "system/ChangeImpact:level", - "system/ChangeRequest:approval", - "system/ChangeRequest:attachments", - "system/ChangeRequest:description", - "system/ChangeRequest:id", - "system/ChangeRequest:impact", - "system/ChangeRequest:implementation", - "system/ChangeRequest:metadata", - "system/ChangeRequest:priority", - "system/ChangeRequest:requestedAt", - "system/ChangeRequest:requestedBy", - "system/ChangeRequest:rollbackPlan", - "system/ChangeRequest:schedule", - "system/ChangeRequest:securityImpact", - "system/ChangeRequest:status", - "system/ChangeRequest:title", - "system/ChangeRequest:type", "system/ChangeSet:author", "system/ChangeSet:createdAt", "system/ChangeSet:dependencies", @@ -479,46 +459,6 @@ "system/HttpDestinationConfig:retry", "system/HttpDestinationConfig:timeout", "system/HttpDestinationConfig:url", - "system/Incident:affectedDataClassifications", - "system/Incident:affectedSystems", - "system/Incident:category", - "system/Incident:correctiveActions", - "system/Incident:description", - "system/Incident:detectedAt", - "system/Incident:id", - "system/Incident:lessonsLearned", - "system/Incident:metadata", - "system/Incident:relatedChangeRequestIds", - "system/Incident:reportedAt", - "system/Incident:reportedBy", - "system/Incident:resolvedAt", - "system/Incident:responsePhases", - "system/Incident:rootCause", - "system/Incident:severity", - "system/Incident:status", - "system/Incident:title", - "system/IncidentNotificationMatrix:escalationChain", - "system/IncidentNotificationMatrix:escalationTimeoutMinutes [RETIRED]", - "system/IncidentNotificationMatrix:rules", - "system/IncidentNotificationRule:channels", - "system/IncidentNotificationRule:notifyRegulators", - "system/IncidentNotificationRule:recipients", - "system/IncidentNotificationRule:regulatorDeadlineHours [RETIRED]", - "system/IncidentNotificationRule:severity", - "system/IncidentNotificationRule:withinMinutes [RETIRED]", - "system/IncidentResponsePhase:assignedTo", - "system/IncidentResponsePhase:completedAt", - "system/IncidentResponsePhase:description", - "system/IncidentResponsePhase:notes", - "system/IncidentResponsePhase:phase", - "system/IncidentResponsePhase:targetHours [RETIRED]", - "system/IncidentResponsePolicy:defaultResponseTeam", - "system/IncidentResponsePolicy:enabled", - "system/IncidentResponsePolicy:notificationMatrix", - "system/IncidentResponsePolicy:regulatoryNotificationThreshold", - "system/IncidentResponsePolicy:requirePostIncidentReview", - "system/IncidentResponsePolicy:retentionDays [RETIRED]", - "system/IncidentResponsePolicy:triageDeadlineHours [RETIRED]", "system/IntervalSchedule:intervalMs", "system/IntervalSchedule:type", "system/Job:_lock", @@ -997,9 +937,6 @@ "system/RetryPolicy:maxRetries", "system/RetryPolicy:maxRetryDelayMs", "system/RetryPolicy:retryDelayMs [RETIRED]", - "system/RollbackPlan:description", - "system/RollbackPlan:steps", - "system/RollbackPlan:testProcedure", "system/RouteHandlerMetadata:handler", "system/RouteHandlerMetadata:metadata", "system/RouteHandlerMetadata:method", @@ -1303,31 +1240,6 @@ "system/TracingConfig:sampling", "system/TracingConfig:spanLimits", "system/TracingConfig:traceIdGenerator", - "system/TrainingCourse:category", - "system/TrainingCourse:description", - "system/TrainingCourse:durationMinutes [RETIRED]", - "system/TrainingCourse:id", - "system/TrainingCourse:mandatory", - "system/TrainingCourse:passingScore", - "system/TrainingCourse:targetRoles", - "system/TrainingCourse:title", - "system/TrainingCourse:validityDays [RETIRED]", - "system/TrainingCourse:version", - "system/TrainingPlan:courses", - "system/TrainingPlan:enabled", - "system/TrainingPlan:gracePeriodDays [RETIRED]", - "system/TrainingPlan:recertificationIntervalDays [RETIRED]", - "system/TrainingPlan:reminderDaysBefore [RETIRED]", - "system/TrainingPlan:sendReminders", - "system/TrainingPlan:trackCompletion", - "system/TrainingRecord:assignedAt", - "system/TrainingRecord:completedAt", - "system/TrainingRecord:courseId", - "system/TrainingRecord:expiresAt", - "system/TrainingRecord:notes", - "system/TrainingRecord:score", - "system/TrainingRecord:status", - "system/TrainingRecord:userId", "system/TranslationConfig:defaultLocale", "system/TranslationConfig:fallbackLocale", "system/TranslationConfig:supportedLocales", diff --git a/packages/spec/declaration-map/system.json b/packages/spec/declaration-map/system.json index 817d308ecd..0a2a1af28d 100644 --- a/packages/spec/declaration-map/system.json +++ b/packages/spec/declaration-map/system.json @@ -74,18 +74,8 @@ "CacheTierSchema": "system/CacheTier", "CacheWarmup": "system/CacheWarmup", "CacheWarmupSchema": "system/CacheWarmup", - "ChangeImpact": "system/ChangeImpact", - "ChangeImpactSchema": "system/ChangeImpact", - "ChangePriority": "system/ChangePriority", - "ChangePrioritySchema": "system/ChangePriority", - "ChangeRequest": "system/ChangeRequest", - "ChangeRequestSchema": "system/ChangeRequest", "ChangeSet": "system/ChangeSet", "ChangeSetSchema": "system/ChangeSet", - "ChangeStatus": "system/ChangeStatus", - "ChangeStatusSchema": "system/ChangeStatus", - "ChangeType": "system/ChangeType", - "ChangeTypeSchema": "system/ChangeType", "CollaborationMode": "system/CollaborationMode", "CollaborationSession": "system/CollaborationSession", "CollaborationSessionConfig": "system/CollaborationSessionConfig", @@ -190,22 +180,6 @@ "HistogramBucketConfigSchema": "system/HistogramBucketConfig", "HttpDestinationConfig": "system/HttpDestinationConfig", "HttpDestinationConfigSchema": "system/HttpDestinationConfig", - "Incident": "system/Incident", - "IncidentCategory": "system/IncidentCategory", - "IncidentCategorySchema": "system/IncidentCategory", - "IncidentNotificationMatrix": "system/IncidentNotificationMatrix", - "IncidentNotificationMatrixSchema": "system/IncidentNotificationMatrix", - "IncidentNotificationRule": "system/IncidentNotificationRule", - "IncidentNotificationRuleSchema": "system/IncidentNotificationRule", - "IncidentResponsePhase": "system/IncidentResponsePhase", - "IncidentResponsePhaseSchema": "system/IncidentResponsePhase", - "IncidentResponsePolicy": "system/IncidentResponsePolicy", - "IncidentResponsePolicySchema": "system/IncidentResponsePolicy", - "IncidentSchema": "system/Incident", - "IncidentSeverity": "system/IncidentSeverity", - "IncidentSeveritySchema": "system/IncidentSeverity", - "IncidentStatus": "system/IncidentStatus", - "IncidentStatusSchema": "system/IncidentStatus", "IntervalSchedule": "system/IntervalSchedule", "IntervalScheduleSchema": "system/IntervalSchedule", "Job": "system/Job", @@ -376,8 +350,6 @@ "ResolvedGroupSchema": "system/ResolvedGroup", "ResolvedSettingValue": "system/ResolvedSettingValue", "ResolvedSettingValueSchema": "system/ResolvedSettingValue", - "RollbackPlan": "system/RollbackPlan", - "RollbackPlanSchema": "system/RollbackPlan", "RouteHandlerMetadata": "system/RouteHandlerMetadata", "RouteHandlerMetadataSchema": "system/RouteHandlerMetadata", "RowLevelIsolationStrategy": "system/RowLevelIsolationStrategy", @@ -513,16 +485,6 @@ "TraceStateSchema": "system/TraceState", "TracingConfig": "system/TracingConfig", "TracingConfigSchema": "system/TracingConfig", - "TrainingCategory": "system/TrainingCategory", - "TrainingCategorySchema": "system/TrainingCategory", - "TrainingCompletionStatus": "system/TrainingCompletionStatus", - "TrainingCompletionStatusSchema": "system/TrainingCompletionStatus", - "TrainingCourse": "system/TrainingCourse", - "TrainingCourseSchema": "system/TrainingCourse", - "TrainingPlan": "system/TrainingPlan", - "TrainingPlanSchema": "system/TrainingPlan", - "TrainingRecord": "system/TrainingRecord", - "TrainingRecordSchema": "system/TrainingRecord", "TranslationBundle": "system/TranslationBundle", "TranslationBundleSchema": "system/TranslationBundle", "TranslationConfig": "system/TranslationConfig", diff --git a/packages/spec/export-origins/system.json b/packages/spec/export-origins/system.json index e00823c986..746dd2bf1e 100644 --- a/packages/spec/export-origins/system.json +++ b/packages/spec/export-origins/system.json @@ -110,20 +110,9 @@ "CacheWarmup": "src/system/cache.zod.ts#CacheWarmup (type)", "CacheWarmupParsed": "src/system/cache.zod.ts#CacheWarmupParsed (type)", "CacheWarmupSchema": "src/system/cache.zod.ts#CacheWarmupSchema (const)", - "ChangeImpact": "src/system/change-management.zod.ts#ChangeImpact (type)", - "ChangeImpactSchema": "src/system/change-management.zod.ts#ChangeImpactSchema (const)", - "ChangePriority": "src/system/change-management.zod.ts#ChangePriority (type)", - "ChangePrioritySchema": "src/system/change-management.zod.ts#ChangePrioritySchema (const)", - "ChangeRequest": "src/system/change-management.zod.ts#ChangeRequest (type)", - "ChangeRequestParsed": "src/system/change-management.zod.ts#ChangeRequestParsed (type)", - "ChangeRequestSchema": "src/system/change-management.zod.ts#ChangeRequestSchema (const)", "ChangeSet": "src/system/migration.zod.ts#ChangeSet (type)", "ChangeSetParsed": "src/system/migration.zod.ts#ChangeSetParsed (type)", "ChangeSetSchema": "src/system/migration.zod.ts#ChangeSetSchema (const)", - "ChangeStatus": "src/system/change-management.zod.ts#ChangeStatus (type)", - "ChangeStatusSchema": "src/system/change-management.zod.ts#ChangeStatusSchema (const)", - "ChangeType": "src/system/change-management.zod.ts#ChangeType (type)", - "ChangeTypeSchema": "src/system/change-management.zod.ts#ChangeTypeSchema (const)", "CollaborationMode": "src/system/collaboration.zod.ts#CollaborationMode (type)", "CollaborationSession": "src/system/collaboration.zod.ts#CollaborationSession (type)", "CollaborationSessionConfig": "src/system/collaboration.zod.ts#CollaborationSessionConfig (type)", @@ -276,25 +265,6 @@ "HttpDestinationConfigSchema": "src/system/logging.zod.ts#HttpDestinationConfigSchema (const)", "ISettingsCapability": "src/system/settings-client.zod.ts#ISettingsCapability (interface)", "ISettingsClient": "src/system/settings-client.zod.ts#ISettingsClient (interface)", - "Incident": "src/system/incident-response.zod.ts#Incident (type)", - "IncidentCategory": "src/system/incident-response.zod.ts#IncidentCategory (type)", - "IncidentCategorySchema": "src/system/incident-response.zod.ts#IncidentCategorySchema (const)", - "IncidentNotificationMatrix": "src/system/incident-response.zod.ts#IncidentNotificationMatrix (type)", - "IncidentNotificationMatrixParsed": "src/system/incident-response.zod.ts#IncidentNotificationMatrixParsed (type)", - "IncidentNotificationMatrixSchema": "src/system/incident-response.zod.ts#IncidentNotificationMatrixSchema (const)", - "IncidentNotificationRule": "src/system/incident-response.zod.ts#IncidentNotificationRule (type)", - "IncidentNotificationRuleParsed": "src/system/incident-response.zod.ts#IncidentNotificationRuleParsed (type)", - "IncidentNotificationRuleSchema": "src/system/incident-response.zod.ts#IncidentNotificationRuleSchema (const)", - "IncidentResponsePhase": "src/system/incident-response.zod.ts#IncidentResponsePhase (type)", - "IncidentResponsePhaseSchema": "src/system/incident-response.zod.ts#IncidentResponsePhaseSchema (const)", - "IncidentResponsePolicy": "src/system/incident-response.zod.ts#IncidentResponsePolicy (type)", - "IncidentResponsePolicyParsed": "src/system/incident-response.zod.ts#IncidentResponsePolicyParsed (type)", - "IncidentResponsePolicySchema": "src/system/incident-response.zod.ts#IncidentResponsePolicySchema (const)", - "IncidentSchema": "src/system/incident-response.zod.ts#IncidentSchema (const)", - "IncidentSeverity": "src/system/incident-response.zod.ts#IncidentSeverity (type)", - "IncidentSeveritySchema": "src/system/incident-response.zod.ts#IncidentSeveritySchema (const)", - "IncidentStatus": "src/system/incident-response.zod.ts#IncidentStatus (type)", - "IncidentStatusSchema": "src/system/incident-response.zod.ts#IncidentStatusSchema (const)", "IntervalSchedule": "src/system/job.zod.ts#IntervalSchedule (type)", "IntervalScheduleSchema": "src/system/job.zod.ts#IntervalScheduleSchema (const)", "Job": "src/system/job.zod.ts#Job (type)", @@ -545,8 +515,6 @@ "RetryPolicy": "src/shared/retry-policy.zod.ts#RetryPolicy (type)", "RetryPolicyParsed": "src/shared/retry-policy.zod.ts#RetryPolicyParsed (type)", "RetryPolicySchema": "src/shared/retry-policy.zod.ts#RetryPolicySchema (const)", - "RollbackPlan": "src/system/change-management.zod.ts#RollbackPlan (type)", - "RollbackPlanSchema": "src/system/change-management.zod.ts#RollbackPlanSchema (const)", "RouteHandlerMetadata": "src/system/http-server.zod.ts#RouteHandlerMetadata (type)", "RouteHandlerMetadataParsed": "src/system/http-server.zod.ts#RouteHandlerMetadataParsed (type)", "RouteHandlerMetadataSchema": "src/system/http-server.zod.ts#RouteHandlerMetadataSchema (const)", @@ -728,18 +696,6 @@ "TracingConfig": "src/system/tracing.zod.ts#TracingConfig (type)", "TracingConfigParsed": "src/system/tracing.zod.ts#TracingConfigParsed (type)", "TracingConfigSchema": "src/system/tracing.zod.ts#TracingConfigSchema (const)", - "TrainingCategory": "src/system/training.zod.ts#TrainingCategory (type)", - "TrainingCategorySchema": "src/system/training.zod.ts#TrainingCategorySchema (const)", - "TrainingCompletionStatus": "src/system/training.zod.ts#TrainingCompletionStatus (type)", - "TrainingCompletionStatusSchema": "src/system/training.zod.ts#TrainingCompletionStatusSchema (const)", - "TrainingCourse": "src/system/training.zod.ts#TrainingCourse (type)", - "TrainingCourseParsed": "src/system/training.zod.ts#TrainingCourseParsed (type)", - "TrainingCourseSchema": "src/system/training.zod.ts#TrainingCourseSchema (const)", - "TrainingPlan": "src/system/training.zod.ts#TrainingPlan (type)", - "TrainingPlanParsed": "src/system/training.zod.ts#TrainingPlanParsed (type)", - "TrainingPlanSchema": "src/system/training.zod.ts#TrainingPlanSchema (const)", - "TrainingRecord": "src/system/training.zod.ts#TrainingRecord (type)", - "TrainingRecordSchema": "src/system/training.zod.ts#TrainingRecordSchema (const)", "TranslateDocumentOptions": "src/system/i18n-resolver.ts#TranslateDocumentOptions (interface)", "TranslationBundle": "src/system/translation.zod.ts#TranslationBundle (type)", "TranslationBundleSchema": "src/system/translation.zod.ts#TranslationBundleSchema (const)", From baf53807b1311ae6bfb2a64515eb1b4c53aa9555 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 5 Sep 2026 15:37:36 +0000 Subject: [PATCH 3/6] test(spec): name the real survivors and the shards' three row spellings in the compliance-families pin Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01M59rPZZFzqhfMUPFqqZTkf --- .../system/compliance-families-retirement.test.ts | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/packages/spec/src/system/compliance-families-retirement.test.ts b/packages/spec/src/system/compliance-families-retirement.test.ts index 31b4440290..40e1c4379e 100644 --- a/packages/spec/src/system/compliance-families-retirement.test.ts +++ b/packages/spec/src/system/compliance-families-retirement.test.ts @@ -143,7 +143,7 @@ const HISTORY_SEMANTIC_IDS = [ */ const MUST_SURVIVE_KERNEL = ['MetadataChangeTypeSchema', 'MetadataChangeType'] as const; /** System-entry neighbours that stay — the ones the retired files imported from. */ -const MUST_SURVIVE_SYSTEM = ['DataClassificationSchema', 'SecurityContextSchema', 'ChangeSetSchema'] as const; +const MUST_SURVIVE_SYSTEM = ['DataClassificationSchema', 'ComplianceFrameworkSchema', 'ChangeSetSchema'] as const; const SPEC_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..'); const SRC_ROOT = path.join(SPEC_ROOT, 'src'); @@ -191,11 +191,14 @@ describe('[#15513] system/ compliance families retirement — the public surface expect(fs.existsSync(path.join(SRC_ROOT, 'system', 'security-context.zod.ts'))).toBe(true); const importers: string[] = []; + const self = fileURLToPath(import.meta.url); const walk = (dir: string) => { for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { const full = path.join(dir, entry.name); if (entry.isDirectory()) walk(full); - else if (entry.name.endsWith('.ts')) { + // This file quotes a resurrected import as its matcher's anti-vacuity + // fixture below, so it is the one file the importer walk must skip. + else if (entry.name.endsWith('.ts') && full !== self) { const src = fs.readFileSync(full, 'utf-8'); if (/(?:import|export)[^;]*['"][^'"]*\/(?:incident-response|training|change-management)\.zod(?:\.js)?['"]/.test(src)) { importers.push(path.relative(SRC_ROOT, full)); @@ -215,7 +218,10 @@ describe('[#15513] system/ compliance families retirement — the public surface } // Anti-vacuity: a surviving neighbour is still there. expect(manifest.schemas).toContain('system/ChangeSet'); - const exact = (name: string) => new RegExp(`"${name}"`); + // Word-bounded rather than quoted: the three shards spell a row three ways + // (`"Name (kind)"`, `"Name": "…"`, a nested origin record), and a retired + // name must be absent under every spelling. + const exact = (name: string) => new RegExp(`\\b${name}\\b`); for (const dir of ['api-surface', 'declaration-map', 'export-origins']) { const text = shard(dir); for (const name of RETIRED_NAMES) { From b1dbf283764c46668928fe04c5b6612679e5f988 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 5 Sep 2026 15:54:16 +0000 Subject: [PATCH 4/6] docs(spec): llms.txt inventory follows the three retired system modules (36 -> 33, 207 -> 204) Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01M59rPZZFzqhfMUPFqqZTkf --- packages/spec/llms.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/spec/llms.txt b/packages/spec/llms.txt index 74bba77ccc..776c25c41e 100644 --- a/packages/spec/llms.txt +++ b/packages/spec/llms.txt @@ -77,7 +77,7 @@ const query = { --- -## 3. Schema Inventory by Domain (207 schemas) +## 3. Schema Inventory by Domain (204 schemas) Counted as `*.zod.ts` modules under `packages/spec/src//` — the sources that ship in this tarball (`files` includes `src/**/*.zod.ts`), so every number @@ -85,7 +85,7 @@ here is verifiable from the installed package. | Domain | Count | Key Schemas | |--------|-------|-------------| -| system | 36 | Auth, Cache, Compliance, Encryption, HTTP Server, License, Logging, Metrics | +| system | 33 | Auth, Cache, Compliance, Encryption, HTTP Server, License, Logging, Metrics | | kernel | 31 | Plugin, Manifest, Events (6 sub-modules), Feature, Context, Package Registry | | data | 30 | Object, Field, Query, Filter, Driver (SQL/NoSQL/Memory/Mongo/Postgres), Cube | | api | 30 | Endpoint, REST Server, Discovery, OData, Batch, WebSocket, Response Envelope, Package Lifecycle | From 70582583fa5687bdf9435941c0602ae5db214b1a Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 5 Sep 2026 16:48:38 +0000 Subject: [PATCH 5/6] chore(spec): regenerate references/index.mdx and the strictness-ledger counts on the merged tree The merge script's designed collection point: both artifacts were taken from main's side of the merge and regenerated on the merged tree (gen:docs, gen:strictness-ledger) so the system rows the retirement removed and the automation rows main added compose. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01M59rPZZFzqhfMUPFqqZTkf --- content/docs/references/index.mdx | 10 +++++----- ...2026-07-unknown-key-strictness-ledger.counts.md | 14 +++++++------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/content/docs/references/index.mdx b/content/docs/references/index.mdx index 79be81be47..3ec51782de 100644 --- a/content/docs/references/index.mdx +++ b/content/docs/references/index.mdx @@ -1,6 +1,6 @@ --- title: Protocol Reference -description: Every schema published by @objectstack/spec — 1572 schemas across 14 protocol modules +description: Every schema published by @objectstack/spec — 1573 schemas across 14 protocol modules --- {/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs live in the module folders under content/docs/. */} @@ -21,7 +21,7 @@ counts are sums of the rows they head. Regenerate with | :--- | ---: | ---: | :--- | | [AI Protocol](/docs/references/ai) | 11 | 66 | Agents, tools, skills, RAG and knowledge sources, model registry, conversations. | | [API Protocol](/docs/references/api) | 31 | 436 | REST contracts, endpoints, routing, realtime, batch, discovery. | -| [Automation Protocol](/docs/references/automation) | 13 | 72 | Flows and their nodes, approvals, ETL pipelines, webhooks, state machines, execution records. | +| [Automation Protocol](/docs/references/automation) | 13 | 73 | Flows and their nodes, approvals, ETL pipelines, webhooks, state machines, execution records. | | [Cloud Protocol](/docs/references/cloud) | 11 | 94 | Environments, packages and versions, marketplace, developer portal, tenancy. | | [Data Protocol](/docs/references/data) | 29 | 166 | Objects, fields, queries, filters, datasources and drivers — the ObjectQL layer. | | [Identity Protocol](/docs/references/identity) | 5 | 27 | Users and accounts, organizations, positions, SCIM provisioning. | @@ -33,7 +33,7 @@ counts are sums of the rows they head. Regenerate with | [Studio Protocol](/docs/references/studio) | 3 | 35 | Studio designer metadata — the authoring surfaces for the protocols above. | | [System Protocol](/docs/references/system) | 33 | 272 | The runtime environment — logging, jobs, cache, metrics, notifications, i18n and compliance. | | [UI Protocol](/docs/references/ui) | 16 | 153 | Apps, pages, views, dashboards, reports, actions and themes — the ObjectUI layer. | -| **Total** | **197** | **1572** | 14 protocol modules | +| **Total** | **197** | **1573** | 14 protocol modules | --- @@ -103,7 +103,7 @@ REST contracts, endpoints, routing, realtime, batch, discovery. ## Automation Protocol -**Source:** `packages/spec/src/automation/` · **Import:** `@objectstack/spec/automation` · **13 pages, 72 schemas** +**Source:** `packages/spec/src/automation/` · **Import:** `@objectstack/spec/automation` · **13 pages, 73 schemas** Flows and their nodes, approvals, ETL pipelines, webhooks, state machines, execution records. @@ -111,7 +111,7 @@ Flows and their nodes, approvals, ETL pipelines, webhooks, state machines, execu | :--- | :--- | | [`approval.zod.ts`](/docs/references/automation/approval) | `ApprovalDecision`, `ApprovalEscalation`, `ApprovalNodeApprover`, `ApprovalNodeConfig`, `ApproverType`, `DecisionOutputDef` | | [`bpmn-interop.zod.ts`](/docs/references/automation/bpmn-interop) | `BpmnDiagnostic`, `BpmnElementMapping`, `BpmnExportOptions`, `BpmnImportOptions`, `BpmnInteropResult`, `BpmnUnmappedStrategy`, `BpmnVersion` | -| [`builtin-node-config.zod.ts`](/docs/references/automation/builtin-node-config) | `AssignmentConfig`, `AssignmentExpressionValue`, `AssignmentValue`, `CreateRecordConfig`, `DeleteRecordConfig`, `GetRecordConfig`, `MapConfig`, `ScreenConfig`, `ScreenFieldConfig`, `UpdateRecordConfig` | +| [`builtin-node-config.zod.ts`](/docs/references/automation/builtin-node-config) | `AssignmentConfig`, `AssignmentExpressionValue`, `AssignmentValue`, `CreateRecordConfig`, `DeleteRecordConfig`, `EndConfig`, `GetRecordConfig`, `MapConfig`, `ScreenConfig`, `ScreenFieldConfig`, `UpdateRecordConfig` | | [`control-flow.zod.ts`](/docs/references/automation/control-flow) | `FlowRegion`, `LoopConfig`, `ParallelBranch`, `ParallelConfig`, `RetryPolicy`, `TryCatchConfig`, `TryCatchErrorValue` | | [`execution.zod.ts`](/docs/references/automation/execution) | `Checkpoint`, `ConcurrencyPolicy`, `ExecutionError`, `ExecutionErrorSeverity`, `ExecutionLog`, `ExecutionStatus`, `ExecutionStepLog`, `ExecutionStepMetrics`, `ExecutionStepSkipReason`, `FlowRunGateSummary`, `FlowRunNodeSummary`, `FlowRunSummary`, `ScheduleState` | | [`flow.zod.ts`](/docs/references/automation/flow) | `Flow`, `FlowEdge`, `FlowNode`, `FlowNodeAction`, `FlowVariable`, `FlowVersionHistory` | diff --git a/docs/audits/2026-07-unknown-key-strictness-ledger.counts.md b/docs/audits/2026-07-unknown-key-strictness-ledger.counts.md index c0ebcf5cec..21af78bdf9 100644 --- a/docs/audits/2026-07-unknown-key-strictness-ledger.counts.md +++ b/docs/audits/2026-07-unknown-key-strictness-ledger.counts.md @@ -21,7 +21,7 @@ regenerate. | Measure | Value | |---|---| | Triaged directories | 5 | -| Object sites in them | 440 | +| Object sites in them | 441 | | Still-open (strip) sites | 124 | | Files carrying at least one | 22 | @@ -46,10 +46,10 @@ The `strict` column is the one the campaign schedules against; it counts both th |---|---|---|---|---|---| | `ui/` | 169 | 157 | 5 | 0 | 7 | | `data/` | 157 | 76 | 1 | 0 | 80 | -| `automation/` | 67 | 42 | 0 | 1 | 24 | +| `automation/` | 68 | 43 | 0 | 1 | 24 | | `security/` | 20 | 7 | 0 | 0 | 13 | | `studio/` | 27 | 27 | 0 | 0 | 0 | -| **total** | **440** | **309** | **6** | **1** | **124** | +| **total** | **441** | **310** | **6** | **1** | **124** | ## File-level triage — site counts @@ -115,7 +115,7 @@ classify and is not listed (it becomes reportable the day it grows its first sit |---|---| | `approval.zod.ts` | 4 | | `bpmn-interop.zod.ts` | 5 | -| `builtin-node-config.zod.ts` | 9 | +| `builtin-node-config.zod.ts` | 10 | | `control-flow.zod.ts` | 6 | | `execution.zod.ts` | 13 | | `flow-function.zod.ts` | 1 | @@ -126,7 +126,7 @@ classify and is not listed (it becomes reportable the day it grows its first sit | `state-machine.zod.ts` | 6 | | `time-relative-trigger.zod.ts` | 1 | | `webhook.zod.ts` | 1 | -| **total** | **67** | +| **total** | **68** | ### `security/` — sites @@ -204,7 +204,7 @@ over it is here. ### `automation/` — open -**24 strip of 67**, in 5 file(s). +**24 strip of 68**, in 5 file(s). | File | Strip | Sites | |---|---|---| @@ -213,7 +213,7 @@ over it is here. | `execution.zod.ts` | 13 | 13 | | `flow.zod.ts` | 1 | 11 | | `node-executor.zod.ts` | 4 | 4 | -| **total** | **24** | **67** | +| **total** | **24** | **68** | | Bucket | Sites | |---|---| From f8ccc47539ae4f189fb2be7bf5e56c53e27c0811 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 5 Sep 2026 18:09:36 +0000 Subject: [PATCH 6/6] fix(spec): narrow the retirement pin's examples radius to non-code files and drop the dead PROTOCOL_MAP row The dispatch-gates self-test pins that no cross-package hint reaches a test file outside packages/**; a bare examples/** glob covered the CRM example's smoke test. The pin now scans only JSON / MD / MDX / YAML under examples/ (every example has its own tsc typecheck) and the declaration + turbo.json name those five extension globs with heldBy witnesses. PROTOCOL_MAP.md linked the deleted change-management module; its row is dropped (the map test's own remedy). Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01M59rPZZFzqhfMUPFqqZTkf --- packages/spec/PROTOCOL_MAP.md | 1 - .../compliance-families-retirement.test.ts | 17 +++++++++-- scripts/cross-package-test-inputs.mjs | 29 +++++++++++++++---- turbo.json | 6 +++- 4 files changed, 42 insertions(+), 11 deletions(-) diff --git a/packages/spec/PROTOCOL_MAP.md b/packages/spec/PROTOCOL_MAP.md index 3e7a28813a..494d5b646e 100644 --- a/packages/spec/PROTOCOL_MAP.md +++ b/packages/spec/PROTOCOL_MAP.md @@ -138,7 +138,6 @@ This document serves as the **Grand Map** of the ObjectStack specification. It l | [`license.zod.ts`](src/system/license.zod.ts) | | **Licensing**. License keys and entitlements. | | [`registry-config.zod.ts`](src/system/registry-config.zod.ts) | | **Registry Config**. Configuration for the package registry. | | [`tenant.zod.ts`](src/system/tenant.zod.ts) | | **Tenant**. Multi-tenant environment isolation. | -| [`change-management.zod.ts`](src/system/change-management.zod.ts) | | **Change Mgmt**. Deployment history and rollbacks. | | [`collaboration.zod.ts`](src/system/collaboration.zod.ts) | | **Collaboration**. Real-time collaboration settings. | --- diff --git a/packages/spec/src/system/compliance-families-retirement.test.ts b/packages/spec/src/system/compliance-families-retirement.test.ts index 40e1c4379e..bab3733479 100644 --- a/packages/spec/src/system/compliance-families-retirement.test.ts +++ b/packages/spec/src/system/compliance-families-retirement.test.ts @@ -302,16 +302,26 @@ describe('[#15513] ADR-0087 registration', () => { // `packages/client-react`'s `realtime-hooks.test.tsx` (the `@objectstack/core` // entry records the measurement). A `.tsx` import of a retired name fails // `tsc` in its own package, which is the enforced channel for typed sources. +// Under `examples/` NO code file is scanned at all (only JSON / MD / MDX / +// YAML): every example is a workspace package with its own `typecheck`, and +// the same self-test pins that no cross-package hint reaches a test file +// outside `packages/**` — its `OUTSIDE_PACKAGES` specimen is the CRM example's +// smoke test, which a bare `examples/**` glob covers and a `.json` / `.md` +// glob cannot. (The specimen's path is deliberately not spelled here: the +// inputs gate collects a quoted whole path as a named read and would demand +// the very declaration the self-test refuses.) // The residue this leg covers is everything `tsc` does not compile: JSON, -// YAML, MD, MDX, and untyped `.js` / `.mjs` / `.cjs`. +// YAML, MD, MDX, and (under `packages/`) untyped `.js` / `.mjs` / `.cjs`. describe('[#15513] tree-scoped absence: nothing inside the declared radius references a retired name', () => { const REPO_ROOT = path.resolve(SPEC_ROOT, '../..'); const THIS_FILE = path.relative(REPO_ROOT, fileURLToPath(import.meta.url)).split(path.sep).join('/'); /** The walked roots — declared in `scripts/cross-package-test-inputs.mjs` under `@objectstack/spec`. */ const WALK_ROOTS = ['packages', 'examples', 'skills', 'content', 'scripts']; - /** Per-extension under `packages/` (never `.tsx`, see above); the other roots are declared whole. */ + /** Per-extension under `packages/` (never `.tsx`, see above); `content/`, `skills/`, `scripts/` are declared whole. */ const SCANNED_EXT = new Set(['.ts', '.mts', '.cts', '.js', '.mjs', '.cjs', '.json', '.md', '.mdx', '.yaml', '.yml']); + /** Under `examples/` only the non-code extensions are scanned AND declared (see above). */ + const EXAMPLES_EXT = new Set(['.json', '.md', '.mdx', '.yaml', '.yml']); /** Build, SCM and cache state — not authored sources. */ const SKIPPED_DIRS = new Set(['node_modules', 'dist', '.git', '.turbo', '.cache', '.objectstack', 'coverage', '.next', '.source']); @@ -383,7 +393,8 @@ describe('[#15513] tree-scoped absence: nothing inside the declared radius refer continue; } if (!entry.isFile()) continue; - if (!SCANNED_EXT.has(path.extname(entry.name))) continue; + const ext = path.extname(entry.name); + if (!(rel.startsWith('examples/') ? EXAMPLES_EXT : SCANNED_EXT).has(ext)) continue; if (entry.name === 'CHANGELOG.md') continue; // release prose records the removal if (EXCLUDED.has(rel) || EXCLUDED_PREFIXES.some((p) => rel.startsWith(p))) continue; visited += 1; diff --git a/scripts/cross-package-test-inputs.mjs b/scripts/cross-package-test-inputs.mjs index f6823cebb4..417916297a 100644 --- a/scripts/cross-package-test-inputs.mjs +++ b/scripts/cross-package-test-inputs.mjs @@ -194,10 +194,19 @@ export const CROSS_PACKAGE_TEST_INPUTS = { // `tsc` in its own package, which is the enforced channel there. // `packages/**/*.ts` and `*.mts` subsume this entry's narrower `.ts` // globs above, which are left as the tests that declared them spelled - // them. `examples/**` and `content/**` are declared whole (the pin scans - // their JSON / MD / MDX / YAML too; `content/**` subsumes the two - // `content/docs/...` rows above). `skills/**` and `scripts/**` were - // already declared by the tests named at their rows. + // them. `content/**` is declared whole (the pin scans its JSON / MD / + // MDX / YAML too; it subsumes the two `content/docs/...` rows above). + // `examples/` is declared per NON-CODE extension only: every example is + // a workspace package with its own `typecheck` (`tsc --noEmit`), so a + // typed resurrection there fails `tsc` in its own package, and the pin + // scans only JSON / MD / MDX / YAML under it. ⛔ Not `examples/**` — that + // glob covers the CRM example's smoke test, and the dispatch-gates + // self-test pins that no cross-package hint reaches a test file outside + // `packages/**` (its `OUTSIDE_PACKAGES` specimen; the path is not spelled + // here for the reason the `@objectstack/types` entry gives — a quoted + // whole path in this module becomes the very hint the case refuses); + // a `.json` / `.md` glob cannot match a `.ts` path. `skills/**` and + // `scripts/**` were already declared by the tests named at their rows. 'packages/**/*.ts', 'packages/**/*.mts', 'packages/**/*.cts', @@ -209,7 +218,11 @@ export const CROSS_PACKAGE_TEST_INPUTS = { 'packages/**/*.mdx', 'packages/**/*.yaml', 'packages/**/*.yml', - 'examples/**', + 'examples/**/*.json', + 'examples/**/*.md', + 'examples/**/*.mdx', + 'examples/**/*.yaml', + 'examples/**/*.yml', 'content/**', ], heldBy: { @@ -234,7 +247,11 @@ export const CROSS_PACKAGE_TEST_INPUTS = { 'packages/**/*.mdx': ['packages/spec/src/system/compliance-families-retirement.test.ts'], 'packages/**/*.yaml': ['packages/spec/src/system/compliance-families-retirement.test.ts'], 'packages/**/*.yml': ['packages/spec/src/system/compliance-families-retirement.test.ts'], - 'examples/**': ['packages/spec/src/system/compliance-families-retirement.test.ts'], + 'examples/**/*.json': ['packages/spec/src/system/compliance-families-retirement.test.ts'], + 'examples/**/*.md': ['packages/spec/src/system/compliance-families-retirement.test.ts'], + 'examples/**/*.mdx': ['packages/spec/src/system/compliance-families-retirement.test.ts'], + 'examples/**/*.yaml': ['packages/spec/src/system/compliance-families-retirement.test.ts'], + 'examples/**/*.yml': ['packages/spec/src/system/compliance-families-retirement.test.ts'], }, }, '@objectstack/core': { diff --git a/turbo.json b/turbo.json index bc9d72c496..02eb2d761b 100644 --- a/turbo.json +++ b/turbo.json @@ -79,7 +79,11 @@ "$TURBO_ROOT$/packages/**/*.mdx", "$TURBO_ROOT$/packages/**/*.yaml", "$TURBO_ROOT$/packages/**/*.yml", - "$TURBO_ROOT$/examples/**", + "$TURBO_ROOT$/examples/**/*.json", + "$TURBO_ROOT$/examples/**/*.md", + "$TURBO_ROOT$/examples/**/*.mdx", + "$TURBO_ROOT$/examples/**/*.yaml", + "$TURBO_ROOT$/examples/**/*.yml", "$TURBO_ROOT$/content/**" ] },