From 09de242e707d143f29cecf21840e5ff626272bda Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 30 Jul 2026 08:42:44 +0000 Subject: [PATCH] docs(skills): the retirement kit becomes a playbook, not archaeology MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The #3896 enforce-or-remove line retired 20+ authorable keys across six metadata types, and every batch re-derived the same procedure from scratch — including the parts that only announce themselves as a red build. Sediment it as an internal skill so the next retirement executes instead of excavates. `.claude/skills/spec-property-retirement/SKILL.md` covers what nothing else documents: the disposition gate (enforce vs experimental vs remove, and the docs-shaped fields deliberately exempt), the three removal routes and how to pick one, the fourteen surfaces a removal touches, and the traps that each cost a red build. Two findings are load-bearing enough to have their own sections: - **The ledger discipline is OPPOSITE per route, and the failures are not symmetric.** `retiredKey()` leaves the key in the walked shape, so its liveness entry must STAY; a strict removal takes the key out, so its entry must GO. Deleting a tombstoned key's entry reports UNCLASSIFIED and fails CI (14 at once in the sweep). The reverse — a leftover entry after a strict removal — is never checked at all, because the gate walks the schema and never walks entries looking for absent keys. One direction fails loudly; the other rots silently, which is how the stale report `aria`/`performance` entries survived a release. - **The build is the referee, not the ledger.** A `dead` verdict is an input to removal, not a substitute for the build's own proof. `view.form.data` was on the worklist and the removal broke `gen:schema` — `defineForm` writes `data.provider='schema'` onto every metadata form. The correct response is to correct the ledger, not force the removal. That correction is also the bug this commit fixes. When `form.data` flipped back to `live`, the schema, ledger, conversion and tests were all corrected, but the v17 dead-cluster row and the liveness README's view row kept listing it as removed — telling authors to delete a key `defineForm` requires. Both now say it stays live, with the reason. The correction path is precisely what had no checklist, so the skill carries one. AGENTS.md gets the two pointers it was missing: the retirement note in the Post-Task Checklist said only `UNKNOWN_KEY_GUIDANCE` while `build-schemas.ts` has prescribed `retiredKey()` first since #3855, and the Skills section listed only the published catalog, leaving both internal playbooks undiscoverable from the file agents are told to read first. Verification: all ten spec gates pass (`check:liveness`, `check:empty-state`, `check:authorable-surface`, `check:docs`, `check:api-surface`, `check:spec-changes`, `check:upgrade-guide`, `check:skill-refs`, `check:skill-docs`, `check:skill-examples`), plus `check:i18n`, `check:doc-authoring`, `check:release-notes` and the skills-catalog boundary test that enforces `metadata.internal: true` on every SKILL.md outside `skills/`. The gate loop the skill ships was run as written — it is the same loop, exit codes captured explicitly rather than piped. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01QuViRSR1j6GJjf9qGbnqFX --- .changeset/spec-property-retirement-skill.md | 4 + .../skills/spec-property-retirement/SKILL.md | 302 ++++++++++++++++++ AGENTS.md | 7 +- content/docs/releases/v17.mdx | 2 +- packages/spec/liveness/README.md | 2 +- 5 files changed, 314 insertions(+), 3 deletions(-) create mode 100644 .changeset/spec-property-retirement-skill.md create mode 100644 .claude/skills/spec-property-retirement/SKILL.md diff --git a/.changeset/spec-property-retirement-skill.md b/.changeset/spec-property-retirement-skill.md new file mode 100644 index 0000000000..9d64ab84ea --- /dev/null +++ b/.changeset/spec-property-retirement-skill.md @@ -0,0 +1,4 @@ +--- +--- + +docs(skills): the `spec-property-retirement` playbook (ADR-0049 enforce-or-remove), plus the `form.data` correction propagated to the v17 notes and the liveness README — releases nothing. diff --git a/.claude/skills/spec-property-retirement/SKILL.md b/.claude/skills/spec-property-retirement/SKILL.md new file mode 100644 index 0000000000..0cb9ee29ce --- /dev/null +++ b/.claude/skills/spec-property-retirement/SKILL.md @@ -0,0 +1,302 @@ +--- +name: spec-property-retirement +description: > + Internal playbook for retiring an authorable `packages/spec` property under + ADR-0049 enforce-or-remove — choosing the removal route, the liveness-ledger + discipline each route implies, the ADR-0087 conversion, the generated + baselines, forms, docs and pin tests a removal needs, and the gates that fail + when any of it is missing. Use when a metadata key is declared-but-unenforced + and the job is to REMOVE it, or when a ledger `dead` verdict needs confirming + before you act on it: "retire this property", "enforce-or-remove", "the ledger + says dead", "remove the inert key", "close out the liveness worklist". NOT a + customer-published skill — internal agent tooling (lives in .claude/, never in + the published `skills/` dir). +metadata: + # Hides this skill from interactive `npx skills add objectstack-ai/objectstack` + # discovery, same as dogfood-verification. Enforced by + # packages/create-objectstack/src/template-consistency.test.ts. + internal: true +--- + +# Spec property retirement (ADR-0049 enforce-or-remove) + +A parsed-but-unenforced property is a silent no-op; for a security or capability +property it is **false compliance** — `tool.permissions` promised an invocation +gate nothing enforced, `flow.active: false` never stopped a flow. ADR-0049 says +such a property must be **enforced**, marked **`experimental`**, or **absent**. +This skill is the third path: what a removal actually costs, and in what order. + +Removing the key is maybe 5% of the work. The other 95% is that a key is +authorable in ~14 places, and **every gate that guards one of them fails +separately and sequentially** — one stale surface masks the rest, so you get one +red build per surface instead of one for all of them. + +Read the verification side first: `packages/spec/liveness/README.md` (how a +verdict is reached, `verifiedAt`, why a preview renderer is not a consumer) and +AGENTS.md §"Touched `packages/spec`?" (the eight generated artifacts). This file +does not repeat them. + +## 0. Before you remove: is removal the right disposition? + +- [ ] **Is it security/capability-shaped?** Then ADR-0049 binds and inertness is + a defect, not debt. `rls.enabled` was "live with wrong evidence" and + actually UNREAD — a disabled policy kept contributing its grant. That one + got **enforced**, not removed. Enforcement wins when the feature exists. +- [ ] **Is it docs-shaped?** `hook.label`, `hook.description`, `flow.description` + have no runtime consumer and are **deliberately KEPT** — they document + intent for the next reader (per ADR-0033, often a model). Record the + exemption in the ledger `note` so the next audit doesn't re-litigate it. + Benign display metadata (`description`, `tags`, `icon`) is never + "misleading"; do not mark it `authorWarn` and do not retire it. +- [ ] **Is there a committed roadmap?** Then `experimental` + a + `[EXPERIMENTAL — not enforced]` `.describe()` marker, not removal. +- [ ] **Same-major bookkeeping.** If an earlier conversion in the *same + unreleased major* renames a key you are now deleting, **absorb** it: fold + the rename into the removal and delete the rename entry. Composed, its + effect is unobservable, and the conversion table's fixture-disjointness + contract (§3) will fail if you stack them. Precedent: `agent.knowledge` + swallowed the `topics`→`sources` rename pre-release. + +## 1. The build is the referee — not the ledger + +A ledger `dead` verdict is an **input** to removal, not a substitute for the +build's own proof. It is a claim with a timestamp, and code moves under it in +both directions (`flow.status` and `action.undoable` were both *understated*). + +So: **attempt the removal, then let the build rule on it.** In the #3896 +close-out, `view.form.data` was on the worklist as dead ("no form-path reader in +either repo") and the removal broke `gen:schema` — `defineForm` writes +`data: { provider: 'schema', schemaId }` onto every `*.form.ts`, and +`metadata-protocol` serves it to the metadata-admin pipeline. The right response +is **not** to force the removal: correct the ledger entry to `live` with the real +evidence and a `verifiedAt`, narrow the conversion, and pin the non-warn. One of +fourteen keys was refuted this way — budget for it. + +Two corollaries: + +- **`tsc` and the gates are your best sweepers.** A `retiredKey()` tombstone + types the key `never`, so *every* authoring site in the monorepo fails to + compile. Two of three `template: true` sites in `examples/app-showcase` were + found by the tombstone after a grep missed them. Let the tombstone find the + callers before you go hunting. +- **A grep can only prove presence.** To prove absence, close the call graph by + hand (declaration → registration → accessor → *caller*) or author the property + and boot the app. See the README's "How to verify a claim without fooling + yourself" — including that `git grep -E` does not honour `\b` on macOS. + +## 2. The fork: which removal route (decides everything downstream) + +| Schema | Route | Mechanism | +|---|---|---| +| **not `.strict()`** | `retiredKey()` tombstone | `retiredKey(guidance)` in `packages/spec/src/shared/retired-key.ts` — `z.never({ error: () => guidance }).optional()`. Two channels: `tsc` (input type `never`) and the parse (the prescription itself, not "unrecognized key"). | +| **`.strict()`** | delete the key + guidance map | Delete from the shape; add an entry to a `*_RETIRED_KEY_GUIDANCE` record consumed by a `z.core.$ZodErrorMap` passed as `z.object(shape, { error: … }).strict()`. Reference: `packages/spec/src/ai/tool.zod.ts:29-93,180`. Also `object.zod.ts`'s `UNKNOWN_KEY_GUIDANCE` for object top-level keys. | +| **nothing parses it** | neither | A prescription nobody can receive is noise. Drop the baseline lines deliberately and say so in the changeset — precedent `packages/spec/src/kernel/plugin-runtime.zod.ts:243-248`. | + +Never plain-delete a key from a non-strict schema: zod strips it silently and +you have replaced one silent no-op with another (the #2169 "Mark Done does +nothing" shape). + +### ⚠ The ledger discipline is OPPOSITE per route — and the two failures are not symmetric + +The liveness gate walks the **schema's shape** and looks up each property in +`packages/spec/liveness/.json`. A `retiredKey()` is still a property in +that shape. Therefore: + +| Route | Key still in the walked shape? | Its ledger entry | +|---|---|---| +| `retiredKey()` tombstone | **YES** (`z.never()` is a property) | **STAYS** — `status: "dead"`, a `verifiedAt`, and a `note` saying REMOVED + why the entry remains | +| strict removal | no | **DELETED**, along with any CLI advisory-lint expectation | + +Get this backwards and: deleting a tombstoned key's entry reports it +**UNCLASSIFIED and fails CI** (14 at once, in the #3896 sweep — the mistake this +section exists to prevent). The other direction is worse in the long run: the +gate never walks entries looking for absent schema keys, so a **leftover entry +after a strict removal rots invisibly** — report `aria`/`performance` sat stale +for a release before a human deleted them as hygiene. One direction fails +loudly; the other never fails at all. + +Note template for a tombstone entry (verbatim house style, e.g. +`liveness/action.json`): + +> `REMOVED (#) — tombstoned at the schema (retiredKey carries the prescription; authoring it is a tsc error and a parse error) and stripped from sources by the protocol- conversion. The entry stays because retiredKey keeps the key in the walked shape (the rls.priority precedent); .` + +### Writing the guidance string + +Five conventions, obeyed by all ~28 tombstones in tree: + +1. Backticked **fully-qualified** key first — `` `flow.errorHandling.fallbackNodeId` ``, not the bare tail. +2. `was removed in @objectstack/spec (#issue[, ADR-XXXX Dn])`. +3. An em-dash clause on **why it was inert or wrong** — "it never had an effect", "no renderer ever read it". +4. The imperative fix: for a rename, "use ``" + "Rename the key; the value (…) is unchanged."; for a removal, "Delete the key." + **what the live mechanism actually is**. +5. ``Run `os migrate meta --from ` to rewrite it automatically.`` — **only** when a conversion rewrites sources. No message names a conversion id; the conversion is referenced by the CLI command. + +This string *is* the migration doc for whoever hits it, including someone +jumping several majors at once, whom the load-path conversion no longer covers. + +## 3. Register the surface (ADR-0087 D2/D3) — or the gate stops you + +`scripts/build-schemas.ts` gate (b) fails any newly-tombstoned key with no +registered migration surface: the tombstone is audible only to whoever *hits* +it, while `spec-changes.json`, the generated upgrade guide and the +`spec_changes` MCP tool are the primary channel and would stay empty. + +- [ ] **A `MetadataConversion`** in `packages/spec/src/conversions/registry.ts`: + kebab-case `id` ending `-removed`, `toMajor`, one + `emit({ from, to: '(removed)', path })` per key (use the shared `stripKeys` + helper), and a `fixture` whose `expectedNotices` equals the **key** count, + not the item count. Walkers (`mapCollection`, `mapFlowNodes`, `renameKey`) + live in `conversions/walk.ts` and are copy-on-write — return the input + reference untouched when nothing matched. +- [ ] **`surface` must end with the bare key.** The matcher is + `surfaces.some((s) => s.endsWith('.' + key))` after + `.flatMap((s) => s.split(' / '))`. Multi-key conversions join clauses with + exactly `' / '` (house style since the tool sweep) and **each clause must + end with its own key**. Caveat: only the last dotted segment is compared, + so the schema name is never checked — `dashboard.aria` would satisfy + `ui/FormView:aria`. Don't lean on the gate for attribution. +- [ ] **`retiredFromLoadPath: true`** — for a retirement, always. Two distinct + justifications, and they are not interchangeable: for a *rename* it means + "no alias window, deliberately" (the tombstone owns the refusal; the entry + exists so `spec-changes.json` and `os migrate meta` still carry it); for a + **default flip** it is load-bearing for correctness — a loader that + auto-applied `field-required-notnull-explicit` would stamp NOT NULL onto + 17-authored `required: true`, silently restoring the tri-binding ADR-0113 + removed. Only `migrate meta --from ` may apply a flip, where "this + source predates the split" is a fact rather than a guess. +- [ ] **A D3 chain step** in `packages/spec/src/migrations/registry.ts` — add the + id to `MIGRATIONS_BY_MAJOR[N].conversionIds` and extend that step's + `rationale`. `conversion.toMajor` **must equal** the step's major. + ⚠ Nothing asserts "every conversion is wired into a step" directly, and a + typo'd id is **silently skipped at replay**; the chain-replay test catches + it only because an unwired fixture never reaches its `after`. So read that + test's failure as "not wired", not "transform broken". +- [ ] **Fixtures must be DISJOINT — twice over.** Every fixture is replayed + through the *whole* table and must equal exactly its own `after`, with + every notice attributed to its own id. Keep `before` minimal and avoid + other entries' keys (a new `objects[].fields` fixture must not carry a bare + `required: true`, or the notNull conversion fires on it). Second + constraint, easy to miss: a `retiredFromLoadPath` fixture must ALSO be + untouched by every *live-window* conversion, since a separate test asserts + it passes through the default load path with zero notices. This + disjointness contract is what forces same-major absorption (§0). +- [ ] **Idempotence is by construction, not by test.** No test replays a + conversion twice. A `stripKeys` deletion is idempotent (`if (!(key in + next)) continue`) and `renameKey` refuses to clobber an existing canonical + value; a default flip is **not** idempotent-safe and relies on its own + guard plus `retiredFromLoadPath`. If your transform is none of those + shapes, prove idempotence yourself — the CLI e2e + (`packages/cli/test/migrate-meta.e2e.test.ts`) replays the migrated + snapshot and asserts `applied` is empty. +- [ ] **Response-surface keys with no source to rewrite** register as a + `SemanticMigration` (D3 `semantic[]`) with non-empty `reason` and + `acceptanceCriteria` instead — `EnhancedApiError.fieldErrors` is the + worked example. + +## 4. The surface checklist + +Work top to bottom; each line has a gate behind it. + +- [ ] **Schema** — tombstone or strict removal (§2), plus the in-schema comment + saying what was removed and what the live mechanism is. +- [ ] **Orphaned value schemas** — a key's `XxxConfigSchema` with no other + consumer goes with it (`PerformanceConfigSchema`, `AIKnowledgeSchema`, + `ToolCategorySchema`). An exported schema with no consumer is read as a + capability by whoever finds it (#3950 precedent). This — and *only* this — + moves `api-surface.json`: that snapshot prints type *references*, not + expanded shapes, so it is blind to key-level narrowing (#3883 removed three + keys from `defineAction`'s input and the snapshot did not change). Its gate + also lives in a different workflow (`TypeScript Type Check`, not + `Check Generated Artifacts`) and reads the built `dist/*.d.ts`. +- [ ] **Conversion + chain step** (§3). +- [ ] **Liveness ledger** — per §2's route table, with `verifiedAt`. Update the + README's per-type row **and its counts** (that table has drifted badly + once; regenerate the counts with the python snippet in the README rather + than hand-editing). +- [ ] **Generated baselines** — `pnpm --filter @objectstack/spec gen:schema` + moves `authorable-surface.json` (tombstone → a new `… [RETIRED]` line; + strict removal → the line **vanishes**, which is gate (a)'s trip wire, so + delete it in the same PR deliberately) and `json-schema.manifest.json`. + Then `gen:spec-changes`, `gen:upgrade-guide`, `gen:api-surface`, + `gen:docs`. See AGENTS.md for the you-changed-X → regenerate-Y table. +- [ ] **Forms** — prune the `{ field: '' }` input from + `packages/spec/src/**/*.form.ts`. A form input for an unenforced capability + is the UI half of false compliance. Leave a one-line comment where it was. +- [ ] **i18n bundles** — pruning a form input changes the extracted labels: + `pnpm i18n:extract` regenerates + `packages/platform-objects/src/apps/translations/*.metadata-forms.generated.ts` + (merge mode; a retirement is a pure deletion). Gated by `pnpm check:i18n`. +- [ ] **CLI advisory lint** — `packages/cli/src/utils/lint-liveness-properties.ts` + is ledger-driven, so a retired key stops warning by itself; update its + **test** to assert the non-warn ("the strict parse owns them now"). +- [ ] **Pin tests** — one negative asserting the prescription itself + (``.toThrow(/.*removed.*use ``/s)`` — the `s` flag is + house style, since the message spans lines) and one positive asserting + `not.toHaveProperty(key)` for the non-strict strip path. Reference + `packages/spec/src/ai/agent.test.ts:69-95`. +- [ ] **Examples** — `examples/app-showcase/**` must stop authoring the key. + `tsc` finds these for you on the tombstone route. +- [ ] **Published skills** — `skills/*/SKILL.md` teaching the key (tables, + `defineX` examples) — gated by `check:skill-examples` and `check:skill-refs`. +- [ ] **Docs** — `content/docs/**` prose, tables and code blocks. Grep the key, + then read the surrounding files: a removed key hides in a `defineFlow` + example three sections from the reference table. +- [ ] **Release notes** — the `### Dead spec clusters removed` table in + `content/docs/releases/v.mdx` **plus** the upgrade checklist. +- [ ] **Changeset** — `major` for `@objectstack/spec`. AGENTS.md: a breaking + changeset must carry the FROM → TO mapping and the one-line fix; it ships + as `CHANGELOG.md` in the npm package and is what an upgrading agent greps + after the tombstone error. `.changeset/tool-inert-keys-removed.md` is the + model — its "The retirement kit:" section is the template to copy. + +**A correction (§1) must propagate to every one of these lines too.** When +`form.data` flipped back to `live`, the ledger, the conversion and the tests were +all corrected — but the release-notes row and the ledger README row kept listing +it as removed, telling authors to delete a key `defineForm` writes. Found and +fixed a PR later. The correction path is the one nobody has a checklist for; use +this one. + +## 5. Run the gates so they can actually fail + +```bash +cd packages/spec && pnpm build # REQUIRED first — see the dist trap below +for c in check:liveness check:empty-state check:authorable-surface check:docs \ + check:api-surface check:spec-changes check:upgrade-guide \ + check:skill-refs check:skill-docs check:skill-examples; do + pnpm -s "$c" >/dev/null 2>&1; e=$? # capture BEFORE anything else runs + printf '%-28s %s\n' "$c" "$( [ $e -eq 0 ] && echo PASS || echo FAIL )" +done +cd ../.. && pnpm check:i18n && pnpm --filter @objectstack/spec test +``` + +`check:liveness`, `check:empty-state`, `check:skill-examples` have no generator — +a failure there is a real finding, not a stale artifact. + +## 6. Traps that have each cost a red build + +- **Stale `dist` (5+ false alarms in one line of work).** Packages load from + `dist`. A local suite failing after you edited `src` usually means the dist is + old, not that you broke it — `check:api-surface` reads `dist/*.d.ts` and will + report phantom "breaking removals". `pnpm turbo run build --filter=...` + before you believe any local red, and before filing a bug about `main`. +- **`| tail -1` masks the exit code.** Piping a gate to `tail` reports the + pipeline's status, so a failing gate reads as green. Capture `exit=$?` + explicitly (as the loop above does). A liveness failure hid behind this. +- **Truncated greps miss authors.** `| head -8` hid the `SKILL.md` + `defineSkill` example; `examples/` had three `template: true` sites, not one. + Search files-by-context, then grep the key inside them — and treat `tsc` and + the gates as the authoritative sweepers. +- **Sequential gates mask each other.** `Check Generated Artifacts` and + `TypeScript Type Check` each run their gates in order and stop at the first + failure. Regenerate everything up front; don't iterate one red build at a time. +- **A green CI can mean a dormant gate.** `check-generated` runs behind a + `paths` filter in `ci.yml`; a path missing from it makes the gate silent on + exactly the PRs that break it (the reason the filter carries + `packages/spec/src/**` wholesale). If you add a generated artifact or a new + input to one, add its paths in the same PR. +- **Editing only a conversion's `summary` still goes stale.** That string is + copied verbatim into `spec-changes.json`'s `to` field and into the upgrade + guide's table row, so a prose-only touch needs `gen:spec-changes` + + `gen:upgrade-guide` like any other change. +- **`--check` mode is the gate; bare mode rewrites.** `gen:*` fixes the file, + `check:*` is the same script asserting it was committed. Never "fix" a + `check:*` failure by editing the generated file by hand. diff --git a/AGENTS.md b/AGENTS.md index 56d0a184d3..1f96370329 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -270,6 +270,11 @@ finding to fix, not an artifact to regenerate. Consult the matching `SKILL.md` when working in its domain: `objectstack-platform`, `objectstack-data`, `objectstack-query`, `objectstack-api`, `objectstack-ui`, `objectstack-automation`, `objectstack-ai`, `objectstack-i18n`, `objectstack-formula` (CEL). +`skills/` is the **published** catalog (it ships to customer projects). Repo-internal +agent playbooks live in `.claude/skills/` and must carry `metadata.internal: true`: +`dogfood-verification` (boot and drive the real app in a browser) and +`spec-property-retirement` (ADR-0049 enforce-or-remove — the full retirement kit). + --- ## Patterns @@ -306,7 +311,7 @@ export default { never `gh pr merge --auto`). A finished task = a merged PR, not a dirty working tree. 3. **Add a changeset for feature work.** When the change is a feature or functional improvement, run `pnpm changeset` (or add a `.changeset/*.md` entry) describing it before committing. Pure bug fixes do **not** require a changeset. - **Breaking changesets must carry their migration.** If the change removes or renames anything an author can write (a spec key, an export, a config field), the changeset body must state the FROM → TO mapping and the one-line fix — this text ships to consumers as `CHANGELOG.md` inside the npm package and is what an upgrading agent greps after the tombstone error. Removing an authorable spec key also requires a tombstone entry in the relevant `UNKNOWN_KEY_GUIDANCE` map (see `object.zod.ts`) so the rejection itself carries the prescription. + **Breaking changesets must carry their migration.** If the change removes or renames anything an author can write (a spec key, an export, a config field), the changeset body must state the FROM → TO mapping and the one-line fix — this text ships to consumers as `CHANGELOG.md` inside the npm package and is what an upgrading agent greps after the tombstone error. Removing an authorable spec key also requires a tombstone so the rejection itself carries the prescription — `retiredKey()` (`packages/spec/src/shared/retired-key.ts`) on a non-strict schema, or an entry in the relevant `UNKNOWN_KEY_GUIDANCE` / `*_RETIRED_KEY_GUIDANCE` map (see `object.zod.ts`, `ai/tool.zod.ts`) when the schema is `.strict()`. The changeset is one of fourteen surfaces a retirement touches — follow the `spec-property-retirement` skill (`.claude/skills/`) rather than reconstructing the kit, and note the two routes imply **opposite** liveness-ledger dispositions. 4. Update `CHANGELOG.md` / `ROADMAP.md` if user-facing or architectural. 5. **Delete temporary artifacts** — screenshots, traces, scratch logs, `.playwright-mcp/`, throwaway `tmp*.ts`, ad-hoc scripts. Repo must look identical to before, minus intended changes. diff --git a/content/docs/releases/v17.mdx b/content/docs/releases/v17.mdx index 09119a4a12..496d8539e1 100644 --- a/content/docs/releases/v17.mdx +++ b/content/docs/releases/v17.mdx @@ -745,7 +745,7 @@ import or the authored key. | `tool.category` / `.permissions` / `.active` / `.builtIn` (+ `ToolCategorySchema`) | authorable and inert — `permissions` gated nothing, `active: false` withdrew nothing; strict-rejected with prescriptions (#3896 close-out) | | `action.shortcut` / `.bulkEnabled` | no keydown path dispatches shortcuts; the multi-select toolbar reads the view's `bulkActions` (#3896 close-out) | | `flow.active` / `.template` / node `outputSchema` / `errorHandling.fallbackNodeId` | `active: false` never stopped a flow — `status` is the enforced lifecycle; faults route via per-node fault edges (#3896 close-out) | -| view: list `responsive`/`performance`, form `data`/`defaultSort`/`aria` | no renderer read any of them; list `aria`/`data` stay live (#3896 close-out) | +| view: list `responsive`/`performance`, form `defaultSort`/`aria` | no renderer read any of them; list `aria`/`data` **and form `data`** stay live — `defineForm` writes `data: { provider: 'schema', schemaId }` onto every metadata form (#3896 close-out) | | `dashboard.aria` / `.performance` / widget `performance` (+ `PerformanceConfigSchema`) | no renderer applied them; virtual scrolling is the live top-level `virtualScroll` (#3896 close-out) | | `agent.knowledge` (+ `AIKnowledgeSchema`) | declaring sources/indexes never scoped retrieval — `search_knowledge` takes `sourceIds` from the LLM's tool-call arguments (#3896 close-out) | | `skill.triggerPhrases` | phrases were never matched; activation is `triggerConditions` + the agent's `skills[]` allowlist (#3896 close-out) | diff --git a/packages/spec/liveness/README.md b/packages/spec/liveness/README.md index 4a0aece274..55eb900cc5 100644 --- a/packages/spec/liveness/README.md +++ b/packages/spec/liveness/README.md @@ -475,7 +475,7 @@ EOF | skill | 8 | – | 1 | – | `permissions` REMOVED 2026-07 (#3704); `triggerPhrases` REMOVED 2026-07-30 (#3896 close-out sweep — phrases were never matched; activation is `triggerConditions` + the agent's `skills[]` + /skill-name pinning) | | dataset | 19 | – | 0 | – | `measures.certified` (declared-but-unenforced governance flag) REMOVED in 16.0 (#2377) | | page | 16 | – | – | 1 | fully live + one planned | -| view | 70 | 0 | 4 | – | list/form drilled via `children` (#2998 Track B); list.{responsive,performance} + form.{data,defaultSort,aria} REMOVED 2026-07-30 (#3896 close-out sweep — list aria/data stay live); form.{buttons,defaults} live (framework#1894 / #2998); audit-era DEAD lines superseded by re-verification; level-2 dead residue (userActions.buttons, addRecord.mode/formView, tabs[].order) noted on parents — one drill level only | +| view | 70 | 0 | 4 | – | list/form drilled via `children` (#2998 Track B); list.{responsive,performance} + form.{defaultSort,aria} REMOVED 2026-07-30 (#3896 close-out sweep — list aria/data stay live); **form.data was that sweep's one CORRECTION** — the removal attempt broke the build (`defineForm` writes `data.provider='schema'` onto every metadata form, `metadata-protocol` serves it), so it stands `live` with re-verified evidence; form.{buttons,defaults} live (framework#1894 / #2998); audit-era DEAD lines superseded by re-verification; level-2 dead residue (userActions.buttons, addRecord.mode/formView, tabs[].order) noted on parents — one drill level only | | report | 13 | 0 | 0 | – | dataset-bound (ADR-0021); the aria/performance LEDGER entries were stale — the keys left the schema in the report-liveness close-out; deleted 2026-07-30 as hygiene. Audit-era `chart` DEAD superseded (framework#1890 / #3441) | | dashboard | 10 | 0 | 2 | – | ADR-0021 dataset widgets (#3251; DashboardWidgetSchema `.strict()`); `aria`/`performance` (and widget `performance` + PerformanceConfigSchema) REMOVED 2026-07-30 (#3896 close-out sweep — no renderer applied any of them); audit-era `globalFilters`/`dateRange` DEAD superseded (framework#2501) |