11import { readFileSync , writeFileSync } from "node:fs" ;
22import { join } from "node:path" ;
3+ import { err , ok , type Result } from "neverthrow" ;
34import {
45 Document ,
56 type DocumentOptions ,
@@ -14,6 +15,12 @@ import { z } from "zod";
1415import { OUTPUT_DECLS } from "../../src/action/io.js" ;
1516import type { InputDecl } from "../../src/flows/inputs.js" ;
1617import { INPUT_DECLS } from "../../src/flows/inputs.js" ;
18+ import {
19+ renderedMismatch ,
20+ renderTable ,
21+ tableFault ,
22+ tableRoundTrip ,
23+ } from "../../src/report/markdown.js" ;
1724import { UNDECLARED_POLICY_SECTIONS , type UndeclaredPolicySection } from "../../src/schema.js" ;
1825import { overrideAdviceLevel } from "../../src/sections/contract/errors.js" ;
1926import type { SectionMeta } from "../../src/sections/contract/module.js" ;
@@ -32,12 +39,7 @@ import { SECTIONS } from "../../src/sections/registry.js";
3239import { agree } from "../../src/text.js" ;
3340import type { UndeclaredPolicy } from "../../src/types.js" ;
3441import { countWord } from "./lib/count-word.js" ;
35- import {
36- type GeneratedRegion ,
37- regenerateRegions ,
38- renderedMismatch ,
39- } from "./lib/generated-regions.js" ;
40- import { renderTable , tableFault , tableRoundTrip } from "./lib/markdown-table.js" ;
42+ import { type GeneratedRegion , regenerateRegions } from "./lib/generated-regions.js" ;
4143
4244const ROOT = join ( import . meta. dir , ".." , ".." ) ;
4345
@@ -181,8 +183,8 @@ export interface PolicyRowProse {
181183
182184/**
183185 * Why a row's prose cannot be rendered: blank prose would render `delete ()` or a bare policy span, cells
184- * tableCell () admits since the policy word fills them. The renderer refuses it at its own boundary and the parser
185- * through this same check, by the trimmed-blank rule tableCell () uses, so what the renderer writes is exactly what
186+ * cellFault () admits since the policy word fills them. The renderer refuses it at its own boundary and the parser
187+ * through this same check, by the trimmed-blank rule cellFault () uses, so what the renderer writes is exactly what
186188 * the guard reads back. A structural type cannot vouch for a property a narrowing hid, so the value is inspected
187189 * as unknown.
188190 */
@@ -228,7 +230,7 @@ export function policyTableFault(rows: readonly PolicyRow[]): string | undefined
228230 return `the "${ row . section . key } " Defaults row ${ fault } ` ;
229231 }
230232 }
231- return tableFault ( DEFAULTS_TABLE_HEADER , rows . map ( policyCells ) ) ;
233+ return tableFault ( DEFAULTS_TABLE_HEADER , rows . map ( policyCells ) , "keyed" ) ;
232234}
233235
234236export function renderPolicyDefaultsTable (
@@ -252,29 +254,31 @@ export function renderPolicyDefaultsTable(
252254
253255/** A Defaults row read back to the section and prose it renders from; the delete-first order is then the
254256 * renderer's to check, through the byte compare. The dotAll flag keeps the two Unicode line separators
255- * tableCell () admits inside the prose. */
257+ * cellFault () admits inside the prose. */
256258function policyRow (
257259 cells : readonly string [ ] ,
258260 key : string ,
259- ) : { readonly section : KnobbedSection ; readonly prose : PolicyRowProse } | string {
261+ ) : Result < { readonly section : KnobbedSection ; readonly prose : PolicyRowProse } , string > {
260262 const policy = / ^ ( d e l e t e | k e e p ) (?: \( ( .* ) \) ) ? $ / s. exec ( cells [ 1 ] ?? "" ) ;
261263 const buys = / ^ ` ( d e l e t e | k e e p ) ` : ( .* ) $ / s. exec ( cells [ 2 ] ?? "" ) ;
262264 if ( policy === null ) {
263- return "states no delete or keep default" ;
265+ return err ( "states no delete or keep default" ) ;
264266 }
265267 if ( buys === null ) {
266- return "names no policy span before its override prose" ;
268+ return err ( "names no policy span before its override prose" ) ;
267269 }
268270 const undeclaredDefault = policy [ 1 ] === "delete" ? "delete" : "keep" ;
269271 if ( buys [ 1 ] !== OPPOSITE [ undeclaredDefault ] ) {
270- return `names \`${ buys [ 1 ] } \` where the override is the opposite policy, \`${ OPPOSITE [ undeclaredDefault ] } \`` ;
272+ return err (
273+ `names \`${ buys [ 1 ] } \` where the override is the opposite policy, \`${ OPPOSITE [ undeclaredDefault ] } \`` ,
274+ ) ;
271275 }
272276 const row = { caveat : policy [ 2 ] , override : buys [ 2 ] ?? "" } ;
273277 const fault = proseFault ( row ) ;
274278 if ( fault !== undefined ) {
275- return `${ fault } , which the renderer refuses` ;
279+ return err ( `${ fault } , which the renderer refuses` ) ;
276280 }
277- return { section : { key, undeclaredDefault } , prose : row } ;
281+ return ok ( { section : { key, undeclaredDefault } , prose : row } ) ;
278282}
279283
280284/** The row every secret family shares: the value is write-only, so a wrong delete is a loss, not a drift. */
@@ -499,7 +503,7 @@ export const GENERATED_REGIONS: Readonly<Record<string, readonly GeneratedRegion
499503 {
500504 name : "policy-defaults-table" ,
501505 placement : { kind : "under-heading" , heading : "## Defaults per section" } ,
502- body : tableRoundTrip ( DEFAULTS_TABLE_HEADER , policyRow , ( rows ) =>
506+ body : tableRoundTrip ( DEFAULTS_TABLE_HEADER , "keyed" , policyRow , ( rows ) =>
503507 renderPolicyDefaultsTable (
504508 rows . map ( ( row ) => row . section ) ,
505509 Object . fromEntries ( rows . map ( ( row ) => [ row . section . key , row . prose ] ) ) ,
0 commit comments