lazycodex-ai 5.1.45 → 5.1.46
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.
- package/README.de.md +0 -5
- package/README.es.md +0 -5
- package/README.fr.md +0 -5
- package/README.it.md +0 -5
- package/README.ja.md +0 -5
- package/README.ko.md +0 -5
- package/README.md +4 -5
- package/README.pt-br.md +0 -5
- package/README.ru.md +0 -6
- package/README.zh-cn.md +0 -5
- package/dist/cli/index.js +76 -156
- package/dist/cli-node/index.js +76 -156
- package/package.json +1 -1
- package/packages/lsp-daemon/dist/cli.js +3 -3
- package/packages/lsp-daemon/dist/client.js +3 -3
- package/packages/lsp-daemon/dist/index.js +3 -3
- package/packages/lsp-tools-mcp/dist/cli.js +3 -3
- package/packages/lsp-tools-mcp/dist/mcp.js +3 -3
- package/packages/omo-codex/plugin/.codex-plugin/plugin.json +1 -1
- package/packages/omo-codex/plugin/components/bootstrap/dist/cli.js +22 -12
- package/packages/omo-codex/plugin/components/bootstrap/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/bootstrap/package.json +1 -1
- package/packages/omo-codex/plugin/components/comment-checker/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/comment-checker/package.json +1 -1
- package/packages/omo-codex/plugin/components/git-bash/hooks/hooks.json +2 -2
- package/packages/omo-codex/plugin/components/git-bash/package.json +1 -1
- package/packages/omo-codex/plugin/components/lazycodex-executor-verify/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/lazycodex-executor-verify/package.json +1 -1
- package/packages/omo-codex/plugin/components/lsp/dist/.omo-runtime-manifest.json +2 -2
- package/packages/omo-codex/plugin/components/lsp/hooks/hooks.json +2 -2
- package/packages/omo-codex/plugin/components/lsp/package.json +1 -1
- package/packages/omo-codex/plugin/components/rules/hooks/hooks.json +4 -4
- package/packages/omo-codex/plugin/components/rules/package.json +1 -1
- package/packages/omo-codex/plugin/components/teammode/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/teammode/package.json +1 -1
- package/packages/omo-codex/plugin/components/telemetry/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/telemetry/package.json +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/package.json +1 -1
- package/packages/omo-codex/plugin/components/ulw-execute-continuation/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/ulw-execute-continuation/package.json +1 -1
- package/packages/omo-codex/plugin/components/ulw-loop/hooks/hooks.json +5 -5
- package/packages/omo-codex/plugin/components/ulw-loop/package.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-compact-resetting-git-bash-mcp-reminder.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-compact-resetting-lsp-diagnostics-cache.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-compact-resetting-project-rule-cache.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-comments.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-lsp-diagnostics.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-thread-title-hygiene.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-matching-project-rules.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-recording-spawn-admission.json +1 -1
- package/packages/omo-codex/plugin/hooks/pre-tool-use-enforcing-unlimited-goal-budget.json +1 -1
- package/packages/omo-codex/plugin/hooks/pre-tool-use-guarding-ulw-loop-spawns.json +1 -1
- package/packages/omo-codex/plugin/hooks/pre-tool-use-recommending-git-bash-mcp.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-checking-auto-update.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-checking-bootstrap-provisioning.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-loading-project-rules.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-recording-session-telemetry.json +1 -1
- package/packages/omo-codex/plugin/hooks/stop-checking-ulw-execute-continuation.json +1 -1
- package/packages/omo-codex/plugin/hooks/stop-checking-ulw-loop-resume.json +1 -1
- package/packages/omo-codex/plugin/hooks/subagent-stop-verifying-lazycodex-executor-evidence.json +1 -1
- package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ultrawork-trigger.json +1 -1
- package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ulw-loop-steering.json +1 -1
- package/packages/omo-codex/plugin/hooks/user-prompt-submit-loading-project-rules.json +1 -1
- package/packages/omo-codex/plugin/package-lock.json +12 -12
- package/packages/omo-codex/plugin/package.json +1 -1
- package/packages/omo-codex/plugin/scripts/sync-skills.mjs +8 -3
- package/packages/omo-codex/plugin/shared/test/config-loader.test.ts +0 -2
- package/packages/omo-codex/plugin/skills/ast-grep/AGENTS.md +1 -1
- package/packages/omo-codex/plugin/skills/browser/runtime/omowright/manifest.json +1 -1
- package/packages/omo-codex/plugin/skills/frontend/ATTRIBUTION.md +2 -2
- package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/AGENTS.md +1 -1
- package/packages/omo-codex/plugin/skills/ulw-research/AGENTS.md +14 -2
- package/packages/omo-codex/plugin/skills/ulw-research/SKILL.md +36 -11
- package/packages/omo-codex/plugin/skills/ulw-research/references/deliverable-phase.md +73 -13
- package/packages/omo-codex/plugin/skills/ulw-research/references/fact-ledger-grammar.md +19 -1
- package/packages/omo-codex/plugin/skills/ulw-research/references/fact-ledger.md +95 -0
- package/packages/omo-codex/plugin/skills/ulw-research/references/fidelity-claim-kinds.md +19 -0
- package/packages/omo-codex/plugin/skills/ulw-research/references/report-gates.md +21 -4
- package/packages/omo-codex/plugin/skills/ulw-research/references/report-visualization.md +2 -0
- package/packages/omo-codex/plugin/skills/ulw-research/references/unit-rules.md +157 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/claim-kinds.mjs +116 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/cli-support.mjs +19 -5
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/contracts.mjs +27 -6
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-commands-meaning.mjs +191 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-commands-verify.mjs +164 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-commands.mjs +239 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-derive.mjs +66 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-expr.mjs +108 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-fill.mjs +194 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-ledger-kinds.mjs +127 -13
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-ledger-schema.mjs +33 -16
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-ledger.mjs +29 -9
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-locate.mjs +231 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-pairs.mjs +187 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-recompute.mjs +89 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-binding.mjs +130 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-brief.mjs +5 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-claims.mjs +89 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-lexicon.mjs +145 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-records.mjs +163 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-state.mjs +2 -1
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-trace.mjs +85 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-values.mjs +229 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/gates-fidelity.mjs +113 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/gates-static.mjs +28 -8
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/html-lite.mjs +2 -2
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/html-markup.mjs +182 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/md-lite.mjs +193 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/meaning-check.mjs +196 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/outcome.mjs +6 -5
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/placeholder.mjs +184 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/polish-apply.mjs +115 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/polish-diff.mjs +31 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/polish-project.mjs +125 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/polish-rules.mjs +170 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/report-tools-commands.mjs +19 -8
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/report-tools.mjs +15 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/source-commands.mjs +140 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/source-snapshot.mjs +176 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/unit-algebra.mjs +101 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/unit-errors.mjs +23 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/unit-metadata.mjs +31 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/unit-pairs.mjs +72 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/unit-precision.mjs +44 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/unit-rules-shapes.mjs +64 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/unit-rules.mjs +173 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/units.mjs +4 -4
- package/packages/omo-codex/plugin/test/sync-skills-codex-compatibility.test.mjs +19 -0
- package/packages/omo-codex/plugin/test/sync-skills-test-support.mjs +7 -3
- package/packages/omo-codex/scripts/install-dist/install-local.mjs +26 -16
- package/packages/shared-skills/skills/ast-grep/AGENTS.md +1 -1
- package/packages/shared-skills/skills/browser/runtime/omowright/manifest.json +1 -1
- package/packages/shared-skills/skills/frontend/ATTRIBUTION.md +2 -2
- package/packages/shared-skills/skills/ultimate-browsing/engine/AGENTS.md +1 -1
- package/packages/shared-skills/skills/ulw-research/AGENTS.md +14 -2
- package/packages/shared-skills/skills/ulw-research/SKILL.md +37 -12
- package/packages/shared-skills/skills/ulw-research/references/deliverable-phase.md +73 -13
- package/packages/shared-skills/skills/ulw-research/references/fact-ledger-grammar.md +19 -1
- package/packages/shared-skills/skills/ulw-research/references/fact-ledger.md +95 -0
- package/packages/shared-skills/skills/ulw-research/references/fidelity-claim-kinds.md +19 -0
- package/packages/shared-skills/skills/ulw-research/references/report-gates.md +21 -4
- package/packages/shared-skills/skills/ulw-research/references/report-visualization.md +2 -0
- package/packages/shared-skills/skills/ulw-research/references/unit-rules.md +157 -0
- package/packages/shared-skills/skills/ulw-research/scripts/claim-kinds.mjs +116 -0
- package/packages/shared-skills/skills/ulw-research/scripts/claim-kinds.test.ts +193 -0
- package/packages/shared-skills/skills/ulw-research/scripts/cli-support.mjs +19 -5
- package/packages/shared-skills/skills/ulw-research/scripts/cli-support.test.ts +13 -2
- package/packages/shared-skills/skills/ulw-research/scripts/contracts.mjs +27 -6
- package/packages/shared-skills/skills/ulw-research/scripts/contracts.test.ts +3 -0
- package/packages/shared-skills/skills/ulw-research/scripts/copy-returns.test.ts +183 -0
- package/packages/shared-skills/skills/ulw-research/scripts/delivery-barrier.test.ts +245 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fact-commands-meaning.mjs +191 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fact-commands-verify.mjs +164 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fact-commands.mjs +239 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fact-derive.mjs +66 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fact-derive.test.ts +375 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fact-expr.mjs +108 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fact-fill.mjs +194 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fact-fill.test.ts +221 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger-grammar.test.ts +9 -2
- package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger-kinds.mjs +127 -13
- package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger-kinds.test.ts +18 -3
- package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger-legacy.test.ts +160 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger-schema.mjs +33 -16
- package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger.mjs +29 -9
- package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger.test.ts +2 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fact-locate.mjs +231 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fact-locate.test.ts +118 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fact-pairs.mjs +187 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fact-pairs.test.ts +167 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fact-recompute.mjs +89 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fact-unit-dimensions.test.ts +409 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fact-unit-rules.test.ts +388 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fidelity-binding.mjs +130 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fidelity-binding.test.ts +81 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fidelity-brief.mjs +5 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fidelity-claims.mjs +89 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fidelity-lexicon.mjs +145 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fidelity-lexicon.test.ts +117 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fidelity-records.mjs +163 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fidelity-state.mjs +2 -1
- package/packages/shared-skills/skills/ulw-research/scripts/fidelity-trace.mjs +85 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fidelity-trace.test.ts +58 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fidelity-values.mjs +229 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fidelity-values.test.ts +270 -0
- package/packages/shared-skills/skills/ulw-research/scripts/gates-fidelity.mjs +113 -0
- package/packages/shared-skills/skills/ulw-research/scripts/gates-fidelity.test.ts +73 -0
- package/packages/shared-skills/skills/ulw-research/scripts/gates-static.mjs +28 -8
- package/packages/shared-skills/skills/ulw-research/scripts/html-lite.mjs +2 -2
- package/packages/shared-skills/skills/ulw-research/scripts/html-markup.mjs +182 -0
- package/packages/shared-skills/skills/ulw-research/scripts/md-lite.mjs +193 -0
- package/packages/shared-skills/skills/ulw-research/scripts/md-lite.test.ts +271 -0
- package/packages/shared-skills/skills/ulw-research/scripts/meaning-check.mjs +196 -0
- package/packages/shared-skills/skills/ulw-research/scripts/meaning-check.test.ts +228 -0
- package/packages/shared-skills/skills/ulw-research/scripts/outcome.mjs +6 -5
- package/packages/shared-skills/skills/ulw-research/scripts/placeholder.mjs +184 -0
- package/packages/shared-skills/skills/ulw-research/scripts/placeholder.test.ts +77 -0
- package/packages/shared-skills/skills/ulw-research/scripts/polish-apply.mjs +115 -0
- package/packages/shared-skills/skills/ulw-research/scripts/polish-apply.test.ts +363 -0
- package/packages/shared-skills/skills/ulw-research/scripts/polish-diff.mjs +31 -0
- package/packages/shared-skills/skills/ulw-research/scripts/polish-diff.test.ts +168 -0
- package/packages/shared-skills/skills/ulw-research/scripts/polish-markup.test.ts +187 -0
- package/packages/shared-skills/skills/ulw-research/scripts/polish-project.mjs +125 -0
- package/packages/shared-skills/skills/ulw-research/scripts/polish-rules.mjs +170 -0
- package/packages/shared-skills/skills/ulw-research/scripts/report-tools-commands.mjs +19 -8
- package/packages/shared-skills/skills/ulw-research/scripts/report-tools.mjs +15 -0
- package/packages/shared-skills/skills/ulw-research/scripts/report-tools.test.ts +458 -2
- package/packages/shared-skills/skills/ulw-research/scripts/source-commands.mjs +140 -0
- package/packages/shared-skills/skills/ulw-research/scripts/source-snapshot.mjs +176 -0
- package/packages/shared-skills/skills/ulw-research/scripts/source-snapshot.test.ts +113 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/assets/share.csv +3 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/gen-legacy-dev.mjs +89 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/ledger-basic.json +223 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/ledger-legacy-dev.json +859 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/ledger-verify.json +420 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/report-bad.html +19 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/report-good.html +39 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S4/acronyms.json +3 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S4/meta.json +9 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S4/sentences.jsonl +7 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S4/text.txt +7 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S4.html +19 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S5/acronyms.json +1 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S5/meta.json +9 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S5/sentences.jsonl +1 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S5/text.txt +1 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S9.txt +1 -0
- package/packages/shared-skills/skills/ulw-research/scripts/unit-algebra.mjs +101 -0
- package/packages/shared-skills/skills/ulw-research/scripts/unit-errors.mjs +23 -0
- package/packages/shared-skills/skills/ulw-research/scripts/unit-metadata.mjs +31 -0
- package/packages/shared-skills/skills/ulw-research/scripts/unit-pairs.mjs +72 -0
- package/packages/shared-skills/skills/ulw-research/scripts/unit-precision.mjs +44 -0
- package/packages/shared-skills/skills/ulw-research/scripts/unit-rules-shapes.mjs +64 -0
- package/packages/shared-skills/skills/ulw-research/scripts/unit-rules.mjs +173 -0
- package/packages/shared-skills/skills/ulw-research/scripts/units.mjs +4 -4
|
@@ -44,7 +44,7 @@ Raised only by the fidelity verifier (the fact ledger, fill, verify, meaning, an
|
|
|
44
44
|
| `pointer_label_mismatch` | blocker | true | A placeholder's label differs from the referenced ledger row's label (a typo guard on the id). | The placeholder's label is not the row's label; check the id, then the label. |
|
|
45
45
|
| `quote_anchor_unresolved` | blocker | true | A quote cut's from or to anchor is absent, occurs more than once, or appears in reverse order in the quote. | A from or to anchor is missing or occurs more than once in the quote; pick unique anchor words. |
|
|
46
46
|
| `unit_duplicated` | blocker | true | A unit-bearing placeholder is followed by another copy of its own unit. | The unit after the placeholder repeats the row's unit; use :value or remove the unit. |
|
|
47
|
-
| `polish_diff_fail` | blocker | true | The polish pass changed protected fact
|
|
47
|
+
| `polish_diff_fail` | blocker | true | The polish pass changed protected fact spans, numerals or units, tickers, table rows, or any markup byte (including every attribute, comments, and script/style/template content). | Revert to the pre-polish artifact and apply text-node edits only; markup changes require the full fidelity gates. |
|
|
48
48
|
| `fidelity_interview_incomplete` | blocker | true | A fidelity field in the brief is still pending or missing when a fidelity command runs. | A fidelity field is pending or missing in brief.md; answer the open question or record its default, then rerun. |
|
|
49
49
|
| `derived_not_allowed` | blocker | true | A placeholder resolves to a derived, converted, or assumed row while the brief allows no derived numbers. | The brief allows no derived or assumed numbers; cite a source row directly or change the brief with the requester. |
|
|
50
50
|
| `source_outside_floor` | blocker | true | A ledger row's source lies outside the source floor the brief records (pointed sources only, or the allowed source classes). | The row's source is outside the source floor the brief records; use a source the brief allows or remove the fact. |
|
|
@@ -53,7 +53,8 @@ Raised only by the fidelity verifier (the fact ledger, fill, verify, meaning, an
|
|
|
53
53
|
| `quote_unbound` | blocker | true | A direct quotation in the report has no ledger quote row behind it; a citation marker alone does not bind it. | A direct quotation has no ledger quote row; register it with fact quote and insert it through its placeholder. |
|
|
54
54
|
| `edition_unverified` | blocker | true | A delivered edition has no fact verify record whose artifact hash matches the delivered source. | The delivered file has no matching fact verify record; run fact verify on that edition and record it with --verified-by. |
|
|
55
55
|
| `untraceable_number` | major | false | A number or date in prose or a table cell resolves to no ledger row and is not structural; a citation does not exempt it. | Replace the typed number with a {{F<n>}} placeholder backed by a ledger row, or register it as assumed with a rationale. |
|
|
56
|
-
| `unsupported_verification` | major | false | A
|
|
56
|
+
| `unsupported_verification` | major | false | A fact bound to a `verified` mark (`data-claim-kind="verified"`, from a `{{V:F<n>:label}}` placeholder) cites a row whose own locator artifact does not sit in a recorded command-output snapshot, or the mark binds no fact of its own. Another command row in the same sentence never counts. Unmarked prose is never raised here. | A sentence marked verified (data-claim-kind="verified") cites a row that is not recorded command output; cite the recorded output row or drop the verified mark. |
|
|
57
|
+
| `claim_kind_mismatch` | major | false | A fact bound to a claim kind cites a row of a kind that claim kind does not accept, a fact sits under marks of two different kinds (nested marks), a mark binds no fact of its own, or a mark names an unknown kind; the kinds are in [fidelity-claim-kinds.md](fidelity-claim-kinds.md). | An element marked with a claim kind (data-claim-kind) cites a row that kind does not accept, or the kind is unknown; cite a row of that kind or fix the mark (references/fidelity-claim-kinds.md). |
|
|
57
58
|
| `conflicting_readings` | major | false | Two active ledger rows with the same value and unit but different periods or qualifiers are cited as one fact. | Two ledger rows with different periods or qualifiers are cited as one fact; split the sentence or retire one row. |
|
|
58
59
|
| `hedge_dropped` | major | false | A hedge, condition, or negation class present in the source quote is absent from the report sentence. | Restore the qualifier the source uses (estimated, about, not yet). |
|
|
59
60
|
| `direction_flipped` | major | false | The source states an increase and the sentence a decrease, or the reverse. | The source states the opposite direction; rewrite from the quote. |
|
|
@@ -61,20 +62,22 @@ Raised only by the fidelity verifier (the fact ledger, fill, verify, meaning, an
|
|
|
61
62
|
| `qualifier_dropped` | major | false | A bounding or approximation qualifier recorded on the ledger row (up to, at least, about) is absent near the value in the sentence. | The source bounds this value (up to, at least, about); restore the qualifier or use :q. |
|
|
62
63
|
| `unbound_sentence` | major | false | A findings sentence carries no fact, quote, citation marker, or claim binding and is not marked as opinion or recommendation. | Bind the sentence to a fact, a quote, a citation, or a claim-graph node with data-claim. |
|
|
63
64
|
| `meaning_drift_confirmed` | major | false | Two independent meaning readers flag the same drift kind on the same sentence and quote pair. | Two independent readers found the same drift; rewrite from the quote or dispute with a reason. |
|
|
65
|
+
| `meaning_unverified` | major | false | `fact meaning-check` found a pair no reader judged, or a force_strong pair no strong reader judged; an empty drift list is not a clean check. | No meaning reader judged this pair, or no strong reader judged a force_strong pair; run the missing reader on pairs.json and rerun fact meaning-check. |
|
|
64
66
|
| `polish_edit_rejected` | major | false | A proposed polish edit changes a protected number, hedge, qualifier, link, or claim id. | The edit changed a number, a hedge, a qualifier, a link, or a claim id; re-propose the language fix only. |
|
|
65
67
|
| `polish_hedging_decreased` | major | false | The polish pass removed a hedge the source grounds. | The polish pass removed qualifiers; restore them. |
|
|
66
|
-
| `polish_text_churn` | major | false | More than half of the visible prose changed during the polish pass. | More than half the text changed during polish; the pass composed instead of proofreading. |
|
|
68
|
+
| `polish_text_churn` | major | false | More than half of the visible prose changed during the polish pass (`POLISH_CHURN_LIMIT` in `scripts/polish-rules.mjs`). | More than half the text changed during polish; the pass composed instead of proofreading. |
|
|
67
69
|
| `snapshot_mismatch` | major | false | An operation that needs one collection window uses operands from unequal windows or observations outside the window. | The operands come from different collection windows; use rows from one window or state the windows separately. |
|
|
68
70
|
| `join_integrity_fail` | major | false | A table row combines facts from different source observation rows, or a declared observation id contradicts the row's lineage. | A table row combines facts from different source observations; keep each row to one observation or split it. |
|
|
69
71
|
| `quote_stale_version` | major | false | The quoted source revision differs from the revision the report targets for the same scope. | The quoted source revision differs from the revision the report targets; re-snapshot the target revision or date the quote. |
|
|
70
72
|
| `quote_fuzzy_match` | minor | false | An exact quote match failed but a candidate with similarity of at least 0.95 and identical protected tokens exists (OCR noise); diagnostic only. | The source text is OCR-noisy; confirm the snapshot and prefer the raw document. |
|
|
71
73
|
| `name_outside_quote` | minor | false | An uppercase Latin token of 2 to 6 letters in the sentence is absent from the supporting source text and that source's acronym table. | A name in the sentence is not in the cited quote; add the source or remove the name. |
|
|
74
|
+
| `verification_claim_unmarked` | minor | false | A sentence reads as a first-person run ("we ran", "we have run", "I ran", "we executed", "we verified by", "by running" after we/our, or the Korean rule in fidelity-claim-kinds.md) and cites a fact not marked `verified` (or cites none and its block is unmarked). A warning only; it never blocks. | The sentence reads as a check we ran but carries no verified mark; mark it {{V:F<n>:label}} on a recorded command-output row, or reword it. |
|
|
72
75
|
| `scope_widened_warn` | minor | false | The sentence uses a broader scope term than the quote's narrow term, and no supporting fact carries the broad scope. | The sentence uses a broader scope word than the quote; confirm the scope or narrow it. |
|
|
73
76
|
| `particle_unverified` | minor | false | A particle after a filled value could not be checked against the value's final reading. | The particle after this value could not be checked; read it aloud and fix it by hand if wrong. |
|
|
74
77
|
| `fallback_without_entity` | minor | false | A quote that replaced a twice-failed sentence has no entity for its attribution line. | The replacement quote has no entity for attribution; set one on the row. |
|
|
75
78
|
| `meaning_drift_warn` | minor | false | One meaning reader flags a change of scope, subject, or certainty without independent agreement. | A separate reader found the sentence changes scope, subject, or certainty; rewrite from the quote. |
|
|
76
79
|
| `polish_edit_unlocated` | minor | false | A polish edit's before-text matches zero or several places in the current text. | The edit's before-text was not found exactly once; re-propose against the current text. |
|
|
77
|
-
| `polish_link_changed` | minor | false | The polish pass altered or removed
|
|
80
|
+
| `polish_link_changed` | minor | false | The polish pass altered or removed an href link target, or an internal target stopped resolving. | A link was altered or removed during polish; restore it. |
|
|
78
81
|
| `polish_name_changed` | minor | false | A ledger-backed proper noun present before polish is absent afterward. | A proper noun changed during polish; restore it. |
|
|
79
82
|
|
|
80
83
|
### Layout Gate (Rendering & Presentation)
|
|
@@ -101,6 +104,20 @@ Gates emit a manifest-level outcome with one of three statuses:
|
|
|
101
104
|
|
|
102
105
|
The `layout` gate only runs when a probe file (a rendering artifact: PDF, PNG, or HTML snapshot) is provided alongside the manifest. If no probe file is available, the manifest records `layout: not_run` and the gate does not emit defects.
|
|
103
106
|
|
|
107
|
+
## Polish Text-Node Contract
|
|
108
|
+
|
|
109
|
+
Polish edits change text nodes only; markup changes go through the full fidelity gates, not polish.
|
|
110
|
+
|
|
111
|
+
The primary rule is a whole-document skeleton comparison. Both gates scan the pre-polish and the polished document with the same tokenizer (`scripts/html-markup.mjs`) and require the skeleton to be byte-identical in document order: every tag token, comment, bogus comment, the frozen content of `script`, `style` and `template`, and every character reference of a text node (`&` up to the first `;` or non-name character). An accepted edit carries no `<`, `>` or `&` and may not touch a character reference, so an edited span holds no skeleton token and a legitimate polish never moves this sequence. Any difference names the first differing token and its byte offset: `polish-apply` refuses the edit, and `polish-diff` reports the blocker `polish_diff_fail` ("skeleton changed at token ..."). The character guards below are the early, actionable refusals; this comparison is what makes the guarantee complete, so a hand edit from `R&D` to `R<D` is blocked even though no edit was involved.
|
|
112
|
+
|
|
113
|
+
An edit's `before` and `after` may not contain `<`, `>` or `&` at all. `polish-apply` refuses such an edit before any other check, as `polish_edit_rejected` with the reason `edit contains markup characters (<, >, &): route this section through the full fidelity gates`, so the refusal is visible, never a silent drop. The rule holds whatever the tokenizer models: an edit without these characters cannot create, alter, close or escape a tag, comment, bogus comment, CDATA section, entity or script. The characters alone are not enough, because a located region can start right after an existing `&` or inside a character reference: `polish-apply` also refuses, as `edit touches a character reference: route this section through the full fidelity gates`, any edit whose located region overlaps a character reference or opens one. Literal `&` and `<` in prose are entities in the source (`R&D`, `p < 0.05`) and stay usable only by leaving their bytes untouched: the proofread lane narrows an edit to the words around an entity or tag (`budget grew` to `budget rose`), never one that spans it. Wording that cannot be fixed without touching an entity or markup goes through the full fidelity gates.
|
|
114
|
+
|
|
115
|
+
Before comparing, both gates refuse with `polish_diff_fail` ("ambiguous markup: ...") any document holding a construct the polish tokenizer does not model exactly: `</` followed by a non-letter (`</>` included), `<!--` inside a `script` or `style` element, `<![CDATA[`, `<?`, `--!>`, or `<` inside a tag or attribute value. Renderer output contains none of these. The tokenizer still follows the HTML spec for the bogus comment (`</` + non-letter, `<?`, `<!` up to the next `>`), the abrupt comment closes `<!-->` and `<!--->`, and the script-data escaped and double-escaped states.
|
|
116
|
+
|
|
117
|
+
The shared rule in `scripts/polish-rules.mjs` runs per edit against the current and original report in `polish-apply`, and against the whole document in `polish-diff`. The sequence of opening and closing tags, every attribute name and value (including `href`, `id`, `class`, and `style`), comments, declarations, and all content of `script`, `style`, and `template` elements must be byte-identical. Attribute reordering, different quoting, tag case, whitespace inside a tag, and a self-closing slash are markup changes too. Any difference raises the blocker `polish_diff_fail`; apply rejects it with reason `markup changed`.
|
|
118
|
+
|
|
119
|
+
Text nodes remain subject to the existing fact-span, number/unit, ticker, hedge, qualifier, name, and churn checks. Protected numbers, tickers, and required hedges cannot migrate between text nodes. A text edit inside an existing fact span is still a fact-span change, including under `aria-hidden`. Script/style/template content is frozen through the end of input when unclosed. No CSS or HTML visibility model is used by polish.
|
|
120
|
+
|
|
104
121
|
## Lineage and Citation Markers
|
|
105
122
|
|
|
106
123
|
Defect codes and their hints are delivered via the outcome manifest under `residualDefects` (if repair did not exhaust). Each defect entry includes:
|
|
@@ -44,6 +44,8 @@ uv run --with numpy --with polars --with matplotlib python plot_<name>.py
|
|
|
44
44
|
- Keep chart text as real text: set `matplotlib.rcParams["svg.fonttype"] = "none"` before saving an SVG. The default converts text to paths, and the static gate then sees a chart with no title, ticks, or labels.
|
|
45
45
|
- Use the design spec's font family and palette tokens (`rcParams["font.family"]`, explicit colors from the spec). One accent color for the series that carries the finding; neutral tones for the rest.
|
|
46
46
|
- Title, axis labels with units, value labels on bars or end points, ticks at round values computed from the data range.
|
|
47
|
+
- Plot only from the fact ledger: export the rows with `node "$SKILL_DIR/scripts/report-tools.mjs" fact export --csv --out "$SESSION_DIR/assets/<chart>.csv" --session-dir "$SESSION_DIR"`, draw from that CSV, and give the chart figure `data-chart-source="assets/<chart>.csv"`.
|
|
48
|
+
- Emit the axis tick labels inside one `<g class="ticks" data-tick-step=".." data-tick-min=".." data-tick-max="..">` group, so the fidelity gate can check every tick against the declared grid and every value label against the ledger.
|
|
47
49
|
- One message per chart. A chart that needs a paragraph to explain is two charts.
|
|
48
50
|
- For a web deliverable, HTML/CSS bars or an inline SVG built from the same table are preferred over an image.
|
|
49
51
|
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# Unit rules for derived facts
|
|
2
|
+
|
|
3
|
+
The one definition of which unit a derived or converted fact row carries, and the reviewer's checklist for
|
|
4
|
+
`scripts/unit-rules.mjs`. ONE registry (`OPERATION_RULES`) owns, per operation: arity, operand-unit guards, context
|
|
5
|
+
fields, result-unit inference, and malformed shapes. `derive()` (`fact-derive.mjs`) and `recompute()`
|
|
6
|
+
(`fact-recompute.mjs`) both call it; neither has op-specific unit code. `row.unit` is NEVER used to choose arithmetic;
|
|
7
|
+
numeric guards may read `exact` values, unit selection may not. The property tests in
|
|
8
|
+
`scripts/fact-unit-rules.test.ts` iterate the registry, so a new operation needs a table row here and cases there.
|
|
9
|
+
|
|
10
|
+
## Units
|
|
11
|
+
- Canonical unit text is stored and compared exactly (case-sensitive symbols). Registered aliases
|
|
12
|
+
(`pp` -> `%p`, `kilometre` -> `km`, ...) are canonicalized ONLY at input (derive/fact add); a relabel to an alias
|
|
13
|
+
at rest is a relabel and is detected. Unit text with no alias is stored as given: whitespace is trimmed only around
|
|
14
|
+
`*`, `/`, `^`, and parentheses, so `million people`, `metric ton`, and `USD per person` keep their inner spaces.
|
|
15
|
+
- A scale word (`million`, `M`, `bn`, `billion`, `thousand`, `e6`, ...; the `scale` rows of `units.mjs`) is a
|
|
16
|
+
multiplier, not a unit, and denomination scaling is out of scope, so it never becomes a unit atom: `million`,
|
|
17
|
+
`USD/M`, and `million*person` are refused at input (derive, and every operand read). Either store the scaled numeral
|
|
18
|
+
in the counted unit (5 million people as `5000000 person`) or keep the whole phrase as plain unit text:
|
|
19
|
+
`million people` has no table alias as a whole, so it is stored as given and sums only with `million people`, never
|
|
20
|
+
with `person`.
|
|
21
|
+
- `null` = dimensionless. Omitted unit on a derived spec is an error, not null (distinguish omission from declared null).
|
|
22
|
+
- Percent `%` stores percentage numerals (20% stores 20). Delta `%p` (percentage points). 1 basis point = 0.01 %p
|
|
23
|
+
(input alias `bp` -> `%p` with /100 scaling).
|
|
24
|
+
- Composite units are canonical algebraic strings built by the registry (`USD/kg`, `person`), never by concatenating
|
|
25
|
+
operand strings: equal factors cancel (`USD/(USD/person)` -> `person`).
|
|
26
|
+
- Affine scales (temperature: `C`, `F`) are flagged in the unit table; sum/diff/growth/share/ratio and every expr
|
|
27
|
+
node refuse them (temperatures are not summed or differenced as absolute values); only convert takes them.
|
|
28
|
+
Affine atoms in composites are refused before cancellation. Exponents are exact signed bigint integers, including zero.
|
|
29
|
+
|
|
30
|
+
## Operation table
|
|
31
|
+
|
|
32
|
+
| op | arity | operand guards | context fields (persisted on the row) | result unit | malformed -> recompute ok:false |
|
|
33
|
+
|---|---|---|---|---|---|
|
|
34
|
+
| sum | n>=2 | non-affine units, all equal after canonicalization; if `%`: `disjoint: true` AND `reference` (one denominator id/text) equal on every operand AND rationale | disjoint, reference, rationale | the common unit (`%` stays `%`) | <2 operands; missing id; no context; unequal/affine units; `%` without disjoint, reference, or rationale |
|
|
35
|
+
| diff | 2 | non-affine units; typed pairs only: U-U -> U (same non-% unit); %-% -> `%p`; %-%p -> `%`; %p-%p -> `%p`; %p-% REFUSED | none | per the pair rule | arity; invalid pair (`%p - %`, `m - person`); affine units; stray context |
|
|
36
|
+
| growth | 2 (from,to) | equal non-affine units; same entity; two different valid_at; from.exact > 0 | none | `%` (= 100(to-from)/from) | arity; unequal/affine units; entity/valid_at; from<=0; stray context |
|
|
37
|
+
| share | 2 (part,whole) | equal non-affine units; whole.exact > 0; 0 <= part <= whole | none | `%` (= 100 part/whole) | arity; units; whole<=0; part outside [0,whole]; stray context |
|
|
38
|
+
| ratio | 2 (num,den) | non-affine units; den.exact != 0; unequal `%`/`%p` REFUSED | none | registry composite: equal units -> null; else canonical `U(num)/U(den)` with cancellation | arity; den=0; stray context |
|
|
39
|
+
| convert | 1 | U(a) has a registered factor to `target`; currencies REFUSED (use expr with a dated rate row) | target (persisted as canonical text; the ONLY accepted declared unit; `row.unit` is compared to it exactly, never canonicalized at rest) | the persisted target | arity; no context; target missing; target not canonical (an alias at rest); currency target; no factor; declared unit != persisted target |
|
|
40
|
+
| expr | n>=1 | formula ids == operand set; node rules: `+`/`-` follow the sum/diff typed-pair rules, affine refusal included (so `20% - 10%` is `%p`, never `%`, and `25 C + 30 C` is refused); `+` also takes `%` + `%p` in either order -> `%` (as diff's `%-%p`), while `%` + `%` is refused (use sum); `*` and `/` compose canonical composites, `%`/`%p` operands normalize by /100 to dimensionless first; result must be fully inferred: NO rationale escape | none | the inferred unit; an explicit null->`%` conversion is a separate `convert`-like node (`pct()`) | formula/operand id mismatch; parse error; /0; uninferable unit; affine operands; stray context |
|
|
41
|
+
|
|
42
|
+
Every row's malformed column also holds the two operand-identity shapes below (`repeated operand`, `self reference`).
|
|
43
|
+
|
|
44
|
+
## Operand identity (applies to every row)
|
|
45
|
+
- Operand ids are DISTINCT within one derived row. sum of F1, F1 is not a sum of two shares, and diff, ratio, share,
|
|
46
|
+
growth, or convert of an operand with itself is meaningless and would dodge every pair guard. A repeated id is
|
|
47
|
+
refused by `derive()` (`operand F1 is repeated`) and is `ok:false` at `recompute()` (malformed shape
|
|
48
|
+
`repeated operand`). The check sits in the registry (`expectedUnit` and `malformed`), so no operation can skip it.
|
|
49
|
+
- expr may reference the same id in several leaves (`F1 * F1` is a legitimate square). Its stored operand list is the
|
|
50
|
+
formula's id SET, which must equal the formula ids (`formula id mismatch`); an id listed twice in that list is the
|
|
51
|
+
`repeated operand` shape too.
|
|
52
|
+
- A derived row may not cite itself, directly (`self reference`: its own id among its operands) or through nested
|
|
53
|
+
derived operands (a cycle). `recompute()` refuses both. `derive()` recomputes every derived operand first and
|
|
54
|
+
refuses one that does not recompute (tampered, cyclic, or self-citing), so nothing is built on such a row.
|
|
55
|
+
|
|
56
|
+
## Invariants (property tests iterate the table)
|
|
57
|
+
1. For each op: generate valid typed operands (rational boundaries, aliases at input, composites, %/%p, affine,
|
|
58
|
+
metadata, conversion targets, repeated expr refs), derive, assert `recompute(row).ok === true`.
|
|
59
|
+
2. Relabel: for each op, set `unit` to every different canonical string/null (incl. aliases and other valid targets),
|
|
60
|
+
regenerate render, keep exact: `recompute(row).ok === false` naming the expected unit; and `derive()` with that
|
|
61
|
+
declared unit is refused.
|
|
62
|
+
3. Malformed: per op, every shape in the table's malformed column has one case, named in the registry's
|
|
63
|
+
`OPERATION_RULES[op].malformed` list (the test asserts its case names equal that list): `recompute` returns
|
|
64
|
+
`ok:false` for that shape's own reason, never throws.
|
|
65
|
+
4. Nested derived rows are verified recursively (operands may be derived rows); cycles and self references are
|
|
66
|
+
refused, by `recompute()` and by `derive()` before it builds on a derived operand.
|
|
67
|
+
5. Render precision (`renderPrecision` in `unit-rules.mjs`): fewest decimal places for sum, diff, an expr whose root
|
|
68
|
+
node is `+` or `-`, and every `%p` result; fewest significant figures otherwise (an expr `*` or `/` root with a
|
|
69
|
+
non-`%p` result included). Operand values and expr literals both count.
|
|
70
|
+
|
|
71
|
+
## Decisions
|
|
72
|
+
- Q1: the canonical composite goes in `unit` (consumers compare `unit` text); no side field.
|
|
73
|
+
- Q2: expr with an unresolved unit is REFUSED; a known-unit `a*b/c` is inferable, so no rationale escape exists.
|
|
74
|
+
- Q3: canonical `%p`, input alias `pp` (matches units.mjs).
|
|
75
|
+
- Q4: strict null metadata: null matches only null; required metadata must be present.
|
|
76
|
+
|
|
77
|
+
## Out of scope (follow-ups)
|
|
78
|
+
Per-capita identity, named index/base and index-point differences, weighted means, dated FX / nominal-real bases,
|
|
79
|
+
same-currency denomination scaling (M/B), summing ratios over a common base. Each needs its own table row + tests.
|
|
80
|
+
|
|
81
|
+
## Dimensions
|
|
82
|
+
|
|
83
|
+
1. Enforced: kind and operation agree; a changed discriminator cannot disable recursive verification.
|
|
84
|
+
2. Enforced: expression and direct arithmetic check population, snapshot, and accounting metadata; common metadata propagates.
|
|
85
|
+
3. Enforced: stored `precision: { places, sigfigs }` carries operand precision independently of display trailing zeros.
|
|
86
|
+
4. Enforced: scaled aliases normalize at every composite factor and exponent, scaling the input numeral exactly.
|
|
87
|
+
5. Enforced: derive and recompute return classified refusals with `shape`, `expected`, and `detail`; derive never appends on refusal.
|
|
88
|
+
6. Enforced: aliases canonicalize at input; noncanonical stored results and conversion targets are refused, and a source unit that needs rescaling (a scaled alias) is refused as an operand.
|
|
89
|
+
7. Enforced: stored rationals are reduced integer strings with positive denominators and canonical zero.
|
|
90
|
+
8. Enforced: operation contexts have closed keys and types, including non-percent sums and context-free operations.
|
|
91
|
+
9. Enforced: formula, factor, derived_from, raw, render, metadata, precision, and compatibility are compared on replay.
|
|
92
|
+
10. Enforced: metadata object equality ignores key insertion order; missing and invalid metadata never equal explicit null.
|
|
93
|
+
11. Enforced: new derivations require active transitive dependencies; historical arithmetic remains reproducible after retraction.
|
|
94
|
+
12. Enforced: graph and expression traversal use explicit stacks; shared nodes are evaluated once per verification.
|
|
95
|
+
13. Enforced: factor exponents use bigint, preserve grouping and quotient direction, and canonicalize idempotently.
|
|
96
|
+
14. Enforced: affine atoms cannot hide in composites or cancellation; scale words are never unit atoms.
|
|
97
|
+
15. Enforced: disjoint percentage shares are individually in [0,100], total at most 100, and use exactly one reference.
|
|
98
|
+
16. Enforced: currency table conversion is refused; FX expressions require a dated rate and persist compatibility `unchecked`.
|
|
99
|
+
17. Enforced: symbols are case-sensitive; only registered spelling aliases normalize; opaque Unicode and phrases stay distinct.
|
|
100
|
+
18. Enforced: missing, undefined, and empty required fields do not substitute for explicit null; zero remains valid.
|
|
101
|
+
19. Enforced: identifiers allocate exactly beyond 2^53 using bigint; operand lists remain distinct.
|
|
102
|
+
20. Enforced: a supplied expression operand list must equal the formula's unique leaf set.
|
|
103
|
+
21. Enforced: additive expression roots and percentage-point results use decimal places; other roots count literal significant figures.
|
|
104
|
+
22. Enforced: cloned snapshots with the same spec and timestamp produce identical rows; replay appends a new identity.
|
|
105
|
+
23. Documented limitation: coherent rewrites of trusted operands, context, and all dependent audit fields require external provenance to detect.
|
|
106
|
+
24. Enforced: arity belongs to each table row; sums are permutation-invariant while ordered operations keep positional meaning.
|
|
107
|
+
25. Enforced: denominators, positive growth starts, and bounded shares obey numeric domains; ordinary negative arithmetic remains valid.
|
|
108
|
+
26. Enforced: percent, percentage-point, and fraction semantics follow typed pairs, multiplicative normalization, and explicit pct nodes.
|
|
109
|
+
27. Enforced: table factors and affine offsets compose exactly; unknown and cross-dimension conversions are refused.
|
|
110
|
+
28. Enforced: only decimal literals, fact identifiers, binary arithmetic, parentheses, and pct parse; unsupported syntax is refused.
|
|
111
|
+
29. Enforced: rounding is half away from zero; values are plain decimals and display uses canonical units and grouping.
|
|
112
|
+
30. Enforced: seeded factor trees and conversion compositions have expected values computed independently of OPERATION_RULES.
|
|
113
|
+
31. Enforced: a ledger written before `context`, `precision`, and `compatibility` existed still loads and accepts new derivations. On measured, quote, and assumed rows a missing column reads as null, and a registered alias unit is canonicalized on read (unknown or scaled text stays as given); the file changes only on the next write. Derived and converted rows without those columns load, but `recompute` reports shape `legacy derived row` and `derive` refuses them as operands with the same shape until they are regenerated.
|
|
114
|
+
32. Enforced: value/exact agreement. When a row stores both, `exact` must equal `value` at the value's decimal places; a legacy row that disagrees loads and is flagged `dimension 32`, and `addRow` refuses a new one.
|
|
115
|
+
|
|
116
|
+
## Precedence: dimension 31 wins at read time
|
|
117
|
+
|
|
118
|
+
Anything dev's shipped writer could write loads and validates. Every row is first read through `readRow`: exacts that
|
|
119
|
+
are integer pairs are reduced (sign on the numerator, no leading zeros, positive denominator), decimal values lose
|
|
120
|
+
leading zeros, and source rows get canonical units and null later columns. Measured exacts stay as stored because they
|
|
121
|
+
are always the reduced exact of `value`. Only then do the strict dimensions run. They apply in full to derived and
|
|
122
|
+
converted rows that carry this version's columns, and to every row this version writes. A source row that loads but
|
|
123
|
+
still breaks a strict rule (a scale word as unit, empty metadata, an operation or derivation fields) is flagged when it
|
|
124
|
+
is used: `derive` and `recompute` refuse it with shape `dimension 6`, `dimension 18`, or `dimension 1`, and `addRow`
|
|
125
|
+
refuses to write a new one. Normalization never strips user data: an `exact` carrying keys beyond `num` and `den` is left exactly as written and flagged `dimension 7` on use, never silently reduced to `{ num, den }`. Reading a normalized file again changes nothing. The legacy test fixture is generated by
|
|
126
|
+
dev's own `addRow` (`scripts/tests/fixtures/fidelity/gen-legacy-dev.mjs`, which pins the dev commit).
|
|
127
|
+
|
|
128
|
+
## Trust boundary
|
|
129
|
+
|
|
130
|
+
Recomputation verifies arithmetic against the ledger's trusted operands and operation context, not against an external
|
|
131
|
+
immutable source. A zero-metre conversion rewritten from km to cm together with its target, formula, factor, precision,
|
|
132
|
+
and display is indistinguishable from an honestly generated cm conversion. Single-field relabels are detected; a coherent
|
|
133
|
+
rewrite needs an immutable ledger baseline or source provenance outside this arithmetic verifier.
|
|
134
|
+
|
|
135
|
+
Rows carry no per-row schema marker, so a current derived row with `context`, `precision`, and `compatibility` all
|
|
136
|
+
deleted reads as a legacy row: `validateLedger` skips its at-rest unit and row-shape checks, and a relabeled unit on
|
|
137
|
+
it passes validation. It never becomes trusted again: `recompute` and `derive` refuse it as `legacy derived row`.
|
|
138
|
+
|
|
139
|
+
## Implementation notes
|
|
140
|
+
|
|
141
|
+
- Context fields live in the row's `context` column (null or an object on derived and converted rows, always null on
|
|
142
|
+
measured, quote, and assumed rows: a `KIND_RULES` column):
|
|
143
|
+
sum stores `{ disjoint, reference }`, convert stores `{ target }`. The sum rationale stays in `rationale`.
|
|
144
|
+
Schema and recomputation both require the operation's context. Context-free rows carry explicit null.
|
|
145
|
+
- `schemaVersion` remains 1. A derived or converted row from an older writer (no `context`, `precision`, or
|
|
146
|
+
`compatibility`) is kept as written and never used as an operand: regenerate it with `derive` to carry precision,
|
|
147
|
+
compatibility, and complete audit fields. Older source rows need nothing: they are read with null later columns.
|
|
148
|
+
- `reference` is either one text (the caller asserts one shared denominator) or a map from each operand id to its
|
|
149
|
+
denominator; the map passes only when every operand names the same non-empty denominator.
|
|
150
|
+
- `addRow` canonicalizes source units and scales alias numerals, including `100 bp/person` to `1 %p/person`.
|
|
151
|
+
At rest, source-row aliases are canonicalized on read (dimension 31); current derived rows with alias units are refused. A declared result unit with a nontrivial alias scale is refused.
|
|
152
|
+
Scale exponents with absolute value above 10000 are explicitly refused instead of allocating an unbounded numeral.
|
|
153
|
+
- A ratio over `%` or `%p` operands with unequal units is refused (use expr, where `%` enters `*` and `/` as a fraction).
|
|
154
|
+
- convert reads its target from `to`; a declared `unit` is optional and must equal that target.
|
|
155
|
+
- expr persists compatibility `unchecked` on the row as well as returning it. Population, snapshot, and accounting
|
|
156
|
+
predicates apply; a currency-rate operand needs a nonempty `valid_at`. This is arithmetic and unit verification,
|
|
157
|
+
not a guarantee of rate freshness, matching dates, population identity, or financial comparability.
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
// claim-kinds.mjs - the claim kinds a report sentence can carry, as one table, CLAIM_KINDS.
|
|
2
|
+
//
|
|
3
|
+
// A claim kind is structural: the writer marks it in the placeholder ({{V:F12:label}}), fill
|
|
4
|
+
// emits it as the data-claim-kind attribute on the element it renders, and the fidelity gate
|
|
5
|
+
// (fidelity-claims.mjs) checks that every marked element cites a row the kind accepts. An
|
|
6
|
+
// unmarked element is a plain fact or quote and is checked only by the value and quote checks.
|
|
7
|
+
// The one lexical piece is `unmarked`: a few first-person run patterns that raise a minor warning
|
|
8
|
+
// when a sentence reads like the kind but carries no mark; they never raise a major. The table in
|
|
9
|
+
// references/fidelity-claim-kinds.md is rendered from CLAIM_KINDS (`node claim-kinds.mjs --render-doc`).
|
|
10
|
+
// Pure; node: builtins only. Korean phrases are \u escapes.
|
|
11
|
+
|
|
12
|
+
import { isCliEntry } from "./entry-guard.mjs"
|
|
13
|
+
|
|
14
|
+
export const CLAIM_ATTRIBUTE = "data-claim-kind"
|
|
15
|
+
|
|
16
|
+
const kind = (entry) => Object.freeze({ ...entry, rowKinds: Object.freeze(entry.rowKinds), sourceKinds: entry.sourceKinds && Object.freeze(entry.sourceKinds), unmarked: entry.unmarked && Object.freeze({ ...entry.unmarked, patterns: Object.freeze(entry.unmarked.patterns) }) })
|
|
17
|
+
|
|
18
|
+
// Korean pieces as \u escapes: WE_SUBJECT = uri|jeohui as a standalone subject word (+ ga|neun, or " tim-i"),
|
|
19
|
+
// WE_RUN = silhaeng-haet/hayeo/hae | dol-lyeot/lyeo, DIRECT = jikjeop, RUN = silhaeng | dol-li/ryeo/ryeot/rin/ril,
|
|
20
|
+
// SUBJECT = a word ending in the particle ga|i|eun|neun|kkeseo (a third party ran it, not us).
|
|
21
|
+
const KO_WE_SUBJECT = "(?:^|[\\s,.;!?(])(?:\uC6B0\uB9AC|\uC800\uD76C)(?:[\uAC00\uB294]|\\s+\uD300\uC774)\\s+"
|
|
22
|
+
const KO_WE_RUN = "(?:\uC2E4\uD589(?:\uD588|\uD558\uC5EC|\uD574)|\uB3CC[\uB838\uB824])"
|
|
23
|
+
const KO_RUN = "(?:\uC2E4\uD589|\uB3CC[\uB9AC\uB824\uB838\uB9B0\uB9B4])"
|
|
24
|
+
const KO_SUBJECT = "[^\\s,.;!?]*(?:[\uAC00\uC774\uC740\uB294]|\uAED8\uC11C)(?=\\s)"
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* kind -> { prefix: placeholder prefix (null = the default form), placeholder: an example,
|
|
28
|
+
* rowKinds: ledger row kinds it accepts, sourceKinds: meta.json kinds the snapshot holding the
|
|
29
|
+
* cited row's own locator artifact must have (null = any), defect: code when a marked fact cites a
|
|
30
|
+
* row it refuses, note: the rule's limits, unmarked: { code, rule, patterns } for an unmarked
|
|
31
|
+
* sentence that reads like the kind (a minor warning only), or null }.
|
|
32
|
+
*/
|
|
33
|
+
export const CLAIM_KINDS = Object.freeze({
|
|
34
|
+
fact: kind({
|
|
35
|
+
prefix: null,
|
|
36
|
+
placeholder: "{{F12:label}}",
|
|
37
|
+
rowKinds: ["measured", "converted", "derived"],
|
|
38
|
+
sourceKinds: null,
|
|
39
|
+
defect: "claim_kind_mismatch",
|
|
40
|
+
note: "Accepts exactly what an unmarked numeral accepts, so marking fact is never stricter than not marking. Neither accepts an `assumed` row: an unmarked assumption reads as a fact, so it needs its `{{A:F12:label}}` mark (`claim_kind_mismatch` otherwise).",
|
|
41
|
+
unmarked: null,
|
|
42
|
+
}),
|
|
43
|
+
quote: kind({ prefix: null, placeholder: "{{Q7:label}}", rowKinds: ["quote"], sourceKinds: null, defect: "claim_kind_mismatch", note: "", unmarked: null }),
|
|
44
|
+
verified: kind({
|
|
45
|
+
prefix: "V",
|
|
46
|
+
placeholder: "{{V:F12:label}}",
|
|
47
|
+
rowKinds: ["measured", "quote"],
|
|
48
|
+
sourceKinds: ["command"],
|
|
49
|
+
defect: "unsupported_verification",
|
|
50
|
+
note: "The cited row itself must have a locator artifact inside `sources/<source_id>/` whose `meta.json` kind is `command`; another command row in the sentence never counts. Whether it is the right command for the claim is the meaning verifier's call (minor), not this gate's.",
|
|
51
|
+
unmarked: {
|
|
52
|
+
code: "verification_claim_unmarked",
|
|
53
|
+
rule: "a first-person run: we/I (have) ran, run, executed, verified by (not ran out of); by running after we/our in the sentence; \\uC6B0\\uB9AC or \\uC800\\uD76C as the subject word (+ \\uAC00, \\uB294, or \\uD300\\uC774), then \\uC2E4\\uD589\\uD588, \\uC2E4\\uD589\\uD558\\uC5EC, \\uC2E4\\uD589\\uD574, \\uB3CC\\uB838, or \\uB3CC\\uB824 in the clause with no third-party subject between; \\uC9C1\\uC811 with no third-party subject, then \\uC2E4\\uD589 or \\uB3CC\\uB9AC in the clause",
|
|
54
|
+
patterns: [
|
|
55
|
+
/\b(?:we|I)(?:'ve)?\s+(?:(?:have|had|also|then|first|just|again)\s+)*(?:ran|run|re-?ran|re-?run|executed|verified\s+by)\b(?!\s+out\s+of\b)/i,
|
|
56
|
+
/\b(?:we|our)\b.*?\bby\s+running\b/i,
|
|
57
|
+
new RegExp(`${KO_WE_SUBJECT}(?:(?!${KO_SUBJECT})[^\\s,.;!?]+\\s+)*?${KO_WE_RUN}`),
|
|
58
|
+
new RegExp(`(?:^|[,.;!?])\\s*(?:(?!${KO_SUBJECT})[^\\s,.;!?]+\\s+)*\uC9C1\uC811\\s*(?:[^\\s,.;!?]+\\s*)??${KO_RUN}`),
|
|
59
|
+
],
|
|
60
|
+
},
|
|
61
|
+
}),
|
|
62
|
+
derived: kind({ prefix: "D", placeholder: "{{D:F12:label}}", rowKinds: ["derived", "converted"], sourceKinds: null, defect: "claim_kind_mismatch", note: "", unmarked: null }),
|
|
63
|
+
assumed: kind({
|
|
64
|
+
prefix: "A",
|
|
65
|
+
placeholder: "{{A:F12:label}}",
|
|
66
|
+
rowKinds: ["assumed"],
|
|
67
|
+
sourceKinds: null,
|
|
68
|
+
defect: "claim_kind_mismatch",
|
|
69
|
+
note: "Numeric assumptions only (an assumed ledger row carries a decimal value); a qualitative assumption stays prose.",
|
|
70
|
+
unmarked: null,
|
|
71
|
+
}),
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
/** Whether a row satisfies a claim kind, given the meta.json kind of the snapshot holding its own locator artifact. */
|
|
75
|
+
export function claimSatisfied(name, row, sourceKind) {
|
|
76
|
+
const entry = CLAIM_KINDS[name]
|
|
77
|
+
return Boolean(entry && row && entry.rowKinds.includes(row.kind) && (entry.sourceKinds === null || entry.sourceKinds.includes(sourceKind)))
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** The kind an unmarked element citing `row` must be marked with (an assumed row needs `assumed`), or null when unmarked is fine. */
|
|
81
|
+
export function unmarkedRequires(row) {
|
|
82
|
+
if (!row || ["fact", "quote"].some((name) => CLAIM_KINDS[name].rowKinds.includes(row.kind))) return null
|
|
83
|
+
return Object.keys(CLAIM_KINDS).find((name) => CLAIM_KINDS[name].rowKinds.includes(row.kind)) ?? null
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** The kinds whose unmarked patterns match the sentence. */
|
|
87
|
+
export function unmarkedKinds(text) {
|
|
88
|
+
return Object.entries(CLAIM_KINDS)
|
|
89
|
+
.filter(([, entry]) => entry.unmarked?.patterns.some((re) => re.test(text)))
|
|
90
|
+
.map(([name]) => name)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export const CLAIM_TABLE_BEGIN = "<!-- claim-kinds:begin (rendered from CLAIM_KINDS; do not edit by hand) -->"
|
|
94
|
+
export const CLAIM_TABLE_END = "<!-- claim-kinds:end -->"
|
|
95
|
+
|
|
96
|
+
const code = (text) => `\`${text}\``
|
|
97
|
+
|
|
98
|
+
/** The Markdown table of references/fidelity-claim-kinds.md. */
|
|
99
|
+
export function renderClaimKindTable() {
|
|
100
|
+
const head = ["| Kind | Placeholder | Attribute fill emits | Accepted row kinds | Required source kind | Defect when unsatisfied | Unmarked warning | Notes |", "|---|---|---|---|---|---|---|---|"]
|
|
101
|
+
const lines = Object.entries(CLAIM_KINDS).map(([name, entry]) => {
|
|
102
|
+
const attribute = entry.prefix === null ? `none (or ${code(`${CLAIM_ATTRIBUTE}="${name}"`)})` : code(`${CLAIM_ATTRIBUTE}="${name}"`)
|
|
103
|
+
const source = entry.sourceKinds === null ? "any" : entry.sourceKinds.map(code).join(", ")
|
|
104
|
+
const warn = entry.unmarked ? `${code(entry.unmarked.code)} on ${entry.unmarked.rule}` : "none"
|
|
105
|
+
return `| ${code(name)} | ${code(entry.placeholder)} | ${attribute} | ${entry.rowKinds.map(code).join(", ")} | ${source} | ${code(entry.defect)} | ${warn} | ${entry.note || "-"} |`
|
|
106
|
+
})
|
|
107
|
+
return `${[...head, ...lines].join("\n")}\n`
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (isCliEntry(import.meta.url)) {
|
|
111
|
+
if (process.argv[2] === "--render-doc" && process.argv.length === 3) process.stdout.write(renderClaimKindTable())
|
|
112
|
+
else {
|
|
113
|
+
process.stderr.write("usage: node claim-kinds.mjs --render-doc\n")
|
|
114
|
+
process.exitCode = 2
|
|
115
|
+
}
|
|
116
|
+
}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test"
|
|
2
|
+
import { spawnSync } from "node:child_process"
|
|
3
|
+
import { readFileSync } from "node:fs"
|
|
4
|
+
import { join } from "node:path"
|
|
5
|
+
|
|
6
|
+
import { CLAIM_ATTRIBUTE, CLAIM_KINDS, CLAIM_TABLE_BEGIN, CLAIM_TABLE_END, renderClaimKindTable, unmarkedKinds } from "./claim-kinds.mjs"
|
|
7
|
+
import { DEFECT_CODES } from "./contracts.mjs"
|
|
8
|
+
import { valueChecks } from "./fidelity-values.mjs"
|
|
9
|
+
import { buildContext } from "./gates-static.mjs"
|
|
10
|
+
import { parseHtml } from "./html-lite.mjs"
|
|
11
|
+
|
|
12
|
+
// Protects the claim-kind contract the writer (fill) and the gate share: a fact marked with a
|
|
13
|
+
// claim kind must cite a row that kind accepts, a mark binds only the facts it owns, and the doc
|
|
14
|
+
// table writers read is rendered from CLAIM_KINDS. Expectations come from ORACLE below, written by
|
|
15
|
+
// hand from the design, never from the table under test.
|
|
16
|
+
|
|
17
|
+
type Defect = { code: string; severity: string }
|
|
18
|
+
type Row = Record<string, unknown> & { fact_id: string; kind: string; source_id: string | null; raw?: string; quote?: string }
|
|
19
|
+
|
|
20
|
+
const FIX = join(import.meta.dir, "tests", "fixtures", "fidelity")
|
|
21
|
+
const BASE: { rows: Row[] } = JSON.parse(readFileSync(join(FIX, "ledger-verify.json"), "utf8"))
|
|
22
|
+
const byId = (id: string) => BASE.rows.find((row) => row.fact_id === id) as Row
|
|
23
|
+
const [F1, F7, F8, Q1] = ["F1", "F7", "F8", "Q1"].map(byId) as [Row, Row, Row, Row]
|
|
24
|
+
const plain = { render: null, operation: null, operands: [], derived_from: [], formula: null, factor: null }
|
|
25
|
+
const F9: Row = { ...F8, ...plain, fact_id: "F9", kind: "assumed", label: "growth_rate", raw: "15%", value: "15", unit: "%", exact: { num: "15", den: "1" }, rationale: "planning assumption" }
|
|
26
|
+
const F10: Row = { ...F8, ...plain, fact_id: "F10", kind: "derived", label: "share_sum", raw: "39%", value: "39", unit: "%", exact: { num: "39", den: "1" }, operation: "sum", operands: ["F5", "F6"] }
|
|
27
|
+
const F11: Row = { ...F7, fact_id: "F11", locator: null }
|
|
28
|
+
const F12: Row = { ...F7, fact_id: "F12", locator: { artifact: "missing.txt", char_start: 0, char_end: 9 } }
|
|
29
|
+
const F13: Row = { ...F7, fact_id: "F13", locator: { artifact: "../S4/text.txt", char_start: 0, char_end: 9 } }
|
|
30
|
+
const LEDGER = { ...BASE, rows: [...BASE.rows, F9, F10, F11, F12, F13] }
|
|
31
|
+
const SUBJECTS = [F1, F7, F8, F9, F10, F11, F12, F13, Q1]
|
|
32
|
+
|
|
33
|
+
/** Hand-written oracle: the ledger row kinds each claim kind accepts, and whether the cited row must be command-backed. */
|
|
34
|
+
const ORACLE: Record<string, { rows: string[]; command: boolean }> = {
|
|
35
|
+
fact: { rows: ["measured", "converted", "derived"], command: false },
|
|
36
|
+
quote: { rows: ["quote"], command: false },
|
|
37
|
+
verified: { rows: ["measured", "quote"], command: true },
|
|
38
|
+
derived: { rows: ["derived", "converted"], command: false },
|
|
39
|
+
assumed: { rows: ["assumed"], command: false },
|
|
40
|
+
}
|
|
41
|
+
/** Rows an unmarked element refuses (an unmarked numeral reads as a fact, so an assumption needs its A mark). */
|
|
42
|
+
const UNMARKED_REFUSED = new Set(["F9"])
|
|
43
|
+
/** Rows whose own locator artifact sits in a snapshot whose meta.json kind is command (S5/text.txt). */
|
|
44
|
+
const COMMAND_BACKED = new Set(["F7"])
|
|
45
|
+
const MAJOR_FOR: Record<string, string> = { fact: "claim_kind_mismatch", quote: "claim_kind_mismatch", verified: "unsupported_verification", derived: "claim_kind_mismatch", assumed: "claim_kind_mismatch" }
|
|
46
|
+
const expected = (kind: string, row: Row) => {
|
|
47
|
+
const rule = ORACLE[kind] as { rows: string[]; command: boolean }
|
|
48
|
+
return rule.rows.includes(row.kind) && (!rule.command || COMMAND_BACKED.has(row.fact_id)) ? [] : [MAJOR_FOR[kind] as string]
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const CLAIM_CODES = new Set(["claim_kind_mismatch", "unsupported_verification", "verification_claim_unmarked"])
|
|
52
|
+
const run = (body: string) => valueChecks(buildContext(parseHtml(`<h2>Findings</h2>\n${body}`), { baseDir: FIX }), LEDGER, null, join(FIX, "sources"), {}) as Defect[]
|
|
53
|
+
const claims = (body: string) => run(body).filter((d) => CLAIM_CODES.has(d.code))
|
|
54
|
+
const codes = (body: string) => claims(body).map((d) => d.code).sort()
|
|
55
|
+
|
|
56
|
+
const value = (row: Row, kind?: string) => {
|
|
57
|
+
const mark = kind ? ` ${CLAIM_ATTRIBUTE}="${kind}"` : ""
|
|
58
|
+
return row.kind === "quote" ? `<q data-fact="${row.fact_id}"${mark} data-quote-sel="full">${row.quote}</q>` : `<data class="fact"${mark} data-fact="${row.fact_id}">${row.raw}</data>`
|
|
59
|
+
}
|
|
60
|
+
const prose = (inner: string, lead = "The figure is") => `<p>${lead} ${inner} [S4].</p>`
|
|
61
|
+
const cell = (inner: string) => `<table><tr><th>Item</th><td>${inner}</td></tr></table>`
|
|
62
|
+
|
|
63
|
+
describe("every claim kind against the hand-written oracle", () => {
|
|
64
|
+
for (const kind of Object.keys(ORACLE)) {
|
|
65
|
+
for (const row of SUBJECTS) {
|
|
66
|
+
const want = expected(kind, row)
|
|
67
|
+
const label = want.length === 0 ? "no claim defect" : `${want[0]} major`
|
|
68
|
+
test(`#given ${kind} marked on ${row.fact_id} (${row.kind}) in prose and in a table cell #then ${label}`, () => {
|
|
69
|
+
for (const body of [prose(value(row, kind)), cell(value(row, kind))]) {
|
|
70
|
+
expect(codes(body)).toEqual(want)
|
|
71
|
+
for (const defect of claims(body)) expect(defect.severity).toBe("major")
|
|
72
|
+
}
|
|
73
|
+
})
|
|
74
|
+
test(`#given ${kind} marked on an enclosing element over ${row.fact_id} #then the parent's mark binds it: ${label}`, () => {
|
|
75
|
+
expect(codes(`<p><span ${CLAIM_ATTRIBUTE}="${kind}">The figure is ${value(row)}</span> [S4].</p>`)).toEqual(want)
|
|
76
|
+
})
|
|
77
|
+
test(`#given ${kind} marked on ${row.fact_id} beside an unmarked command row #then the neighbour never satisfies it: ${label}`, () => {
|
|
78
|
+
expect(codes(prose(`${value(row, kind)}, and the suite reported ${value(F7)}`))).toEqual(want)
|
|
79
|
+
})
|
|
80
|
+
for (const inner of Object.keys(ORACLE).filter((other) => other !== kind)) {
|
|
81
|
+
test(`#given ${inner} nested inside ${kind} over ${row.fact_id} #then mismatch, the parent owns nothing, and the inner kind is still checked`, () => {
|
|
82
|
+
const body = `<p><span ${CLAIM_ATTRIBUTE}="${kind}">The figure is ${value(row, inner)}</span> [S4].</p>`
|
|
83
|
+
expect(codes(body)).toEqual(["claim_kind_mismatch", MAJOR_FOR[kind] as string, ...expected(inner, row)].sort())
|
|
84
|
+
})
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
for (const row of SUBJECTS) {
|
|
90
|
+
const refused = UNMARKED_REFUSED.has(row.fact_id) ? ["claim_kind_mismatch:major"] : []
|
|
91
|
+
const refusedLabel = refused.length ? "claim_kind_mismatch major" : "no claim code"
|
|
92
|
+
test(`#given ${row.fact_id} unmarked in a first-person run sentence #then the minor verification_claim_unmarked, plus ${refusedLabel}`, () => {
|
|
93
|
+
const found = claims(prose(value(row), "We ran the check and it reported"))
|
|
94
|
+
expect(found.map((d) => `${d.code}:${d.severity}`).sort()).toEqual([...refused, "verification_claim_unmarked:minor"].sort())
|
|
95
|
+
})
|
|
96
|
+
test(`#given ${row.fact_id} unmarked in a plain sentence in prose and in a table cell #then ${refusedLabel}`, () => {
|
|
97
|
+
for (const body of [prose(value(row)), cell(value(row))]) expect(claims(body).map((d) => `${d.code}:${d.severity}`)).toEqual(refused)
|
|
98
|
+
})
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
test("#given an unknown claim kind #then claim_kind_mismatch", () => {
|
|
102
|
+
expect(codes(prose(value(F1, "rumoured")))).toEqual(["claim_kind_mismatch"])
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
test("#given every defect and warning code the table names #then contracts defines it, and warnings are never major", () => {
|
|
106
|
+
for (const entry of Object.values(CLAIM_KINDS) as { defect: string; unmarked: { code: string } | null }[]) {
|
|
107
|
+
expect(DEFECT_CODES[entry.defect]).toBeDefined()
|
|
108
|
+
if (entry.unmarked) expect(DEFECT_CODES[entry.unmarked.code].severity).toBe("minor")
|
|
109
|
+
}
|
|
110
|
+
})
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
describe("design-check regressions", () => {
|
|
114
|
+
test("#given verified wrapping a fact-marked page row (F1 shadowing) #then the parent is unsupported and the nesting is a mismatch", () => {
|
|
115
|
+
expect(codes(`<p><span ${CLAIM_ATTRIBUTE}="verified">The inspection put the span at ${value(F1, "fact")}</span> [S4].</p>`)).toEqual(["claim_kind_mismatch", "unsupported_verification"])
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
test("#given one verified value and an unmarked value in the same run sentence (F2) #then the unmarked one still warns", () => {
|
|
119
|
+
expect(codes(prose(`${value(F7, "verified")}, and the span is ${value(F1)}`, "We ran the suite and it reported"))).toEqual(["verification_claim_unmarked"])
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
test("#given a truthful converted and derived value marked fact (F3) #then no stricter than unmarked", () => {
|
|
123
|
+
for (const row of [F8, F9, F10]) expect(codes(prose(value(row, "fact")))).toEqual(codes(prose(value(row))))
|
|
124
|
+
expect(codes(prose(value(F8, "fact")))).toEqual([])
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
test("#given the assumed row F9 unmarked or marked fact #then both refuse it, and the unmarked message names the A mark", () => {
|
|
128
|
+
expect(codes(prose(value(F9)))).toEqual(["claim_kind_mismatch"])
|
|
129
|
+
expect(codes(prose(value(F9, "fact")))).toEqual(["claim_kind_mismatch"])
|
|
130
|
+
expect(JSON.stringify(claims(prose(value(F9))))).toContain("{{A:F9}}")
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
test("#given the assumed row F9 marked {{A:F9}} (data-claim-kind=assumed) #then no claim defect", () => {
|
|
134
|
+
expect(codes(prose(value(F9, "assumed")))).toEqual([])
|
|
135
|
+
expect(codes(cell(value(F9, "assumed")))).toEqual([])
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
test("#given verified citing a command-sourced row with no, a missing, or an escaping locator artifact (F4) #then unsupported_verification", () => {
|
|
139
|
+
for (const row of [F11, F12, F13]) expect(codes(prose(value(row, "verified")))).toEqual(["unsupported_verification"])
|
|
140
|
+
})
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
describe("unmarked phrases", () => {
|
|
144
|
+
const fires = [
|
|
145
|
+
"We verified the span by running the measurement script.",
|
|
146
|
+
"We ran the suite twice.",
|
|
147
|
+
"We executed the export.",
|
|
148
|
+
"We verified by hand-counting the rows.",
|
|
149
|
+
"We have run the tests.",
|
|
150
|
+
"I ran the suite.",
|
|
151
|
+
"Our numbers were obtained by running the script.",
|
|
152
|
+
"우리가 실행해 확인한 길이",
|
|
153
|
+
"저희가 스크립트를 실행하여 검증한 값",
|
|
154
|
+
"저희가 테스트를 돌렸다",
|
|
155
|
+
"우리 팀이 벤치마크를 돌려 얻은 값",
|
|
156
|
+
"그래서 우리는 스크립트를 실행했다",
|
|
157
|
+
"직접 실행한 결과",
|
|
158
|
+
]
|
|
159
|
+
const quiet = [
|
|
160
|
+
"The tests showed a 12 % drop.",
|
|
161
|
+
"The bridge, which ran across the river, spans 320 m.",
|
|
162
|
+
"The span, as confirmed with the annual inspection report, is 320 m.",
|
|
163
|
+
"Clinical tests confirmed the diagnosis.",
|
|
164
|
+
"The authors obtained estimates by running simulations.",
|
|
165
|
+
"We ran out of time.",
|
|
166
|
+
"확인된 길이는 최대 320 m",
|
|
167
|
+
"개발자가 직접 실행한 결과를 보고했다",
|
|
168
|
+
"실행해 확인한 길이",
|
|
169
|
+
"우리나라에서 실행된 정책은 320개다",
|
|
170
|
+
"우리 정부가 실행한 예산",
|
|
171
|
+
"우리가 의뢰한 업체가 테스트를 돌렸다",
|
|
172
|
+
"우리는 결과를 확인했다",
|
|
173
|
+
]
|
|
174
|
+
for (const text of fires) test(`#given "${text}" #then verified`, () => expect(unmarkedKinds(text)).toEqual(["verified"]))
|
|
175
|
+
for (const text of quiet) test(`#given "${text}" #then nothing`, () => expect(unmarkedKinds(text)).toEqual([]))
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
describe("references/fidelity-claim-kinds.md is rendered from CLAIM_KINDS", () => {
|
|
179
|
+
const doc = readFileSync(join(import.meta.dir, "..", "references", "fidelity-claim-kinds.md"), "utf8")
|
|
180
|
+
|
|
181
|
+
test("the committed table section equals renderClaimKindTable() byte for byte", () => {
|
|
182
|
+
const [begin, end] = [doc.indexOf(CLAIM_TABLE_BEGIN), doc.indexOf(CLAIM_TABLE_END)]
|
|
183
|
+
expect(begin).toBeGreaterThanOrEqual(0)
|
|
184
|
+
expect(end).toBeGreaterThan(begin)
|
|
185
|
+
expect(doc.slice(begin + CLAIM_TABLE_BEGIN.length + 1, end)).toBe(renderClaimKindTable())
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
test("node claim-kinds.mjs --render-doc prints the same table", () => {
|
|
189
|
+
const result = spawnSync("node", [join(import.meta.dir, "claim-kinds.mjs"), "--render-doc"], { encoding: "utf8" })
|
|
190
|
+
expect(result.status).toBe(0)
|
|
191
|
+
expect(result.stdout).toBe(renderClaimKindTable())
|
|
192
|
+
})
|
|
193
|
+
})
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
// cli-support.mjs - argument parsing, typed usage errors, exit-code mapping, and JSON file IO
|
|
2
2
|
// shared by every report-tools subcommand. node: builtins only.
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import { createHash } from "node:crypto"
|
|
5
|
+
import { readFileSync, renameSync, rmSync, writeFileSync } from "node:fs"
|
|
5
6
|
import { basename, dirname, join } from "node:path"
|
|
6
7
|
|
|
7
8
|
/** A usage or input error: always exit code 2. */
|
|
@@ -87,11 +88,24 @@ export function readJsonFile(path) {
|
|
|
87
88
|
}
|
|
88
89
|
}
|
|
89
90
|
|
|
90
|
-
/**
|
|
91
|
-
export
|
|
91
|
+
/** The sha256 hex digest of a string or bytes: the one hash every record and receipt carries. */
|
|
92
|
+
export const sha256Of = (content) => createHash("sha256").update(content).digest("hex")
|
|
93
|
+
|
|
94
|
+
/** Write text through a sibling temp file + rename; a failed write removes the temp file. */
|
|
95
|
+
export function writeTextAtomic(path, text) {
|
|
92
96
|
const temp = join(dirname(path), `.${basename(path)}.${process.pid}.${Date.now()}.tmp`)
|
|
93
|
-
|
|
94
|
-
|
|
97
|
+
try {
|
|
98
|
+
writeFileSync(temp, text)
|
|
99
|
+
renameSync(temp, path)
|
|
100
|
+
} catch (error) {
|
|
101
|
+
rmSync(temp, { force: true })
|
|
102
|
+
throw error
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** Write JSON through writeTextAtomic, so a crash never leaves a half-written file. */
|
|
107
|
+
export function writeJsonAtomic(path, value) {
|
|
108
|
+
writeTextAtomic(path, `${JSON.stringify(value, null, 2)}\n`)
|
|
95
109
|
}
|
|
96
110
|
|
|
97
111
|
/** Print a result: JSON on stdout, one summary line on stderr. */
|