|
1 | | -import { createHash } from "node:crypto"; |
2 | 1 | import { readFile } from "node:fs/promises"; |
3 | 2 | import { join, sep } from "node:path"; |
4 | 3 |
|
@@ -74,25 +73,29 @@ function shapeOf(value: unknown, path: string, into: Set<string>): void { |
74 | 73 | } |
75 | 74 |
|
76 | 75 | /** |
77 | | - * A structural fingerprint of the reference payload's SHAPE, derived from the |
78 | | - * live object rather than hand-copied -- so it moves when `Reference` / |
79 | | - * `ReferenceRule` / `ReferenceTests` / `ReferenceLayout` change shape, and |
80 | | - * cannot silently rot the way a hand-maintained field list would. |
| 76 | + * Every (path, kind) pair the reference payload reaches, sorted. |
| 77 | + * |
| 78 | + * Derived from the live object rather than hand-copied, so it moves when |
| 79 | + * `Reference` / `ReferenceRule` / `ReferenceTests` / `ReferenceLayout` change |
| 80 | + * shape and cannot silently rot the way a hand-maintained field list would. |
81 | 81 | * |
82 | 82 | * This is the check the `v1 -> v2` episode (see `REFERENCE_VERSION`'s own |
83 | | - * comment, and CLAUDE.md's account of it) needed and didn't have: `tests` |
| 83 | + * comment, and CLAUDE.md's account of it) needed and did not have: `tests` |
84 | 84 | * went from an array to `{ grouping, files, cases? }` and nothing forced |
85 | | - * `REFERENCE_VERSION` to move with it. A future change of the same kind now |
86 | | - * changes this hash, and the fix is the same one either way -- bump |
87 | | - * `REFERENCE_VERSION`, update the expected hash below, and say why in the |
88 | | - * commit that touches both. |
| 85 | + * `REFERENCE_VERSION` to move with it. A change of that kind now fails here, |
| 86 | + * and the fix is to bump `REFERENCE_VERSION`, update the list below, and say |
| 87 | + * why in the commit that touches both. |
| 88 | + * |
| 89 | + * ASSERTED AS A LIST, NOT A HASH, ON PURPOSE. A digest comparison reports |
| 90 | + * "expected 0add… to be ab12…", which says the shape moved but not what moved, |
| 91 | + * leaving whoever hit it to rebuild the shape set by hand to find the one added |
| 92 | + * field. A guard that fires this rarely has to be legible the one time it does, |
| 93 | + * and 56 lines is a small price for a diff that names the field. |
89 | 94 | */ |
90 | | -function shapeFingerprint(value: unknown): string { |
| 95 | +function shapePaths(value: unknown): string[] { |
91 | 96 | const paths = new Set<string>(); |
92 | 97 | shapeOf(value, "$", paths); |
93 | | - return createHash("sha256") |
94 | | - .update([...paths].toSorted().join("\n")) |
95 | | - .digest("hex"); |
| 98 | + return [...paths].toSorted(); |
96 | 99 | } |
97 | 100 |
|
98 | 101 | describe("the demo reference payload", () => { |
@@ -319,9 +322,64 @@ describe("the demo reference payload", () => { |
319 | 322 | // Bump `REFERENCE_VERSION`, update its doc comment to say what changed and |
320 | 323 | // why, regenerate `assets/reference.json` (`pnpm --filter @taskless/cli |
321 | 324 | // reference`), and update the hash below to match. |
322 | | - expect(shapeFingerprint(reference)).toBe( |
323 | | - "0add32751f84d71a2684d7586a4d1b1d2fd4dbf543efeabc3a842b57b27ad787" |
324 | | - ); |
| 325 | + expect(shapePaths(reference)).toEqual([ |
| 326 | + "$.constraints:array", |
| 327 | + "$.constraints[].enforcedBy:string", |
| 328 | + "$.constraints[].engine:string", |
| 329 | + "$.constraints[].id:string", |
| 330 | + "$.constraints[].rationale:string", |
| 331 | + "$.constraints[].summary:string", |
| 332 | + "$.constraints[]:object", |
| 333 | + "$.layout.engines.runtime.capturesDirectory:string", |
| 334 | + "$.layout.engines.runtime.fixtureLayout:string", |
| 335 | + "$.layout.engines.runtime.ruleConfigFile:null", |
| 336 | + "$.layout.engines.runtime.ruleFile:string", |
| 337 | + "$.layout.engines.runtime:object", |
| 338 | + "$.layout.engines.sg.capturesDirectory:null", |
| 339 | + "$.layout.engines.sg.fixtureLayout:string", |
| 340 | + "$.layout.engines.sg.ruleConfigFile:null", |
| 341 | + "$.layout.engines.sg.ruleFile:string", |
| 342 | + "$.layout.engines.sg:object", |
| 343 | + "$.layout.engines.vale.capturesDirectory:null", |
| 344 | + "$.layout.engines.vale.fixtureLayout:string", |
| 345 | + "$.layout.engines.vale.ruleConfigFile:string", |
| 346 | + "$.layout.engines.vale.ruleFile:string", |
| 347 | + "$.layout.engines.vale:object", |
| 348 | + "$.layout.engines:object", |
| 349 | + "$.layout.ruleDirectory:string", |
| 350 | + "$.layout.rulesRoot:string", |
| 351 | + "$.layout.testsDirectory:string", |
| 352 | + "$.layout:object", |
| 353 | + "$.protocol:array", |
| 354 | + "$.protocol[]:string", |
| 355 | + "$.rules:array", |
| 356 | + "$.rules[].directory:string", |
| 357 | + "$.rules[].engine:string", |
| 358 | + "$.rules[].id:string", |
| 359 | + "$.rules[].prompt:string", |
| 360 | + "$.rules[].rule:array", |
| 361 | + "$.rules[].ruleFile:string", |
| 362 | + "$.rules[].rule[].content:string", |
| 363 | + "$.rules[].rule[].path:string", |
| 364 | + "$.rules[].rule[]:object", |
| 365 | + "$.rules[].signature:string", |
| 366 | + "$.rules[].tests.cases:array", |
| 367 | + "$.rules[].tests.cases[].bucket:string", |
| 368 | + "$.rules[].tests.cases[].files:array", |
| 369 | + "$.rules[].tests.cases[].files[]:string", |
| 370 | + "$.rules[].tests.cases[].name:string", |
| 371 | + "$.rules[].tests.cases[].path:string", |
| 372 | + "$.rules[].tests.cases[]:object", |
| 373 | + "$.rules[].tests.files:array", |
| 374 | + "$.rules[].tests.files[].content:string", |
| 375 | + "$.rules[].tests.files[].path:string", |
| 376 | + "$.rules[].tests.files[]:object", |
| 377 | + "$.rules[].tests.grouping:string", |
| 378 | + "$.rules[].tests:object", |
| 379 | + "$.rules[]:object", |
| 380 | + "$.version:number", |
| 381 | + "$:object", |
| 382 | + ]); |
325 | 383 | }); |
326 | 384 |
|
327 | 385 | it("carries the prompt each rule answers", async () => { |
|
0 commit comments