pagegraph 0.14.1 → 0.15.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/README.md +169 -10
  2. package/dist/audit.d.ts +1 -1
  3. package/dist/audit.js +1 -1
  4. package/dist/{checks-BS-aDXB_.js → checks-BEhJPNzN.js} +12 -1
  5. package/dist/checks-BEhJPNzN.js.map +1 -0
  6. package/dist/{claims-Bch7Lxlo.d.ts → claims-CCf2_Jox.d.ts} +2 -2
  7. package/dist/{claims-Bv7voeEV.js → claims-CQlzze1K.js} +2 -2
  8. package/dist/{claims-Bv7voeEV.js.map → claims-CQlzze1K.js.map} +1 -1
  9. package/dist/claims.d.ts +2 -2
  10. package/dist/claims.js +1 -1
  11. package/dist/cli.js +1355 -616
  12. package/dist/cli.js.map +1 -1
  13. package/dist/config.d.ts +7 -3
  14. package/dist/config.js +1 -1
  15. package/dist/config.js.map +1 -1
  16. package/dist/{content-CHd0X4s6.js → content-D5jZprMq.js} +2 -2
  17. package/dist/{content-CHd0X4s6.js.map → content-D5jZprMq.js.map} +1 -1
  18. package/dist/{content-DgNr7apS.d.ts → content-DerWgQy9.d.ts} +11 -3
  19. package/dist/{dist-cFoAzruL.js → dist-Br2oOB2Q.js} +2 -2
  20. package/dist/{dist-cFoAzruL.js.map → dist-Br2oOB2Q.js.map} +1 -1
  21. package/dist/{document-CztEu3Vd.d.ts → document-D9P8yY5g.d.ts} +4 -4
  22. package/dist/{graph-D1PIndsC.d.ts → graph-ChrvU-6X.d.ts} +27 -4
  23. package/dist/{graph-DMNOLXLu.js → graph-UONEaqIX.js} +43 -5
  24. package/dist/graph-UONEaqIX.js.map +1 -0
  25. package/dist/{index-C5w9ripz.d.ts → index-DzjUmbHY.d.ts} +5 -5
  26. package/dist/index.d.ts +6 -6
  27. package/dist/index.js +5 -6
  28. package/dist/index.js.map +1 -1
  29. package/dist/{page-heads-BfjWQvau.d.ts → page-heads-DDz_2_hq.d.ts} +3 -2
  30. package/dist/{page-heads-BgbaIjj9.js → page-heads-YL8nyYjF.js} +4 -3
  31. package/dist/page-heads-YL8nyYjF.js.map +1 -0
  32. package/dist/{pi-D3mMmYey.js → pi-Bz4BgZdC.js} +184 -154
  33. package/dist/pi-Bz4BgZdC.js.map +1 -0
  34. package/dist/{projections-Bk-qOOsR.d.ts → projections-Dqo_ih-J.d.ts} +2 -2
  35. package/dist/react.d.ts +5 -3
  36. package/dist/react.js +43 -19
  37. package/dist/react.js.map +1 -1
  38. package/dist/{state-BuJP_3Lr.js → state-2ikjo7uI.js} +638 -25
  39. package/dist/state-2ikjo7uI.js.map +1 -0
  40. package/dist/tanstack-start/index.d.ts +1 -1
  41. package/dist/tanstack-start/index.js +14 -5
  42. package/dist/tanstack-start/index.js.map +1 -1
  43. package/dist/tanstack-start/react.d.ts +1 -1
  44. package/dist/tanstack-start/server.d.ts +2 -2
  45. package/dist/tanstack-start/server.js +1 -1
  46. package/dist/tanstack-start/server.js.map +1 -1
  47. package/dist/{vite-graph-loader-DZCwejuJ.d.ts → vite-graph-loader-BtLgkN0V.d.ts} +7 -5
  48. package/package.json +1 -1
  49. package/dist/checks-BS-aDXB_.js.map +0 -1
  50. package/dist/declare-D5o7hwmv.js +0 -9
  51. package/dist/declare-D5o7hwmv.js.map +0 -1
  52. package/dist/graph-DMNOLXLu.js.map +0 -1
  53. package/dist/page-heads-BgbaIjj9.js.map +0 -1
  54. package/dist/pi-D3mMmYey.js.map +0 -1
  55. package/dist/state-BuJP_3Lr.js.map +0 -1
package/README.md CHANGED
@@ -352,6 +352,71 @@ route tree — in a Node environment named `pagegraph`. A route that imports a m
352
352
  only the server runtime provides (`cloudflare:workers`) fails with its file name; add an
353
353
  app-only route to `exclude` (route-file globs).
354
354
 
355
+ ### Per-page Open Graph images
356
+
357
+ Declare an image alongside a static page's title and description:
358
+
359
+ ```ts
360
+ head: {
361
+ title: "Pricing — Example",
362
+ description: "Simple volume pricing.",
363
+ image: { url: "/og/pricing.png", width: 1200, height: 630, alt: "Example pricing" },
364
+ }
365
+ ```
366
+
367
+ For collection pages, return `image` from `contentCollection`'s `entry` mapping and pass the
368
+ same value to `seoHead(ctx, { ...head, image })` at render time. Existing `article.image` strings
369
+ remain supported. Precedence is `head.image`, then `article.image`, then the resolver.
370
+
371
+ Share a resolver between the build plugin and the render API so every graph consumer and the
372
+ rendered head use the same image:
373
+
374
+ ```ts
375
+ // src/lib/seo/og-image.ts
376
+ import type { OgImageResolver } from "pagegraph";
377
+
378
+ export const ogImage: OgImageResolver = (node) => ({
379
+ url: `/og/${node.kind}/${node.path === "/" ? "index" : node.path.slice(1)}.png`,
380
+ width: 1200,
381
+ height: 630,
382
+ alt: node.head?.title ?? node.path,
383
+ });
384
+
385
+ // vite.config.ts
386
+ pagegraph({ origin: "https://example.com", ogImage, /* other plugin options */ });
387
+
388
+ // src/lib/seo.ts
389
+ createSeo({ origin: "https://example.com", ogImage, site, organization, website });
390
+ ```
391
+
392
+ The resolver is synchronous and returns an image or `undefined`. It receives a concrete node
393
+ with its path, kind, and head metadata (the shared `OgImageNode` shape). The resolver path
394
+ honors `head.canonicalPath` while graph path ownership stays unchanged. Keep it
395
+ deterministic and safe to import in the server/browser runtime. The consumer renders the PNGs
396
+ at build time; pagegraph only resolves metadata. The graph stores the absolute URL in
397
+ `node.head.image`, preserved by graph serialization and exposed by `pageHeads` for downstream
398
+ head, llms, and Markdown tooling. A generic `buildSeoGraph` caller supplies `origin` and
399
+ `ogImage` to resolve images in the same way.
400
+
401
+ An image emits `og:image`, optional `og:image:width`, `og:image:height`, and `og:image:alt`, plus
402
+ `twitter:image` and `twitter:card=summary_large_image`. Without an image, the card is `summary`.
403
+ `site.defaultImage` remains the Article JSON-LD default; it does not satisfy social-image coverage.
404
+
405
+ `pagegraph check` fails sitemap-eligible pages without a resolved image. Same-origin images
406
+ must name files under the app's `public/` directory, including absolute URLs resolved from
407
+ local paths; external URLs are not fetched. Generate the files before running the check.
408
+ Configure the policy in `pagegraph.config.ts`:
409
+
410
+ ```ts
411
+ export default defineSeoConfig({
412
+ loadGraph: tanstackStartGraph({ root: import.meta.dirname }),
413
+ ogImage: {
414
+ severity: "structural", // default: error; "editorial" warns, "off" skips both checks
415
+ publicDirectory: "public", // relative to the config directory; default: public
416
+ },
417
+ });
418
+ ```
419
+
355
420
  **4. Serve robots.txt and sitemap.xml.** One line each; the output depends on the
356
421
  deployment's origin and indexability, so they stay routes.
357
422
 
@@ -412,7 +477,8 @@ declines Executor approval requests, and only provider calls that succeeded coun
412
477
  Use `executor_search` to discover tools and their TypeScript shapes, then `executor_execute` with
413
478
  a TypeScript snippet calling `tools.<namespace>.<tool>(input)` and a top-level `return`.
414
479
  Repository tools are `read_file`, `list_files`, and `search_text`; structured results go through
415
- `submit_result`. Improve action turns expose `edit_file` and `write_file` in write mode.
480
+ `submit_result`. Improve action turns submit structured edits instead of writing files; see
481
+ [Review and apply edits](#review-and-apply-edits).
416
482
 
417
483
  Enable workflows in `pagegraph.config.ts`:
418
484
 
@@ -435,6 +501,9 @@ export default defineSeoConfig({
435
501
  "research.keywords": ["docs/seo/measurement.md"],
436
502
  },
437
503
  },
504
+ // Optional read root for repository tools and context.files, relative to this config file.
505
+ // Defaults to the config directory's Git top level, or the config directory outside Git.
506
+ repositoryRoot: ".",
438
507
  runsDirectory: ".pagegraph/runs",
439
508
  },
440
509
  });
@@ -449,11 +518,56 @@ pagegraph research keywords \
449
518
  --language en
450
519
  ```
451
520
 
521
+ Pass `--from <run-dir|run-id>` to a workflow command to resume a prior run. Resume restores its
522
+ recorded targets and completed stages, along with the agent session and Executor evidence. Completed
523
+ runs return their recorded output without provider calls. An incomplete run resumes from its recorded
524
+ checkpoint and skips recorded completed calls; PageGraph checks that repository HEAD and files still
525
+ match that checkpoint. If an action started without recording completion, resume is refused because
526
+ replaying it could repeat edits; inspect the run artifacts and repository before starting a new run.
527
+ Earlier unfinished runs without `progress.json` cannot be resumed.
528
+ Completed-run reuse does not load the current app graph. Incomplete resume also compares saved
529
+ context contents independently of Git, including ignored files and projects outside Git. Checkpoint
530
+ updates replace the previous JSON atomically, preserving the last usable checkpoint if a write fails.
531
+ Executor replay matches the exact script or catalog query and reuses its most recent recorded
532
+ completed result. A changed script or query is a new call.
533
+
534
+ ```bash
535
+ pagegraph research keywords --from .pagegraph/runs/2026-10-05T04-52-23-891Z-research-keywords
536
+ ```
537
+
452
538
  Every workflow starts from the selected graph and context files, asks the SEO agent to search
453
539
  Executor's live catalog and inspect the discovered tools' argument schemas, then evaluates its
454
540
  structured observations with Jev. Tool paths are discovered at runtime; the generated skills contain
455
541
  editable starter recipes, not a fixed integration list.
456
542
 
543
+ `workflows.repositoryRoot` sets the read boundary for `read_file`, `list_files`, `search_text`, and
544
+ `context.files`. Its optional path is relative to `pagegraph.config.ts`; by default PageGraph uses
545
+ the config directory's Git top level, falling back to the config directory when it is outside Git.
546
+ Context file paths are now relative to this read root, so adjust existing app-relative paths when the
547
+ default Git root is above the app (for example, `apps/web/AGENTS.md`). Run artifacts under
548
+ `runsDirectory` are readable. Improve edits name files the way `read_file` does and must stay
549
+ inside the app root containing `pagegraph.config.ts`.
550
+
551
+ Before Pi makes a paid model request, PageGraph checks that the selected model's provider key,
552
+ `TYPESAFE_API_KEY`, and required Executor configuration are present. Missing configuration fails
553
+ before a model turn begins.
554
+
555
+ Executor limits preview text for large tool results. In `executor_execute` code, return only the
556
+ fields and rows the workflow needs, for example:
557
+
558
+ ```ts
559
+ return rows.slice(0, 50).map(({ keyword, searchVolume, keywordDifficulty }) => ({
560
+ keyword, searchVolume, keywordDifficulty,
561
+ }));
562
+ ```
563
+
564
+ When PageGraph says a result was truncated, read the named JSON file under
565
+ `<run>/tool-results/` with `read_file`. Use its `offset` and `maxBytes` options to read large files
566
+ in chunks.
567
+
568
+ After the submission retry budget is exhausted, PageGraph retains valid items and records rejected
569
+ items with their indices and validation reasons in `research.json` and `run.json`.
570
+
457
571
  `workflows.agent.timeoutMs` sets the positive integer deadline in milliseconds for each Pi research or action turn
458
572
  (maximum `2147483647`, default `180000`). Research and action share the same agent conversation,
459
573
  so action turns retain the research context.
@@ -493,16 +607,60 @@ The common selectors are repeatable `--page`, `--query`, and `--kind`, plus `--l
493
607
  accept `--competitor`, `--domain`, or `--device desktop|mobile`. Workflow prompts direct the agent
494
608
  to proceed without questions or forms and fail clearly when required input is missing.
495
609
 
496
- The four `improve` workflows write source files by default and require a clean Git tree. Use
497
- `--dry-run` to expose only read tools, or `--allow-dirty` when you explicitly
498
- want the agent to edit alongside existing changes. PageGraph leaves every diff uncommitted and
499
- records modified, added, and deleted files after the workflow finishes. Write tools reject paths
500
- outside the repository, including symlink escapes, and paths under `.git`, `node_modules`,
501
- the runs directory, and the preset directory. There is no shell tool. For `improve links`,
502
- `fetch_page` reads extracted sentences from served pages on suggestion-report origins, respecting
503
- robots rules and the response size limit.
610
+ The four `improve` workflows apply their edits by default and require a clean Git tree. Use
611
+ `--dry-run` to record edits for review without changing source files, or `--allow-dirty` when you
612
+ explicitly want edits applied alongside existing changes. There is no shell tool. For
613
+ `improve links`, `fetch_page` reads extracted sentences from served pages on suggestion-report
614
+ origins, respecting robots rules and the response size limit.
615
+
616
+ ### Review and apply edits
617
+
618
+ The agent never writes files. Its action turn submits `edits` and `reviewItems` through
619
+ `submit_result`:
620
+
621
+ | Field | Meaning |
622
+ | --- | --- |
623
+ | `edits[].path` | File to change, as `read_file` names it; it must be inside the app root |
624
+ | `edits[].oldText` | Exact current text; it must match once. Omit it to create a new file |
625
+ | `edits[].newText` | Replacement text |
626
+ | `edits[].reason`, `edits[].evidence` | Why the change is justified, with repository and provider evidence |
627
+ | `reviewItems[]` | `path`, `reason`, and `evidence` for a change a human must make |
628
+
629
+ PageGraph rejects a submission whose edits do not apply to the current files and returns the reasons
630
+ to the agent. Accepted edits are written to the run directory:
631
+
632
+ - `edits.json` lists each edit with an ID (`e1`, `e2`, …) and paths relative to the app root, plus
633
+ review items. Jev decisions that land in review are included as `decision` review items.
634
+ - `review.md` renders the same content for a human: each edit as a diff with its reason and
635
+ evidence, followed by the review items.
636
+
637
+ The two modes differ only in who applies the edits:
638
+
639
+ | Mode | Source files | Next step |
640
+ | --- | --- | --- |
641
+ | Write (default) | PageGraph applies every edit after recording it | Review the uncommitted diff |
642
+ | `--dry-run` | Unchanged | Review `review.md`, then run `pagegraph apply` |
643
+
644
+ A write run is equivalent to a dry run followed by `pagegraph apply`. Writes reject paths outside the
645
+ app root, including symlink escapes, hard-linked files, and paths under `.git`, `node_modules`, the
646
+ runs directory, and the preset directory. PageGraph leaves every diff uncommitted and records the
647
+ changed files in `run.json`.
648
+
649
+ ```bash
650
+ pagegraph improve metadata --page /pricing --dry-run
651
+ pagegraph apply <run-id> --check # report which edits still apply; no writes
652
+ pagegraph apply <run-id> --only e1,e3 # apply a subset (repeatable or comma-separated)
653
+ pagegraph apply <run-id> # apply every edit that still matches
654
+ ```
655
+
656
+ `pagegraph apply <run-dir|run-id>` runs from the app containing `pagegraph.config.ts` and applies
657
+ edits in recorded order. An edit is `stale` when its `oldText` no longer matches exactly once or
658
+ its new file already exists; `failed` when the write policy refuses the path or the write errors.
659
+ Stale and failed edits are skipped, the others apply, and the command exits non-zero if any edit
660
+ was skipped. `--json` emits a versioned `pagegraph-apply-report`.
504
661
 
505
- Each run writes `.pagegraph/runs/<run-id>/run.json` and `summary.md`. The JSON retains the
662
+ Each run writes `.pagegraph/runs/<run-id>/run.json` and `summary.md`; improve runs also write
663
+ `edits.json` and `review.md`. The JSON retains the
506
664
  deterministic graph, context, Executor tool evidence, Jev question definitions and answers, Git
507
665
  provenance, changed files, and model provenance. Schema-version-2 artifacts record `agent.runtime`
508
666
  as the literal `pi`, with messages and usage. Keep this directory ignored: provider output can be large or account-specific. Workflows fail when the configured model,
@@ -725,6 +883,7 @@ pagegraph links verify <url> --emit-rendered <file> # save the rendered edge se
725
883
  pagegraph links candidates # propose contextual links from the declared graph
726
884
  pagegraph research keywords --query "email api" # combine graph, Executor, and Jev evidence
727
885
  pagegraph improve metadata --page /pricing # apply a clean-tree source improvement
886
+ pagegraph apply <run-id> # apply a dry-run's recorded edits
728
887
  pagegraph sitemap # print sitemap.xml
729
888
  pagegraph robots # print robots.txt
730
889
  ```
package/dist/audit.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import "./content-DgNr7apS.js";
1
+ import "./content-DerWgQy9.js";
2
2
  import * as Effect from "effect/Effect";
3
3
  import * as Context from "effect/Context";
4
4
  import * as Layer from "effect/Layer";
package/dist/audit.js CHANGED
@@ -1,4 +1,4 @@
1
- import { l as extractAnchors, r as extractPageContent, t as checkPageContent } from "./content-CHd0X4s6.js";
1
+ import { l as extractAnchors, r as extractPageContent, t as checkPageContent } from "./content-D5jZprMq.js";
2
2
  import { mkdir, mkdtemp, readFile, rename, rm, writeFile } from "node:fs/promises";
3
3
  import { join } from "node:path";
4
4
  import * as Data$1 from "effect/Data";
@@ -249,6 +249,17 @@ function checkGraph(graph, options = {}) {
249
249
  message: raw.message,
250
250
  fix: raw.fix
251
251
  })));
252
+ const imageSeverity = options.ogImage?.severity ?? "structural";
253
+ if (imageSeverity !== "off") for (const node of graph.nodes.values()) {
254
+ if (!isSitemapEligible(node) || node.head?.image?.url.trim()) continue;
255
+ violations.push({
256
+ rule: "missing-og-image",
257
+ severity: imageSeverity,
258
+ path: node.path,
259
+ message: `Sitemap page "${node.path}" has no resolved OG image.`,
260
+ fix: "Declare staticData.seo.head.image (or a collection image), or supply an ogImage resolver to pagegraph()."
261
+ });
262
+ }
252
263
  const coverage = options.coverage;
253
264
  if (coverage !== void 0 && coverage.length > 0) violations.push(...checkCoverage(graph, coverage));
254
265
  if (options.freshness !== void 0) violations.push(...checkFreshness(graph, options.freshness, options.now));
@@ -349,4 +360,4 @@ const hasStructuralViolations = (violations) => violations.some((violation) => v
349
360
  //#endregion
350
361
  export { globToRegExp as a, checkRenderedCoverage as i, checkFreshness as n, hasStructuralViolations as o, checkGraph as r, freshnessReport as s, checkCoverage as t };
351
362
 
352
- //# sourceMappingURL=checks-BS-aDXB_.js.map
363
+ //# sourceMappingURL=checks-BEhJPNzN.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"checks-BEhJPNzN.js","names":[],"sources":["../src/core/freshness.ts","../src/core/checks.ts"],"sourcesContent":["/**\n * Freshness: how long ago each indexable page last changed, by the dates its\n * declaration carries ({@link lastModified}). Pure — the caller injects `now`,\n * so a test and the CLI read the same verdict from the same graph.\n *\n * Only sitemap-eligible pages are judged: a noindex or redirect page is not a\n * refresh candidate. A page with no date is reported as `undated` rather than\n * stale, because age cannot be inferred; the sitemap omits its `<lastmod>` too.\n */\n\nimport type { SeoKind } from \"./declare\";\nimport type { SeoGraph, SeoSource } from \"./graph\";\nimport { isSitemapEligible, lastModified, parseDeclaredDate } from \"./projections\";\n\n/** Project policy: pages whose last change is older than this are stale. */\nexport interface FreshnessPolicy {\n readonly maxAgeDays: number;\n}\n\nexport interface FreshnessEntry {\n readonly path: string;\n readonly kind: SeoKind;\n readonly source: SeoSource;\n /** The declared date, normalized to ISO 8601. */\n readonly lastModified: string;\n /** Whole days between `lastModified` and the report's `asOf`. */\n readonly ageDays: number;\n}\n\nexport interface FreshnessReport {\n readonly asOf: string;\n readonly maxAgeDays: number;\n /** Pages older than `maxAgeDays`, oldest first: the refresh queue. */\n readonly stale: ReadonlyArray<FreshnessEntry>;\n /** Dated pages within `maxAgeDays`, newest first. */\n readonly fresh: ReadonlyArray<FreshnessEntry>;\n /** Sitemap-eligible pages that declare no date, in graph order. */\n readonly undated: ReadonlyArray<string>;\n /** Sitemap-eligible pages whose date does not parse; `check` fails on each. */\n readonly invalid: ReadonlyArray<string>;\n}\n\nconst DAY_MS = 86_400_000;\n\n/**\n * Split the graph's sitemap-eligible pages into stale, fresh, undated, and\n * invalid. An invalid date cannot be aged, but it is listed so a report never\n * reads as complete while it skipped a page.\n */\nexport function freshnessReport(\n graph: SeoGraph,\n policy: FreshnessPolicy,\n now: Date,\n): FreshnessReport {\n const stale: Array<FreshnessEntry> = [];\n const fresh: Array<FreshnessEntry> = [];\n const undated: Array<string> = [];\n const invalid: Array<string> = [];\n\n for (const node of graph.nodes.values()) {\n if (!isSitemapEligible(node)) continue;\n const declared = lastModified(node);\n if (declared === undefined) {\n undated.push(node.path);\n continue;\n }\n const time = parseDeclaredDate(declared);\n if (time === undefined) {\n invalid.push(node.path);\n continue;\n }\n const entry: FreshnessEntry = {\n path: node.path,\n kind: node.kind,\n source: node.source,\n lastModified: new Date(time).toISOString(),\n ageDays: Math.max(0, Math.floor((now.getTime() - time) / DAY_MS)),\n };\n (entry.ageDays > policy.maxAgeDays ? stale : fresh).push(entry);\n }\n\n stale.sort((a, b) => b.ageDays - a.ageDays || (a.path < b.path ? -1 : 1));\n fresh.sort((a, b) => a.ageDays - b.ageDays || (a.path < b.path ? -1 : 1));\n return {\n asOf: now.toISOString(),\n maxAgeDays: policy.maxAgeDays,\n stale,\n fresh,\n undated,\n invalid,\n };\n}\n","/**\n * The SEO check engine: a set of rules run against the derived {@link SeoGraph}.\n * Pure — no I/O, no `clientEnv`, no React. The CLI's `pagegraph check` command and the\n * vitest suite both call {@link checkGraph}; nothing else derives correctness.\n *\n * Two severities, mapped to the CLI's exit contract:\n * - `structural` — a declaration is internally broken (a link points nowhere, a\n * card would render empty, a page contradicts its own robots/sitemap intent).\n * Any structural violation fails `pagegraph check` (exit 1); these must not ship.\n * - `editorial` — a quality smell (duplicate or mis-sized titles/descriptions).\n * Reported as warnings; `pagegraph check` still exits 0 when only these are present.\n *\n * The graph only knows what declarations and frontmatter carry, so the rules are\n * scoped to that: per-node title/description live only on collection *instances*,\n * never on structural route nodes (whose head tags are composed at render time and\n * are not in the graph). Rules are data-driven and listed once in\n * {@link CHECK_RULES}.\n */\n\nimport type { SitemapPolicy } from \"./declare\";\nimport { freshnessReport, type FreshnessPolicy } from \"./freshness\";\nimport type { SeoGraph, SeoNode } from \"./graph\";\nimport type { LinkEdge } from \"./links\";\nimport { isSitemapEligible, parseDeclaredDate } from \"./projections\";\n\nexport type Severity = \"structural\" | \"editorial\";\n\n/** OG image requirements use the same severity and exit contract as other checks. */\nexport interface OgImagePolicy {\n /** Defaults to structural (error); editorial warns, off disables image checks. */\n readonly severity?: Severity | \"off\" | undefined;\n /** Static asset directory, relative to the CLI config directory; defaults to public. */\n readonly publicDirectory?: string | undefined;\n}\n\nexport interface Violation {\n severity: Severity;\n rule: string;\n path?: string | undefined;\n message: string;\n fix?: string | undefined;\n}\n\n/**\n * A contextual-link coverage rule: every sitemap-eligible page matching the\n * `path` glob must have at least `minInbound` incoming contextual links. Unlike\n * the static rules, these come from project policy (a CLI flag or\n * `pagegraph.config.ts`), because \"which pages are money pages\" is app knowledge.\n *\n * The same rule shape is evaluated twice: `checkCoverage` counts declared\n * `related` edges, and `checkRenderedCoverage` counts the contextual anchors a\n * crawl actually served. The target universe is always the declared graph's\n * sitemap-eligible pages — the rendered pass only swaps the edge source.\n */\nexport interface CoverageRule {\n /** Path glob: `*` matches within a segment, `**` matches across segments. */\n readonly path: string;\n /** Minimum incoming contextual links the matched page needs. */\n readonly minInbound: number;\n}\n\n/** A single finding before its rule's `severity`/`rule` name are attached. */\ninterface RawViolation {\n path?: string | undefined;\n message: string;\n fix?: string | undefined;\n}\n\ninterface CheckRule {\n readonly name: string;\n readonly severity: Severity;\n readonly evaluate: (graph: SeoGraph) => ReadonlyArray<RawViolation>;\n}\n\n/** A positive sitemap policy — the author asked for this page to be indexed. */\nconst hasPositiveSitemap = (sitemap: SitemapPolicy | false | undefined): sitemap is SitemapPolicy =>\n sitemap !== undefined && sitemap !== false;\n\n/** Content and manifest instances carry page-level title/description. */\nconst isInstance = (node: SeoNode): boolean => node.source !== \"route\";\n\n/** Group instance nodes by a present, non-empty string field for duplicate detection. */\nconst groupInstancesBy = (\n graph: SeoGraph,\n field: (node: SeoNode) => string | undefined,\n): Map<string, Array<SeoNode>> => {\n const groups = new Map<string, Array<SeoNode>>();\n for (const node of graph.nodes.values()) {\n if (!isInstance(node)) continue;\n const value = field(node)?.trim();\n if (!value) continue;\n const bucket = groups.get(value);\n if (bucket) bucket.push(node);\n else groups.set(value, [node]);\n }\n return groups;\n};\n\nconst DESCRIPTION_MAX = 160;\nconst DESCRIPTION_MIN = 50;\n\n/**\n * Every check, in one place. `checkGraph` runs them in order and stamps each\n * finding with its rule name and severity, so the output is grouped by rule and\n * deterministic (nodes iterate in graph insertion order, edges in array order).\n */\nconst CHECK_RULES: ReadonlyArray<CheckRule> = [\n {\n name: \"path-owner-collision\",\n severity: \"structural\",\n evaluate: (graph) =>\n (graph.collisions ?? []).map((collision) => ({\n path: collision.path,\n message: `Canonical path \"${collision.path}\" is owned by multiple sources: ${collision.sources.join(\", \")}.`,\n fix: \"Give every concrete page one canonical path and one graph owner.\",\n })),\n },\n {\n name: \"canonical-path-collision\",\n severity: \"structural\",\n evaluate: (graph) => {\n const groups = new Map<string, Array<string>>();\n for (const path of graph.nodes.keys()) {\n const canonical = path.toLowerCase().replace(/\\/$/, \"\") || \"/\";\n const paths = groups.get(canonical);\n if (paths) paths.push(path);\n else groups.set(canonical, [path]);\n }\n return [...groups.values()]\n .filter((paths) => paths.length > 1)\n .flatMap((paths) =>\n paths.map((path) => ({\n path,\n message: `Canonical path collides with ${paths.filter((candidate) => candidate !== path).join(\", \")}.`,\n fix: \"Use one lowercase, trailing-slash-normalized canonical path.\",\n })),\n );\n },\n },\n {\n name: \"self-edge\",\n severity: \"structural\",\n evaluate: (graph) =>\n graph.edges\n .filter((edge) => edge.from === edge.to)\n .map((edge) => ({\n path: edge.from,\n message: `${edge.type} edge points back to its own source node.`,\n fix: \"Remove the self-reference from the canonical manifest or route declaration.\",\n })),\n },\n {\n name: \"duplicate-edge\",\n severity: \"structural\",\n evaluate: (graph) => {\n const seen = new Set<string>();\n const duplicates: Array<RawViolation> = [];\n for (const edge of graph.edges) {\n const key = `${edge.from}\\u0000${edge.to}\\u0000${edge.type}`;\n if (seen.has(key)) {\n duplicates.push({\n path: edge.from,\n message: `Duplicate ${edge.type} edge from \"${edge.from}\" to \"${edge.to}\".`,\n fix: \"Declare each graph relationship exactly once.\",\n });\n } else {\n seen.add(key);\n }\n }\n return duplicates;\n },\n },\n {\n // A `related` or `redirect` edge points at a path with no node — the target\n // route/page was renamed or deleted and the declaration wasn't updated.\n name: \"dead-edge\",\n severity: \"structural\",\n evaluate: (graph) =>\n graph.edges\n .filter((edge) => edge.type !== \"crumb-parent\" && !graph.nodes.has(edge.to))\n .map((edge) => ({\n path: edge.from,\n message: `${edge.type} edge from \"${edge.from}\" points at \"${edge.to}\", which is not a node in the graph.`,\n fix: `Update the ${edge.type === \"redirect\" ? \"redirectTo\" : \"related\"} target on the \"${edge.from}\" route, or restore \"${edge.to}\".`,\n })),\n },\n {\n // A content instance with no usable title can't render a legible <title> or card.\n name: \"instance-missing-title\",\n severity: \"structural\",\n evaluate: (graph) =>\n [...graph.nodes.values()]\n .filter((node) => isInstance(node) && !node.instance?.title.trim())\n .map((node) => ({\n path: node.path,\n message: `Content page \"${node.path}\" has no title.`,\n fix: \"Add a `title` to the page frontmatter.\",\n })),\n },\n {\n // The declaration asks for the page to be in the sitemap yet also marks it\n // noindex — contradictory intent. The projection resolves it (noindex wins,\n // excluded), but the declaration should say one thing.\n name: \"sitemap-noindex-contradiction\",\n severity: \"structural\",\n evaluate: (graph) =>\n [...graph.nodes.values()]\n .filter(\n (node) =>\n hasPositiveSitemap(node.policy.sitemap) &&\n node.policy.robots?.toLowerCase().includes(\"noindex\"),\n )\n .map((node) => ({\n path: node.path,\n message: `\"${node.path}\" declares a sitemap policy but its robots value is \"${node.policy.robots}\".`,\n fix: \"Drop the sitemap policy (or set `sitemap: false`) on a noindex page, or remove the noindex robots value.\",\n })),\n },\n {\n // Robots declarations are house-convention lowercase: the sitemap projection\n // matches `includes(\"noindex\")` literally, so a miscased value (\"Noindex\")\n // would silently stay sitemap-eligible. This gate makes that unrepresentable.\n name: \"robots-not-lowercase\",\n severity: \"structural\",\n evaluate: (graph) =>\n [...graph.nodes.values()]\n .filter(\n (node) =>\n node.policy.robots !== undefined &&\n node.policy.robots !== node.policy.robots.toLowerCase(),\n )\n .map((node) => ({\n path: node.path,\n message: `\"${node.path}\" declares robots \"${node.policy.robots}\" — robots values must be lowercase.`,\n fix: 'Lowercase the robots declaration (e.g. \"noindex, follow\").',\n })),\n },\n {\n // A `related` card for a route target renders from that route's `link`\n // metadata; without it the card has no title/description and renders empty.\n // (Content-instance targets render from their frontmatter, so they're exempt.)\n name: \"related-target-missing-link\",\n severity: \"structural\",\n evaluate: (graph) => {\n const linklessTargets = new Set<string>();\n for (const edge of graph.edges) {\n if (edge.type !== \"related\") continue;\n const target = graph.nodes.get(edge.to);\n if (target && target.source === \"route\" && target.policy.link === undefined) {\n linklessTargets.add(edge.to);\n }\n }\n return [...linklessTargets].map((path) => ({\n path,\n message: `Route \"${path}\" is a related-link target but declares no link metadata; its card would render empty.`,\n fix: `Add \\`link: { title, description }\\` to the \"${path}\" route's staticData.seo.`,\n }));\n },\n },\n {\n // A redirect/alias node should never advertise itself in the sitemap.\n name: \"redirect-in-sitemap\",\n severity: \"structural\",\n evaluate: (graph) =>\n [...graph.nodes.values()]\n .filter(\n (node) => node.policy.redirectTo !== undefined && hasPositiveSitemap(node.policy.sitemap),\n )\n .map((node) => ({\n path: node.path,\n message: `Redirect \"${node.path}\" (→ \"${node.policy.redirectTo}\") also declares a sitemap policy.`,\n fix: \"Remove the sitemap policy from the redirect route; only its target belongs in the sitemap.\",\n })),\n },\n {\n // Every declared date feeds the sitemap's `<lastmod>`, and the sitemap\n // projection refuses a date it cannot parse. Catch it here, at the\n // declaration, instead of as a failed sitemap request.\n name: \"invalid-date\",\n severity: \"structural\",\n evaluate: (graph) =>\n [...graph.nodes.values()].flatMap((node) => {\n const dates: Array<[string, string | undefined]> = node.instance\n ? [\n [\"publishedAt\", node.instance.publishedAt],\n [\"modifiedAt\", node.instance.modifiedAt],\n ]\n : [[\"modifiedAt\", node.policy.modifiedAt]];\n return dates.flatMap(([field, value]) =>\n value !== undefined && parseDeclaredDate(value) === undefined\n ? [\n {\n path: node.path,\n message: `${field} \"${value}\" is not an ISO 8601 date on a real calendar day.`,\n fix: `Write ${field} as an ISO 8601 date, e.g. \"2026-09-29\".`,\n },\n ]\n : [],\n );\n }),\n },\n {\n name: \"duplicate-title\",\n severity: \"editorial\",\n evaluate: (graph) =>\n [...groupInstancesBy(graph, (node) => node.instance?.title).entries()]\n .filter(([, nodes]) => nodes.length > 1)\n .flatMap(([title, nodes]) =>\n nodes.map((node) => ({\n path: node.path,\n message: `Title \"${title}\" is shared by ${nodes.length} pages.`,\n fix: \"Give each page a distinct title.\",\n })),\n ),\n },\n {\n name: \"duplicate-description\",\n severity: \"editorial\",\n evaluate: (graph) =>\n [...groupInstancesBy(graph, (node) => node.instance?.description).entries()]\n .filter(([, nodes]) => nodes.length > 1)\n .flatMap(([, nodes]) =>\n nodes.map((node) => ({\n path: node.path,\n message: `Description is shared by ${nodes.length} pages.`,\n fix: \"Write a distinct meta description for each page.\",\n })),\n ),\n },\n {\n // Meta descriptions outside ~50–160 chars either get truncated in SERPs or\n // read as too thin.\n name: \"description-length\",\n severity: \"editorial\",\n evaluate: (graph) =>\n [...graph.nodes.values()].flatMap((node) => {\n if (!isInstance(node)) return [];\n const description = node.instance?.description?.trim();\n if (!description) return [];\n if (description.length > DESCRIPTION_MAX) {\n return [\n {\n path: node.path,\n message: `Description is ${description.length} chars (max ${DESCRIPTION_MAX}); it will be truncated in results.`,\n fix: `Trim the description to ${DESCRIPTION_MAX} characters or fewer.`,\n },\n ];\n }\n if (description.length < DESCRIPTION_MIN) {\n return [\n {\n path: node.path,\n message: `Description is ${description.length} chars (min ${DESCRIPTION_MIN}); it reads as thin.`,\n fix: `Expand the description to at least ${DESCRIPTION_MIN} characters.`,\n },\n ];\n }\n return [];\n }),\n },\n];\n\n/**\n * Options for {@link checkGraph}. Freshness needs a clock, and the engine stays\n * pure, so a freshness policy is only accepted together with the `now` it is\n * judged against.\n */\nexport type CheckGraphOptions = {\n readonly coverage?: ReadonlyArray<CoverageRule> | undefined;\n readonly ogImage?: OgImagePolicy | undefined;\n} & (\n | { readonly freshness: FreshnessPolicy; readonly now: Date }\n | { readonly freshness?: undefined; readonly now?: undefined }\n);\n\n/**\n * Run every static rule against the graph, then any caller-supplied\n * {@link CoverageRule}s and freshness policy, and return the flat list of\n * violations. With no options the result is exactly the static rule set.\n */\nexport function checkGraph(graph: SeoGraph, options: CheckGraphOptions = {}): Array<Violation> {\n const violations: Array<Violation> = CHECK_RULES.flatMap((rule) =>\n rule.evaluate(graph).map((raw): Violation => ({\n severity: rule.severity,\n rule: rule.name,\n path: raw.path,\n message: raw.message,\n fix: raw.fix,\n })),\n );\n const imageSeverity = options.ogImage?.severity ?? \"structural\";\n if (imageSeverity !== \"off\") {\n for (const node of graph.nodes.values()) {\n if (!isSitemapEligible(node) || node.head?.image?.url.trim()) continue;\n violations.push({\n rule: \"missing-og-image\",\n severity: imageSeverity,\n path: node.path,\n message: `Sitemap page \"${node.path}\" has no resolved OG image.`,\n fix: \"Declare staticData.seo.head.image (or a collection image), or supply an ogImage resolver to pagegraph().\",\n });\n }\n }\n const coverage = options.coverage;\n if (coverage !== undefined && coverage.length > 0) {\n violations.push(...checkCoverage(graph, coverage));\n }\n if (options.freshness !== undefined) {\n violations.push(...checkFreshness(graph, options.freshness, options.now));\n }\n return violations;\n}\n\n/**\n * One editorial `stale-page` violation per sitemap-eligible page whose last\n * declared change is older than the policy allows. Undated pages are not\n * violations: see {@link freshnessReport} for the full picture.\n */\nexport function checkFreshness(\n graph: SeoGraph,\n policy: FreshnessPolicy,\n now: Date,\n): Array<Violation> {\n return freshnessReport(graph, policy, now).stale.map((entry) => ({\n severity: \"editorial\",\n rule: \"stale-page\",\n path: entry.path,\n message: `Last changed ${entry.lastModified.slice(0, 10)}, ${entry.ageDays} days ago (limit ${policy.maxAgeDays}).`,\n fix: \"Review the page; when its content changes, update its modifiedAt.\",\n }));\n}\n\n/** Escape a glob for `RegExp`, then expand `**`, `*`, and `?` to path-aware forms. */\nexport const globToRegExp = (glob: string): RegExp => {\n const escaped = glob.replace(/[.+^${}()|[\\]\\\\]/g, \"\\\\$&\");\n const source = escaped\n .replace(/\\*\\*/g, \"\\u0000\")\n .replace(/\\*/g, \"[^/]*\")\n .replace(/\\?/g, \"[^/]\")\n .replace(/\\u0000/g, \".*\");\n return new RegExp(`^${source}$`);\n};\n\n/**\n * The minimal view a coverage rule needs: which paths exist (for the glob), which\n * of them are sitemap-eligible (the enforced set), and how many contextual links\n * each receives. Declared and rendered coverage differ only in how this surface\n * is built, so the rule engine itself stays single-sourced.\n */\ninterface CoverageSurface {\n readonly paths: ReadonlyArray<string>;\n readonly eligiblePaths: ReadonlyArray<string>;\n readonly inbound: (path: string) => number;\n}\n\n/**\n * Enforce contextual-link coverage rules over an explicit surface.\n *\n * A rule that matches no sitemap-eligible page is itself a violation: a typo or\n * a rule aimed at a noindex page would otherwise pass silently forever.\n */\nconst checkCoverageRules = (\n surface: CoverageSurface,\n rules: ReadonlyArray<CoverageRule>,\n): Array<Violation> => {\n const violations: Array<Violation> = [];\n for (const rule of rules) {\n const matcher = globToRegExp(rule.path);\n const matched = surface.paths.filter((path) => matcher.test(path));\n const eligible = surface.eligiblePaths.filter((path) => matcher.test(path));\n\n if (eligible.length === 0) {\n violations.push({\n severity: \"structural\",\n rule: \"coverage-rule-unmatched\",\n message:\n matched.length === 0\n ? `Coverage rule \"${rule.path}\" matches no page in the graph.`\n : `Coverage rule \"${rule.path}\" matches only pages that are not sitemap-eligible.`,\n fix: \"Point the rule at a sitemap-eligible path, or drop it.\",\n });\n continue;\n }\n\n for (const path of eligible) {\n const inbound = surface.inbound(path);\n if (inbound >= rule.minInbound) continue;\n violations.push({\n severity: \"structural\",\n rule: \"inbound-link-coverage\",\n path,\n message: `\"${path}\" has ${inbound} incoming contextual link(s); the coverage rule requires ${rule.minInbound}.`,\n fix: `Add ${rule.minInbound - inbound} contextual link(s) pointing at \"${path}\".`,\n });\n }\n }\n return violations;\n};\n\n/** The sitemap-eligible path set of a declared graph, in graph insertion order. */\nconst eligiblePathsOf = (graph: SeoGraph): ReadonlyArray<string> =>\n [...graph.nodes.values()].filter(isSitemapEligible).map((node) => node.path);\n\n/**\n * Enforce contextual-link coverage against the *declared* graph: only `related`\n * edges count — breadcrumb ancestry is navigation, not context.\n */\nexport function checkCoverage(\n graph: SeoGraph,\n rules: ReadonlyArray<CoverageRule>,\n): Array<Violation> {\n return checkCoverageRules(\n {\n paths: [...graph.nodes.keys()],\n eligiblePaths: eligiblePathsOf(graph),\n inbound: (path) =>\n graph.edges.filter((edge) => edge.type === \"related\" && edge.to === path).length,\n },\n rules,\n );\n}\n\n/**\n * Enforce contextual-link coverage against the *rendered* graph: only same-origin\n * body-region anchors count. Nav, header, and footer anchors are site chrome, so\n * a page linked solely from them has no contextual inbound link — the same\n * distinction the declared pass draws by counting `related` edges alone.\n *\n * The target universe is the declared graph's sitemap-eligible pages: the globs\n * in `pagegraph.config.ts` are written against declared paths, and the crawl is what\n * supplies the served edges. Pass the crawl's `internalEdges` (each edge carries\n * its `region`).\n */\nexport function checkRenderedCoverage(\n graph: SeoGraph,\n renderedEdges: ReadonlyArray<LinkEdge>,\n rules: ReadonlyArray<CoverageRule>,\n): Array<Violation> {\n const inbound = new Map<string, number>();\n for (const edge of renderedEdges) {\n if (edge.region !== \"body\") continue;\n inbound.set(edge.to, (inbound.get(edge.to) ?? 0) + 1);\n }\n return checkCoverageRules(\n {\n paths: [...graph.nodes.keys()],\n eligiblePaths: eligiblePathsOf(graph),\n inbound: (path) => inbound.get(path) ?? 0,\n },\n rules,\n );\n}\n\n/** Structural violations fail `pagegraph check`; editorial-only stays green. */\nexport const hasStructuralViolations = (violations: ReadonlyArray<Violation>): boolean =>\n violations.some((violation) => violation.severity === \"structural\");\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { o as Facts, p as PageDocument, t as PageHead } from "./page-heads-BfjWQvau.js";
1
+ import { o as Facts, p as PageDocument, t as PageHead } from "./page-heads-DDz_2_hq.js";
2
2
  import "effect/Effect";
3
3
  import * as Schema from "effect/Schema";
4
4
  import { DecisionModel } from "effect/ai";
@@ -89,4 +89,4 @@ declare function resolveClaimsDocuments(documents: ReadonlyArray<PageDocument>,
89
89
  declare function claimsInputs(documents: ReadonlyArray<PageDocument>, heads: ReadonlyArray<PageHead>, facts: Facts, options?: Pick<ClaimsOptions, "context" | "excludeHeads">): ReadonlyArray<ClaimsInput>;
90
90
  //#endregion
91
91
  export { claimsFamily as a, resolveClaimsDocuments as c, DecisionCacheInvalid as d, ClaimsVerdict as i, DecisionCacheMiss as l, ClaimsOptions as n, claimsInput as o, ClaimsRules as r, claimsInputs as s, ClaimsInput as t, DecisionFamily as u };
92
- //# sourceMappingURL=claims-Bch7Lxlo.d.ts.map
92
+ //# sourceMappingURL=claims-CCf2_Jox.d.ts.map
@@ -1,4 +1,4 @@
1
- import { a as globToRegExp } from "./checks-BS-aDXB_.js";
1
+ import { a as globToRegExp } from "./checks-BEhJPNzN.js";
2
2
  import { i as HEAD_SECTION_ID, o as hashDocument } from "./inline-BbS93GAT.js";
3
3
  import { r as sectionMarkdown } from "./markdown-C6dRE9zD.js";
4
4
  import { existsSync, mkdirSync, mkdtempSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
@@ -428,4 +428,4 @@ const replayClaims = Effect.fn("Claims.replay")(function* (root, documents, head
428
428
  //#endregion
429
429
  export { claimsFamily as a, resolveClaimsDocuments as c, runClaims as i, claimsRunOptions as n, claimsInput as o, replayClaims as r, claimsInputs as s, ClaimsFailed as t };
430
430
 
431
- //# sourceMappingURL=claims-Bv7voeEV.js.map
431
+ //# sourceMappingURL=claims-CQlzze1K.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"claims-Bv7voeEV.js","names":[],"sources":["../src/decide/answers.ts","../src/decide/cache.ts","../src/decide/families/claims.ts","../src/decide/run.ts","../src/claims.ts"],"sourcesContent":["/**\n * Answer identity and validation shared by every decision family: how an input\n * and a definition hash into a cache key, and whether stored answers have the\n * shape the provider path would have validated.\n */\n\nimport { createHash } from \"node:crypto\";\n\nimport type * as Decision from \"effect/ai/Decision\";\n\nconst isUnitInterval = (value: unknown): value is number =>\n typeof value === \"number\" && Number.isFinite(value) && value >= 0 && value <= 1;\n\nconst isPlainObject = (value: unknown): value is Record<string, unknown> =>\n value !== null && typeof value === \"object\" && !Array.isArray(value);\n\n/** A probability distribution over exactly `labels`: unit-range, summing to 1. */\nconst isDistribution = (labels: ReadonlyArray<string>, value: unknown): boolean => {\n // Extra labels would be read by evaluators that scan the distribution, so\n // require the cached keys to match the declared labels exactly.\n if (!isPlainObject(value) || Object.keys(value).length !== labels.length) return false;\n let total = 0;\n for (const label of labels) {\n const probability = value[label];\n if (!isUnitInterval(probability)) return false;\n total += probability;\n }\n return Math.abs(total - 1) <= 1e-6;\n};\n\n/**\n * Whether cached answers have the shape `DecisionModel` would have validated —\n * the same labels, ranges, and distributions. A cache file is only trusted when\n * it round-trips through this check, so a partially malformed entry cannot\n * produce a confident verdict.\n */\nexport const validCachedAnswers = (\n decisions: Readonly<Record<string, Decision.Any>>,\n answers: unknown,\n): boolean => {\n if (!isPlainObject(answers)) return false;\n for (const key of Object.keys(decisions)) {\n const answer = answers[key];\n if (!isPlainObject(answer)) return false;\n const decision = decisions[key]!;\n switch (decision._tag) {\n case \"Probability\":\n if (!isUnitInterval(answer.probability)) return false;\n break;\n case \"Classify\":\n if (\n typeof answer.label !== \"string\" ||\n !Object.hasOwn(decision.criteria, answer.label) ||\n !isDistribution(Object.keys(decision.criteria), answer.probabilities)\n ) {\n return false;\n }\n break;\n case \"Rate\":\n if (\n typeof answer.rating !== \"number\" ||\n !Number.isFinite(answer.rating) ||\n answer.rating < 0 ||\n answer.rating > decision.criteria.length - 1 ||\n typeof answer.label !== \"string\" ||\n !decision.criteria.includes(answer.label) ||\n !isDistribution(decision.criteria, answer.probabilities)\n ) {\n return false;\n }\n break;\n }\n }\n return true;\n};\n\n/** sha256 of the canonical input JSON — deterministic for a schema-decoded input. */\nexport const inputHash = (input: unknown): string =>\n createHash(\"sha256\").update(JSON.stringify(input)).digest(\"hex\");\n\n/** Bump when a family's answer shape changes, so old cache files stop matching. */\nexport const DECISION_ANSWER_SCHEMA_VERSION = 1;\n\n/** JSON with object keys sorted at every level, so key order never changes a hash. */\nconst canonicalJson = (value: unknown): string =>\n JSON.stringify(value, (_key, entry: unknown) =>\n entry !== null && typeof entry === \"object\" && !Array.isArray(entry)\n ? Object.fromEntries(Object.entries(entry).sort(([left], [right]) => (left < right ? -1 : left > right ? 1 : 0)))\n : entry,\n );\n\n/**\n * sha256 of what the model is asked: each decision's kind, instructions, and\n * criteria. Editing a prompt or a label therefore invalidates its answers.\n */\nexport const definitionFingerprint = (decisions: Readonly<Record<string, Decision.Any>>): string =>\n createHash(\"sha256\")\n .update(\n canonicalJson(\n Object.fromEntries(\n Object.entries(decisions).map(([name, decision]) => [\n name,\n { kind: decision._tag, instructions: decision.instructions, criteria: decision.criteria ?? null },\n ]),\n ),\n ),\n )\n .digest(\"hex\");\n\n/**\n * Cache identity. Answers depend on the family, the model, the question asked,\n * and the input; the answer-schema version guards shape changes.\n */\nexport const cacheKey = (parts: {\n readonly family: string;\n readonly model: string;\n readonly decisions: Readonly<Record<string, Decision.Any>>;\n readonly inputHash: string;\n}): string =>\n createHash(\"sha256\")\n .update(\n [\n parts.family,\n parts.model,\n `v${DECISION_ANSWER_SCHEMA_VERSION}`,\n definitionFingerprint(parts.decisions),\n parts.inputHash,\n ].join(\"\\u0000\"),\n )\n .digest(\"hex\");\n","/**\n * Answer cache keyed by {@link cacheKey}: one JSON file per key in a directory.\n *\n * Only model answers are cached; verdicts are recomputed from answers and the\n * current threshold, so changing a threshold never serves a stale verdict.\n *\n * Two policies:\n * - `scratch`: a working cache for agentic runs. A missing, unreadable, or\n * malformed file is a miss, and the provider answers again.\n * - `committed`: answers reviewed and committed with the code they judge, read\n * by gates that replay without a model. Only absence is a miss; an unreadable\n * or malformed file fails, so a corrupted answer can never pass silently.\n * Writes are atomic, so an interrupted refresh cannot leave a partial file.\n */\n\nimport { existsSync, mkdirSync, mkdtempSync, readFileSync, renameSync, rmSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\n\nimport * as Data from \"effect/Data\";\nimport * as Effect from \"effect/Effect\";\nimport type * as Decision from \"effect/ai/Decision\";\n\nimport { validCachedAnswers } from \"./answers\";\n\nexport type DecisionCachePolicy = \"scratch\" | \"committed\";\n\nexport interface DecisionCache {\n readonly directory: string;\n readonly policy: DecisionCachePolicy;\n}\n\n/** A committed answer that exists but cannot be trusted. */\nexport class DecisionCacheInvalid extends Data.TaggedError(\"DecisionCacheInvalid\")<{\n readonly file: string;\n readonly message: string;\n}> {}\n\nconst answerFile = (cache: DecisionCache, key: string): string => join(cache.directory, `${key}.json`);\n\n/**\n * Cached answers for `key` when they match `decisions`, `undefined` on a miss.\n * Under `committed`, an unreadable or mismatched file fails instead of missing.\n */\nexport const readAnswers = (\n cache: DecisionCache,\n key: string,\n decisions: Readonly<Record<string, Decision.Any>>,\n): Effect.Effect<unknown | undefined, DecisionCacheInvalid> =>\n Effect.gen(function* () {\n const file = answerFile(cache, key);\n if (!existsSync(file)) return undefined;\n const invalid = (reason: string) =>\n cache.policy === \"scratch\"\n ? Effect.succeed(undefined)\n : Effect.fail(\n new DecisionCacheInvalid({\n file,\n message: `Committed decision answers in ${file} ${reason}; re-ask them and commit the result.`,\n }),\n );\n const parsed = yield* Effect.try({\n try: (): unknown => JSON.parse(readFileSync(file, \"utf8\")),\n catch: () => file,\n }).pipe(Effect.option);\n if (parsed._tag === \"None\") return yield* invalid(\"are not readable JSON\");\n if (!validCachedAnswers(decisions, parsed.value)) return yield* invalid(\"do not match the decision definition\");\n return parsed.value;\n });\n\n/** Persist answers for `key`, creating the directory on first write. */\nexport const writeAnswers = (cache: DecisionCache, key: string, answers: unknown): Effect.Effect<void> =>\n Effect.sync(() => {\n mkdirSync(cache.directory, { recursive: true });\n const contents = `${JSON.stringify(answers, null, 2)}\\n`;\n if (cache.policy === \"scratch\") {\n writeFileSync(answerFile(cache, key), contents, \"utf8\");\n return;\n }\n // Stage beside the target so the rename stays on one filesystem.\n const staging = mkdtempSync(join(cache.directory, \".answer-\"));\n try {\n const staged = join(staging, \"answer.json\");\n writeFileSync(staged, contents, \"utf8\");\n renameSync(staged, answerFile(cache, key));\n } finally {\n rmSync(staging, { recursive: true, force: true });\n }\n });\n","import * as Schema from \"effect/Schema\";\nimport type * as Decision from \"effect/ai/Decision\";\n\nimport { globToRegExp } from \"../../core/checks\";\nimport type { PageHead } from \"../../core/page-heads\";\nimport { HEAD_SECTION_ID, hashDocument, type Message, type PageDocument, type SectionDocument } from \"../../markdown/document\";\nimport type { Facts } from \"../../markdown/facts\";\nimport { sectionMarkdown } from \"../../markdown/markdown\";\nimport type { DecisionFamily } from \"../run\";\n\nconst FactDefinition = Schema.Struct({\n kind: Schema.Literals([\"number\", \"money\", \"list\", \"text\"]),\n value: Schema.Json,\n text: Schema.String,\n items: Schema.optionalKey(Schema.Array(Schema.String)),\n});\n\n/** Authored prose, code-owned facts, and evidence supplied to adopter rules. */\nexport const claimsInput = Schema.Struct({\n path: Schema.String,\n evidence: Schema.optionalKey(Schema.String),\n section: Schema.Struct({\n id: Schema.String,\n kind: Schema.String,\n markdown: Schema.String,\n messages: Schema.Array(Schema.Struct({\n hash: Schema.String, text: Schema.String, facts: Schema.Array(Schema.String),\n })),\n }),\n facts: Schema.Record(Schema.String, FactDefinition),\n context: Schema.optionalKey(Schema.String),\n});\nexport type ClaimsInput = typeof claimsInput.Type;\nexport type ClaimsRules = Decision.Definition<typeof claimsInput, Record<string, Decision.Probability>>;\nexport interface ClaimsOptions {\n readonly rules: ClaimsRules;\n readonly model: string;\n readonly cutoff?: number;\n readonly context?: string;\n /** Exclude metadata by path glob; captured sections remain in the gate. */\n readonly excludeHeads?: ReadonlyArray<string>;\n}\nexport type ClaimsVerdict = \"pass\" | \"violation\";\nconst ClaimsAnswers = Schema.Record(Schema.String, Schema.Struct({ probability: Schema.Finite }));\n\n/** Claims use a single upper cutoff; probabilities below it pass without a review band. */\nexport function claimsFamily(rules: ClaimsRules): DecisionFamily<ClaimsInput> {\n if (Object.keys(rules.decisions).length === 0) throw new Error(\"Claims need at least one decision rule\");\n if (Object.values(rules.decisions).some((rule) => rule._tag !== \"Probability\"))\n throw new Error(\"Claims rules must be probability decisions\");\n return {\n name: \"claims\",\n input: claimsInput,\n definitionFor: () => rules,\n inputRef: (input) => `${input.path} › ${input.section.id}`,\n evaluate: (_input, answers, cutoff): ClaimsVerdict =>\n Object.values(Schema.decodeUnknownSync(ClaimsAnswers)(answers))\n .some((answer) => answer.probability >= cutoff) ? \"violation\" : \"pass\",\n };\n}\n\n/** Heads describe only pages outside capture; duplicate ownership fails before evaluation. */\nexport function resolveClaimsDocuments(\n documents: ReadonlyArray<PageDocument>,\n heads: ReadonlyArray<PageHead>,\n excludeHeads: ReadonlyArray<string> = [],\n): ReadonlyArray<PageDocument> {\n const paths = new Set<string>();\n for (const document of documents) {\n if (paths.has(document.path)) throw new Error(`Duplicate claims page path \"${document.path}\"`);\n paths.add(document.path);\n }\n const seenHeads = new Set<string>();\n const excludes = excludeHeads.map(globToRegExp);\n const extra: Array<PageDocument> = [];\n for (const head of heads) {\n if (seenHeads.has(head.path)) throw new Error(`Duplicate claims head path \"${head.path}\"`);\n seenHeads.add(head.path);\n if (paths.has(head.path)) throw new Error(`Duplicate claims page path \"${head.path}\"`);\n if (excludes.some((pattern) => pattern.test(head.path))) continue;\n const document = { ...head, sections: [], messages: [], facts: [] };\n extra.push({ ...document, hash: hashDocument(document) });\n }\n return [...documents, ...extra];\n}\nfunction sectionMessages(section: SectionDocument): ReadonlyArray<Message> {\n return [\n ...(section.title === undefined ? [] : [section.title]),\n ...section.body.flatMap((entry) => entry.type === \"message\" ? [entry.message] : []),\n ...section.items.flatMap((item) => [\n ...(item.title === undefined ? [] : [item.title]),\n ...item.body.flatMap((entry) => entry.type === \"message\" ? [entry.message] : []),\n ...item.cells,\n ]),\n ];\n}\n\n/** Source locations do not enter model input or answer identity. */\nexport function claimsInputs(\n documents: ReadonlyArray<PageDocument>,\n heads: ReadonlyArray<PageHead>,\n facts: Facts,\n options: Pick<ClaimsOptions, \"context\" | \"excludeHeads\"> = {},\n): ReadonlyArray<ClaimsInput> {\n const excludes = (options.excludeHeads ?? []).map(globToRegExp);\n return resolveClaimsDocuments(documents, heads, options.excludeHeads).flatMap((document) => {\n const sections: ReadonlyArray<SectionDocument> = [\n ...document.sections,\n ...(excludes.some((pattern) => pattern.test(document.path)) ? [] : [{\n id: HEAD_SECTION_ID, kind: \"head\", claimsMarkdown: `${document.title}\\n\\n${document.description}`,\n body: [], items: [], source: document.path, audience: \"all\" as const,\n }]),\n ];\n return sections.map((section) => {\n const evidence = document.sections.filter((entry) => entry.evidence === true && entry !== section)\n .map((entry) => entry.claimsMarkdown ?? sectionMarkdown(entry, { includeHumans: true })).join(\"\\n\\n\");\n return Schema.decodeUnknownSync(claimsInput)({\n path: document.path,\n ...(evidence === \"\" ? {} : { evidence }),\n section: {\n id: section.id, kind: section.kind,\n markdown: section.claimsMarkdown ?? sectionMarkdown(section, { includeHumans: true }),\n messages: sectionMessages(section).map(({ hash, text, facts }) => ({ hash, text, facts })),\n },\n facts,\n ...(options.context === undefined ? {} : { context: options.context }),\n });\n });\n });\n}\n","/**\n * The decision runner. A family supplies its input schema, a definition built\n * from the input (families with dynamic labels build one per input), a way to\n * reference the input in a report, and a pure `evaluate` that maps the model's\n * answers to a verdict. The runner hashes, reads and writes the answer cache,\n * dispatches with bounded concurrency, and buckets confident answers apart from\n * `review`. `runDecisions` asks the model on a miss; `replayDecisions` answers\n * from a committed cache alone and fails on a miss.\n *\n * Build and CLI entries reach Effect and the provider through this module;\n * runtime entries remain Effect-free and Node-free.\n */\n\nimport * as Data from \"effect/Data\";\nimport * as Effect from \"effect/Effect\";\nimport * as Schema from \"effect/Schema\";\nimport { DecisionModel } from \"effect/ai\";\nimport type * as AiError from \"effect/ai/AiError\";\nimport type * as Decision from \"effect/ai/Decision\";\n\nimport { cacheKey, inputHash } from \"./answers\";\nimport { readAnswers, writeAnswers, type DecisionCache, type DecisionCacheInvalid } from \"./cache\";\nimport { DecisionRecord, type DecisionBatchReport } from \"./record\";\n\n/**\n * One decision family. `answers` in `evaluate` is erased because each family\n * reads its own `Decision.Answers` shape; the family is the only place that\n * shape is known, and a `DecisionModel` validates it before it arrives.\n */\nexport interface DecisionFamily<Input> {\n readonly name: string;\n readonly input: Schema.ConstraintDecoder<Input>;\n /** Build the definition for one input; dynamic-label families build per input. */\n readonly definitionFor: (input: Input) => Decision.Definition<any, any>;\n /** Stable human reference for the input in a report. */\n readonly inputRef: (input: Input) => string;\n /** Map validated answers to a verdict; `\"review\"` means a human decides. */\n readonly evaluate: (input: Input, answers: any, threshold: number) => string;\n /**\n * Optional input-level guard for constraints a schema cannot express, such as\n * \"dynamic-label families need at least two candidates\". Returns a message to\n * fail the run, or `undefined` when the input is usable.\n */\n readonly validate?: (input: Input) => string | undefined;\n}\n\n/** A probability is confident when it is at or beyond `threshold` on either side. */\nexport const isConfident = (probability: number, threshold: number): boolean =>\n probability >= threshold || probability <= 1 - threshold;\n\n/** True when a probability sits inside the review band `(1 - threshold, threshold)`. */\nexport const inReviewBand = (probability: number, threshold: number): boolean =>\n !isConfident(probability, threshold);\n\n/** True when any probability sits inside the review band. */\nexport const anyInReviewBand = (\n probabilities: ReadonlyArray<number>,\n threshold: number,\n): boolean => probabilities.some((probability) => inReviewBand(probability, threshold));\n\nexport interface RunOptions<Input> {\n readonly family: DecisionFamily<Input>;\n readonly inputs: ReadonlyArray<Input>;\n readonly model: string;\n readonly threshold: number;\n readonly concurrency?: number;\n /** Answer cache; absent means every input is asked. */\n readonly cache?: DecisionCache;\n}\n\n/** Replay found no committed answer for an input. */\nexport class DecisionCacheMiss extends Data.TaggedError(\"DecisionCacheMiss\")<{\n readonly family: string;\n readonly inputRef: string;\n readonly key: string;\n readonly message: string;\n}> {}\n\n/** Group records into resolved/review buckets and count each verdict. */\nexport const buildDecisionReport = (options: {\n readonly family: string;\n readonly model: string;\n readonly threshold: number;\n readonly records: ReadonlyArray<DecisionRecord>;\n}): DecisionBatchReport => {\n const verdicts: Record<string, number> = {};\n const resolved: Array<DecisionRecord> = [];\n const review: Array<DecisionRecord> = [];\n for (const record of options.records) {\n verdicts[record.verdict] = (verdicts[record.verdict] ?? 0) + 1;\n if (record.review) review.push(record);\n else resolved.push(record);\n }\n return {\n kind: \"decide\",\n schemaVersion: 1,\n family: options.family,\n model: options.model,\n threshold: options.threshold,\n counts: {\n inputs: options.records.length,\n resolved: resolved.length,\n review: review.length,\n },\n verdicts,\n resolved,\n review,\n };\n};\n\ninterface Answered {\n readonly answers: unknown;\n readonly usage?: DecisionRecord[\"usage\"];\n}\n\n/**\n * Answer each input from the cache, or through `ask` on a miss, and bucket the\n * results. Inputs run with bounded concurrency.\n */\nconst decideAll = <Input, E, R>(\n options: RunOptions<Input>,\n ask: (input: Input, key: string) => Effect.Effect<Answered, E, R>,\n reuseCache: boolean,\n): Effect.Effect<DecisionBatchReport, E | DecisionCacheInvalid, R> =>\n Effect.gen(function* () {\n const records = yield* Effect.forEach(\n options.inputs,\n (input, index) =>\n Effect.gen(function* () {\n const definition = options.family.definitionFor(input);\n const hash = inputHash(input);\n const key = cacheKey({\n family: options.family.name,\n model: options.model,\n decisions: definition.decisions,\n inputHash: hash,\n });\n const cached =\n options.cache === undefined || !reuseCache\n ? undefined\n : yield* readAnswers(options.cache, key, definition.decisions);\n const answered: Answered = cached === undefined ? yield* ask(input, key) : { answers: cached };\n const verdict = options.family.evaluate(input, answered.answers, options.threshold);\n return {\n decisionId: `${options.family.name}:${index}`,\n schemaVersion: 1,\n family: options.family.name,\n model: options.model,\n threshold: options.threshold,\n inputHash: hash,\n inputRef: options.family.inputRef(input),\n verdict,\n review: verdict === \"review\",\n answers: answered.answers,\n ...(answered.usage === undefined ? {} : { usage: answered.usage }),\n } satisfies DecisionRecord;\n }),\n { concurrency: options.concurrency ?? 4 },\n );\n return buildDecisionReport({\n family: options.family.name,\n model: options.model,\n threshold: options.threshold,\n records,\n });\n });\n\n/**\n * Answer every input through the ambient `DecisionModel`. One provider call\n * answers every decision for an input; a cached answer skips the provider unless\n * `refresh` asks again. New answers are written to the cache.\n */\nexport const runDecisions = <Input>(\n options: RunOptions<Input> & { readonly refresh?: boolean },\n): Effect.Effect<\n DecisionBatchReport,\n AiError.AiError | DecisionCacheInvalid,\n DecisionModel.DecisionModel\n> =>\n decideAll(\n options,\n (input, key) =>\n Effect.gen(function* () {\n const response = yield* DecisionModel.decide(options.family.definitionFor(input), { input });\n if (options.cache !== undefined) yield* writeAnswers(options.cache, key, response.answers);\n return {\n answers: response.answers,\n usage: { inputTokens: response.usage.inputTokens, outputTokens: response.usage.outputTokens },\n };\n }),\n options.refresh !== true,\n );\n\n/**\n * Answer every input from the cache alone. Needs no model or credentials, so a\n * gate can run it offline; an input without an answer fails the replay.\n */\nexport const replayDecisions = <Input>(\n options: RunOptions<Input> & { readonly cache: DecisionCache },\n): Effect.Effect<DecisionBatchReport, DecisionCacheMiss | DecisionCacheInvalid> =>\n decideAll(\n options,\n (input, key) =>\n Effect.fail(\n new DecisionCacheMiss({\n family: options.family.name,\n inputRef: options.family.inputRef(input),\n key,\n message: `No ${options.family.name} answer for ${options.family.inputRef(input)}; ask the model and commit the answers.`,\n }),\n ),\n true,\n );\n\nexport const decodeFamilyInputs = <Input>(\n family: DecisionFamily<Input>,\n raw: ReadonlyArray<unknown>,\n): ReadonlyArray<Input> => {\n const decode = Schema.decodeUnknownSync(family.input);\n return raw.map((value) => decode(value));\n};\n","import { resolve } from \"node:path\";\n\nimport * as Data from \"effect/Data\";\nimport * as Effect from \"effect/Effect\";\nimport * as Schema from \"effect/Schema\";\n\nimport { DecisionCacheInvalid } from \"./decide/cache\";\n\nimport type { PageHead } from \"./core/page-heads\";\nimport { claimsFamily, claimsInputs, resolveClaimsDocuments, type ClaimsOptions } from \"./decide/families/claims\";\nimport type { DecisionBatchReport } from \"./decide/record\";\nimport { DecisionCacheMiss, replayDecisions, runDecisions } from \"./decide/run\";\nimport type { PageDocument } from \"./markdown/document\";\nimport type { Facts } from \"./markdown/facts\";\n\nexport { claimsFamily, claimsInput, claimsInputs, resolveClaimsDocuments } from \"./decide/families/claims\";\nexport type { ClaimsInput, ClaimsOptions, ClaimsRules, ClaimsVerdict } from \"./decide/families/claims\";\n\nexport interface ClaimsFinding {\n readonly path: string;\n readonly section: string;\n readonly source: string;\n readonly rule: string;\n readonly probability: number;\n}\nexport interface ClaimsReport {\n readonly version: 1;\n readonly mode: \"ask\" | \"replay\";\n readonly pages: number;\n readonly messages: number;\n readonly cached: number;\n readonly asked: number;\n readonly cutoff: number;\n readonly findings: ReadonlyArray<ClaimsFinding>;\n}\nexport class ClaimsFailed extends Data.TaggedError(\"ClaimsFailed\")<{\n readonly message: string;\n readonly findings: ReadonlyArray<ClaimsFinding>;\n readonly report: ClaimsReport;\n}> {}\n\n/** Cutoff affects verdicts alone; committed identity belongs to the decision runner. */\nexport function claimsRunOptions(\n root: string,\n documents: ReadonlyArray<PageDocument>,\n heads: ReadonlyArray<PageHead>,\n facts: Facts,\n options: ClaimsOptions,\n) {\n const cutoff = options.cutoff === undefined ? 0.8 : options.cutoff;\n if (!Number.isFinite(cutoff) || cutoff < 0 || cutoff > 1)\n throw new Error(\"Claims cutoff must be between 0 and 1\");\n if (!options.model.trim()) throw new Error(\"Claims model is required\");\n return {\n family: claimsFamily(options.rules),\n inputs: claimsInputs(documents, heads, facts, options),\n model: options.model,\n threshold: cutoff,\n cache: { directory: resolve(root, \".pagegraph/decisions/claims\"), policy: \"committed\" as const },\n };\n}\nconst ProbabilityAnswers = Schema.Record(Schema.String, Schema.Struct({ probability: Schema.Finite }));\nfunction claimsReport(\n batch: DecisionBatchReport,\n documents: ReadonlyArray<PageDocument>,\n heads: ReadonlyArray<PageHead>,\n facts: Facts,\n options: ClaimsOptions,\n mode: ClaimsReport[\"mode\"],\n): ClaimsReport {\n const inputs = claimsInputs(documents, heads, facts, options);\n const sources = new Map(documents.map((document) => [document.path, document]));\n const records = [...batch.resolved, ...batch.review];\n const findings = records.flatMap((record): Array<ClaimsFinding> => {\n const index = Number(record.decisionId.slice(\"claims:\".length));\n const input = inputs[index];\n if (input === undefined) throw new Error(`Unknown claims decision ${record.decisionId}`);\n return Object.entries(Schema.decodeUnknownSync(ProbabilityAnswers)(record.answers))\n .flatMap(([rule, answer]) => answer.probability >= batch.threshold ? [{\n path: input.path, section: input.section.id,\n source: sources.get(input.path)?.sections.find((section) => section.id === input.section.id)?.source ?? input.path,\n rule, probability: answer.probability,\n }] : []);\n });\n const asked = records.filter((record) => record.usage !== undefined).length;\n const resolved = resolveClaimsDocuments(documents, heads, options.excludeHeads);\n return {\n version: 1, mode, pages: resolved.length,\n messages: documents.reduce((count, document) => count + document.messages.length, 0),\n cached: records.length - asked, asked, cutoff: batch.threshold, findings,\n };\n}\n\n/** Ask only for absent answers unless refresh is requested, then persist committed answers. */\nexport const runClaims = Effect.fn(\"Claims.run\")(function* (\n root: string,\n documents: ReadonlyArray<PageDocument>,\n heads: ReadonlyArray<PageHead>,\n facts: Facts,\n options: ClaimsOptions,\n runOptions: { readonly refresh?: boolean } = {},\n) {\n const batch = yield* runDecisions({ ...claimsRunOptions(root, documents, heads, facts, options), ...runOptions });\n return claimsReport(batch, documents, heads, facts, options, \"ask\");\n});\n\n/** Offline gates have no model requirement; absent answers and violations fail with author guidance. */\nexport const replayClaims = Effect.fn(\"Claims.replay\")(function* (\n root: string,\n documents: ReadonlyArray<PageDocument>,\n heads: ReadonlyArray<PageHead>,\n facts: Facts,\n options: ClaimsOptions,\n) {\n const batch = yield* replayDecisions(claimsRunOptions(root, documents, heads, facts, options)).pipe(\n Effect.catchTag(\"DecisionCacheInvalid\", (error) => Effect.fail(new DecisionCacheInvalid({\n file: error.file, message: `${error.message} Run pagegraph claims check --refresh and commit the answers.`,\n }))),\n Effect.catchTag(\"DecisionCacheMiss\", (error) => Effect.fail(new DecisionCacheMiss({\n ...error,\n message: `No claims answer for ${error.inputRef} (rules: ${Object.keys(options.rules.decisions).join(\", \")}). Run pagegraph claims check and commit the answers.`,\n }))),\n );\n const report = claimsReport(batch, documents, heads, facts, options, \"replay\");\n if (report.findings.length > 0) return yield* new ClaimsFailed({\n message: report.findings.map((finding) =>\n `Claims violation at ${finding.path} › ${finding.section}: ${finding.rule} (${finding.probability.toFixed(2)}).`,\n ).join(\"\\n\") + \"\\nRun pagegraph claims check and commit the answers.\",\n findings: report.findings, report,\n });\n return report;\n});\n"],"mappings":"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"}
1
+ {"version":3,"file":"claims-CQlzze1K.js","names":[],"sources":["../src/decide/answers.ts","../src/decide/cache.ts","../src/decide/families/claims.ts","../src/decide/run.ts","../src/claims.ts"],"sourcesContent":["/**\n * Answer identity and validation shared by every decision family: how an input\n * and a definition hash into a cache key, and whether stored answers have the\n * shape the provider path would have validated.\n */\n\nimport { createHash } from \"node:crypto\";\n\nimport type * as Decision from \"effect/ai/Decision\";\n\nconst isUnitInterval = (value: unknown): value is number =>\n typeof value === \"number\" && Number.isFinite(value) && value >= 0 && value <= 1;\n\nconst isPlainObject = (value: unknown): value is Record<string, unknown> =>\n value !== null && typeof value === \"object\" && !Array.isArray(value);\n\n/** A probability distribution over exactly `labels`: unit-range, summing to 1. */\nconst isDistribution = (labels: ReadonlyArray<string>, value: unknown): boolean => {\n // Extra labels would be read by evaluators that scan the distribution, so\n // require the cached keys to match the declared labels exactly.\n if (!isPlainObject(value) || Object.keys(value).length !== labels.length) return false;\n let total = 0;\n for (const label of labels) {\n const probability = value[label];\n if (!isUnitInterval(probability)) return false;\n total += probability;\n }\n return Math.abs(total - 1) <= 1e-6;\n};\n\n/**\n * Whether cached answers have the shape `DecisionModel` would have validated —\n * the same labels, ranges, and distributions. A cache file is only trusted when\n * it round-trips through this check, so a partially malformed entry cannot\n * produce a confident verdict.\n */\nexport const validCachedAnswers = (\n decisions: Readonly<Record<string, Decision.Any>>,\n answers: unknown,\n): boolean => {\n if (!isPlainObject(answers)) return false;\n for (const key of Object.keys(decisions)) {\n const answer = answers[key];\n if (!isPlainObject(answer)) return false;\n const decision = decisions[key]!;\n switch (decision._tag) {\n case \"Probability\":\n if (!isUnitInterval(answer.probability)) return false;\n break;\n case \"Classify\":\n if (\n typeof answer.label !== \"string\" ||\n !Object.hasOwn(decision.criteria, answer.label) ||\n !isDistribution(Object.keys(decision.criteria), answer.probabilities)\n ) {\n return false;\n }\n break;\n case \"Rate\":\n if (\n typeof answer.rating !== \"number\" ||\n !Number.isFinite(answer.rating) ||\n answer.rating < 0 ||\n answer.rating > decision.criteria.length - 1 ||\n typeof answer.label !== \"string\" ||\n !decision.criteria.includes(answer.label) ||\n !isDistribution(decision.criteria, answer.probabilities)\n ) {\n return false;\n }\n break;\n }\n }\n return true;\n};\n\n/** sha256 of the canonical input JSON — deterministic for a schema-decoded input. */\nexport const inputHash = (input: unknown): string =>\n createHash(\"sha256\").update(JSON.stringify(input)).digest(\"hex\");\n\n/** Bump when a family's answer shape changes, so old cache files stop matching. */\nexport const DECISION_ANSWER_SCHEMA_VERSION = 1;\n\n/** JSON with object keys sorted at every level, so key order never changes a hash. */\nconst canonicalJson = (value: unknown): string =>\n JSON.stringify(value, (_key, entry: unknown) =>\n entry !== null && typeof entry === \"object\" && !Array.isArray(entry)\n ? Object.fromEntries(Object.entries(entry).sort(([left], [right]) => (left < right ? -1 : left > right ? 1 : 0)))\n : entry,\n );\n\n/**\n * sha256 of what the model is asked: each decision's kind, instructions, and\n * criteria. Editing a prompt or a label therefore invalidates its answers.\n */\nexport const definitionFingerprint = (decisions: Readonly<Record<string, Decision.Any>>): string =>\n createHash(\"sha256\")\n .update(\n canonicalJson(\n Object.fromEntries(\n Object.entries(decisions).map(([name, decision]) => [\n name,\n { kind: decision._tag, instructions: decision.instructions, criteria: decision.criteria ?? null },\n ]),\n ),\n ),\n )\n .digest(\"hex\");\n\n/**\n * Cache identity. Answers depend on the family, the model, the question asked,\n * and the input; the answer-schema version guards shape changes.\n */\nexport const cacheKey = (parts: {\n readonly family: string;\n readonly model: string;\n readonly decisions: Readonly<Record<string, Decision.Any>>;\n readonly inputHash: string;\n}): string =>\n createHash(\"sha256\")\n .update(\n [\n parts.family,\n parts.model,\n `v${DECISION_ANSWER_SCHEMA_VERSION}`,\n definitionFingerprint(parts.decisions),\n parts.inputHash,\n ].join(\"\\u0000\"),\n )\n .digest(\"hex\");\n","/**\n * Answer cache keyed by {@link cacheKey}: one JSON file per key in a directory.\n *\n * Only model answers are cached; verdicts are recomputed from answers and the\n * current threshold, so changing a threshold never serves a stale verdict.\n *\n * Two policies:\n * - `scratch`: a working cache for agentic runs. A missing, unreadable, or\n * malformed file is a miss, and the provider answers again.\n * - `committed`: answers reviewed and committed with the code they judge, read\n * by gates that replay without a model. Only absence is a miss; an unreadable\n * or malformed file fails, so a corrupted answer can never pass silently.\n * Writes are atomic, so an interrupted refresh cannot leave a partial file.\n */\n\nimport { existsSync, mkdirSync, mkdtempSync, readFileSync, renameSync, rmSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\n\nimport * as Data from \"effect/Data\";\nimport * as Effect from \"effect/Effect\";\nimport type * as Decision from \"effect/ai/Decision\";\n\nimport { validCachedAnswers } from \"./answers\";\n\nexport type DecisionCachePolicy = \"scratch\" | \"committed\";\n\nexport interface DecisionCache {\n readonly directory: string;\n readonly policy: DecisionCachePolicy;\n}\n\n/** A committed answer that exists but cannot be trusted. */\nexport class DecisionCacheInvalid extends Data.TaggedError(\"DecisionCacheInvalid\")<{\n readonly file: string;\n readonly message: string;\n}> {}\n\nconst answerFile = (cache: DecisionCache, key: string): string => join(cache.directory, `${key}.json`);\n\n/**\n * Cached answers for `key` when they match `decisions`, `undefined` on a miss.\n * Under `committed`, an unreadable or mismatched file fails instead of missing.\n */\nexport const readAnswers = (\n cache: DecisionCache,\n key: string,\n decisions: Readonly<Record<string, Decision.Any>>,\n): Effect.Effect<unknown | undefined, DecisionCacheInvalid> =>\n Effect.gen(function* () {\n const file = answerFile(cache, key);\n if (!existsSync(file)) return undefined;\n const invalid = (reason: string) =>\n cache.policy === \"scratch\"\n ? Effect.succeed(undefined)\n : Effect.fail(\n new DecisionCacheInvalid({\n file,\n message: `Committed decision answers in ${file} ${reason}; re-ask them and commit the result.`,\n }),\n );\n const parsed = yield* Effect.try({\n try: (): unknown => JSON.parse(readFileSync(file, \"utf8\")),\n catch: () => file,\n }).pipe(Effect.option);\n if (parsed._tag === \"None\") return yield* invalid(\"are not readable JSON\");\n if (!validCachedAnswers(decisions, parsed.value)) return yield* invalid(\"do not match the decision definition\");\n return parsed.value;\n });\n\n/** Persist answers for `key`, creating the directory on first write. */\nexport const writeAnswers = (cache: DecisionCache, key: string, answers: unknown): Effect.Effect<void> =>\n Effect.sync(() => {\n mkdirSync(cache.directory, { recursive: true });\n const contents = `${JSON.stringify(answers, null, 2)}\\n`;\n if (cache.policy === \"scratch\") {\n writeFileSync(answerFile(cache, key), contents, \"utf8\");\n return;\n }\n // Stage beside the target so the rename stays on one filesystem.\n const staging = mkdtempSync(join(cache.directory, \".answer-\"));\n try {\n const staged = join(staging, \"answer.json\");\n writeFileSync(staged, contents, \"utf8\");\n renameSync(staged, answerFile(cache, key));\n } finally {\n rmSync(staging, { recursive: true, force: true });\n }\n });\n","import * as Schema from \"effect/Schema\";\nimport type * as Decision from \"effect/ai/Decision\";\n\nimport { globToRegExp } from \"../../core/checks\";\nimport type { PageHead } from \"../../core/page-heads\";\nimport { HEAD_SECTION_ID, hashDocument, type Message, type PageDocument, type SectionDocument } from \"../../markdown/document\";\nimport type { Facts } from \"../../markdown/facts\";\nimport { sectionMarkdown } from \"../../markdown/markdown\";\nimport type { DecisionFamily } from \"../run\";\n\nconst FactDefinition = Schema.Struct({\n kind: Schema.Literals([\"number\", \"money\", \"list\", \"text\"]),\n value: Schema.Json,\n text: Schema.String,\n items: Schema.optionalKey(Schema.Array(Schema.String)),\n});\n\n/** Authored prose, code-owned facts, and evidence supplied to adopter rules. */\nexport const claimsInput = Schema.Struct({\n path: Schema.String,\n evidence: Schema.optionalKey(Schema.String),\n section: Schema.Struct({\n id: Schema.String,\n kind: Schema.String,\n markdown: Schema.String,\n messages: Schema.Array(Schema.Struct({\n hash: Schema.String, text: Schema.String, facts: Schema.Array(Schema.String),\n })),\n }),\n facts: Schema.Record(Schema.String, FactDefinition),\n context: Schema.optionalKey(Schema.String),\n});\nexport type ClaimsInput = typeof claimsInput.Type;\nexport type ClaimsRules = Decision.Definition<typeof claimsInput, Record<string, Decision.Probability>>;\nexport interface ClaimsOptions {\n readonly rules: ClaimsRules;\n readonly model: string;\n readonly cutoff?: number;\n readonly context?: string;\n /** Exclude metadata by path glob; captured sections remain in the gate. */\n readonly excludeHeads?: ReadonlyArray<string>;\n}\nexport type ClaimsVerdict = \"pass\" | \"violation\";\nconst ClaimsAnswers = Schema.Record(Schema.String, Schema.Struct({ probability: Schema.Finite }));\n\n/** Claims use a single upper cutoff; probabilities below it pass without a review band. */\nexport function claimsFamily(rules: ClaimsRules): DecisionFamily<ClaimsInput> {\n if (Object.keys(rules.decisions).length === 0) throw new Error(\"Claims need at least one decision rule\");\n if (Object.values(rules.decisions).some((rule) => rule._tag !== \"Probability\"))\n throw new Error(\"Claims rules must be probability decisions\");\n return {\n name: \"claims\",\n input: claimsInput,\n definitionFor: () => rules,\n inputRef: (input) => `${input.path} › ${input.section.id}`,\n evaluate: (_input, answers, cutoff): ClaimsVerdict =>\n Object.values(Schema.decodeUnknownSync(ClaimsAnswers)(answers))\n .some((answer) => answer.probability >= cutoff) ? \"violation\" : \"pass\",\n };\n}\n\n/** Heads describe only pages outside capture; duplicate ownership fails before evaluation. */\nexport function resolveClaimsDocuments(\n documents: ReadonlyArray<PageDocument>,\n heads: ReadonlyArray<PageHead>,\n excludeHeads: ReadonlyArray<string> = [],\n): ReadonlyArray<PageDocument> {\n const paths = new Set<string>();\n for (const document of documents) {\n if (paths.has(document.path)) throw new Error(`Duplicate claims page path \"${document.path}\"`);\n paths.add(document.path);\n }\n const seenHeads = new Set<string>();\n const excludes = excludeHeads.map(globToRegExp);\n const extra: Array<PageDocument> = [];\n for (const head of heads) {\n if (seenHeads.has(head.path)) throw new Error(`Duplicate claims head path \"${head.path}\"`);\n seenHeads.add(head.path);\n if (paths.has(head.path)) throw new Error(`Duplicate claims page path \"${head.path}\"`);\n if (excludes.some((pattern) => pattern.test(head.path))) continue;\n const document = { ...head, sections: [], messages: [], facts: [] };\n extra.push({ ...document, hash: hashDocument(document) });\n }\n return [...documents, ...extra];\n}\nfunction sectionMessages(section: SectionDocument): ReadonlyArray<Message> {\n return [\n ...(section.title === undefined ? [] : [section.title]),\n ...section.body.flatMap((entry) => entry.type === \"message\" ? [entry.message] : []),\n ...section.items.flatMap((item) => [\n ...(item.title === undefined ? [] : [item.title]),\n ...item.body.flatMap((entry) => entry.type === \"message\" ? [entry.message] : []),\n ...item.cells,\n ]),\n ];\n}\n\n/** Source locations do not enter model input or answer identity. */\nexport function claimsInputs(\n documents: ReadonlyArray<PageDocument>,\n heads: ReadonlyArray<PageHead>,\n facts: Facts,\n options: Pick<ClaimsOptions, \"context\" | \"excludeHeads\"> = {},\n): ReadonlyArray<ClaimsInput> {\n const excludes = (options.excludeHeads ?? []).map(globToRegExp);\n return resolveClaimsDocuments(documents, heads, options.excludeHeads).flatMap((document) => {\n const sections: ReadonlyArray<SectionDocument> = [\n ...document.sections,\n ...(excludes.some((pattern) => pattern.test(document.path)) ? [] : [{\n id: HEAD_SECTION_ID, kind: \"head\", claimsMarkdown: `${document.title}\\n\\n${document.description}`,\n body: [], items: [], source: document.path, audience: \"all\" as const,\n }]),\n ];\n return sections.map((section) => {\n const evidence = document.sections.filter((entry) => entry.evidence === true && entry !== section)\n .map((entry) => entry.claimsMarkdown ?? sectionMarkdown(entry, { includeHumans: true })).join(\"\\n\\n\");\n return Schema.decodeUnknownSync(claimsInput)({\n path: document.path,\n ...(evidence === \"\" ? {} : { evidence }),\n section: {\n id: section.id, kind: section.kind,\n markdown: section.claimsMarkdown ?? sectionMarkdown(section, { includeHumans: true }),\n messages: sectionMessages(section).map(({ hash, text, facts }) => ({ hash, text, facts })),\n },\n facts,\n ...(options.context === undefined ? {} : { context: options.context }),\n });\n });\n });\n}\n","/**\n * The decision runner. A family supplies its input schema, a definition built\n * from the input (families with dynamic labels build one per input), a way to\n * reference the input in a report, and a pure `evaluate` that maps the model's\n * answers to a verdict. The runner hashes, reads and writes the answer cache,\n * dispatches with bounded concurrency, and buckets confident answers apart from\n * `review`. `runDecisions` asks the model on a miss; `replayDecisions` answers\n * from a committed cache alone and fails on a miss.\n *\n * Build and CLI entries reach Effect and the provider through this module;\n * runtime entries remain Effect-free and Node-free.\n */\n\nimport * as Data from \"effect/Data\";\nimport * as Effect from \"effect/Effect\";\nimport * as Schema from \"effect/Schema\";\nimport { DecisionModel } from \"effect/ai\";\nimport type * as AiError from \"effect/ai/AiError\";\nimport type * as Decision from \"effect/ai/Decision\";\n\nimport { cacheKey, inputHash } from \"./answers\";\nimport { readAnswers, writeAnswers, type DecisionCache, type DecisionCacheInvalid } from \"./cache\";\nimport { DecisionRecord, type DecisionBatchReport } from \"./record\";\n\n/**\n * One decision family. `answers` in `evaluate` is erased because each family\n * reads its own `Decision.Answers` shape; the family is the only place that\n * shape is known, and a `DecisionModel` validates it before it arrives.\n */\nexport interface DecisionFamily<Input> {\n readonly name: string;\n readonly input: Schema.ConstraintDecoder<Input>;\n /** Build the definition for one input; dynamic-label families build per input. */\n readonly definitionFor: (input: Input) => Decision.Definition<any, any>;\n /** Stable human reference for the input in a report. */\n readonly inputRef: (input: Input) => string;\n /** Map validated answers to a verdict; `\"review\"` means a human decides. */\n readonly evaluate: (input: Input, answers: any, threshold: number) => string;\n /**\n * Optional input-level guard for constraints a schema cannot express, such as\n * \"dynamic-label families need at least two candidates\". Returns a message to\n * fail the run, or `undefined` when the input is usable.\n */\n readonly validate?: (input: Input) => string | undefined;\n}\n\n/** A probability is confident when it is at or beyond `threshold` on either side. */\nexport const isConfident = (probability: number, threshold: number): boolean =>\n probability >= threshold || probability <= 1 - threshold;\n\n/** True when a probability sits inside the review band `(1 - threshold, threshold)`. */\nexport const inReviewBand = (probability: number, threshold: number): boolean =>\n !isConfident(probability, threshold);\n\n/** True when any probability sits inside the review band. */\nexport const anyInReviewBand = (\n probabilities: ReadonlyArray<number>,\n threshold: number,\n): boolean => probabilities.some((probability) => inReviewBand(probability, threshold));\n\nexport interface RunOptions<Input> {\n readonly family: DecisionFamily<Input>;\n readonly inputs: ReadonlyArray<Input>;\n readonly model: string;\n readonly threshold: number;\n readonly concurrency?: number;\n /** Answer cache; absent means every input is asked. */\n readonly cache?: DecisionCache;\n}\n\n/** Replay found no committed answer for an input. */\nexport class DecisionCacheMiss extends Data.TaggedError(\"DecisionCacheMiss\")<{\n readonly family: string;\n readonly inputRef: string;\n readonly key: string;\n readonly message: string;\n}> {}\n\n/** Group records into resolved/review buckets and count each verdict. */\nexport const buildDecisionReport = (options: {\n readonly family: string;\n readonly model: string;\n readonly threshold: number;\n readonly records: ReadonlyArray<DecisionRecord>;\n}): DecisionBatchReport => {\n const verdicts: Record<string, number> = {};\n const resolved: Array<DecisionRecord> = [];\n const review: Array<DecisionRecord> = [];\n for (const record of options.records) {\n verdicts[record.verdict] = (verdicts[record.verdict] ?? 0) + 1;\n if (record.review) review.push(record);\n else resolved.push(record);\n }\n return {\n kind: \"decide\",\n schemaVersion: 1,\n family: options.family,\n model: options.model,\n threshold: options.threshold,\n counts: {\n inputs: options.records.length,\n resolved: resolved.length,\n review: review.length,\n },\n verdicts,\n resolved,\n review,\n };\n};\n\ninterface Answered {\n readonly answers: unknown;\n readonly usage?: DecisionRecord[\"usage\"];\n}\n\n/**\n * Answer each input from the cache, or through `ask` on a miss, and bucket the\n * results. Inputs run with bounded concurrency.\n */\nconst decideAll = <Input, E, R>(\n options: RunOptions<Input>,\n ask: (input: Input, key: string) => Effect.Effect<Answered, E, R>,\n reuseCache: boolean,\n): Effect.Effect<DecisionBatchReport, E | DecisionCacheInvalid, R> =>\n Effect.gen(function* () {\n const records = yield* Effect.forEach(\n options.inputs,\n (input, index) =>\n Effect.gen(function* () {\n const definition = options.family.definitionFor(input);\n const hash = inputHash(input);\n const key = cacheKey({\n family: options.family.name,\n model: options.model,\n decisions: definition.decisions,\n inputHash: hash,\n });\n const cached =\n options.cache === undefined || !reuseCache\n ? undefined\n : yield* readAnswers(options.cache, key, definition.decisions);\n const answered: Answered = cached === undefined ? yield* ask(input, key) : { answers: cached };\n const verdict = options.family.evaluate(input, answered.answers, options.threshold);\n return {\n decisionId: `${options.family.name}:${index}`,\n schemaVersion: 1,\n family: options.family.name,\n model: options.model,\n threshold: options.threshold,\n inputHash: hash,\n inputRef: options.family.inputRef(input),\n verdict,\n review: verdict === \"review\",\n answers: answered.answers,\n ...(answered.usage === undefined ? {} : { usage: answered.usage }),\n } satisfies DecisionRecord;\n }),\n { concurrency: options.concurrency ?? 4 },\n );\n return buildDecisionReport({\n family: options.family.name,\n model: options.model,\n threshold: options.threshold,\n records,\n });\n });\n\n/**\n * Answer every input through the ambient `DecisionModel`. One provider call\n * answers every decision for an input; a cached answer skips the provider unless\n * `refresh` asks again. New answers are written to the cache.\n */\nexport const runDecisions = <Input>(\n options: RunOptions<Input> & { readonly refresh?: boolean },\n): Effect.Effect<\n DecisionBatchReport,\n AiError.AiError | DecisionCacheInvalid,\n DecisionModel.DecisionModel\n> =>\n decideAll(\n options,\n (input, key) =>\n Effect.gen(function* () {\n const response = yield* DecisionModel.decide(options.family.definitionFor(input), { input });\n if (options.cache !== undefined) yield* writeAnswers(options.cache, key, response.answers);\n return {\n answers: response.answers,\n usage: { inputTokens: response.usage.inputTokens, outputTokens: response.usage.outputTokens },\n };\n }),\n options.refresh !== true,\n );\n\n/**\n * Answer every input from the cache alone. Needs no model or credentials, so a\n * gate can run it offline; an input without an answer fails the replay.\n */\nexport const replayDecisions = <Input>(\n options: RunOptions<Input> & { readonly cache: DecisionCache },\n): Effect.Effect<DecisionBatchReport, DecisionCacheMiss | DecisionCacheInvalid> =>\n decideAll(\n options,\n (input, key) =>\n Effect.fail(\n new DecisionCacheMiss({\n family: options.family.name,\n inputRef: options.family.inputRef(input),\n key,\n message: `No ${options.family.name} answer for ${options.family.inputRef(input)}; ask the model and commit the answers.`,\n }),\n ),\n true,\n );\n\nexport const decodeFamilyInputs = <Input>(\n family: DecisionFamily<Input>,\n raw: ReadonlyArray<unknown>,\n): ReadonlyArray<Input> => {\n const decode = Schema.decodeUnknownSync(family.input);\n return raw.map((value) => decode(value));\n};\n","import { resolve } from \"node:path\";\n\nimport * as Data from \"effect/Data\";\nimport * as Effect from \"effect/Effect\";\nimport * as Schema from \"effect/Schema\";\n\nimport { DecisionCacheInvalid } from \"./decide/cache\";\n\nimport type { PageHead } from \"./core/page-heads\";\nimport { claimsFamily, claimsInputs, resolveClaimsDocuments, type ClaimsOptions } from \"./decide/families/claims\";\nimport type { DecisionBatchReport } from \"./decide/record\";\nimport { DecisionCacheMiss, replayDecisions, runDecisions } from \"./decide/run\";\nimport type { PageDocument } from \"./markdown/document\";\nimport type { Facts } from \"./markdown/facts\";\n\nexport { claimsFamily, claimsInput, claimsInputs, resolveClaimsDocuments } from \"./decide/families/claims\";\nexport type { ClaimsInput, ClaimsOptions, ClaimsRules, ClaimsVerdict } from \"./decide/families/claims\";\n\nexport interface ClaimsFinding {\n readonly path: string;\n readonly section: string;\n readonly source: string;\n readonly rule: string;\n readonly probability: number;\n}\nexport interface ClaimsReport {\n readonly version: 1;\n readonly mode: \"ask\" | \"replay\";\n readonly pages: number;\n readonly messages: number;\n readonly cached: number;\n readonly asked: number;\n readonly cutoff: number;\n readonly findings: ReadonlyArray<ClaimsFinding>;\n}\nexport class ClaimsFailed extends Data.TaggedError(\"ClaimsFailed\")<{\n readonly message: string;\n readonly findings: ReadonlyArray<ClaimsFinding>;\n readonly report: ClaimsReport;\n}> {}\n\n/** Cutoff affects verdicts alone; committed identity belongs to the decision runner. */\nexport function claimsRunOptions(\n root: string,\n documents: ReadonlyArray<PageDocument>,\n heads: ReadonlyArray<PageHead>,\n facts: Facts,\n options: ClaimsOptions,\n) {\n const cutoff = options.cutoff === undefined ? 0.8 : options.cutoff;\n if (!Number.isFinite(cutoff) || cutoff < 0 || cutoff > 1)\n throw new Error(\"Claims cutoff must be between 0 and 1\");\n if (!options.model.trim()) throw new Error(\"Claims model is required\");\n return {\n family: claimsFamily(options.rules),\n inputs: claimsInputs(documents, heads, facts, options),\n model: options.model,\n threshold: cutoff,\n cache: { directory: resolve(root, \".pagegraph/decisions/claims\"), policy: \"committed\" as const },\n };\n}\nconst ProbabilityAnswers = Schema.Record(Schema.String, Schema.Struct({ probability: Schema.Finite }));\nfunction claimsReport(\n batch: DecisionBatchReport,\n documents: ReadonlyArray<PageDocument>,\n heads: ReadonlyArray<PageHead>,\n facts: Facts,\n options: ClaimsOptions,\n mode: ClaimsReport[\"mode\"],\n): ClaimsReport {\n const inputs = claimsInputs(documents, heads, facts, options);\n const sources = new Map(documents.map((document) => [document.path, document]));\n const records = [...batch.resolved, ...batch.review];\n const findings = records.flatMap((record): Array<ClaimsFinding> => {\n const index = Number(record.decisionId.slice(\"claims:\".length));\n const input = inputs[index];\n if (input === undefined) throw new Error(`Unknown claims decision ${record.decisionId}`);\n return Object.entries(Schema.decodeUnknownSync(ProbabilityAnswers)(record.answers))\n .flatMap(([rule, answer]) => answer.probability >= batch.threshold ? [{\n path: input.path, section: input.section.id,\n source: sources.get(input.path)?.sections.find((section) => section.id === input.section.id)?.source ?? input.path,\n rule, probability: answer.probability,\n }] : []);\n });\n const asked = records.filter((record) => record.usage !== undefined).length;\n const resolved = resolveClaimsDocuments(documents, heads, options.excludeHeads);\n return {\n version: 1, mode, pages: resolved.length,\n messages: documents.reduce((count, document) => count + document.messages.length, 0),\n cached: records.length - asked, asked, cutoff: batch.threshold, findings,\n };\n}\n\n/** Ask only for absent answers unless refresh is requested, then persist committed answers. */\nexport const runClaims = Effect.fn(\"Claims.run\")(function* (\n root: string,\n documents: ReadonlyArray<PageDocument>,\n heads: ReadonlyArray<PageHead>,\n facts: Facts,\n options: ClaimsOptions,\n runOptions: { readonly refresh?: boolean } = {},\n) {\n const batch = yield* runDecisions({ ...claimsRunOptions(root, documents, heads, facts, options), ...runOptions });\n return claimsReport(batch, documents, heads, facts, options, \"ask\");\n});\n\n/** Offline gates have no model requirement; absent answers and violations fail with author guidance. */\nexport const replayClaims = Effect.fn(\"Claims.replay\")(function* (\n root: string,\n documents: ReadonlyArray<PageDocument>,\n heads: ReadonlyArray<PageHead>,\n facts: Facts,\n options: ClaimsOptions,\n) {\n const batch = yield* replayDecisions(claimsRunOptions(root, documents, heads, facts, options)).pipe(\n Effect.catchTag(\"DecisionCacheInvalid\", (error) => Effect.fail(new DecisionCacheInvalid({\n file: error.file, message: `${error.message} Run pagegraph claims check --refresh and commit the answers.`,\n }))),\n Effect.catchTag(\"DecisionCacheMiss\", (error) => Effect.fail(new DecisionCacheMiss({\n ...error,\n message: `No claims answer for ${error.inputRef} (rules: ${Object.keys(options.rules.decisions).join(\", \")}). Run pagegraph claims check and commit the answers.`,\n }))),\n );\n const report = claimsReport(batch, documents, heads, facts, options, \"replay\");\n if (report.findings.length > 0) return yield* new ClaimsFailed({\n message: report.findings.map((finding) =>\n `Claims violation at ${finding.path} › ${finding.section}: ${finding.rule} (${finding.probability.toFixed(2)}).`,\n ).join(\"\\n\") + \"\\nRun pagegraph claims check and commit the answers.\",\n findings: report.findings, report,\n });\n return report;\n});\n"],"mappings":"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"}
package/dist/claims.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { a as claimsFamily, c as resolveClaimsDocuments, d as DecisionCacheInvalid, i as ClaimsVerdict, l as DecisionCacheMiss, n as ClaimsOptions, o as claimsInput, r as ClaimsRules, s as claimsInputs, t as ClaimsInput, u as DecisionFamily } from "./claims-Bch7Lxlo.js";
2
- import { a as FactDefinition, o as Facts, p as PageDocument, t as PageHead } from "./page-heads-BfjWQvau.js";
1
+ import { a as claimsFamily, c as resolveClaimsDocuments, d as DecisionCacheInvalid, i as ClaimsVerdict, l as DecisionCacheMiss, n as ClaimsOptions, o as claimsInput, r as ClaimsRules, s as claimsInputs, t as ClaimsInput, u as DecisionFamily } from "./claims-CCf2_Jox.js";
2
+ import { a as FactDefinition, o as Facts, p as PageDocument, t as PageHead } from "./page-heads-DDz_2_hq.js";
3
3
  import * as Effect from "effect/Effect";
4
4
  import * as Schema from "effect/Schema";
5
5
  //#region src/claims.d.ts
package/dist/claims.js CHANGED
@@ -1,2 +1,2 @@
1
- import { a as claimsFamily, c as resolveClaimsDocuments, i as runClaims, n as claimsRunOptions, o as claimsInput, r as replayClaims, s as claimsInputs, t as ClaimsFailed } from "./claims-Bv7voeEV.js";
1
+ import { a as claimsFamily, c as resolveClaimsDocuments, i as runClaims, n as claimsRunOptions, o as claimsInput, r as replayClaims, s as claimsInputs, t as ClaimsFailed } from "./claims-CQlzze1K.js";
2
2
  export { ClaimsFailed, claimsFamily, claimsInput, claimsInputs, claimsRunOptions, replayClaims, resolveClaimsDocuments, runClaims };