|
13 | 13 | * Discrimination proof for the guard below: with `ACTION_DEF_KEYS` complete these |
14 | 14 | * pass; dropping any single entry (e.g. `target`) fails with |
15 | 15 | * "ActionDef declares keys the inventory is missing: target". |
| 16 | + * |
| 17 | + * ## OWED TO objectui#11344 — a bounded ledger, not a skip |
| 18 | + * |
| 19 | + * `@objectstack/spec` 17.6.0's `ActionSchema` declares `outcomeMessages`, and |
| 20 | + * objectui does not read it yet: objectui#11344 builds the reader and lists the |
| 21 | + * key with it. Until then the key is booked in `OWED_TO_OBJECTUI_11344` below. |
| 22 | + * Its row asserts today's difference, and "lists every key the spec |
| 23 | + * ActionSchema declares" requires `missing` to EQUAL the ledger exactly, so a |
| 24 | + * new spec key is red, and so is the booked key once objectui lists it. |
16 | 25 | */ |
17 | 26 | import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; |
18 | 27 | import { readFileSync } from 'node:fs'; |
@@ -87,6 +96,38 @@ function specActionKeys(): string[] { |
87 | 96 | return keys; |
88 | 97 | } |
89 | 98 |
|
| 99 | +/** |
| 100 | + * ⚠️ OWED TO objectui#11344 — the spec `ActionSchema` keys objectui's inventory |
| 101 | + * does NOT list yet, booked rather than skipped. |
| 102 | + * |
| 103 | + * `@objectstack/spec` 17.6.0 declares `outcomeMessages` on `ActionSchema` |
| 104 | + * (objectstack#21095): success copy per handler outcome, and the spec's own |
| 105 | + * landing order names objectui#11344 as its console reader. objectui does not |
| 106 | + * read it yet, so `SPEC_ACTION_KEYS` does not list it and the runner's |
| 107 | + * unknown-key warning still names it. objectui#11344 owns the reader, and lists |
| 108 | + * the key in the same change. |
| 109 | + * |
| 110 | + * Booked by objectui#11438 ruling A″ (record 5968177777), which applies |
| 111 | + * objectui#11111 decision 3 = B (record 5902351047) to the bump. The listed |
| 112 | + * key's row asserts TODAY's difference (the spec declares it; objectui's |
| 113 | + * inventory does not know it), and "lists every key the spec ActionSchema |
| 114 | + * declares" requires `missing` to EQUAL this list, with `stale` still empty. A |
| 115 | + * new spec key is red, and so is a listed key objectui starts knowing: the |
| 116 | + * entry goes stale, by name, when objectui#11344's reader lands, and that change |
| 117 | + * strikes it. |
| 118 | + * |
| 119 | + * EXPIRES when objectui#11344's slice lands, or 2026-11-02, whichever is first. |
| 120 | + * The landing is caught by the rows below going red; the date is read by |
| 121 | + * objectui#11344, not by a clock. |
| 122 | + */ |
| 123 | +const OWED_TO_OBJECTUI_11344 = ['outcomeMessages']; |
| 124 | + |
| 125 | +/** The reason every owed row prints when it fails. */ |
| 126 | +const OWED_REASON = |
| 127 | + 'OWED TO objectui#11344: objectui does not read the spec ActionSchema key yet, so the inventory does not list it. ' + |
| 128 | + 'Booked by objectui#11438 ruling A″ (record 5968177777), applying objectui#11111 decision 3 = B ' + |
| 129 | + '(record 5902351047). Expires when objectui#11344\'s slice lands, or 2026-11-02, whichever is first.'; |
| 130 | + |
90 | 131 | describe('action key inventory (objectstack#4075 step 1)', () => { |
91 | 132 | it('ActionDef has NO index signature, and ActionContext still does', () => { |
92 | 133 | // The successor to step 1's inverted pin. That one asserted the index |
@@ -124,8 +165,13 @@ describe('action key inventory (objectstack#4075 step 1)', () => { |
124 | 165 | const stale = (SPEC_ACTION_KEYS as readonly string[]).filter((k) => !spec.includes(k)); |
125 | 166 | // `missing` means the spec grew a key objectui does not know about; `stale` |
126 | 167 | // means it dropped one. Either way the inventory has to be re-derived, and |
127 | | - // the diff names exactly which key moved. |
128 | | - expect({ missing, stale }).toEqual({ missing: [], stale: [] }); |
| 168 | + // the diff names exactly which key moved. The one exception is the booked |
| 169 | + // ledger: `missing` must EQUAL `OWED_TO_OBJECTUI_11344`, not merely contain |
| 170 | + // it, so the booking cannot outlive the reader that strikes it. |
| 171 | + expect({ missing: [...missing].sort(), stale }, OWED_REASON).toEqual({ |
| 172 | + missing: [...OWED_TO_OBJECTUI_11344].sort(), |
| 173 | + stale: [], |
| 174 | + }); |
129 | 175 | }); |
130 | 176 |
|
131 | 177 | it('`execute` is still a live spec tombstone, so it must not count as known', () => { |
@@ -181,6 +227,24 @@ describe('action key inventory (objectstack#4075 step 1)', () => { |
181 | 227 | }); |
182 | 228 | }); |
183 | 229 |
|
| 230 | +describe('objectui#11344 — OWED: the spec ActionSchema declares these keys and objectui does not know them today', () => { |
| 231 | + it.each(OWED_TO_OBJECTUI_11344)('OWED TO objectui#11344: `%s` is declared by the spec ActionSchema, and neither the inventory nor the runner knows it', (key) => { |
| 232 | + // All three halves are asserted together. The first keeps the ledger |
| 233 | + // honest: a key the spec does not declare is not owed to anyone. The other |
| 234 | + // two are the difference: `SPEC_ACTION_KEYS` is the inventory the cap row |
| 235 | + // reads, and `KNOWN_ACTION_KEYS` is what the unknown-key warning reads, so a |
| 236 | + // reader that lists the key on either side turns this row red. |
| 237 | + expect( |
| 238 | + { |
| 239 | + declaredBySpec: specActionKeys().includes(key), |
| 240 | + inSpecActionKeys: (SPEC_ACTION_KEYS as readonly string[]).includes(key), |
| 241 | + knownToTheRunner: KNOWN_ACTION_KEYS.has(key), |
| 242 | + }, |
| 243 | + OWED_REASON, |
| 244 | + ).toEqual({ declaredBySpec: true, inSpecActionKeys: false, knownToTheRunner: false }); |
| 245 | + }); |
| 246 | +}); |
| 247 | + |
184 | 248 | describe('unknown-key warning', () => { |
185 | 249 | let warn: ReturnType<typeof vi.spyOn>; |
186 | 250 |
|
|
0 commit comments