From 4b1065f37feaa5d7c365ba89808477c331c8798f Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 27 Oct 2026 01:29:15 +0000 Subject: [PATCH 01/21] k9: migrate all 113 K9 files to contract v1.0.0 Bring every tracked `*.k9.ncl` / `*.k9` file into line with the K9 contract v1.0.0 that standards' K9-DEED conformance lane enforces: - 98 panel-clade configs: `K9!` magic line, SPDX header, and a `pedigree` envelope (schema_version, component_type, security.leash 'Kennel, metadata.name/version/author, side_effects). The old top-level name/version/magic_number/author/leash keys are folded into it. Budgets.idr is unchanged (gen-budgets --check is current). - 4 layout presets: `K9!` envelope with an inline 'Yard pedigree; the preset is checked against pedigree-layout.ncl's contracts. pedigree-layout.ncl exports its contracts by explicit binding (a bare `{ Foo, }` field declares Foo without defining it) and PaneSizesSum100 uses std.contract.from_validator. database-design's `verisim_panel` is renamed to the schema's `verisimdb_panel`. - svc/k9 templates and the container deploy template are renamed to `.k9.ncl.in` (templates are not instances, per the contract); the setup-repo example is replaced by standards' canonical one, and the references in the READMEs, manifests and TOPOLOGY are updated. - generated/k9iser/*.k9 are untracked: .gitignore already ignores them. Checked with standards' k9-validate.sh --strict --layer L2 at 1fdc61c: 106 pass, 1 routed (coordination.k9), 0 fail. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01BCQeL169ZnUirUVaDXQLA3 --- .machine_readable/svc/k9/README.adoc | 6 +- .../svc/k9/examples/setup-repo.k9.ncl | 38 +++++++++ ...te-hunt.k9.ncl => template-hunt.k9.ncl.in} | 12 +++ ...ennel.k9.ncl => template-kennel.k9.ncl.in} | 12 +++ ...te-yard.k9.ncl => template-yard.k9.ncl.in} | 12 +++ layouts/database-design.k9.ncl | 27 ++++-- layouts/language-design.k9.ncl | 25 ++++-- layouts/logic-and-proofs.k9.ncl | 25 ++++-- layouts/pedigree-layout.ncl | 82 +++++-------------- layouts/protocol-design.k9.ncl | 25 ++++-- panel-clades/0-AI-MANIFEST.a2ml | 2 +- panel-clades/README.adoc | 2 +- panel-clades/TOPOLOGY.adoc | 2 +- panel-clades/clades/aerie/config.k9.ncl | 24 ++++-- .../clades/agentic-bridge/config.k9.ncl | 24 ++++-- panel-clades/clades/ai/config.k9.ncl | 24 ++++-- panel-clades/clades/ambientops/config.k9.ncl | 24 ++++-- .../clades/architect-mode/config.k9.ncl | 24 ++++-- .../clades/asset-manager/config.k9.ncl | 24 ++++-- .../clades/automation-bridge/config.k9.ncl | 24 ++++-- .../clades/automation-router/config.k9.ncl | 24 ++++-- .../clades/balance-analyser/config.k9.ncl | 24 ++++-- .../clades/beta-feedback-hub/config.k9.ncl | 24 ++++-- panel-clades/clades/boj/config.k9.ncl | 24 ++++-- .../clades/build-dashboard/config.k9.ncl | 24 ++++-- panel-clades/clades/capture/config.k9.ncl | 24 ++++-- .../clades/clade-browser/config.k9.ncl | 24 ++++-- panel-clades/clades/cloudguard/config.k9.ncl | 24 ++++-- panel-clades/clades/code-review/config.k9.ncl | 24 ++++-- .../clades/compatibility-matrix/config.k9.ncl | 24 ++++-- .../contractile-completeness/config.k9.ncl | 24 ++++-- .../clades/coprocessors/config.k9.ncl | 24 ++++-- .../clades/database-bridge/config.k9.ncl | 24 ++++-- panel-clades/clades/databases/config.k9.ncl | 24 ++++-- .../clades/debugging-workbench/config.k9.ncl | 24 ++++-- .../device-network-designer/config.k9.ncl | 24 ++++-- .../clades/dlc-workshop/config.k9.ncl | 24 ++++-- panel-clades/clades/echidna/config.k9.ncl | 24 ++++-- .../clades/editor-bridge/config.k9.ncl | 24 ++++-- panel-clades/clades/evangeliser/config.k9.ncl | 24 ++++-- .../exploratory-workbench/config.k9.ncl | 24 ++++-- panel-clades/clades/farm/config.k9.ncl | 24 ++++-- .../clades/feedback-routing/config.k9.ncl | 24 ++++-- panel-clades/clades/fleet/config.k9.ncl | 24 ++++-- panel-clades/clades/floor-raise/config.k9.ncl | 24 ++++-- .../clades/functional-tester/config.k9.ncl | 24 ++++-- .../clades/game-preview/config.k9.ncl | 24 ++++-- .../clades/generator-mode/config.k9.ncl | 24 ++++-- panel-clades/clades/gsa-actions/config.k9.ncl | 22 +++-- panel-clades/clades/gsa-browser/config.k9.ncl | 22 +++-- panel-clades/clades/gsa-config/config.k9.ncl | 22 +++-- panel-clades/clades/gsa-game/config.k9.ncl | 22 +++-- panel-clades/clades/gsa-health/config.k9.ncl | 22 +++-- panel-clades/clades/gsa-history/config.k9.ncl | 22 +++-- panel-clades/clades/gsa-logs/config.k9.ncl | 22 +++-- panel-clades/clades/gsa-search/config.k9.ncl | 22 +++-- panel-clades/clades/gsa/config.k9.ncl | 22 +++-- .../clades/guard-ai-tuner/config.k9.ncl | 24 ++++-- panel-clades/clades/help/config.k9.ncl | 24 ++++-- panel-clades/clades/hypatia/config.k9.ncl | 24 ++++-- panel-clades/clades/interfaces/config.k9.ncl | 24 ++++-- .../clades/language-forge/config.k9.ncl | 24 ++++-- .../clades/level-architect/config.k9.ncl | 24 ++++-- panel-clades/clades/load-tester/config.k9.ncl | 24 ++++-- .../clades/manifest-coverage/config.k9.ncl | 24 ++++-- panel-clades/clades/mass-panic/config.k9.ncl | 24 ++++-- .../clades/merge-coordinator/config.k9.ncl | 24 ++++-- panel-clades/clades/migration/config.k9.ncl | 24 ++++-- panel-clades/clades/minter/config.k9.ncl | 24 ++++-- .../clades/multiplayer-monitor/config.k9.ncl | 24 ++++-- panel-clades/clades/my-lang/config.k9.ncl | 24 ++++-- .../clades/network-topology/config.k9.ncl | 24 ++++-- .../clades/neurosym-bridge/config.k9.ncl | 24 ++++-- panel-clades/clades/observatory/config.k9.ncl | 24 ++++-- .../clades/panic-attack/config.k9.ncl | 24 ++++-- .../clades/performance-profiler/config.k9.ncl | 24 ++++-- panel-clades/clades/playgrounds/config.k9.ncl | 24 ++++-- .../clades/playtest-recorder/config.k9.ncl | 24 ++++-- panel-clades/clades/plaza/config.k9.ncl | 24 ++++-- .../clades/proofs-bridge/config.k9.ncl | 24 ++++-- .../clades/protocol-bridge/config.k9.ncl | 24 ++++-- .../clades/protocol-squisher/config.k9.ncl | 24 ++++-- .../clades/proven-adoption/config.k9.ncl | 24 ++++-- panel-clades/clades/provisioner/config.k9.ncl | 24 ++++-- .../clades/regression-guard/config.k9.ncl | 24 ++++-- .../clades/release-manager/config.k9.ncl | 24 ++++-- panel-clades/clades/repoloader/config.k9.ncl | 24 ++++-- panel-clades/clades/reposystem/config.k9.ncl | 24 ++++-- panel-clades/clades/script-gist/config.k9.ncl | 24 ++++-- .../clades/scripting-bridge/config.k9.ncl | 24 ++++-- panel-clades/clades/security/config.k9.ncl | 24 ++++-- .../clades/soak-monitor/config.k9.ncl | 24 ++++-- .../clades/spec-browser/config.k9.ncl | 24 ++++-- .../clades/strategy-drift/config.k9.ncl | 24 ++++-- panel-clades/clades/tangle-viz/config.k9.ncl | 24 ++++-- .../clades/team-dashboard/config.k9.ncl | 24 ++++-- panel-clades/clades/tentacles/config.k9.ncl | 24 ++++-- panel-clades/clades/tsdm/config.k9.ncl | 24 ++++-- panel-clades/clades/typell/config.k9.ncl | 24 ++++-- .../clades/typing-bridge/config.k9.ncl | 24 ++++-- panel-clades/clades/ums/config.k9.ncl | 24 ++++-- .../clades/unit-test-runner/config.k9.ncl | 24 ++++-- panel-clades/clades/vab/config.k9.ncl | 24 ++++-- .../clades/valence-shell/config.k9.ncl | 24 ++++-- .../verification-dashboard/config.k9.ncl | 24 ++++-- .../clades/verisim-feeds/config.k9.ncl | 24 ++++-- .../clades/vexometer-friction/config.k9.ncl | 24 ++++-- .../clades/vm-inspector/config.k9.ncl | 24 ++++-- panel-clades/clades/voicetag/config.k9.ncl | 24 ++++-- .../clades/wiring-inspector/config.k9.ncl | 24 ++++-- panel-clades/clades/workspace/config.k9.ncl | 24 ++++-- panel-clades/container/0-AI-MANIFEST.a2ml | 4 +- panel-clades/container/README.adoc | 4 +- .../{deploy.k9.ncl => deploy.k9.ncl.in} | 9 ++ 114 files changed, 1947 insertions(+), 674 deletions(-) rename .machine_readable/svc/k9/{template-hunt.k9.ncl => template-hunt.k9.ncl.in} (87%) rename .machine_readable/svc/k9/{template-kennel.k9.ncl => template-kennel.k9.ncl.in} (67%) rename .machine_readable/svc/k9/{template-yard.k9.ncl => template-yard.k9.ncl.in} (79%) rename panel-clades/container/{deploy.k9.ncl => deploy.k9.ncl.in} (91%) diff --git a/.machine_readable/svc/k9/README.adoc b/.machine_readable/svc/k9/README.adoc index 19e89113..bb7b59ac 100644 --- a/.machine_readable/svc/k9/README.adoc +++ b/.machine_readable/svc/k9/README.adoc @@ -136,9 +136,9 @@ K9 contractiles integrate with other RSR standards: Use these as starting points for your own K9 components: -- `template-kennel.k9.ncl` - Pure data template -- `template-yard.k9.ncl` - Validated config template -- `template-hunt.k9.ncl` - Full execution template +- `template-kennel.k9.ncl.in` - Pure data template +- `template-yard.k9.ncl.in` - Validated config template +- `template-hunt.k9.ncl.in` - Full execution template == Dependencies diff --git a/.machine_readable/svc/k9/examples/setup-repo.k9.ncl b/.machine_readable/svc/k9/examples/setup-repo.k9.ncl index 523e8176..081c34dd 100644 --- a/.machine_readable/svc/k9/examples/setup-repo.k9.ncl +++ b/.machine_readable/svc/k9/examples/setup-repo.k9.ncl @@ -15,6 +15,17 @@ K9! allow_filesystem_write = true, allow_subprocess = true, signature_required = true, + # §8.3/§8.4 — the grant. Each flag above is a REQUEST for a capability, + # and a request the grant does not cover is a contradiction inside the + # pedigree, not a preference (K9-S007). Without this list the component + # asked for the network, the filesystem and subprocesses while granting + # itself nothing, so "default-deny" denied nothing. Keep it in step with + # the recipes below. + capabilities = [ + "net.fetch", # add-license fetches the licence text over HTTPS + "fs.write", # create-structure and create-checkpoint-files write + "process.spawn", # git, just, nickel, curl and mkdir are child processes + ], }, metadata = { name = "setup-repo", @@ -28,6 +39,33 @@ K9! "Review Just recipes before execution", "Use dry-run mode first: ./must --dry-run run setup-repo.k9.ncl", ], + # §6.5/K9-S010 — mandatory at 'Hunt and may not be a placeholder: §9 + # requires a dry_run precondition, i.e. a plan that was produced AND + # reviewed, and a reviewer cannot review a plan for a component that + # requests network, filesystem and subprocess access while describing none + # of it. Each entry names the recipe that causes it. + side_effects = [ + "creates src/, docs/, tests/, scripts/, .github/workflows/ and .machine_readable/contractiles/k9/ under the current directory (create-structure)", + "writes STATE.a2ml, ECOSYSTEM.a2ml and META.a2ml in the repository root, overwriting any existing copies (create-checkpoint-files)", + "writes README.adoc in the repository root when add-readme is selected (add-readme)", + "downloads the licence text from https://raw.githubusercontent.com/hyperpolymath/pmpl/main/LICENSE to ./LICENSE when add-license is selected (add-license)", + "runs git init and sets repository-local user.name and user.email (init-git)", + "spawns git, just, nickel, curl, mkdir and the shell builtins as child processes (every recipe)", + "deletes STATE.a2ml, ECOSYSTEM.a2ml and META.a2ml from the current directory, after a 5-second pause, when clean is selected (clean)", + ], + # §10.1/K9-S009 — a 'Hunt component MUST carry a signature block. This one + # is what an example can honestly carry: a CLAIM that a signature exists, + # not a verified signature. With no external verifier the verdict is + # 'Present_Unverified (§10.2), and 'Present_Unverified is not the Hunt + # `signature` precondition (§10.4) — so this file is conforming, and it is + # still NOT authorised to run. Replace the block before use: + # ./must sign setup-repo.k9.ncl + signature = { + algorithm = "Ed25519", + key_id = "setup-repo-example", + payload_hash = "sha256:0000000000000000000000000000000000000000000000000000000000000000", + signature = "EXAMPLE-NOT-A-REAL-SIGNATURE", + }, }, # Configuration with contracts diff --git a/.machine_readable/svc/k9/template-hunt.k9.ncl b/.machine_readable/svc/k9/template-hunt.k9.ncl.in similarity index 87% rename from .machine_readable/svc/k9/template-hunt.k9.ncl rename to .machine_readable/svc/k9/template-hunt.k9.ncl.in index a9cc350e..d36b97fc 100644 --- a/.machine_readable/svc/k9/template-hunt.k9.ncl +++ b/.machine_readable/svc/k9/template-hunt.k9.ncl.in @@ -1,5 +1,17 @@ K9! # SPDX-License-Identifier: MPL-2.0 +# +# .k9.ncl.in — a TEMPLATE, not a component (standards#1058, MIGRATION-1058 M3). +# The `.in` suffix is deliberate: a template is never loaded, so it must not +# claim the reserved `.k9.ncl` component suffix. Nothing tries to leash it and +# no gate mistakes its TODOs for a component's declaration. The placeholders +# are the point. Instantiate them, and the copy becomes a real component: +# +# cp template-hunt.k9.ncl.in my-task.k9.ncl # then fill in every TODO +# +# The `K9!` line is kept so the INSTANTIATED file carries the envelope, which +# §11.2 makes the thing that makes a leash enforceable at all. +# # K9 Hunt-level template: Full execution with Just recipes # Security Level: Hunt (full system access) # ⚠️ SIGNATURE REQUIRED - Review carefully before use diff --git a/.machine_readable/svc/k9/template-kennel.k9.ncl b/.machine_readable/svc/k9/template-kennel.k9.ncl.in similarity index 67% rename from .machine_readable/svc/k9/template-kennel.k9.ncl rename to .machine_readable/svc/k9/template-kennel.k9.ncl.in index fa7e3f35..6881ea50 100644 --- a/.machine_readable/svc/k9/template-kennel.k9.ncl +++ b/.machine_readable/svc/k9/template-kennel.k9.ncl.in @@ -1,5 +1,17 @@ K9! # SPDX-License-Identifier: MPL-2.0 +# +# .k9.ncl.in — a TEMPLATE, not a component (standards#1058, MIGRATION-1058 M3). +# The `.in` suffix is deliberate: a template is never loaded, so it must not +# claim the reserved `.k9.ncl` component suffix. Nothing tries to leash it and +# no gate mistakes its TODOs for a component's declaration. The placeholders +# are the point. Instantiate them, and the copy becomes a real component: +# +# cp template-kennel.k9.ncl.in my-task.k9.ncl # then fill in every TODO +# +# The `K9!` line is kept so the INSTANTIATED file carries the envelope, which +# §11.2 makes the thing that makes a leash enforceable at all. +# # K9 Kennel-level template: Pure data configuration # Security Level: Kennel (data-only, no execution) # No signature required - safe for any use diff --git a/.machine_readable/svc/k9/template-yard.k9.ncl b/.machine_readable/svc/k9/template-yard.k9.ncl.in similarity index 79% rename from .machine_readable/svc/k9/template-yard.k9.ncl rename to .machine_readable/svc/k9/template-yard.k9.ncl.in index 358671cf..cf7324c9 100644 --- a/.machine_readable/svc/k9/template-yard.k9.ncl +++ b/.machine_readable/svc/k9/template-yard.k9.ncl.in @@ -1,5 +1,17 @@ K9! # SPDX-License-Identifier: MPL-2.0 +# +# .k9.ncl.in — a TEMPLATE, not a component (standards#1058, MIGRATION-1058 M3). +# The `.in` suffix is deliberate: a template is never loaded, so it must not +# claim the reserved `.k9.ncl` component suffix. Nothing tries to leash it and +# no gate mistakes its TODOs for a component's declaration. The placeholders +# are the point. Instantiate them, and the copy becomes a real component: +# +# cp template-yard.k9.ncl.in my-task.k9.ncl # then fill in every TODO +# +# The `K9!` line is kept so the INSTANTIATED file carries the envelope, which +# §11.2 makes the thing that makes a leash enforceable at all. +# # K9 Yard-level template: Configuration with validation # Security Level: Yard (Nickel evaluation with contracts) # Signature recommended but not required diff --git a/layouts/database-design.k9.ncl b/layouts/database-design.k9.ncl index 82095d58..be35693b 100644 --- a/layouts/database-design.k9.ncl +++ b/layouts/database-design.k9.ncl @@ -1,3 +1,4 @@ +K9! # SPDX-License-Identifier: MPL-2.0 # database-design.k9.ncl — PanLL discipline layout for database work. # @@ -12,10 +13,10 @@ # # Security level: Yard (pure configuration, no I/O) -let { LayoutPreset, LayoutPedigree, PaneSizesSum100, .. } = +let { LayoutPreset, PaneSizesSum100, .. } = import "./pedigree-layout.ncl" in -let layout | LayoutPreset | PaneSizesSum100 = { +let preset | LayoutPreset | PaneSizesSum100 = { name = "Database Design", description = "Optimised for database design, entity browsing, drift detection, and query development. VeriSimDB controls are front-and-centre.", version = "1.0.0", @@ -29,7 +30,7 @@ let layout | LayoutPreset | PaneSizesSum100 = { pane_w_size = 50, # VeriSimDB is the primary tool — always expanded - verisim_panel = 'Expanded, + verisimdb_panel = 'Expanded, # ECHIDNA for schema invariant proofs — available but secondary echidna_panel = 'Collapsed, # Security for database stress-testing — available but secondary @@ -52,12 +53,24 @@ let layout | LayoutPreset | PaneSizesSum100 = { } in { - pedigree = LayoutPedigree & { + pedigree = { + schema_version = "1.0.0", + component_type = "panll-layout-preset", + security = { + leash = 'Yard, + trust_level = "contract-eval-only", + allow_network = false, + allow_filesystem_write = false, + allow_subprocess = false, + capabilities = [], + }, metadata = { name = "panll-layout-database-design", - magic_number = "K9!", - description = layout.description, + version = preset.version, + description = preset.description, + breed = "application/vnd.k9+nickel", }, + side_effects = [], }, - layout, + layout = preset, } diff --git a/layouts/language-design.k9.ncl b/layouts/language-design.k9.ncl index fead921b..3af6dd10 100644 --- a/layouts/language-design.k9.ncl +++ b/layouts/language-design.k9.ncl @@ -1,3 +1,4 @@ +K9! # SPDX-License-Identifier: MPL-2.0 # language-design.k9.ncl — PanLL discipline layout for programming language work. # @@ -11,10 +12,10 @@ # # Security level: Yard (pure configuration, no I/O) -let { LayoutPreset, LayoutPedigree, PaneSizesSum100, .. } = +let { LayoutPreset, PaneSizesSum100, .. } = import "./pedigree-layout.ncl" in -let layout | LayoutPreset | PaneSizesSum100 = { +let preset | LayoutPreset | PaneSizesSum100 = { name = "Language Design", description = "Optimised for programming language design, type system verification, parser testing, and AST browsing. Metatheory proofs and grammar tools are front-and-centre.", version = "1.0.0", @@ -50,12 +51,24 @@ let layout | LayoutPreset | PaneSizesSum100 = { } in { - pedigree = LayoutPedigree & { + pedigree = { + schema_version = "1.0.0", + component_type = "panll-layout-preset", + security = { + leash = 'Yard, + trust_level = "contract-eval-only", + allow_network = false, + allow_filesystem_write = false, + allow_subprocess = false, + capabilities = [], + }, metadata = { name = "panll-layout-language-design", - magic_number = "K9!", - description = layout.description, + version = preset.version, + description = preset.description, + breed = "application/vnd.k9+nickel", }, + side_effects = [], }, - layout, + layout = preset, } diff --git a/layouts/logic-and-proofs.k9.ncl b/layouts/logic-and-proofs.k9.ncl index 0f72d026..04b68fbf 100644 --- a/layouts/logic-and-proofs.k9.ncl +++ b/layouts/logic-and-proofs.k9.ncl @@ -1,3 +1,4 @@ +K9! # SPDX-License-Identifier: MPL-2.0 # logic-and-proofs.k9.ncl — PanLL discipline layout for formal methods work. # @@ -10,10 +11,10 @@ # # Security level: Yard (pure configuration, no I/O) -let { LayoutPreset, LayoutPedigree, PaneSizesSum100, .. } = +let { LayoutPreset, PaneSizesSum100, .. } = import "./pedigree-layout.ncl" in -let layout | LayoutPreset | PaneSizesSum100 = { +let preset | LayoutPreset | PaneSizesSum100 = { name = "Logic & Proofs", description = "Optimised for theorem proving, formal verification, and mathematical logic. Proof pipeline and tactic suggestions are front-and-centre.", version = "1.0.0", @@ -50,12 +51,24 @@ let layout | LayoutPreset | PaneSizesSum100 = { } in { - pedigree = LayoutPedigree & { + pedigree = { + schema_version = "1.0.0", + component_type = "panll-layout-preset", + security = { + leash = 'Yard, + trust_level = "contract-eval-only", + allow_network = false, + allow_filesystem_write = false, + allow_subprocess = false, + capabilities = [], + }, metadata = { name = "panll-layout-logic-and-proofs", - magic_number = "K9!", - description = layout.description, + version = preset.version, + description = preset.description, + breed = "application/vnd.k9+nickel", }, + side_effects = [], }, - layout, + layout = preset, } diff --git a/layouts/pedigree-layout.ncl b/layouts/pedigree-layout.ncl index 5d995ee5..786880c1 100644 --- a/layouts/pedigree-layout.ncl +++ b/layouts/pedigree-layout.ncl @@ -14,21 +14,19 @@ # Contracts — validation predicates for layout fields # ───────────────────────────────────────────────────────────── -let ValidPercentage = std.contract.from_predicate (fun p => p >= 0 && p <= 100) in +let valid_percentage = std.contract.from_predicate (fun p => p >= 0 && p <= 100) in -let PaneSizesSum100 = fun label value => +let pane_sizes_sum_100 = std.contract.from_validator (fun value => let total = value.pane_l_size + value.pane_n_size + value.pane_w_size in if total == 100 then - value + 'Ok else - std.contract.blame_with_message - "Pane sizes must sum to 100 (got %{std.string.from_number total})" - label -in + 'Error { message = "Pane sizes must sum to 100 (got %{std.string.from_number total})" } +) in -let PanelVisibility = [| 'Expanded, 'Collapsed, 'Hidden |] in +let panel_visibility = [| 'Expanded, 'Collapsed, 'Hidden |] in -let KnownProver = [| +let known_prover = [| 'Coq, 'Lean4, 'Isabelle, @@ -43,7 +41,7 @@ let KnownProver = [| # Layout Preset Schema # ───────────────────────────────────────────────────────────── -let LayoutPreset = { +let layout_preset = { # Identity name | String, description | String, @@ -55,17 +53,17 @@ let LayoutPreset = { | optional, # Pane sizing (must sum to 100) - pane_l_size | ValidPercentage, - pane_n_size | ValidPercentage, - pane_w_size | ValidPercentage, + pane_l_size | valid_percentage, + pane_n_size | valid_percentage, + pane_w_size | valid_percentage, # Panel visibility - echidna_panel | PanelVisibility | default = 'Collapsed, - verisimdb_panel | PanelVisibility | default = 'Collapsed, - security_panel | PanelVisibility | default = 'Hidden, + echidna_panel | panel_visibility | default = 'Collapsed, + verisimdb_panel | panel_visibility | default = 'Collapsed, + security_panel | panel_visibility | default = 'Hidden, # Prover configuration - default_provers | Array KnownProver | default = ['Coq, 'Lean4, 'Z3], + default_provers | Array known_prover | default = ['Coq, 'Lean4, 'Z3], # Default notation mode default_notation | String | default = "panll-universal", @@ -74,48 +72,12 @@ let LayoutPreset = { promoted_actions | Array String | default = [], } in -# ───────────────────────────────────────────────────────────── -# K9 Pedigree for layout presets (Yard level — no I/O) -# ───────────────────────────────────────────────────────────── - -let LayoutPedigree = { - metadata = { - name = "panll-layout-preset", - version = "1.0.0", - breed = "application/vnd.k9+nickel", - description = "PanLL discipline layout preset — configures pane sizing, panel visibility, prover priority, and toolbar for a specific workflow.", - }, - target = { - os = 'Linux, - is_edge = false, - requires_podman = false, - min_memory_mb = 0, - }, - security = { - trust_level = 'Yard, - allow_network = false, - allow_filesystem_write = false, - allow_subprocess = false, - }, - validation = { - checksum = "pending", - pedigree_version = "1.0.0", - hunt_authorized = false, - }, - recipes = { - install = "echo 'Copy .k9.ncl to layouts/'", - validate = "nickel typecheck", - deploy = "echo 'No deployment — config only'", - migrate = "echo 'No migration needed'", - }, -} in - -# Export contracts for use by layout presets +# Export contracts for use by layout presets. Each field names its binding +# explicitly: `{ Foo, }` in a Nickel record declares Foo without defining it. { - LayoutPreset, - LayoutPedigree, - PaneSizesSum100, - PanelVisibility, - KnownProver, - ValidPercentage, + LayoutPreset = layout_preset, + PaneSizesSum100 = pane_sizes_sum_100, + PanelVisibility = panel_visibility, + KnownProver = known_prover, + ValidPercentage = valid_percentage, } diff --git a/layouts/protocol-design.k9.ncl b/layouts/protocol-design.k9.ncl index 500874f2..e4c2552e 100644 --- a/layouts/protocol-design.k9.ncl +++ b/layouts/protocol-design.k9.ncl @@ -1,3 +1,4 @@ +K9! # SPDX-License-Identifier: MPL-2.0 # protocol-design.k9.ncl — PanLL discipline layout for protocol work. # @@ -11,10 +12,10 @@ # # Security level: Yard (pure configuration, no I/O) -let { LayoutPreset, LayoutPedigree, PaneSizesSum100, .. } = +let { LayoutPreset, PaneSizesSum100, .. } = import "./pedigree-layout.ncl" in -let layout | LayoutPreset | PaneSizesSum100 = { +let preset | LayoutPreset | PaneSizesSum100 = { name = "Protocol Design", description = "Optimised for protocol specification, state machine modelling, liveness/safety verification, and message flow analysis. Model checking and protocol fuzzing are front-and-centre.", version = "1.0.0", @@ -51,12 +52,24 @@ let layout | LayoutPreset | PaneSizesSum100 = { } in { - pedigree = LayoutPedigree & { + pedigree = { + schema_version = "1.0.0", + component_type = "panll-layout-preset", + security = { + leash = 'Yard, + trust_level = "contract-eval-only", + allow_network = false, + allow_filesystem_write = false, + allow_subprocess = false, + capabilities = [], + }, metadata = { name = "panll-layout-protocol-design", - magic_number = "K9!", - description = layout.description, + version = preset.version, + description = preset.description, + breed = "application/vnd.k9+nickel", }, + side_effects = [], }, - layout, + layout = preset, } diff --git a/panel-clades/0-AI-MANIFEST.a2ml b/panel-clades/0-AI-MANIFEST.a2ml index 2e49ceb0..2af166db 100644 --- a/panel-clades/0-AI-MANIFEST.a2ml +++ b/panel-clades/0-AI-MANIFEST.a2ml @@ -99,7 +99,7 @@ This repo contains: │ ├── ct-build.sh # Build/sign/verify pipeline │ ├── entrypoint.sh # Container entrypoint │ ├── vordr.toml # Runtime monitoring -│ ├── deploy.k9.ncl # k9-svc Hunt-level deployment +│ ├── deploy.k9.ncl.in # k9-svc Hunt-level deployment (template) │ ├── 0-AI-MANIFEST.a2ml # AI manifest for container dir │ └── README.adoc # Container directory documentation └── .machine_readable/ # ALL machine-readable content diff --git a/panel-clades/README.adoc b/panel-clades/README.adoc index 348fe520..de8c59bc 100644 --- a/panel-clades/README.adoc +++ b/panel-clades/README.adoc @@ -123,7 +123,7 @@ just container-down # Stop services |`vordr.toml` |Runtime monitoring configuration -|`deploy.k9.ncl` +|`deploy.k9.ncl.in` |k9-svc Hunt-level deployment descriptor (Nickel) |`0-AI-MANIFEST.a2ml` diff --git a/panel-clades/TOPOLOGY.adoc b/panel-clades/TOPOLOGY.adoc index 3291d609..ca40eb79 100644 --- a/panel-clades/TOPOLOGY.adoc +++ b/panel-clades/TOPOLOGY.adoc @@ -31,7 +31,7 @@ │ │ │ OCI │ │ cerro-torre │ │ │ │ │ │ Build │ │ svalinn/vordr │ │ │ │ │ └──────────┘ └───────────────┘ │ │ - │ │ ct-build.sh deploy.k9.ncl │ │ + │ │ ct-build.sh deploy.k9.ncl.in │ │ │ └──────────────────────────────────┘ │ └────────│─────────────────│──────────────┘ │ │ diff --git a/panel-clades/clades/aerie/config.k9.ncl b/panel-clades/clades/aerie/config.k9.ncl index 9fc8e40c..82deb70e 100644 --- a/panel-clades/clades/aerie/config.k9.ncl +++ b/panel-clades/clades/aerie/config.k9.ncl @@ -1,13 +1,25 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 # K9 Kennel: Aerie Panel Configuration # Auto-generated from PanelRegistry — pure data, no execution -# SPDX-License-Identifier: MPL-2.0 { pedigree = { - name = "aerie-config", - version = "0.1.0", - magic_number = "K9!", - author = "Jonathan D.A. Jewell ", - leash = 'Kennel, + schema_version = "1.0.0", + component_type = "panel-clade-config", + security = { + leash = 'Kennel, + trust_level = "data-only", + allow_network = false, + allow_filesystem_write = false, + allow_subprocess = false, + capabilities = [], + }, + metadata = { + name = "aerie-config", + version = "0.1.0", + author = "Jonathan D.A. Jewell ", + }, + side_effects = [], }, panel = { id = "Aerie", diff --git a/panel-clades/clades/agentic-bridge/config.k9.ncl b/panel-clades/clades/agentic-bridge/config.k9.ncl index 6771fb94..5e055d12 100644 --- a/panel-clades/clades/agentic-bridge/config.k9.ncl +++ b/panel-clades/clades/agentic-bridge/config.k9.ncl @@ -1,13 +1,25 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 # K9 Kennel: Agentic Bridge Panel Configuration # Auto-generated from PanelRegistry — pure data, no execution -# SPDX-License-Identifier: MPL-2.0 { pedigree = { - name = "agentic-bridge-config", - version = "0.1.0", - magic_number = "K9!", - author = "Jonathan D.A. Jewell ", - leash = 'Kennel, + schema_version = "1.0.0", + component_type = "panel-clade-config", + security = { + leash = 'Kennel, + trust_level = "data-only", + allow_network = false, + allow_filesystem_write = false, + allow_subprocess = false, + capabilities = [], + }, + metadata = { + name = "agentic-bridge-config", + version = "0.1.0", + author = "Jonathan D.A. Jewell ", + }, + side_effects = [], }, panel = { id = "Agentic Bridge", diff --git a/panel-clades/clades/ai/config.k9.ncl b/panel-clades/clades/ai/config.k9.ncl index 9969800c..9edd4a2b 100644 --- a/panel-clades/clades/ai/config.k9.ncl +++ b/panel-clades/clades/ai/config.k9.ncl @@ -1,13 +1,25 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 # K9 Kennel: AI Panel Configuration # Auto-generated from PanelRegistry — pure data, no execution -# SPDX-License-Identifier: MPL-2.0 { pedigree = { - name = "ai-config", - version = "0.1.0", - magic_number = "K9!", - author = "Jonathan D.A. Jewell ", - leash = 'Kennel, + schema_version = "1.0.0", + component_type = "panel-clade-config", + security = { + leash = 'Kennel, + trust_level = "data-only", + allow_network = false, + allow_filesystem_write = false, + allow_subprocess = false, + capabilities = [], + }, + metadata = { + name = "ai-config", + version = "0.1.0", + author = "Jonathan D.A. Jewell ", + }, + side_effects = [], }, panel = { id = "AI", diff --git a/panel-clades/clades/ambientops/config.k9.ncl b/panel-clades/clades/ambientops/config.k9.ncl index 9add3efa..a3c89457 100644 --- a/panel-clades/clades/ambientops/config.k9.ncl +++ b/panel-clades/clades/ambientops/config.k9.ncl @@ -1,13 +1,25 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 # K9 Kennel: AmbientOps Panel Configuration # Auto-generated from PanelRegistry — pure data, no execution -# SPDX-License-Identifier: MPL-2.0 { pedigree = { - name = "ambientops-config", - version = "0.1.0", - magic_number = "K9!", - author = "Jonathan D.A. Jewell ", - leash = 'Kennel, + schema_version = "1.0.0", + component_type = "panel-clade-config", + security = { + leash = 'Kennel, + trust_level = "data-only", + allow_network = false, + allow_filesystem_write = false, + allow_subprocess = false, + capabilities = [], + }, + metadata = { + name = "ambientops-config", + version = "0.1.0", + author = "Jonathan D.A. Jewell ", + }, + side_effects = [], }, panel = { id = "AmbientOps", diff --git a/panel-clades/clades/architect-mode/config.k9.ncl b/panel-clades/clades/architect-mode/config.k9.ncl index b84a3d77..8d0341bc 100644 --- a/panel-clades/clades/architect-mode/config.k9.ncl +++ b/panel-clades/clades/architect-mode/config.k9.ncl @@ -1,13 +1,25 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 # K9 Kennel: Architect Mode Panel Configuration # Auto-generated from PanelRegistry — pure data, no execution -# SPDX-License-Identifier: MPL-2.0 { pedigree = { - name = "architect-mode-config", - version = "0.1.0", - magic_number = "K9!", - author = "Jonathan D.A. Jewell ", - leash = 'Kennel, + schema_version = "1.0.0", + component_type = "panel-clade-config", + security = { + leash = 'Kennel, + trust_level = "data-only", + allow_network = false, + allow_filesystem_write = false, + allow_subprocess = false, + capabilities = [], + }, + metadata = { + name = "architect-mode-config", + version = "0.1.0", + author = "Jonathan D.A. Jewell ", + }, + side_effects = [], }, panel = { id = "Architect Mode", diff --git a/panel-clades/clades/asset-manager/config.k9.ncl b/panel-clades/clades/asset-manager/config.k9.ncl index 140265b3..223f5949 100644 --- a/panel-clades/clades/asset-manager/config.k9.ncl +++ b/panel-clades/clades/asset-manager/config.k9.ncl @@ -1,13 +1,25 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 # K9 Kennel: Asset Manager Panel Configuration # Auto-generated from PanelRegistry — pure data, no execution -# SPDX-License-Identifier: MPL-2.0 { pedigree = { - name = "asset-manager-config", - version = "0.1.0", - magic_number = "K9!", - author = "Jonathan D.A. Jewell ", - leash = 'Kennel, + schema_version = "1.0.0", + component_type = "panel-clade-config", + security = { + leash = 'Kennel, + trust_level = "data-only", + allow_network = false, + allow_filesystem_write = false, + allow_subprocess = false, + capabilities = [], + }, + metadata = { + name = "asset-manager-config", + version = "0.1.0", + author = "Jonathan D.A. Jewell ", + }, + side_effects = [], }, panel = { id = "Asset Manager", diff --git a/panel-clades/clades/automation-bridge/config.k9.ncl b/panel-clades/clades/automation-bridge/config.k9.ncl index 047a5770..ec026400 100644 --- a/panel-clades/clades/automation-bridge/config.k9.ncl +++ b/panel-clades/clades/automation-bridge/config.k9.ncl @@ -1,13 +1,25 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 # K9 Kennel: Automation Bridge Panel Configuration # Auto-generated from PanelRegistry — pure data, no execution -# SPDX-License-Identifier: MPL-2.0 { pedigree = { - name = "automation-bridge-config", - version = "0.1.0", - magic_number = "K9!", - author = "Jonathan D.A. Jewell ", - leash = 'Kennel, + schema_version = "1.0.0", + component_type = "panel-clade-config", + security = { + leash = 'Kennel, + trust_level = "data-only", + allow_network = false, + allow_filesystem_write = false, + allow_subprocess = false, + capabilities = [], + }, + metadata = { + name = "automation-bridge-config", + version = "0.1.0", + author = "Jonathan D.A. Jewell ", + }, + side_effects = [], }, panel = { id = "Automation Bridge", diff --git a/panel-clades/clades/automation-router/config.k9.ncl b/panel-clades/clades/automation-router/config.k9.ncl index c9de7f85..cb0e8bdc 100644 --- a/panel-clades/clades/automation-router/config.k9.ncl +++ b/panel-clades/clades/automation-router/config.k9.ncl @@ -1,13 +1,25 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 # K9 Kennel: Automation Router Panel Configuration # Auto-generated from PanelRegistry — pure data, no execution -# SPDX-License-Identifier: MPL-2.0 { pedigree = { - name = "automation-router-config", - version = "0.1.0", - magic_number = "K9!", - author = "Jonathan D.A. Jewell ", - leash = 'Kennel, + schema_version = "1.0.0", + component_type = "panel-clade-config", + security = { + leash = 'Kennel, + trust_level = "data-only", + allow_network = false, + allow_filesystem_write = false, + allow_subprocess = false, + capabilities = [], + }, + metadata = { + name = "automation-router-config", + version = "0.1.0", + author = "Jonathan D.A. Jewell ", + }, + side_effects = [], }, panel = { id = "Automation Router", diff --git a/panel-clades/clades/balance-analyser/config.k9.ncl b/panel-clades/clades/balance-analyser/config.k9.ncl index b52e09d8..7974ab5b 100644 --- a/panel-clades/clades/balance-analyser/config.k9.ncl +++ b/panel-clades/clades/balance-analyser/config.k9.ncl @@ -1,13 +1,25 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 # K9 Kennel: Balance Analyser Panel Configuration # Auto-generated from PanelRegistry — pure data, no execution -# SPDX-License-Identifier: MPL-2.0 { pedigree = { - name = "balance-analyser-config", - version = "0.1.0", - magic_number = "K9!", - author = "Jonathan D.A. Jewell ", - leash = 'Kennel, + schema_version = "1.0.0", + component_type = "panel-clade-config", + security = { + leash = 'Kennel, + trust_level = "data-only", + allow_network = false, + allow_filesystem_write = false, + allow_subprocess = false, + capabilities = [], + }, + metadata = { + name = "balance-analyser-config", + version = "0.1.0", + author = "Jonathan D.A. Jewell ", + }, + side_effects = [], }, panel = { id = "Balance Analyser", diff --git a/panel-clades/clades/beta-feedback-hub/config.k9.ncl b/panel-clades/clades/beta-feedback-hub/config.k9.ncl index e1e1ec16..fac4453f 100644 --- a/panel-clades/clades/beta-feedback-hub/config.k9.ncl +++ b/panel-clades/clades/beta-feedback-hub/config.k9.ncl @@ -1,13 +1,25 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 # K9 Kennel: Beta Feedback Hub Panel Configuration # Auto-generated from PanelRegistry — pure data, no execution -# SPDX-License-Identifier: MPL-2.0 { pedigree = { - name = "beta-feedback-hub-config", - version = "0.1.0", - magic_number = "K9!", - author = "Jonathan D.A. Jewell ", - leash = 'Kennel, + schema_version = "1.0.0", + component_type = "panel-clade-config", + security = { + leash = 'Kennel, + trust_level = "data-only", + allow_network = false, + allow_filesystem_write = false, + allow_subprocess = false, + capabilities = [], + }, + metadata = { + name = "beta-feedback-hub-config", + version = "0.1.0", + author = "Jonathan D.A. Jewell ", + }, + side_effects = [], }, panel = { id = "Beta Feedback Hub", diff --git a/panel-clades/clades/boj/config.k9.ncl b/panel-clades/clades/boj/config.k9.ncl index 8afd4fb8..21aa9754 100644 --- a/panel-clades/clades/boj/config.k9.ncl +++ b/panel-clades/clades/boj/config.k9.ncl @@ -1,13 +1,25 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 # K9 Kennel: BoJ Panel Configuration # Auto-generated from PanelRegistry — pure data, no execution -# SPDX-License-Identifier: MPL-2.0 { pedigree = { - name = "boj-config", - version = "0.1.0", - magic_number = "K9!", - author = "Jonathan D.A. Jewell ", - leash = 'Kennel, + schema_version = "1.0.0", + component_type = "panel-clade-config", + security = { + leash = 'Kennel, + trust_level = "data-only", + allow_network = false, + allow_filesystem_write = false, + allow_subprocess = false, + capabilities = [], + }, + metadata = { + name = "boj-config", + version = "0.1.0", + author = "Jonathan D.A. Jewell ", + }, + side_effects = [], }, panel = { id = "BoJ", diff --git a/panel-clades/clades/build-dashboard/config.k9.ncl b/panel-clades/clades/build-dashboard/config.k9.ncl index 9d919ef2..be88cb66 100644 --- a/panel-clades/clades/build-dashboard/config.k9.ncl +++ b/panel-clades/clades/build-dashboard/config.k9.ncl @@ -1,13 +1,25 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 # K9 Kennel: Build Dashboard Panel Configuration # Auto-generated from PanelRegistry — pure data, no execution -# SPDX-License-Identifier: MPL-2.0 { pedigree = { - name = "build-dashboard-config", - version = "0.1.0", - magic_number = "K9!", - author = "Jonathan D.A. Jewell ", - leash = 'Kennel, + schema_version = "1.0.0", + component_type = "panel-clade-config", + security = { + leash = 'Kennel, + trust_level = "data-only", + allow_network = false, + allow_filesystem_write = false, + allow_subprocess = false, + capabilities = [], + }, + metadata = { + name = "build-dashboard-config", + version = "0.1.0", + author = "Jonathan D.A. Jewell ", + }, + side_effects = [], }, panel = { id = "Build Dashboard", diff --git a/panel-clades/clades/capture/config.k9.ncl b/panel-clades/clades/capture/config.k9.ncl index 7405f213..99d56a27 100644 --- a/panel-clades/clades/capture/config.k9.ncl +++ b/panel-clades/clades/capture/config.k9.ncl @@ -1,13 +1,25 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 # K9 Kennel: Capture Panel Configuration # Auto-generated from PanelRegistry — pure data, no execution -# SPDX-License-Identifier: MPL-2.0 { pedigree = { - name = "capture-config", - version = "0.1.0", - magic_number = "K9!", - author = "Jonathan D.A. Jewell ", - leash = 'Kennel, + schema_version = "1.0.0", + component_type = "panel-clade-config", + security = { + leash = 'Kennel, + trust_level = "data-only", + allow_network = false, + allow_filesystem_write = false, + allow_subprocess = false, + capabilities = [], + }, + metadata = { + name = "capture-config", + version = "0.1.0", + author = "Jonathan D.A. Jewell ", + }, + side_effects = [], }, panel = { id = "Capture", diff --git a/panel-clades/clades/clade-browser/config.k9.ncl b/panel-clades/clades/clade-browser/config.k9.ncl index 5185a408..f5b729fd 100644 --- a/panel-clades/clades/clade-browser/config.k9.ncl +++ b/panel-clades/clades/clade-browser/config.k9.ncl @@ -1,13 +1,25 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 # K9 Kennel: Clade Browser Panel Configuration # Auto-generated from PanelRegistry — pure data, no execution -# SPDX-License-Identifier: MPL-2.0 { pedigree = { - name = "clade-browser-config", - version = "0.1.0", - magic_number = "K9!", - author = "Jonathan D.A. Jewell ", - leash = 'Kennel, + schema_version = "1.0.0", + component_type = "panel-clade-config", + security = { + leash = 'Kennel, + trust_level = "data-only", + allow_network = false, + allow_filesystem_write = false, + allow_subprocess = false, + capabilities = [], + }, + metadata = { + name = "clade-browser-config", + version = "0.1.0", + author = "Jonathan D.A. Jewell ", + }, + side_effects = [], }, panel = { id = "Clade Browser", diff --git a/panel-clades/clades/cloudguard/config.k9.ncl b/panel-clades/clades/cloudguard/config.k9.ncl index 736b729a..5f9fa5c9 100644 --- a/panel-clades/clades/cloudguard/config.k9.ncl +++ b/panel-clades/clades/cloudguard/config.k9.ncl @@ -1,13 +1,25 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 # K9 Kennel: CloudGuard Panel Configuration # Auto-generated from PanelRegistry — pure data, no execution -# SPDX-License-Identifier: MPL-2.0 { pedigree = { - name = "cloudguard-config", - version = "0.1.0", - magic_number = "K9!", - author = "Jonathan D.A. Jewell ", - leash = 'Kennel, + schema_version = "1.0.0", + component_type = "panel-clade-config", + security = { + leash = 'Kennel, + trust_level = "data-only", + allow_network = false, + allow_filesystem_write = false, + allow_subprocess = false, + capabilities = [], + }, + metadata = { + name = "cloudguard-config", + version = "0.1.0", + author = "Jonathan D.A. Jewell ", + }, + side_effects = [], }, panel = { id = "CloudGuard", diff --git a/panel-clades/clades/code-review/config.k9.ncl b/panel-clades/clades/code-review/config.k9.ncl index 0f8467f8..ef152711 100644 --- a/panel-clades/clades/code-review/config.k9.ncl +++ b/panel-clades/clades/code-review/config.k9.ncl @@ -1,13 +1,25 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 # K9 Kennel: Code Review Panel Configuration # Auto-generated from PanelRegistry — pure data, no execution -# SPDX-License-Identifier: MPL-2.0 { pedigree = { - name = "code-review-config", - version = "0.1.0", - magic_number = "K9!", - author = "Jonathan D.A. Jewell ", - leash = 'Kennel, + schema_version = "1.0.0", + component_type = "panel-clade-config", + security = { + leash = 'Kennel, + trust_level = "data-only", + allow_network = false, + allow_filesystem_write = false, + allow_subprocess = false, + capabilities = [], + }, + metadata = { + name = "code-review-config", + version = "0.1.0", + author = "Jonathan D.A. Jewell ", + }, + side_effects = [], }, panel = { id = "Code Review", diff --git a/panel-clades/clades/compatibility-matrix/config.k9.ncl b/panel-clades/clades/compatibility-matrix/config.k9.ncl index 4f1d5cb7..4e0bbf12 100644 --- a/panel-clades/clades/compatibility-matrix/config.k9.ncl +++ b/panel-clades/clades/compatibility-matrix/config.k9.ncl @@ -1,13 +1,25 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 # K9 Kennel: Compatibility Matrix Panel Configuration # Auto-generated from PanelRegistry — pure data, no execution -# SPDX-License-Identifier: MPL-2.0 { pedigree = { - name = "compatibility-matrix-config", - version = "0.1.0", - magic_number = "K9!", - author = "Jonathan D.A. Jewell ", - leash = 'Kennel, + schema_version = "1.0.0", + component_type = "panel-clade-config", + security = { + leash = 'Kennel, + trust_level = "data-only", + allow_network = false, + allow_filesystem_write = false, + allow_subprocess = false, + capabilities = [], + }, + metadata = { + name = "compatibility-matrix-config", + version = "0.1.0", + author = "Jonathan D.A. Jewell ", + }, + side_effects = [], }, panel = { id = "Compatibility Matrix", diff --git a/panel-clades/clades/contractile-completeness/config.k9.ncl b/panel-clades/clades/contractile-completeness/config.k9.ncl index 64520e6d..52a5ca32 100644 --- a/panel-clades/clades/contractile-completeness/config.k9.ncl +++ b/panel-clades/clades/contractile-completeness/config.k9.ncl @@ -1,13 +1,25 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 # K9 Kennel: Contractile Completeness Panel Configuration # Auto-generated from PanelRegistry — pure data, no execution -# SPDX-License-Identifier: MPL-2.0 { pedigree = { - name = "contractile-completeness-config", - version = "0.1.0", - magic_number = "K9!", - author = "Jonathan D.A. Jewell ", - leash = 'Kennel, + schema_version = "1.0.0", + component_type = "panel-clade-config", + security = { + leash = 'Kennel, + trust_level = "data-only", + allow_network = false, + allow_filesystem_write = false, + allow_subprocess = false, + capabilities = [], + }, + metadata = { + name = "contractile-completeness-config", + version = "0.1.0", + author = "Jonathan D.A. Jewell ", + }, + side_effects = [], }, panel = { id = "ContractileCompleteness", diff --git a/panel-clades/clades/coprocessors/config.k9.ncl b/panel-clades/clades/coprocessors/config.k9.ncl index d0eb6d5a..2306fc33 100644 --- a/panel-clades/clades/coprocessors/config.k9.ncl +++ b/panel-clades/clades/coprocessors/config.k9.ncl @@ -1,13 +1,25 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 # K9 Kennel: Coprocessors Panel Configuration # Auto-generated from PanelRegistry — pure data, no execution -# SPDX-License-Identifier: MPL-2.0 { pedigree = { - name = "coprocessors-config", - version = "0.1.0", - magic_number = "K9!", - author = "Jonathan D.A. Jewell ", - leash = 'Kennel, + schema_version = "1.0.0", + component_type = "panel-clade-config", + security = { + leash = 'Kennel, + trust_level = "data-only", + allow_network = false, + allow_filesystem_write = false, + allow_subprocess = false, + capabilities = [], + }, + metadata = { + name = "coprocessors-config", + version = "0.1.0", + author = "Jonathan D.A. Jewell ", + }, + side_effects = [], }, panel = { id = "Coprocessors", diff --git a/panel-clades/clades/database-bridge/config.k9.ncl b/panel-clades/clades/database-bridge/config.k9.ncl index 5cebe692..43c3ebe2 100644 --- a/panel-clades/clades/database-bridge/config.k9.ncl +++ b/panel-clades/clades/database-bridge/config.k9.ncl @@ -1,13 +1,25 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 # K9 Kennel: Database Bridge Panel Configuration # Auto-generated from PanelRegistry — pure data, no execution -# SPDX-License-Identifier: MPL-2.0 { pedigree = { - name = "database-bridge-config", - version = "0.1.0", - magic_number = "K9!", - author = "Jonathan D.A. Jewell ", - leash = 'Kennel, + schema_version = "1.0.0", + component_type = "panel-clade-config", + security = { + leash = 'Kennel, + trust_level = "data-only", + allow_network = false, + allow_filesystem_write = false, + allow_subprocess = false, + capabilities = [], + }, + metadata = { + name = "database-bridge-config", + version = "0.1.0", + author = "Jonathan D.A. Jewell ", + }, + side_effects = [], }, panel = { id = "Database Bridge", diff --git a/panel-clades/clades/databases/config.k9.ncl b/panel-clades/clades/databases/config.k9.ncl index 20cf14e0..4bd1d599 100644 --- a/panel-clades/clades/databases/config.k9.ncl +++ b/panel-clades/clades/databases/config.k9.ncl @@ -1,13 +1,25 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 # K9 Kennel: Databases Panel Configuration # Auto-generated from PanelRegistry — pure data, no execution -# SPDX-License-Identifier: MPL-2.0 { pedigree = { - name = "databases-config", - version = "0.1.0", - magic_number = "K9!", - author = "Jonathan D.A. Jewell ", - leash = 'Kennel, + schema_version = "1.0.0", + component_type = "panel-clade-config", + security = { + leash = 'Kennel, + trust_level = "data-only", + allow_network = false, + allow_filesystem_write = false, + allow_subprocess = false, + capabilities = [], + }, + metadata = { + name = "databases-config", + version = "0.1.0", + author = "Jonathan D.A. Jewell ", + }, + side_effects = [], }, panel = { id = "Databases", diff --git a/panel-clades/clades/debugging-workbench/config.k9.ncl b/panel-clades/clades/debugging-workbench/config.k9.ncl index 16dc4e3d..16927cd2 100644 --- a/panel-clades/clades/debugging-workbench/config.k9.ncl +++ b/panel-clades/clades/debugging-workbench/config.k9.ncl @@ -1,13 +1,25 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 # K9 Kennel: Debugging Workbench Panel Configuration # Auto-generated from PanelRegistry — pure data, no execution -# SPDX-License-Identifier: MPL-2.0 { pedigree = { - name = "debugging-workbench-config", - version = "0.1.0", - magic_number = "K9!", - author = "Jonathan D.A. Jewell ", - leash = 'Kennel, + schema_version = "1.0.0", + component_type = "panel-clade-config", + security = { + leash = 'Kennel, + trust_level = "data-only", + allow_network = false, + allow_filesystem_write = false, + allow_subprocess = false, + capabilities = [], + }, + metadata = { + name = "debugging-workbench-config", + version = "0.1.0", + author = "Jonathan D.A. Jewell ", + }, + side_effects = [], }, panel = { id = "Debugging Workbench", diff --git a/panel-clades/clades/device-network-designer/config.k9.ncl b/panel-clades/clades/device-network-designer/config.k9.ncl index a2085627..8fe71653 100644 --- a/panel-clades/clades/device-network-designer/config.k9.ncl +++ b/panel-clades/clades/device-network-designer/config.k9.ncl @@ -1,13 +1,25 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 # K9 Kennel: Device Network Designer Panel Configuration # Auto-generated from PanelRegistry — pure data, no execution -# SPDX-License-Identifier: MPL-2.0 { pedigree = { - name = "device-network-designer-config", - version = "0.1.0", - magic_number = "K9!", - author = "Jonathan D.A. Jewell ", - leash = 'Kennel, + schema_version = "1.0.0", + component_type = "panel-clade-config", + security = { + leash = 'Kennel, + trust_level = "data-only", + allow_network = false, + allow_filesystem_write = false, + allow_subprocess = false, + capabilities = [], + }, + metadata = { + name = "device-network-designer-config", + version = "0.1.0", + author = "Jonathan D.A. Jewell ", + }, + side_effects = [], }, panel = { id = "Device Network Designer", diff --git a/panel-clades/clades/dlc-workshop/config.k9.ncl b/panel-clades/clades/dlc-workshop/config.k9.ncl index 4b05b8b4..70699e86 100644 --- a/panel-clades/clades/dlc-workshop/config.k9.ncl +++ b/panel-clades/clades/dlc-workshop/config.k9.ncl @@ -1,13 +1,25 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 # K9 Kennel: DLC Workshop Panel Configuration # Auto-generated from PanelRegistry — pure data, no execution -# SPDX-License-Identifier: MPL-2.0 { pedigree = { - name = "dlc-workshop-config", - version = "0.1.0", - magic_number = "K9!", - author = "Jonathan D.A. Jewell ", - leash = 'Kennel, + schema_version = "1.0.0", + component_type = "panel-clade-config", + security = { + leash = 'Kennel, + trust_level = "data-only", + allow_network = false, + allow_filesystem_write = false, + allow_subprocess = false, + capabilities = [], + }, + metadata = { + name = "dlc-workshop-config", + version = "0.1.0", + author = "Jonathan D.A. Jewell ", + }, + side_effects = [], }, panel = { id = "DLC Workshop", diff --git a/panel-clades/clades/echidna/config.k9.ncl b/panel-clades/clades/echidna/config.k9.ncl index ddb4f33b..a209c3c9 100644 --- a/panel-clades/clades/echidna/config.k9.ncl +++ b/panel-clades/clades/echidna/config.k9.ncl @@ -1,13 +1,25 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 # K9 Kennel: ECHIDNA Panel Configuration # Auto-generated from PanelRegistry — pure data, no execution -# SPDX-License-Identifier: MPL-2.0 { pedigree = { - name = "echidna-config", - version = "0.1.0", - magic_number = "K9!", - author = "Jonathan D.A. Jewell ", - leash = 'Kennel, + schema_version = "1.0.0", + component_type = "panel-clade-config", + security = { + leash = 'Kennel, + trust_level = "data-only", + allow_network = false, + allow_filesystem_write = false, + allow_subprocess = false, + capabilities = [], + }, + metadata = { + name = "echidna-config", + version = "0.1.0", + author = "Jonathan D.A. Jewell ", + }, + side_effects = [], }, panel = { id = "ECHIDNA", diff --git a/panel-clades/clades/editor-bridge/config.k9.ncl b/panel-clades/clades/editor-bridge/config.k9.ncl index 39d0385a..71999061 100644 --- a/panel-clades/clades/editor-bridge/config.k9.ncl +++ b/panel-clades/clades/editor-bridge/config.k9.ncl @@ -1,13 +1,25 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 # K9 Kennel: Editor Bridge Panel Configuration # Auto-generated from PanelRegistry — pure data, no execution -# SPDX-License-Identifier: MPL-2.0 { pedigree = { - name = "editor-bridge-config", - version = "0.1.0", - magic_number = "K9!", - author = "Jonathan D.A. Jewell ", - leash = 'Kennel, + schema_version = "1.0.0", + component_type = "panel-clade-config", + security = { + leash = 'Kennel, + trust_level = "data-only", + allow_network = false, + allow_filesystem_write = false, + allow_subprocess = false, + capabilities = [], + }, + metadata = { + name = "editor-bridge-config", + version = "0.1.0", + author = "Jonathan D.A. Jewell ", + }, + side_effects = [], }, panel = { id = "Editor Bridge", diff --git a/panel-clades/clades/evangeliser/config.k9.ncl b/panel-clades/clades/evangeliser/config.k9.ncl index ba8f6063..3c4cea76 100644 --- a/panel-clades/clades/evangeliser/config.k9.ncl +++ b/panel-clades/clades/evangeliser/config.k9.ncl @@ -1,13 +1,25 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 # K9 Kennel: Evangeliser Panel Configuration # Auto-generated from PanelRegistry — pure data, no execution -# SPDX-License-Identifier: MPL-2.0 { pedigree = { - name = "evangeliser-config", - version = "0.1.0", - magic_number = "K9!", - author = "Jonathan D.A. Jewell ", - leash = 'Kennel, + schema_version = "1.0.0", + component_type = "panel-clade-config", + security = { + leash = 'Kennel, + trust_level = "data-only", + allow_network = false, + allow_filesystem_write = false, + allow_subprocess = false, + capabilities = [], + }, + metadata = { + name = "evangeliser-config", + version = "0.1.0", + author = "Jonathan D.A. Jewell ", + }, + side_effects = [], }, panel = { id = "Evangeliser", diff --git a/panel-clades/clades/exploratory-workbench/config.k9.ncl b/panel-clades/clades/exploratory-workbench/config.k9.ncl index 838bdd68..0ea703b5 100644 --- a/panel-clades/clades/exploratory-workbench/config.k9.ncl +++ b/panel-clades/clades/exploratory-workbench/config.k9.ncl @@ -1,13 +1,25 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 # K9 Kennel: Exploratory Workbench Panel Configuration # Auto-generated from PanelRegistry — pure data, no execution -# SPDX-License-Identifier: MPL-2.0 { pedigree = { - name = "exploratory-workbench-config", - version = "0.1.0", - magic_number = "K9!", - author = "Jonathan D.A. Jewell ", - leash = 'Kennel, + schema_version = "1.0.0", + component_type = "panel-clade-config", + security = { + leash = 'Kennel, + trust_level = "data-only", + allow_network = false, + allow_filesystem_write = false, + allow_subprocess = false, + capabilities = [], + }, + metadata = { + name = "exploratory-workbench-config", + version = "0.1.0", + author = "Jonathan D.A. Jewell ", + }, + side_effects = [], }, panel = { id = "Exploratory Workbench", diff --git a/panel-clades/clades/farm/config.k9.ncl b/panel-clades/clades/farm/config.k9.ncl index 3fe7a23a..f2a5d29d 100644 --- a/panel-clades/clades/farm/config.k9.ncl +++ b/panel-clades/clades/farm/config.k9.ncl @@ -1,13 +1,25 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 # K9 Kennel: Farm Panel Configuration # Auto-generated from PanelRegistry — pure data, no execution -# SPDX-License-Identifier: MPL-2.0 { pedigree = { - name = "farm-config", - version = "0.1.0", - magic_number = "K9!", - author = "Jonathan D.A. Jewell ", - leash = 'Kennel, + schema_version = "1.0.0", + component_type = "panel-clade-config", + security = { + leash = 'Kennel, + trust_level = "data-only", + allow_network = false, + allow_filesystem_write = false, + allow_subprocess = false, + capabilities = [], + }, + metadata = { + name = "farm-config", + version = "0.1.0", + author = "Jonathan D.A. Jewell ", + }, + side_effects = [], }, panel = { id = "Farm", diff --git a/panel-clades/clades/feedback-routing/config.k9.ncl b/panel-clades/clades/feedback-routing/config.k9.ncl index e482612f..d535732a 100644 --- a/panel-clades/clades/feedback-routing/config.k9.ncl +++ b/panel-clades/clades/feedback-routing/config.k9.ncl @@ -1,13 +1,25 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 # K9 Kennel: Feedback Routing Panel Configuration # Auto-generated from PanelRegistry — pure data, no execution -# SPDX-License-Identifier: MPL-2.0 { pedigree = { - name = "feedback-routing-config", - version = "0.1.0", - magic_number = "K9!", - author = "Jonathan D.A. Jewell ", - leash = 'Kennel, + schema_version = "1.0.0", + component_type = "panel-clade-config", + security = { + leash = 'Kennel, + trust_level = "data-only", + allow_network = false, + allow_filesystem_write = false, + allow_subprocess = false, + capabilities = [], + }, + metadata = { + name = "feedback-routing-config", + version = "0.1.0", + author = "Jonathan D.A. Jewell ", + }, + side_effects = [], }, panel = { id = "FeedbackRouting", diff --git a/panel-clades/clades/fleet/config.k9.ncl b/panel-clades/clades/fleet/config.k9.ncl index 00f9e929..cadb4680 100644 --- a/panel-clades/clades/fleet/config.k9.ncl +++ b/panel-clades/clades/fleet/config.k9.ncl @@ -1,13 +1,25 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 # K9 Kennel: Fleet Panel Configuration # Auto-generated from PanelRegistry — pure data, no execution -# SPDX-License-Identifier: MPL-2.0 { pedigree = { - name = "fleet-config", - version = "0.1.0", - magic_number = "K9!", - author = "Jonathan D.A. Jewell ", - leash = 'Kennel, + schema_version = "1.0.0", + component_type = "panel-clade-config", + security = { + leash = 'Kennel, + trust_level = "data-only", + allow_network = false, + allow_filesystem_write = false, + allow_subprocess = false, + capabilities = [], + }, + metadata = { + name = "fleet-config", + version = "0.1.0", + author = "Jonathan D.A. Jewell ", + }, + side_effects = [], }, panel = { id = "Fleet", diff --git a/panel-clades/clades/floor-raise/config.k9.ncl b/panel-clades/clades/floor-raise/config.k9.ncl index c0a53fbb..243e3813 100644 --- a/panel-clades/clades/floor-raise/config.k9.ncl +++ b/panel-clades/clades/floor-raise/config.k9.ncl @@ -1,13 +1,25 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 # K9 Kennel: Floor Raise Panel Configuration # Auto-generated from PanelRegistry — pure data, no execution -# SPDX-License-Identifier: MPL-2.0 { pedigree = { - name = "floor-raise-config", - version = "0.1.0", - magic_number = "K9!", - author = "Jonathan D.A. Jewell ", - leash = 'Kennel, + schema_version = "1.0.0", + component_type = "panel-clade-config", + security = { + leash = 'Kennel, + trust_level = "data-only", + allow_network = false, + allow_filesystem_write = false, + allow_subprocess = false, + capabilities = [], + }, + metadata = { + name = "floor-raise-config", + version = "0.1.0", + author = "Jonathan D.A. Jewell ", + }, + side_effects = [], }, panel = { id = "FloorRaise", diff --git a/panel-clades/clades/functional-tester/config.k9.ncl b/panel-clades/clades/functional-tester/config.k9.ncl index d0d9c814..373b0546 100644 --- a/panel-clades/clades/functional-tester/config.k9.ncl +++ b/panel-clades/clades/functional-tester/config.k9.ncl @@ -1,13 +1,25 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 # K9 Kennel: Functional Tester Panel Configuration # Auto-generated from PanelRegistry — pure data, no execution -# SPDX-License-Identifier: MPL-2.0 { pedigree = { - name = "functional-tester-config", - version = "0.1.0", - magic_number = "K9!", - author = "Jonathan D.A. Jewell ", - leash = 'Kennel, + schema_version = "1.0.0", + component_type = "panel-clade-config", + security = { + leash = 'Kennel, + trust_level = "data-only", + allow_network = false, + allow_filesystem_write = false, + allow_subprocess = false, + capabilities = [], + }, + metadata = { + name = "functional-tester-config", + version = "0.1.0", + author = "Jonathan D.A. Jewell ", + }, + side_effects = [], }, panel = { id = "Functional Tester", diff --git a/panel-clades/clades/game-preview/config.k9.ncl b/panel-clades/clades/game-preview/config.k9.ncl index 2b034fa6..0fb67aea 100644 --- a/panel-clades/clades/game-preview/config.k9.ncl +++ b/panel-clades/clades/game-preview/config.k9.ncl @@ -1,13 +1,25 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 # K9 Kennel: Game Preview Panel Configuration # Auto-generated from PanelRegistry — pure data, no execution -# SPDX-License-Identifier: MPL-2.0 { pedigree = { - name = "game-preview-config", - version = "0.1.0", - magic_number = "K9!", - author = "Jonathan D.A. Jewell ", - leash = 'Kennel, + schema_version = "1.0.0", + component_type = "panel-clade-config", + security = { + leash = 'Kennel, + trust_level = "data-only", + allow_network = false, + allow_filesystem_write = false, + allow_subprocess = false, + capabilities = [], + }, + metadata = { + name = "game-preview-config", + version = "0.1.0", + author = "Jonathan D.A. Jewell ", + }, + side_effects = [], }, panel = { id = "Game Preview", diff --git a/panel-clades/clades/generator-mode/config.k9.ncl b/panel-clades/clades/generator-mode/config.k9.ncl index eacbea9d..bd0f7ef5 100644 --- a/panel-clades/clades/generator-mode/config.k9.ncl +++ b/panel-clades/clades/generator-mode/config.k9.ncl @@ -1,13 +1,25 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 # K9 Kennel: Generator Mode Panel Configuration # Auto-generated from PanelRegistry — pure data, no execution -# SPDX-License-Identifier: MPL-2.0 { pedigree = { - name = "generator-mode-config", - version = "0.1.0", - magic_number = "K9!", - author = "Jonathan D.A. Jewell ", - leash = 'Kennel, + schema_version = "1.0.0", + component_type = "panel-clade-config", + security = { + leash = 'Kennel, + trust_level = "data-only", + allow_network = false, + allow_filesystem_write = false, + allow_subprocess = false, + capabilities = [], + }, + metadata = { + name = "generator-mode-config", + version = "0.1.0", + author = "Jonathan D.A. Jewell ", + }, + side_effects = [], }, panel = { id = "Generator Mode", diff --git a/panel-clades/clades/gsa-actions/config.k9.ncl b/panel-clades/clades/gsa-actions/config.k9.ncl index fe774424..97aa2ddb 100644 --- a/panel-clades/clades/gsa-actions/config.k9.ncl +++ b/panel-clades/clades/gsa-actions/config.k9.ncl @@ -1,11 +1,23 @@ +K9! # SPDX-License-Identifier: MPL-2.0 { pedigree = { - name = "gsa-actions-config", - version = "1.0.0", - magic_number = "K9!", - author = "Jonathan D.A. Jewell ", - leash = 'Kennel, + schema_version = "1.0.0", + component_type = "panel-clade-config", + security = { + leash = 'Kennel, + trust_level = "data-only", + allow_network = false, + allow_filesystem_write = false, + allow_subprocess = false, + capabilities = [], + }, + metadata = { + name = "gsa-actions-config", + version = "1.0.0", + author = "Jonathan D.A. Jewell ", + }, + side_effects = [], }, panel = { id = "GsaServerActions", diff --git a/panel-clades/clades/gsa-browser/config.k9.ncl b/panel-clades/clades/gsa-browser/config.k9.ncl index d0303303..95ecbd72 100644 --- a/panel-clades/clades/gsa-browser/config.k9.ncl +++ b/panel-clades/clades/gsa-browser/config.k9.ncl @@ -1,11 +1,23 @@ +K9! # SPDX-License-Identifier: MPL-2.0 { pedigree = { - name = "gsa-browser-config", - version = "1.0.0", - magic_number = "K9!", - author = "Jonathan D.A. Jewell ", - leash = 'Kennel, + schema_version = "1.0.0", + component_type = "panel-clade-config", + security = { + leash = 'Kennel, + trust_level = "data-only", + allow_network = false, + allow_filesystem_write = false, + allow_subprocess = false, + capabilities = [], + }, + metadata = { + name = "gsa-browser-config", + version = "1.0.0", + author = "Jonathan D.A. Jewell ", + }, + side_effects = [], }, panel = { id = "GsaServerBrowser", diff --git a/panel-clades/clades/gsa-config/config.k9.ncl b/panel-clades/clades/gsa-config/config.k9.ncl index 3dea2914..f4f2864e 100644 --- a/panel-clades/clades/gsa-config/config.k9.ncl +++ b/panel-clades/clades/gsa-config/config.k9.ncl @@ -1,11 +1,23 @@ +K9! # SPDX-License-Identifier: MPL-2.0 { pedigree = { - name = "gsa-config-config", - version = "1.0.0", - magic_number = "K9!", - author = "Jonathan D.A. Jewell ", - leash = 'Kennel, + schema_version = "1.0.0", + component_type = "panel-clade-config", + security = { + leash = 'Kennel, + trust_level = "data-only", + allow_network = false, + allow_filesystem_write = false, + allow_subprocess = false, + capabilities = [], + }, + metadata = { + name = "gsa-config-config", + version = "1.0.0", + author = "Jonathan D.A. Jewell ", + }, + side_effects = [], }, panel = { id = "GsaConfigEditor", diff --git a/panel-clades/clades/gsa-game/config.k9.ncl b/panel-clades/clades/gsa-game/config.k9.ncl index 2fb87977..a38bc750 100644 --- a/panel-clades/clades/gsa-game/config.k9.ncl +++ b/panel-clades/clades/gsa-game/config.k9.ncl @@ -1,12 +1,24 @@ +K9! # SPDX-License-Identifier: MPL-2.0 # Abstract game profile clade — children inherit this configuration { pedigree = { - name = "gsa-game-config", - version = "1.0.0", - magic_number = "K9!", - author = "Jonathan D.A. Jewell ", - leash = 'Kennel, + schema_version = "1.0.0", + component_type = "panel-clade-config", + security = { + leash = 'Kennel, + trust_level = "data-only", + allow_network = false, + allow_filesystem_write = false, + allow_subprocess = false, + capabilities = [], + }, + metadata = { + name = "gsa-game-config", + version = "1.0.0", + author = "Jonathan D.A. Jewell ", + }, + side_effects = [], }, panel = { id = "GsaGame", diff --git a/panel-clades/clades/gsa-health/config.k9.ncl b/panel-clades/clades/gsa-health/config.k9.ncl index ce0b80a1..6f558fb5 100644 --- a/panel-clades/clades/gsa-health/config.k9.ncl +++ b/panel-clades/clades/gsa-health/config.k9.ncl @@ -1,11 +1,23 @@ +K9! # SPDX-License-Identifier: MPL-2.0 { pedigree = { - name = "gsa-health-config", - version = "1.0.0", - magic_number = "K9!", - author = "Jonathan D.A. Jewell ", - leash = 'Kennel, + schema_version = "1.0.0", + component_type = "panel-clade-config", + security = { + leash = 'Kennel, + trust_level = "data-only", + allow_network = false, + allow_filesystem_write = false, + allow_subprocess = false, + capabilities = [], + }, + metadata = { + name = "gsa-health-config", + version = "1.0.0", + author = "Jonathan D.A. Jewell ", + }, + side_effects = [], }, panel = { id = "GsaHealthDashboard", diff --git a/panel-clades/clades/gsa-history/config.k9.ncl b/panel-clades/clades/gsa-history/config.k9.ncl index 5d46f63d..f9b49daa 100644 --- a/panel-clades/clades/gsa-history/config.k9.ncl +++ b/panel-clades/clades/gsa-history/config.k9.ncl @@ -1,11 +1,23 @@ +K9! # SPDX-License-Identifier: MPL-2.0 { pedigree = { - name = "gsa-history-config", - version = "1.0.0", - magic_number = "K9!", - author = "Jonathan D.A. Jewell ", - leash = 'Kennel, + schema_version = "1.0.0", + component_type = "panel-clade-config", + security = { + leash = 'Kennel, + trust_level = "data-only", + allow_network = false, + allow_filesystem_write = false, + allow_subprocess = false, + capabilities = [], + }, + metadata = { + name = "gsa-history-config", + version = "1.0.0", + author = "Jonathan D.A. Jewell ", + }, + side_effects = [], }, panel = { id = "GsaConfigHistory", diff --git a/panel-clades/clades/gsa-logs/config.k9.ncl b/panel-clades/clades/gsa-logs/config.k9.ncl index 6242c750..5220e0db 100644 --- a/panel-clades/clades/gsa-logs/config.k9.ncl +++ b/panel-clades/clades/gsa-logs/config.k9.ncl @@ -1,11 +1,23 @@ +K9! # SPDX-License-Identifier: MPL-2.0 { pedigree = { - name = "gsa-logs-config", - version = "1.0.0", - magic_number = "K9!", - author = "Jonathan D.A. Jewell ", - leash = 'Kennel, + schema_version = "1.0.0", + component_type = "panel-clade-config", + security = { + leash = 'Kennel, + trust_level = "data-only", + allow_network = false, + allow_filesystem_write = false, + allow_subprocess = false, + capabilities = [], + }, + metadata = { + name = "gsa-logs-config", + version = "1.0.0", + author = "Jonathan D.A. Jewell ", + }, + side_effects = [], }, panel = { id = "GsaLiveLogs", diff --git a/panel-clades/clades/gsa-search/config.k9.ncl b/panel-clades/clades/gsa-search/config.k9.ncl index bdb49abb..0f3abc55 100644 --- a/panel-clades/clades/gsa-search/config.k9.ncl +++ b/panel-clades/clades/gsa-search/config.k9.ncl @@ -1,11 +1,23 @@ +K9! # SPDX-License-Identifier: MPL-2.0 { pedigree = { - name = "gsa-search-config", - version = "1.0.0", - magic_number = "K9!", - author = "Jonathan D.A. Jewell ", - leash = 'Kennel, + schema_version = "1.0.0", + component_type = "panel-clade-config", + security = { + leash = 'Kennel, + trust_level = "data-only", + allow_network = false, + allow_filesystem_write = false, + allow_subprocess = false, + capabilities = [], + }, + metadata = { + name = "gsa-search-config", + version = "1.0.0", + author = "Jonathan D.A. Jewell ", + }, + side_effects = [], }, panel = { id = "GsaCrossSearch", diff --git a/panel-clades/clades/gsa/config.k9.ncl b/panel-clades/clades/gsa/config.k9.ncl index 030cb3cb..b33b015e 100644 --- a/panel-clades/clades/gsa/config.k9.ncl +++ b/panel-clades/clades/gsa/config.k9.ncl @@ -1,12 +1,24 @@ +K9! # SPDX-License-Identifier: MPL-2.0 # K9 kennel configuration for GSA root panel clade { pedigree = { - name = "gsa-config", - version = "1.0.0", - magic_number = "K9!", - author = "Jonathan D.A. Jewell ", - leash = 'Kennel, + schema_version = "1.0.0", + component_type = "panel-clade-config", + security = { + leash = 'Kennel, + trust_level = "data-only", + allow_network = false, + allow_filesystem_write = false, + allow_subprocess = false, + capabilities = [], + }, + metadata = { + name = "gsa-config", + version = "1.0.0", + author = "Jonathan D.A. Jewell ", + }, + side_effects = [], }, panel = { id = "Gsa", diff --git a/panel-clades/clades/guard-ai-tuner/config.k9.ncl b/panel-clades/clades/guard-ai-tuner/config.k9.ncl index d39478d3..23b87b92 100644 --- a/panel-clades/clades/guard-ai-tuner/config.k9.ncl +++ b/panel-clades/clades/guard-ai-tuner/config.k9.ncl @@ -1,13 +1,25 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 # K9 Kennel: Guard AI Tuner Panel Configuration # Auto-generated from PanelRegistry — pure data, no execution -# SPDX-License-Identifier: MPL-2.0 { pedigree = { - name = "guard-ai-tuner-config", - version = "0.1.0", - magic_number = "K9!", - author = "Jonathan D.A. Jewell ", - leash = 'Kennel, + schema_version = "1.0.0", + component_type = "panel-clade-config", + security = { + leash = 'Kennel, + trust_level = "data-only", + allow_network = false, + allow_filesystem_write = false, + allow_subprocess = false, + capabilities = [], + }, + metadata = { + name = "guard-ai-tuner-config", + version = "0.1.0", + author = "Jonathan D.A. Jewell ", + }, + side_effects = [], }, panel = { id = "Guard AI Tuner", diff --git a/panel-clades/clades/help/config.k9.ncl b/panel-clades/clades/help/config.k9.ncl index 88fc5b3e..5d19cb1e 100644 --- a/panel-clades/clades/help/config.k9.ncl +++ b/panel-clades/clades/help/config.k9.ncl @@ -1,13 +1,25 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 # K9 Kennel: Help Panel Configuration # Auto-generated from PanelRegistry — pure data, no execution -# SPDX-License-Identifier: MPL-2.0 { pedigree = { - name = "help-config", - version = "0.1.0", - magic_number = "K9!", - author = "Jonathan D.A. Jewell ", - leash = 'Kennel, + schema_version = "1.0.0", + component_type = "panel-clade-config", + security = { + leash = 'Kennel, + trust_level = "data-only", + allow_network = false, + allow_filesystem_write = false, + allow_subprocess = false, + capabilities = [], + }, + metadata = { + name = "help-config", + version = "0.1.0", + author = "Jonathan D.A. Jewell ", + }, + side_effects = [], }, panel = { id = "Help", diff --git a/panel-clades/clades/hypatia/config.k9.ncl b/panel-clades/clades/hypatia/config.k9.ncl index 88a5b5b4..a6f0d075 100644 --- a/panel-clades/clades/hypatia/config.k9.ncl +++ b/panel-clades/clades/hypatia/config.k9.ncl @@ -1,13 +1,25 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 # K9 Kennel: Hypatia Panel Configuration # Auto-generated from PanelRegistry — pure data, no execution -# SPDX-License-Identifier: MPL-2.0 { pedigree = { - name = "hypatia-config", - version = "0.1.0", - magic_number = "K9!", - author = "Jonathan D.A. Jewell ", - leash = 'Kennel, + schema_version = "1.0.0", + component_type = "panel-clade-config", + security = { + leash = 'Kennel, + trust_level = "data-only", + allow_network = false, + allow_filesystem_write = false, + allow_subprocess = false, + capabilities = [], + }, + metadata = { + name = "hypatia-config", + version = "0.1.0", + author = "Jonathan D.A. Jewell ", + }, + side_effects = [], }, panel = { id = "Hypatia", diff --git a/panel-clades/clades/interfaces/config.k9.ncl b/panel-clades/clades/interfaces/config.k9.ncl index 6a245853..3940ecd8 100644 --- a/panel-clades/clades/interfaces/config.k9.ncl +++ b/panel-clades/clades/interfaces/config.k9.ncl @@ -1,13 +1,25 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 # K9 Kennel: Interfaces Panel Configuration # Auto-generated from PanelRegistry — pure data, no execution -# SPDX-License-Identifier: MPL-2.0 { pedigree = { - name = "interfaces-config", - version = "0.1.0", - magic_number = "K9!", - author = "Jonathan D.A. Jewell ", - leash = 'Kennel, + schema_version = "1.0.0", + component_type = "panel-clade-config", + security = { + leash = 'Kennel, + trust_level = "data-only", + allow_network = false, + allow_filesystem_write = false, + allow_subprocess = false, + capabilities = [], + }, + metadata = { + name = "interfaces-config", + version = "0.1.0", + author = "Jonathan D.A. Jewell ", + }, + side_effects = [], }, panel = { id = "Interfaces", diff --git a/panel-clades/clades/language-forge/config.k9.ncl b/panel-clades/clades/language-forge/config.k9.ncl index 4f029494..f86f82af 100644 --- a/panel-clades/clades/language-forge/config.k9.ncl +++ b/panel-clades/clades/language-forge/config.k9.ncl @@ -1,13 +1,25 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 # K9 Kennel: Language Forge Panel Configuration # Auto-generated from PanelRegistry — pure data, no execution -# SPDX-License-Identifier: MPL-2.0 { pedigree = { - name = "language-forge-config", - version = "0.1.0", - magic_number = "K9!", - author = "Jonathan D.A. Jewell ", - leash = 'Kennel, + schema_version = "1.0.0", + component_type = "panel-clade-config", + security = { + leash = 'Kennel, + trust_level = "data-only", + allow_network = false, + allow_filesystem_write = false, + allow_subprocess = false, + capabilities = [], + }, + metadata = { + name = "language-forge-config", + version = "0.1.0", + author = "Jonathan D.A. Jewell ", + }, + side_effects = [], }, panel = { id = "Language Forge", diff --git a/panel-clades/clades/level-architect/config.k9.ncl b/panel-clades/clades/level-architect/config.k9.ncl index 01f693e0..5582c6eb 100644 --- a/panel-clades/clades/level-architect/config.k9.ncl +++ b/panel-clades/clades/level-architect/config.k9.ncl @@ -1,13 +1,25 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 # K9 Kennel: Level Architect Panel Configuration # Auto-generated from PanelRegistry — pure data, no execution -# SPDX-License-Identifier: MPL-2.0 { pedigree = { - name = "level-architect-config", - version = "0.1.0", - magic_number = "K9!", - author = "Jonathan D.A. Jewell ", - leash = 'Kennel, + schema_version = "1.0.0", + component_type = "panel-clade-config", + security = { + leash = 'Kennel, + trust_level = "data-only", + allow_network = false, + allow_filesystem_write = false, + allow_subprocess = false, + capabilities = [], + }, + metadata = { + name = "level-architect-config", + version = "0.1.0", + author = "Jonathan D.A. Jewell ", + }, + side_effects = [], }, panel = { id = "Level Architect", diff --git a/panel-clades/clades/load-tester/config.k9.ncl b/panel-clades/clades/load-tester/config.k9.ncl index c1d86b76..76a6f3a4 100644 --- a/panel-clades/clades/load-tester/config.k9.ncl +++ b/panel-clades/clades/load-tester/config.k9.ncl @@ -1,13 +1,25 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 # K9 Kennel: Load Tester Panel Configuration # Auto-generated from PanelRegistry — pure data, no execution -# SPDX-License-Identifier: MPL-2.0 { pedigree = { - name = "load-tester-config", - version = "0.1.0", - magic_number = "K9!", - author = "Jonathan D.A. Jewell ", - leash = 'Kennel, + schema_version = "1.0.0", + component_type = "panel-clade-config", + security = { + leash = 'Kennel, + trust_level = "data-only", + allow_network = false, + allow_filesystem_write = false, + allow_subprocess = false, + capabilities = [], + }, + metadata = { + name = "load-tester-config", + version = "0.1.0", + author = "Jonathan D.A. Jewell ", + }, + side_effects = [], }, panel = { id = "Load Tester", diff --git a/panel-clades/clades/manifest-coverage/config.k9.ncl b/panel-clades/clades/manifest-coverage/config.k9.ncl index c40732f6..6aecdd44 100644 --- a/panel-clades/clades/manifest-coverage/config.k9.ncl +++ b/panel-clades/clades/manifest-coverage/config.k9.ncl @@ -1,13 +1,25 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 # K9 Kennel: Manifest Coverage Panel Configuration # Auto-generated from PanelRegistry — pure data, no execution -# SPDX-License-Identifier: MPL-2.0 { pedigree = { - name = "manifest-coverage-config", - version = "0.1.0", - magic_number = "K9!", - author = "Jonathan D.A. Jewell ", - leash = 'Kennel, + schema_version = "1.0.0", + component_type = "panel-clade-config", + security = { + leash = 'Kennel, + trust_level = "data-only", + allow_network = false, + allow_filesystem_write = false, + allow_subprocess = false, + capabilities = [], + }, + metadata = { + name = "manifest-coverage-config", + version = "0.1.0", + author = "Jonathan D.A. Jewell ", + }, + side_effects = [], }, panel = { id = "ManifestCoverage", diff --git a/panel-clades/clades/mass-panic/config.k9.ncl b/panel-clades/clades/mass-panic/config.k9.ncl index 41c65dce..32736831 100644 --- a/panel-clades/clades/mass-panic/config.k9.ncl +++ b/panel-clades/clades/mass-panic/config.k9.ncl @@ -1,13 +1,25 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 # K9 Kennel: Mass Panic Panel Configuration # Auto-generated from PanelRegistry — pure data, no execution -# SPDX-License-Identifier: MPL-2.0 { pedigree = { - name = "mass-panic-config", - version = "0.1.0", - magic_number = "K9!", - author = "Jonathan D.A. Jewell ", - leash = 'Kennel, + schema_version = "1.0.0", + component_type = "panel-clade-config", + security = { + leash = 'Kennel, + trust_level = "data-only", + allow_network = false, + allow_filesystem_write = false, + allow_subprocess = false, + capabilities = [], + }, + metadata = { + name = "mass-panic-config", + version = "0.1.0", + author = "Jonathan D.A. Jewell ", + }, + side_effects = [], }, panel = { id = "Mass Panic", diff --git a/panel-clades/clades/merge-coordinator/config.k9.ncl b/panel-clades/clades/merge-coordinator/config.k9.ncl index 3578ab64..9940b750 100644 --- a/panel-clades/clades/merge-coordinator/config.k9.ncl +++ b/panel-clades/clades/merge-coordinator/config.k9.ncl @@ -1,13 +1,25 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 # K9 Kennel: Merge Coordinator Panel Configuration # Auto-generated from PanelRegistry — pure data, no execution -# SPDX-License-Identifier: MPL-2.0 { pedigree = { - name = "merge-coordinator-config", - version = "0.1.0", - magic_number = "K9!", - author = "Jonathan D.A. Jewell ", - leash = 'Kennel, + schema_version = "1.0.0", + component_type = "panel-clade-config", + security = { + leash = 'Kennel, + trust_level = "data-only", + allow_network = false, + allow_filesystem_write = false, + allow_subprocess = false, + capabilities = [], + }, + metadata = { + name = "merge-coordinator-config", + version = "0.1.0", + author = "Jonathan D.A. Jewell ", + }, + side_effects = [], }, panel = { id = "Merge Coordinator", diff --git a/panel-clades/clades/migration/config.k9.ncl b/panel-clades/clades/migration/config.k9.ncl index a8b86d3b..2e4ef802 100644 --- a/panel-clades/clades/migration/config.k9.ncl +++ b/panel-clades/clades/migration/config.k9.ncl @@ -1,13 +1,25 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 # K9 Kennel: Migration Panel Configuration # Auto-generated from PanelRegistry — pure data, no execution -# SPDX-License-Identifier: MPL-2.0 { pedigree = { - name = "migration-config", - version = "0.1.0", - magic_number = "K9!", - author = "Jonathan D.A. Jewell ", - leash = 'Kennel, + schema_version = "1.0.0", + component_type = "panel-clade-config", + security = { + leash = 'Kennel, + trust_level = "data-only", + allow_network = false, + allow_filesystem_write = false, + allow_subprocess = false, + capabilities = [], + }, + metadata = { + name = "migration-config", + version = "0.1.0", + author = "Jonathan D.A. Jewell ", + }, + side_effects = [], }, panel = { id = "Migration", diff --git a/panel-clades/clades/minter/config.k9.ncl b/panel-clades/clades/minter/config.k9.ncl index a2b4d742..f3ae2794 100644 --- a/panel-clades/clades/minter/config.k9.ncl +++ b/panel-clades/clades/minter/config.k9.ncl @@ -1,13 +1,25 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 # K9 Kennel: Minter Panel Configuration # Auto-generated from PanelRegistry — pure data, no execution -# SPDX-License-Identifier: MPL-2.0 { pedigree = { - name = "minter-config", - version = "0.1.0", - magic_number = "K9!", - author = "Jonathan D.A. Jewell ", - leash = 'Kennel, + schema_version = "1.0.0", + component_type = "panel-clade-config", + security = { + leash = 'Kennel, + trust_level = "data-only", + allow_network = false, + allow_filesystem_write = false, + allow_subprocess = false, + capabilities = [], + }, + metadata = { + name = "minter-config", + version = "0.1.0", + author = "Jonathan D.A. Jewell ", + }, + side_effects = [], }, panel = { id = "Minter", diff --git a/panel-clades/clades/multiplayer-monitor/config.k9.ncl b/panel-clades/clades/multiplayer-monitor/config.k9.ncl index aed23d26..48ecc5c3 100644 --- a/panel-clades/clades/multiplayer-monitor/config.k9.ncl +++ b/panel-clades/clades/multiplayer-monitor/config.k9.ncl @@ -1,13 +1,25 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 # K9 Kennel: Multiplayer Monitor Panel Configuration # Auto-generated from PanelRegistry — pure data, no execution -# SPDX-License-Identifier: MPL-2.0 { pedigree = { - name = "multiplayer-monitor-config", - version = "0.1.0", - magic_number = "K9!", - author = "Jonathan D.A. Jewell ", - leash = 'Kennel, + schema_version = "1.0.0", + component_type = "panel-clade-config", + security = { + leash = 'Kennel, + trust_level = "data-only", + allow_network = false, + allow_filesystem_write = false, + allow_subprocess = false, + capabilities = [], + }, + metadata = { + name = "multiplayer-monitor-config", + version = "0.1.0", + author = "Jonathan D.A. Jewell ", + }, + side_effects = [], }, panel = { id = "Multiplayer Monitor", diff --git a/panel-clades/clades/my-lang/config.k9.ncl b/panel-clades/clades/my-lang/config.k9.ncl index a063b70a..92d60034 100644 --- a/panel-clades/clades/my-lang/config.k9.ncl +++ b/panel-clades/clades/my-lang/config.k9.ncl @@ -1,13 +1,25 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 # K9 Kennel: My-Lang Panel Configuration # Auto-generated from PanelRegistry — pure data, no execution -# SPDX-License-Identifier: MPL-2.0 { pedigree = { - name = "my-lang-config", - version = "0.1.0", - magic_number = "K9!", - author = "Jonathan D.A. Jewell ", - leash = 'Kennel, + schema_version = "1.0.0", + component_type = "panel-clade-config", + security = { + leash = 'Kennel, + trust_level = "data-only", + allow_network = false, + allow_filesystem_write = false, + allow_subprocess = false, + capabilities = [], + }, + metadata = { + name = "my-lang-config", + version = "0.1.0", + author = "Jonathan D.A. Jewell ", + }, + side_effects = [], }, panel = { id = "My-Lang", diff --git a/panel-clades/clades/network-topology/config.k9.ncl b/panel-clades/clades/network-topology/config.k9.ncl index 92ec72b3..73871936 100644 --- a/panel-clades/clades/network-topology/config.k9.ncl +++ b/panel-clades/clades/network-topology/config.k9.ncl @@ -1,13 +1,25 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 # K9 Kennel: Network Topology Panel Configuration # Auto-generated from PanelRegistry — pure data, no execution -# SPDX-License-Identifier: MPL-2.0 { pedigree = { - name = "network-topology-config", - version = "0.1.0", - magic_number = "K9!", - author = "Jonathan D.A. Jewell ", - leash = 'Kennel, + schema_version = "1.0.0", + component_type = "panel-clade-config", + security = { + leash = 'Kennel, + trust_level = "data-only", + allow_network = false, + allow_filesystem_write = false, + allow_subprocess = false, + capabilities = [], + }, + metadata = { + name = "network-topology-config", + version = "0.1.0", + author = "Jonathan D.A. Jewell ", + }, + side_effects = [], }, panel = { id = "Network Topology", diff --git a/panel-clades/clades/neurosym-bridge/config.k9.ncl b/panel-clades/clades/neurosym-bridge/config.k9.ncl index fb856382..b3fc09fd 100644 --- a/panel-clades/clades/neurosym-bridge/config.k9.ncl +++ b/panel-clades/clades/neurosym-bridge/config.k9.ncl @@ -1,13 +1,25 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 # K9 Kennel: Neurosymbolic Bridge Panel Configuration # Auto-generated from PanelRegistry — pure data, no execution -# SPDX-License-Identifier: MPL-2.0 { pedigree = { - name = "neurosym-bridge-config", - version = "0.1.0", - magic_number = "K9!", - author = "Jonathan D.A. Jewell ", - leash = 'Kennel, + schema_version = "1.0.0", + component_type = "panel-clade-config", + security = { + leash = 'Kennel, + trust_level = "data-only", + allow_network = false, + allow_filesystem_write = false, + allow_subprocess = false, + capabilities = [], + }, + metadata = { + name = "neurosym-bridge-config", + version = "0.1.0", + author = "Jonathan D.A. Jewell ", + }, + side_effects = [], }, panel = { id = "Neurosymbolic Bridge", diff --git a/panel-clades/clades/observatory/config.k9.ncl b/panel-clades/clades/observatory/config.k9.ncl index 7fd8f7ef..9bfb8294 100644 --- a/panel-clades/clades/observatory/config.k9.ncl +++ b/panel-clades/clades/observatory/config.k9.ncl @@ -1,13 +1,25 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 # K9 Kennel: Observatory Panel Configuration # Auto-generated from PanelRegistry — pure data, no execution -# SPDX-License-Identifier: MPL-2.0 { pedigree = { - name = "observatory-config", - version = "0.1.0", - magic_number = "K9!", - author = "Jonathan D.A. Jewell ", - leash = 'Kennel, + schema_version = "1.0.0", + component_type = "panel-clade-config", + security = { + leash = 'Kennel, + trust_level = "data-only", + allow_network = false, + allow_filesystem_write = false, + allow_subprocess = false, + capabilities = [], + }, + metadata = { + name = "observatory-config", + version = "0.1.0", + author = "Jonathan D.A. Jewell ", + }, + side_effects = [], }, panel = { id = "Observatory", diff --git a/panel-clades/clades/panic-attack/config.k9.ncl b/panel-clades/clades/panic-attack/config.k9.ncl index bbff0777..a2a6afd9 100644 --- a/panel-clades/clades/panic-attack/config.k9.ncl +++ b/panel-clades/clades/panic-attack/config.k9.ncl @@ -1,13 +1,25 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 # K9 Kennel: panic-attack Panel Configuration # Auto-generated from PanelRegistry — pure data, no execution -# SPDX-License-Identifier: MPL-2.0 { pedigree = { - name = "panic-attack-config", - version = "0.1.0", - magic_number = "K9!", - author = "Jonathan D.A. Jewell ", - leash = 'Kennel, + schema_version = "1.0.0", + component_type = "panel-clade-config", + security = { + leash = 'Kennel, + trust_level = "data-only", + allow_network = false, + allow_filesystem_write = false, + allow_subprocess = false, + capabilities = [], + }, + metadata = { + name = "panic-attack-config", + version = "0.1.0", + author = "Jonathan D.A. Jewell ", + }, + side_effects = [], }, panel = { id = "panic-attack", diff --git a/panel-clades/clades/performance-profiler/config.k9.ncl b/panel-clades/clades/performance-profiler/config.k9.ncl index 41f21f74..a1c6f0d9 100644 --- a/panel-clades/clades/performance-profiler/config.k9.ncl +++ b/panel-clades/clades/performance-profiler/config.k9.ncl @@ -1,13 +1,25 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 # K9 Kennel: Performance Profiler Panel Configuration # Auto-generated from PanelRegistry — pure data, no execution -# SPDX-License-Identifier: MPL-2.0 { pedigree = { - name = "performance-profiler-config", - version = "0.1.0", - magic_number = "K9!", - author = "Jonathan D.A. Jewell ", - leash = 'Kennel, + schema_version = "1.0.0", + component_type = "panel-clade-config", + security = { + leash = 'Kennel, + trust_level = "data-only", + allow_network = false, + allow_filesystem_write = false, + allow_subprocess = false, + capabilities = [], + }, + metadata = { + name = "performance-profiler-config", + version = "0.1.0", + author = "Jonathan D.A. Jewell ", + }, + side_effects = [], }, panel = { id = "Performance Profiler", diff --git a/panel-clades/clades/playgrounds/config.k9.ncl b/panel-clades/clades/playgrounds/config.k9.ncl index d0a71553..64e04b61 100644 --- a/panel-clades/clades/playgrounds/config.k9.ncl +++ b/panel-clades/clades/playgrounds/config.k9.ncl @@ -1,13 +1,25 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 # K9 Kennel: Playgrounds Panel Configuration # Auto-generated from PanelRegistry — pure data, no execution -# SPDX-License-Identifier: MPL-2.0 { pedigree = { - name = "playgrounds-config", - version = "0.1.0", - magic_number = "K9!", - author = "Jonathan D.A. Jewell ", - leash = 'Kennel, + schema_version = "1.0.0", + component_type = "panel-clade-config", + security = { + leash = 'Kennel, + trust_level = "data-only", + allow_network = false, + allow_filesystem_write = false, + allow_subprocess = false, + capabilities = [], + }, + metadata = { + name = "playgrounds-config", + version = "0.1.0", + author = "Jonathan D.A. Jewell ", + }, + side_effects = [], }, panel = { id = "Playgrounds", diff --git a/panel-clades/clades/playtest-recorder/config.k9.ncl b/panel-clades/clades/playtest-recorder/config.k9.ncl index ed35c934..1477ee1f 100644 --- a/panel-clades/clades/playtest-recorder/config.k9.ncl +++ b/panel-clades/clades/playtest-recorder/config.k9.ncl @@ -1,13 +1,25 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 # K9 Kennel: Playtest Recorder Panel Configuration # Auto-generated from PanelRegistry — pure data, no execution -# SPDX-License-Identifier: MPL-2.0 { pedigree = { - name = "playtest-recorder-config", - version = "0.1.0", - magic_number = "K9!", - author = "Jonathan D.A. Jewell ", - leash = 'Kennel, + schema_version = "1.0.0", + component_type = "panel-clade-config", + security = { + leash = 'Kennel, + trust_level = "data-only", + allow_network = false, + allow_filesystem_write = false, + allow_subprocess = false, + capabilities = [], + }, + metadata = { + name = "playtest-recorder-config", + version = "0.1.0", + author = "Jonathan D.A. Jewell ", + }, + side_effects = [], }, panel = { id = "Playtest Recorder", diff --git a/panel-clades/clades/plaza/config.k9.ncl b/panel-clades/clades/plaza/config.k9.ncl index 56edf15c..5ed2fe41 100644 --- a/panel-clades/clades/plaza/config.k9.ncl +++ b/panel-clades/clades/plaza/config.k9.ncl @@ -1,13 +1,25 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 # K9 Kennel: Palimpsest Plaza Panel Configuration # Auto-generated from PanelRegistry — pure data, no execution -# SPDX-License-Identifier: MPL-2.0 { pedigree = { - name = "plaza-config", - version = "0.1.0", - magic_number = "K9!", - author = "Jonathan D.A. Jewell ", - leash = 'Kennel, + schema_version = "1.0.0", + component_type = "panel-clade-config", + security = { + leash = 'Kennel, + trust_level = "data-only", + allow_network = false, + allow_filesystem_write = false, + allow_subprocess = false, + capabilities = [], + }, + metadata = { + name = "plaza-config", + version = "0.1.0", + author = "Jonathan D.A. Jewell ", + }, + side_effects = [], }, panel = { id = "Palimpsest Plaza", diff --git a/panel-clades/clades/proofs-bridge/config.k9.ncl b/panel-clades/clades/proofs-bridge/config.k9.ncl index 87dbff7a..0c0023cb 100644 --- a/panel-clades/clades/proofs-bridge/config.k9.ncl +++ b/panel-clades/clades/proofs-bridge/config.k9.ncl @@ -1,13 +1,25 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 # K9 Kennel: Proofs Bridge Panel Configuration # Auto-generated from PanelRegistry — pure data, no execution -# SPDX-License-Identifier: MPL-2.0 { pedigree = { - name = "proofs-bridge-config", - version = "0.1.0", - magic_number = "K9!", - author = "Jonathan D.A. Jewell ", - leash = 'Kennel, + schema_version = "1.0.0", + component_type = "panel-clade-config", + security = { + leash = 'Kennel, + trust_level = "data-only", + allow_network = false, + allow_filesystem_write = false, + allow_subprocess = false, + capabilities = [], + }, + metadata = { + name = "proofs-bridge-config", + version = "0.1.0", + author = "Jonathan D.A. Jewell ", + }, + side_effects = [], }, panel = { id = "Proofs Bridge", diff --git a/panel-clades/clades/protocol-bridge/config.k9.ncl b/panel-clades/clades/protocol-bridge/config.k9.ncl index 36b3c9ae..f58df8de 100644 --- a/panel-clades/clades/protocol-bridge/config.k9.ncl +++ b/panel-clades/clades/protocol-bridge/config.k9.ncl @@ -1,13 +1,25 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 # K9 Kennel: Protocol Bridge Panel Configuration # Auto-generated from PanelRegistry — pure data, no execution -# SPDX-License-Identifier: MPL-2.0 { pedigree = { - name = "protocol-bridge-config", - version = "0.1.0", - magic_number = "K9!", - author = "Jonathan D.A. Jewell ", - leash = 'Kennel, + schema_version = "1.0.0", + component_type = "panel-clade-config", + security = { + leash = 'Kennel, + trust_level = "data-only", + allow_network = false, + allow_filesystem_write = false, + allow_subprocess = false, + capabilities = [], + }, + metadata = { + name = "protocol-bridge-config", + version = "0.1.0", + author = "Jonathan D.A. Jewell ", + }, + side_effects = [], }, panel = { id = "Protocol Bridge", diff --git a/panel-clades/clades/protocol-squisher/config.k9.ncl b/panel-clades/clades/protocol-squisher/config.k9.ncl index eedd28ae..9b0a8681 100644 --- a/panel-clades/clades/protocol-squisher/config.k9.ncl +++ b/panel-clades/clades/protocol-squisher/config.k9.ncl @@ -1,13 +1,25 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 # K9 Kennel: Protocol-Squisher Panel Configuration # Auto-generated from PanelRegistry — pure data, no execution -# SPDX-License-Identifier: MPL-2.0 { pedigree = { - name = "protocol-squisher-config", - version = "0.1.0", - magic_number = "K9!", - author = "Jonathan D.A. Jewell ", - leash = 'Kennel, + schema_version = "1.0.0", + component_type = "panel-clade-config", + security = { + leash = 'Kennel, + trust_level = "data-only", + allow_network = false, + allow_filesystem_write = false, + allow_subprocess = false, + capabilities = [], + }, + metadata = { + name = "protocol-squisher-config", + version = "0.1.0", + author = "Jonathan D.A. Jewell ", + }, + side_effects = [], }, panel = { id = "Protocol-Squisher", diff --git a/panel-clades/clades/proven-adoption/config.k9.ncl b/panel-clades/clades/proven-adoption/config.k9.ncl index cbb62b9f..aeb5ed77 100644 --- a/panel-clades/clades/proven-adoption/config.k9.ncl +++ b/panel-clades/clades/proven-adoption/config.k9.ncl @@ -1,13 +1,25 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 # K9 Kennel: Proven Adoption Panel Configuration # Auto-generated from PanelRegistry — pure data, no execution -# SPDX-License-Identifier: MPL-2.0 { pedigree = { - name = "proven-adoption-config", - version = "0.1.0", - magic_number = "K9!", - author = "Jonathan D.A. Jewell ", - leash = 'Kennel, + schema_version = "1.0.0", + component_type = "panel-clade-config", + security = { + leash = 'Kennel, + trust_level = "data-only", + allow_network = false, + allow_filesystem_write = false, + allow_subprocess = false, + capabilities = [], + }, + metadata = { + name = "proven-adoption-config", + version = "0.1.0", + author = "Jonathan D.A. Jewell ", + }, + side_effects = [], }, panel = { id = "ProvenAdoption", diff --git a/panel-clades/clades/provisioner/config.k9.ncl b/panel-clades/clades/provisioner/config.k9.ncl index dbe63bf8..e4bf2875 100644 --- a/panel-clades/clades/provisioner/config.k9.ncl +++ b/panel-clades/clades/provisioner/config.k9.ncl @@ -1,13 +1,25 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 # K9 Kennel: Provisioner Panel Configuration # Auto-generated from PanelRegistry — pure data, no execution -# SPDX-License-Identifier: MPL-2.0 { pedigree = { - name = "provisioner-config", - version = "0.1.0", - magic_number = "K9!", - author = "Jonathan D.A. Jewell ", - leash = 'Kennel, + schema_version = "1.0.0", + component_type = "panel-clade-config", + security = { + leash = 'Kennel, + trust_level = "data-only", + allow_network = false, + allow_filesystem_write = false, + allow_subprocess = false, + capabilities = [], + }, + metadata = { + name = "provisioner-config", + version = "0.1.0", + author = "Jonathan D.A. Jewell ", + }, + side_effects = [], }, panel = { id = "Provisioner", diff --git a/panel-clades/clades/regression-guard/config.k9.ncl b/panel-clades/clades/regression-guard/config.k9.ncl index 3fe35cd5..80d294c2 100644 --- a/panel-clades/clades/regression-guard/config.k9.ncl +++ b/panel-clades/clades/regression-guard/config.k9.ncl @@ -1,13 +1,25 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 # K9 Kennel: Regression Guard Panel Configuration # Auto-generated from PanelRegistry — pure data, no execution -# SPDX-License-Identifier: MPL-2.0 { pedigree = { - name = "regression-guard-config", - version = "0.1.0", - magic_number = "K9!", - author = "Jonathan D.A. Jewell ", - leash = 'Kennel, + schema_version = "1.0.0", + component_type = "panel-clade-config", + security = { + leash = 'Kennel, + trust_level = "data-only", + allow_network = false, + allow_filesystem_write = false, + allow_subprocess = false, + capabilities = [], + }, + metadata = { + name = "regression-guard-config", + version = "0.1.0", + author = "Jonathan D.A. Jewell ", + }, + side_effects = [], }, panel = { id = "Regression Guard", diff --git a/panel-clades/clades/release-manager/config.k9.ncl b/panel-clades/clades/release-manager/config.k9.ncl index 76df2c3d..df6cd8a2 100644 --- a/panel-clades/clades/release-manager/config.k9.ncl +++ b/panel-clades/clades/release-manager/config.k9.ncl @@ -1,13 +1,25 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 # K9 Kennel: Release Manager Panel Configuration # Auto-generated from PanelRegistry — pure data, no execution -# SPDX-License-Identifier: MPL-2.0 { pedigree = { - name = "release-manager-config", - version = "0.1.0", - magic_number = "K9!", - author = "Jonathan D.A. Jewell ", - leash = 'Kennel, + schema_version = "1.0.0", + component_type = "panel-clade-config", + security = { + leash = 'Kennel, + trust_level = "data-only", + allow_network = false, + allow_filesystem_write = false, + allow_subprocess = false, + capabilities = [], + }, + metadata = { + name = "release-manager-config", + version = "0.1.0", + author = "Jonathan D.A. Jewell ", + }, + side_effects = [], }, panel = { id = "Release Manager", diff --git a/panel-clades/clades/repoloader/config.k9.ncl b/panel-clades/clades/repoloader/config.k9.ncl index c4c8f4f2..9ec0f9ef 100644 --- a/panel-clades/clades/repoloader/config.k9.ncl +++ b/panel-clades/clades/repoloader/config.k9.ncl @@ -1,13 +1,25 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 # K9 Kennel: Repo Loader Panel Configuration # Auto-generated from PanelRegistry — pure data, no execution -# SPDX-License-Identifier: MPL-2.0 { pedigree = { - name = "repoloader-config", - version = "0.1.0", - magic_number = "K9!", - author = "Jonathan D.A. Jewell ", - leash = 'Kennel, + schema_version = "1.0.0", + component_type = "panel-clade-config", + security = { + leash = 'Kennel, + trust_level = "data-only", + allow_network = false, + allow_filesystem_write = false, + allow_subprocess = false, + capabilities = [], + }, + metadata = { + name = "repoloader-config", + version = "0.1.0", + author = "Jonathan D.A. Jewell ", + }, + side_effects = [], }, panel = { id = "Repo Loader", diff --git a/panel-clades/clades/reposystem/config.k9.ncl b/panel-clades/clades/reposystem/config.k9.ncl index fe0abd61..2a0eb38b 100644 --- a/panel-clades/clades/reposystem/config.k9.ncl +++ b/panel-clades/clades/reposystem/config.k9.ncl @@ -1,13 +1,25 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 # K9 Kennel: Reposystem Panel Configuration # Auto-generated from PanelRegistry — pure data, no execution -# SPDX-License-Identifier: MPL-2.0 { pedigree = { - name = "reposystem-config", - version = "0.1.0", - magic_number = "K9!", - author = "Jonathan D.A. Jewell ", - leash = 'Kennel, + schema_version = "1.0.0", + component_type = "panel-clade-config", + security = { + leash = 'Kennel, + trust_level = "data-only", + allow_network = false, + allow_filesystem_write = false, + allow_subprocess = false, + capabilities = [], + }, + metadata = { + name = "reposystem-config", + version = "0.1.0", + author = "Jonathan D.A. Jewell ", + }, + side_effects = [], }, panel = { id = "Reposystem", diff --git a/panel-clades/clades/script-gist/config.k9.ncl b/panel-clades/clades/script-gist/config.k9.ncl index fe5369fc..f1d9c7f2 100644 --- a/panel-clades/clades/script-gist/config.k9.ncl +++ b/panel-clades/clades/script-gist/config.k9.ncl @@ -1,13 +1,25 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 # K9 Kennel: Script Gist Panel Configuration # Auto-generated from PanelRegistry — pure data, no execution -# SPDX-License-Identifier: MPL-2.0 { pedigree = { - name = "script-gist-config", - version = "0.1.0", - magic_number = "K9!", - author = "Jonathan D.A. Jewell ", - leash = 'Kennel, + schema_version = "1.0.0", + component_type = "panel-clade-config", + security = { + leash = 'Kennel, + trust_level = "data-only", + allow_network = false, + allow_filesystem_write = false, + allow_subprocess = false, + capabilities = [], + }, + metadata = { + name = "script-gist-config", + version = "0.1.0", + author = "Jonathan D.A. Jewell ", + }, + side_effects = [], }, panel = { id = "Script Gist", diff --git a/panel-clades/clades/scripting-bridge/config.k9.ncl b/panel-clades/clades/scripting-bridge/config.k9.ncl index 47a2a81c..b20dc5f8 100644 --- a/panel-clades/clades/scripting-bridge/config.k9.ncl +++ b/panel-clades/clades/scripting-bridge/config.k9.ncl @@ -1,13 +1,25 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 # K9 Kennel: Scripting Bridge Panel Configuration # Auto-generated from PanelRegistry — pure data, no execution -# SPDX-License-Identifier: MPL-2.0 { pedigree = { - name = "scripting-bridge-config", - version = "0.1.0", - magic_number = "K9!", - author = "Jonathan D.A. Jewell ", - leash = 'Kennel, + schema_version = "1.0.0", + component_type = "panel-clade-config", + security = { + leash = 'Kennel, + trust_level = "data-only", + allow_network = false, + allow_filesystem_write = false, + allow_subprocess = false, + capabilities = [], + }, + metadata = { + name = "scripting-bridge-config", + version = "0.1.0", + author = "Jonathan D.A. Jewell ", + }, + side_effects = [], }, panel = { id = "Scripting Bridge", diff --git a/panel-clades/clades/security/config.k9.ncl b/panel-clades/clades/security/config.k9.ncl index 86179cf9..c88e264a 100644 --- a/panel-clades/clades/security/config.k9.ncl +++ b/panel-clades/clades/security/config.k9.ncl @@ -1,13 +1,25 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 # K9 Kennel: Security Panel Configuration # Auto-generated from PanelRegistry — pure data, no execution -# SPDX-License-Identifier: MPL-2.0 { pedigree = { - name = "security-config", - version = "0.1.0", - magic_number = "K9!", - author = "Jonathan D.A. Jewell ", - leash = 'Kennel, + schema_version = "1.0.0", + component_type = "panel-clade-config", + security = { + leash = 'Kennel, + trust_level = "data-only", + allow_network = false, + allow_filesystem_write = false, + allow_subprocess = false, + capabilities = [], + }, + metadata = { + name = "security-config", + version = "0.1.0", + author = "Jonathan D.A. Jewell ", + }, + side_effects = [], }, panel = { id = "Security", diff --git a/panel-clades/clades/soak-monitor/config.k9.ncl b/panel-clades/clades/soak-monitor/config.k9.ncl index 046c4581..2a4e9f4f 100644 --- a/panel-clades/clades/soak-monitor/config.k9.ncl +++ b/panel-clades/clades/soak-monitor/config.k9.ncl @@ -1,13 +1,25 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 # K9 Kennel: Soak Monitor Panel Configuration # Auto-generated from PanelRegistry — pure data, no execution -# SPDX-License-Identifier: MPL-2.0 { pedigree = { - name = "soak-monitor-config", - version = "0.1.0", - magic_number = "K9!", - author = "Jonathan D.A. Jewell ", - leash = 'Kennel, + schema_version = "1.0.0", + component_type = "panel-clade-config", + security = { + leash = 'Kennel, + trust_level = "data-only", + allow_network = false, + allow_filesystem_write = false, + allow_subprocess = false, + capabilities = [], + }, + metadata = { + name = "soak-monitor-config", + version = "0.1.0", + author = "Jonathan D.A. Jewell ", + }, + side_effects = [], }, panel = { id = "Soak Monitor", diff --git a/panel-clades/clades/spec-browser/config.k9.ncl b/panel-clades/clades/spec-browser/config.k9.ncl index 0a32081a..4dbe3350 100644 --- a/panel-clades/clades/spec-browser/config.k9.ncl +++ b/panel-clades/clades/spec-browser/config.k9.ncl @@ -1,13 +1,25 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 # K9 Kennel: Spec Browser Panel Configuration # Auto-generated from PanelRegistry — pure data, no execution -# SPDX-License-Identifier: MPL-2.0 { pedigree = { - name = "spec-browser-config", - version = "0.1.0", - magic_number = "K9!", - author = "Jonathan D.A. Jewell ", - leash = 'Kennel, + schema_version = "1.0.0", + component_type = "panel-clade-config", + security = { + leash = 'Kennel, + trust_level = "data-only", + allow_network = false, + allow_filesystem_write = false, + allow_subprocess = false, + capabilities = [], + }, + metadata = { + name = "spec-browser-config", + version = "0.1.0", + author = "Jonathan D.A. Jewell ", + }, + side_effects = [], }, panel = { id = "Spec Browser", diff --git a/panel-clades/clades/strategy-drift/config.k9.ncl b/panel-clades/clades/strategy-drift/config.k9.ncl index 603306b1..04612a8a 100644 --- a/panel-clades/clades/strategy-drift/config.k9.ncl +++ b/panel-clades/clades/strategy-drift/config.k9.ncl @@ -1,16 +1,28 @@ -# K9 Kennel: Strategy Drift Panel Configuration +K9! # SPDX-License-Identifier: MPL-2.0 +# K9 Kennel: Strategy Drift Panel Configuration # # N3: Panel showing top-prover-per-class over time, with drift events # flagged. Data source: Verisim's /api/v1/proof_attempts/strategy and # /certificates endpoints, plus hypatia's StrategyDrift ETS snapshots. { pedigree = { - name = "strategy-drift-config", - version = "0.1.0", - magic_number = "K9!", - author = "Jonathan D.A. Jewell ", - leash = 'Kennel, + schema_version = "1.0.0", + component_type = "panel-clade-config", + security = { + leash = 'Kennel, + trust_level = "data-only", + allow_network = false, + allow_filesystem_write = false, + allow_subprocess = false, + capabilities = [], + }, + metadata = { + name = "strategy-drift-config", + version = "0.1.0", + author = "Jonathan D.A. Jewell ", + }, + side_effects = [], }, panel = { id = "StrategyDrift", diff --git a/panel-clades/clades/tangle-viz/config.k9.ncl b/panel-clades/clades/tangle-viz/config.k9.ncl index cb406ec5..86833b22 100644 --- a/panel-clades/clades/tangle-viz/config.k9.ncl +++ b/panel-clades/clades/tangle-viz/config.k9.ncl @@ -1,13 +1,25 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 # K9 Kennel: Tangle Viz Panel Configuration # Auto-generated from PanelRegistry — pure data, no execution -# SPDX-License-Identifier: MPL-2.0 { pedigree = { - name = "tangle-viz-config", - version = "0.1.0", - magic_number = "K9!", - author = "Jonathan D.A. Jewell ", - leash = 'Kennel, + schema_version = "1.0.0", + component_type = "panel-clade-config", + security = { + leash = 'Kennel, + trust_level = "data-only", + allow_network = false, + allow_filesystem_write = false, + allow_subprocess = false, + capabilities = [], + }, + metadata = { + name = "tangle-viz-config", + version = "0.1.0", + author = "Jonathan D.A. Jewell ", + }, + side_effects = [], }, panel = { id = "Tangle Viz", diff --git a/panel-clades/clades/team-dashboard/config.k9.ncl b/panel-clades/clades/team-dashboard/config.k9.ncl index 8fc493d4..183828ed 100644 --- a/panel-clades/clades/team-dashboard/config.k9.ncl +++ b/panel-clades/clades/team-dashboard/config.k9.ncl @@ -1,13 +1,25 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 # K9 Kennel: Team Dashboard Panel Configuration # Auto-generated from PanelRegistry — pure data, no execution -# SPDX-License-Identifier: MPL-2.0 { pedigree = { - name = "team-dashboard-config", - version = "0.1.0", - magic_number = "K9!", - author = "Jonathan D.A. Jewell ", - leash = 'Kennel, + schema_version = "1.0.0", + component_type = "panel-clade-config", + security = { + leash = 'Kennel, + trust_level = "data-only", + allow_network = false, + allow_filesystem_write = false, + allow_subprocess = false, + capabilities = [], + }, + metadata = { + name = "team-dashboard-config", + version = "0.1.0", + author = "Jonathan D.A. Jewell ", + }, + side_effects = [], }, panel = { id = "Team Dashboard", diff --git a/panel-clades/clades/tentacles/config.k9.ncl b/panel-clades/clades/tentacles/config.k9.ncl index 934c9ad9..7dd24ed2 100644 --- a/panel-clades/clades/tentacles/config.k9.ncl +++ b/panel-clades/clades/tentacles/config.k9.ncl @@ -1,13 +1,25 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 # K9 Kennel: 7-Tentacles Panel Configuration # Auto-generated from PanelRegistry — pure data, no execution -# SPDX-License-Identifier: MPL-2.0 { pedigree = { - name = "tentacles-config", - version = "0.1.0", - magic_number = "K9!", - author = "Jonathan D.A. Jewell ", - leash = 'Kennel, + schema_version = "1.0.0", + component_type = "panel-clade-config", + security = { + leash = 'Kennel, + trust_level = "data-only", + allow_network = false, + allow_filesystem_write = false, + allow_subprocess = false, + capabilities = [], + }, + metadata = { + name = "tentacles-config", + version = "0.1.0", + author = "Jonathan D.A. Jewell ", + }, + side_effects = [], }, panel = { id = "7-Tentacles", diff --git a/panel-clades/clades/tsdm/config.k9.ncl b/panel-clades/clades/tsdm/config.k9.ncl index 4ce89fd0..3e11e04a 100644 --- a/panel-clades/clades/tsdm/config.k9.ncl +++ b/panel-clades/clades/tsdm/config.k9.ncl @@ -1,13 +1,25 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 # K9 Kennel: TSDM Panel Configuration # Auto-generated from PanelRegistry — pure data, no execution -# SPDX-License-Identifier: MPL-2.0 { pedigree = { - name = "tsdm-config", - version = "0.1.0", - magic_number = "K9!", - author = "Jonathan D.A. Jewell ", - leash = 'Kennel, + schema_version = "1.0.0", + component_type = "panel-clade-config", + security = { + leash = 'Kennel, + trust_level = "data-only", + allow_network = false, + allow_filesystem_write = false, + allow_subprocess = false, + capabilities = [], + }, + metadata = { + name = "tsdm-config", + version = "0.1.0", + author = "Jonathan D.A. Jewell ", + }, + side_effects = [], }, panel = { id = "TSDM", diff --git a/panel-clades/clades/typell/config.k9.ncl b/panel-clades/clades/typell/config.k9.ncl index ccdf2b27..56fb8594 100644 --- a/panel-clades/clades/typell/config.k9.ncl +++ b/panel-clades/clades/typell/config.k9.ncl @@ -1,13 +1,25 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 # K9 Kennel: TypeLL Panel Configuration # Auto-generated from PanelRegistry — pure data, no execution -# SPDX-License-Identifier: MPL-2.0 { pedigree = { - name = "typell-config", - version = "0.1.0", - magic_number = "K9!", - author = "Jonathan D.A. Jewell ", - leash = 'Kennel, + schema_version = "1.0.0", + component_type = "panel-clade-config", + security = { + leash = 'Kennel, + trust_level = "data-only", + allow_network = false, + allow_filesystem_write = false, + allow_subprocess = false, + capabilities = [], + }, + metadata = { + name = "typell-config", + version = "0.1.0", + author = "Jonathan D.A. Jewell ", + }, + side_effects = [], }, panel = { id = "TypeLL", diff --git a/panel-clades/clades/typing-bridge/config.k9.ncl b/panel-clades/clades/typing-bridge/config.k9.ncl index f2c4fb30..b40c1dd5 100644 --- a/panel-clades/clades/typing-bridge/config.k9.ncl +++ b/panel-clades/clades/typing-bridge/config.k9.ncl @@ -1,13 +1,25 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 # K9 Kennel: Typing Bridge Panel Configuration # Auto-generated from PanelRegistry — pure data, no execution -# SPDX-License-Identifier: MPL-2.0 { pedigree = { - name = "typing-bridge-config", - version = "0.1.0", - magic_number = "K9!", - author = "Jonathan D.A. Jewell ", - leash = 'Kennel, + schema_version = "1.0.0", + component_type = "panel-clade-config", + security = { + leash = 'Kennel, + trust_level = "data-only", + allow_network = false, + allow_filesystem_write = false, + allow_subprocess = false, + capabilities = [], + }, + metadata = { + name = "typing-bridge-config", + version = "0.1.0", + author = "Jonathan D.A. Jewell ", + }, + side_effects = [], }, panel = { id = "Typing Bridge", diff --git a/panel-clades/clades/ums/config.k9.ncl b/panel-clades/clades/ums/config.k9.ncl index 7c90388e..0d8e6144 100644 --- a/panel-clades/clades/ums/config.k9.ncl +++ b/panel-clades/clades/ums/config.k9.ncl @@ -1,13 +1,25 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 # K9 Kennel: Universal Modding Studio Panel Configuration # Auto-generated from PanelRegistry — pure data, no execution -# SPDX-License-Identifier: MPL-2.0 { pedigree = { - name = "ums-config", - version = "0.1.0", - magic_number = "K9!", - author = "Jonathan D.A. Jewell ", - leash = 'Kennel, + schema_version = "1.0.0", + component_type = "panel-clade-config", + security = { + leash = 'Kennel, + trust_level = "data-only", + allow_network = false, + allow_filesystem_write = false, + allow_subprocess = false, + capabilities = [], + }, + metadata = { + name = "ums-config", + version = "0.1.0", + author = "Jonathan D.A. Jewell ", + }, + side_effects = [], }, panel = { id = "Universal Modding Studio", diff --git a/panel-clades/clades/unit-test-runner/config.k9.ncl b/panel-clades/clades/unit-test-runner/config.k9.ncl index 4ce2f55d..f57ad2ac 100644 --- a/panel-clades/clades/unit-test-runner/config.k9.ncl +++ b/panel-clades/clades/unit-test-runner/config.k9.ncl @@ -1,13 +1,25 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 # K9 Kennel: Unit Test Runner Panel Configuration # Auto-generated from PanelRegistry — pure data, no execution -# SPDX-License-Identifier: MPL-2.0 { pedigree = { - name = "unit-test-runner-config", - version = "0.1.0", - magic_number = "K9!", - author = "Jonathan D.A. Jewell ", - leash = 'Kennel, + schema_version = "1.0.0", + component_type = "panel-clade-config", + security = { + leash = 'Kennel, + trust_level = "data-only", + allow_network = false, + allow_filesystem_write = false, + allow_subprocess = false, + capabilities = [], + }, + metadata = { + name = "unit-test-runner-config", + version = "0.1.0", + author = "Jonathan D.A. Jewell ", + }, + side_effects = [], }, panel = { id = "Unit Test Runner", diff --git a/panel-clades/clades/vab/config.k9.ncl b/panel-clades/clades/vab/config.k9.ncl index af241d40..ce769b7c 100644 --- a/panel-clades/clades/vab/config.k9.ncl +++ b/panel-clades/clades/vab/config.k9.ncl @@ -1,13 +1,25 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 # K9 Kennel: VAB Panel Configuration # Auto-generated from PanelRegistry — pure data, no execution -# SPDX-License-Identifier: MPL-2.0 { pedigree = { - name = "vab-config", - version = "0.1.0", - magic_number = "K9!", - author = "Jonathan D.A. Jewell ", - leash = 'Kennel, + schema_version = "1.0.0", + component_type = "panel-clade-config", + security = { + leash = 'Kennel, + trust_level = "data-only", + allow_network = false, + allow_filesystem_write = false, + allow_subprocess = false, + capabilities = [], + }, + metadata = { + name = "vab-config", + version = "0.1.0", + author = "Jonathan D.A. Jewell ", + }, + side_effects = [], }, panel = { id = "VAB", diff --git a/panel-clades/clades/valence-shell/config.k9.ncl b/panel-clades/clades/valence-shell/config.k9.ncl index a25b4fbc..1ce340a1 100644 --- a/panel-clades/clades/valence-shell/config.k9.ncl +++ b/panel-clades/clades/valence-shell/config.k9.ncl @@ -1,13 +1,25 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 # K9 Kennel: Valence Shell Panel Configuration # Auto-generated from PanelRegistry — pure data, no execution -# SPDX-License-Identifier: MPL-2.0 { pedigree = { - name = "valence-shell-config", - version = "0.1.0", - magic_number = "K9!", - author = "Jonathan D.A. Jewell ", - leash = 'Kennel, + schema_version = "1.0.0", + component_type = "panel-clade-config", + security = { + leash = 'Kennel, + trust_level = "data-only", + allow_network = false, + allow_filesystem_write = false, + allow_subprocess = false, + capabilities = [], + }, + metadata = { + name = "valence-shell-config", + version = "0.1.0", + author = "Jonathan D.A. Jewell ", + }, + side_effects = [], }, panel = { id = "Valence Shell", diff --git a/panel-clades/clades/verification-dashboard/config.k9.ncl b/panel-clades/clades/verification-dashboard/config.k9.ncl index 92a5c274..527b716d 100644 --- a/panel-clades/clades/verification-dashboard/config.k9.ncl +++ b/panel-clades/clades/verification-dashboard/config.k9.ncl @@ -1,13 +1,25 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 # K9 Kennel: Verification Dashboard Panel Configuration # Auto-generated from PanelRegistry — pure data, no execution -# SPDX-License-Identifier: MPL-2.0 { pedigree = { - name = "verification-dashboard-config", - version = "0.1.0", - magic_number = "K9!", - author = "Jonathan D.A. Jewell ", - leash = 'Kennel, + schema_version = "1.0.0", + component_type = "panel-clade-config", + security = { + leash = 'Kennel, + trust_level = "data-only", + allow_network = false, + allow_filesystem_write = false, + allow_subprocess = false, + capabilities = [], + }, + metadata = { + name = "verification-dashboard-config", + version = "0.1.0", + author = "Jonathan D.A. Jewell ", + }, + side_effects = [], }, panel = { id = "Verification Dashboard", diff --git a/panel-clades/clades/verisim-feeds/config.k9.ncl b/panel-clades/clades/verisim-feeds/config.k9.ncl index 7a17d4ba..aa13be28 100644 --- a/panel-clades/clades/verisim-feeds/config.k9.ncl +++ b/panel-clades/clades/verisim-feeds/config.k9.ncl @@ -1,13 +1,25 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 # K9 Kennel: VeriSimDB Feeds Panel Configuration # Auto-generated from PanelRegistry — pure data, no execution -# SPDX-License-Identifier: MPL-2.0 { pedigree = { - name = "verisim-feeds-config", - version = "0.1.0", - magic_number = "K9!", - author = "Jonathan D.A. Jewell ", - leash = 'Kennel, + schema_version = "1.0.0", + component_type = "panel-clade-config", + security = { + leash = 'Kennel, + trust_level = "data-only", + allow_network = false, + allow_filesystem_write = false, + allow_subprocess = false, + capabilities = [], + }, + metadata = { + name = "verisim-feeds-config", + version = "0.1.0", + author = "Jonathan D.A. Jewell ", + }, + side_effects = [], }, panel = { id = "VerisimdbFeeds", diff --git a/panel-clades/clades/vexometer-friction/config.k9.ncl b/panel-clades/clades/vexometer-friction/config.k9.ncl index 48354f88..f8c76ed0 100644 --- a/panel-clades/clades/vexometer-friction/config.k9.ncl +++ b/panel-clades/clades/vexometer-friction/config.k9.ncl @@ -1,13 +1,25 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 # K9 Kennel: Vexometer Friction Panel Configuration # Auto-generated from PanelRegistry — pure data, no execution -# SPDX-License-Identifier: MPL-2.0 { pedigree = { - name = "vexometer-friction-config", - version = "0.1.0", - magic_number = "K9!", - author = "Jonathan D.A. Jewell ", - leash = 'Kennel, + schema_version = "1.0.0", + component_type = "panel-clade-config", + security = { + leash = 'Kennel, + trust_level = "data-only", + allow_network = false, + allow_filesystem_write = false, + allow_subprocess = false, + capabilities = [], + }, + metadata = { + name = "vexometer-friction-config", + version = "0.1.0", + author = "Jonathan D.A. Jewell ", + }, + side_effects = [], }, panel = { id = "VexometerFriction", diff --git a/panel-clades/clades/vm-inspector/config.k9.ncl b/panel-clades/clades/vm-inspector/config.k9.ncl index fcf74d68..97dafd63 100644 --- a/panel-clades/clades/vm-inspector/config.k9.ncl +++ b/panel-clades/clades/vm-inspector/config.k9.ncl @@ -1,13 +1,25 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 # K9 Kennel: VM Inspector Panel Configuration # Auto-generated from PanelRegistry — pure data, no execution -# SPDX-License-Identifier: MPL-2.0 { pedigree = { - name = "vm-inspector-config", - version = "0.1.0", - magic_number = "K9!", - author = "Jonathan D.A. Jewell ", - leash = 'Kennel, + schema_version = "1.0.0", + component_type = "panel-clade-config", + security = { + leash = 'Kennel, + trust_level = "data-only", + allow_network = false, + allow_filesystem_write = false, + allow_subprocess = false, + capabilities = [], + }, + metadata = { + name = "vm-inspector-config", + version = "0.1.0", + author = "Jonathan D.A. Jewell ", + }, + side_effects = [], }, panel = { id = "VM Inspector", diff --git a/panel-clades/clades/voicetag/config.k9.ncl b/panel-clades/clades/voicetag/config.k9.ncl index 4fb02904..67b10ed8 100644 --- a/panel-clades/clades/voicetag/config.k9.ncl +++ b/panel-clades/clades/voicetag/config.k9.ncl @@ -1,13 +1,25 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 # K9 Kennel: Code MRI Panel Configuration # Auto-generated from PanelRegistry — pure data, no execution -# SPDX-License-Identifier: MPL-2.0 { pedigree = { - name = "voicetag-config", - version = "0.1.0", - magic_number = "K9!", - author = "Jonathan D.A. Jewell ", - leash = 'Kennel, + schema_version = "1.0.0", + component_type = "panel-clade-config", + security = { + leash = 'Kennel, + trust_level = "data-only", + allow_network = false, + allow_filesystem_write = false, + allow_subprocess = false, + capabilities = [], + }, + metadata = { + name = "voicetag-config", + version = "0.1.0", + author = "Jonathan D.A. Jewell ", + }, + side_effects = [], }, panel = { id = "Code MRI", diff --git a/panel-clades/clades/wiring-inspector/config.k9.ncl b/panel-clades/clades/wiring-inspector/config.k9.ncl index d4c68965..fd05ff99 100644 --- a/panel-clades/clades/wiring-inspector/config.k9.ncl +++ b/panel-clades/clades/wiring-inspector/config.k9.ncl @@ -1,13 +1,25 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 # K9 Kennel: Wiring Inspector Panel Configuration # Auto-generated from PanelRegistry — pure data, no execution -# SPDX-License-Identifier: MPL-2.0 { pedigree = { - name = "wiring-inspector-config", - version = "0.1.0", - magic_number = "K9!", - author = "Jonathan D.A. Jewell ", - leash = 'Kennel, + schema_version = "1.0.0", + component_type = "panel-clade-config", + security = { + leash = 'Kennel, + trust_level = "data-only", + allow_network = false, + allow_filesystem_write = false, + allow_subprocess = false, + capabilities = [], + }, + metadata = { + name = "wiring-inspector-config", + version = "0.1.0", + author = "Jonathan D.A. Jewell ", + }, + side_effects = [], }, panel = { id = "Wiring Inspector", diff --git a/panel-clades/clades/workspace/config.k9.ncl b/panel-clades/clades/workspace/config.k9.ncl index 115cd868..5ba85c39 100644 --- a/panel-clades/clades/workspace/config.k9.ncl +++ b/panel-clades/clades/workspace/config.k9.ncl @@ -1,13 +1,25 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 # K9 Kennel: Workspace Panel Configuration # Auto-generated from PanelRegistry — pure data, no execution -# SPDX-License-Identifier: MPL-2.0 { pedigree = { - name = "workspace-config", - version = "0.1.0", - magic_number = "K9!", - author = "Jonathan D.A. Jewell ", - leash = 'Kennel, + schema_version = "1.0.0", + component_type = "panel-clade-config", + security = { + leash = 'Kennel, + trust_level = "data-only", + allow_network = false, + allow_filesystem_write = false, + allow_subprocess = false, + capabilities = [], + }, + metadata = { + name = "workspace-config", + version = "0.1.0", + author = "Jonathan D.A. Jewell ", + }, + side_effects = [], }, panel = { id = "Workspace", diff --git a/panel-clades/container/0-AI-MANIFEST.a2ml b/panel-clades/container/0-AI-MANIFEST.a2ml index e28373a8..16c3f380 100644 --- a/panel-clades/container/0-AI-MANIFEST.a2ml +++ b/panel-clades/container/0-AI-MANIFEST.a2ml @@ -30,7 +30,7 @@ canonical_locations: build_pipeline: "container/ct-build.sh" entrypoint: "container/entrypoint.sh" monitoring: "container/vordr.toml" - deployment: "container/deploy.k9.ncl" + deployment: "container/deploy.k9.ncl.in" example: "container/compose.example.toml" --- @@ -90,7 +90,7 @@ files: detection, resource thresholds, and structured log output. depends_on: [] - - name: "deploy.k9.ncl" + - name: "deploy.k9.ncl.in" role: "Deployment Component" description: | k9-svc deployment specification at Hunt trust level. Full pedigree diff --git a/panel-clades/container/README.adoc b/panel-clades/container/README.adoc index c857aff5..85d1b620 100644 --- a/panel-clades/container/README.adoc +++ b/panel-clades/container/README.adoc @@ -59,7 +59,7 @@ All files use `container` tokens that are replaced by `just container-init` | **vordr** runtime monitoring configuration. Defines health endpoints, crash detection, resource thresholds, and log output. -| `deploy.k9.ncl` +| `deploy.k9.ncl.in` | **k9-svc** deployment component at Hunt trust level. Full pedigree (L1--L5), environment configs (dev/staging/prod), container configuration, and rolling deployment strategy. @@ -155,7 +155,7 @@ For k9-svc managed deployments: [source,bash] ---- # Validate the deployment component -nickel typecheck container/deploy.k9.ncl +nickel typecheck container/deploy.k9.ncl # after rendering deploy.k9.ncl.in # Deploy (requires Hunt-level authorisation) k9-svc deploy container/deploy.k9.ncl --env production diff --git a/panel-clades/container/deploy.k9.ncl b/panel-clades/container/deploy.k9.ncl.in similarity index 91% rename from panel-clades/container/deploy.k9.ncl rename to panel-clades/container/deploy.k9.ncl.in index 0ad0d04a..b632c52d 100644 --- a/panel-clades/container/deploy.k9.ncl +++ b/panel-clades/container/deploy.k9.ncl.in @@ -1,6 +1,15 @@ # SPDX-License-Identifier: MPL-2.0 # deploy.k9.ncl — {{PROJECT_NAME}} deployment component (Hunt level) # +# .k9.ncl.in — a TEMPLATE, not a component (K9 contract v1.0.0, D173). Its +# {{PLACEHOLDER}} tokens are not Nickel, so it must not claim the reserved +# `.k9.ncl` suffix: a template is never loaded or leashed. Fill every token, +# then bring the pedigree up to contract v1.0.0 (schema_version, +# component_type, security.leash = 'Hunt with a capability grant, +# side_effects and a signature block) before saving it as deploy.k9.ncl. +# rsr-template-repo's build/container/stapeln/deploy.k9.ncl.in is the +# v1.0.0-shaped version of this template. +# # k9-svc deployment specification with full pedigree (L1-L5). # Security Level: 'Hunt (requires cryptographic handshake for execution). # From cb77f07c03888e43c79110b4ff10a890876da2a1 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 27 Oct 2026 01:29:16 +0000 Subject: [PATCH 02/21] ci: repin standards to metadatastician and adopt fork-governance MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit hyperpolymath/standards moved to metadatastician/standards. Every caller pinned under the old owner failed at startup with "workflow was not found", because actions.lock recorded the old owner. - Repin governance, hypatia-scan, scorecard, secret-scanner and mirror to metadatastician/standards@1fdc61c (standards main), and rust-ci to metadatastician/standards@412a703 (same commit, new owner). - Add fork-governance.yml per standards' fork-governance ADOPTION.adoc: the DEED, K9-DEED and Hypatia lanes plus one PR comment, all at the same standards commit as canon-ref. DEED runs with require-files: false, since PanLL has no .deed files yet (standards #837). - hypatia-scan.yml loses its pull_request trigger (ADOPTION §3); fork-governance runs the Hypatia lane on PRs. - dogfood-gate.yml drops the k9-ecosystem validate-action job, which rejected the `K9!` envelope that contract v1.0.0 requires; the K9-DEED lane replaces it. - actions.lock: standards records re-keyed to metadatastician (owner_id 297579217), nested uses recomputed over every called file at 1fdc61c, and records nothing references any more are pruned. Verified with standards' check-actions-lock-gate.sh, validate-actions-lock.sh and update-actions-lock.sh --verify-local at 1fdc61c. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01BCQeL169ZnUirUVaDXQLA3 --- .github/workflows/actions.lock | 58 +++++++++++---------- .github/workflows/dogfood-gate.yml | 56 +++----------------- .github/workflows/fork-governance.yml | 74 +++++++++++++++++++++++++++ .github/workflows/governance.yml | 4 +- .github/workflows/hypatia-scan.yml | 11 ++-- .github/workflows/mirror.yml | 2 +- .github/workflows/rust-ci.yml | 4 +- .github/workflows/scorecard.yml | 2 +- .github/workflows/secret-scanner.yml | 2 +- 9 files changed, 124 insertions(+), 89 deletions(-) create mode 100644 .github/workflows/fork-governance.yml diff --git a/.github/workflows/actions.lock b/.github/workflows/actions.lock index 28bf9f33..4657e3d6 100644 --- a/.github/workflows/actions.lock +++ b/.github/workflows/actions.lock @@ -21,16 +21,17 @@ workflows: '.github/workflows/dogfood-gate.yml': - 'actions/checkout@v7.0.1' - 'hyperpolymath/deed-ecosystem@main' - - 'hyperpolymath/k9-ecosystem@main' '.github/workflows/e2e.yml': - 'actions/checkout@v7.0.1' - 'dtolnay/rust-toolchain@v1' - 'goto-bus-stop/setup-zig@v2.2.1' - 'swatinem/rust-cache@v2.9.3' + '.github/workflows/fork-governance.yml': + - 'metadatastician/standards@1fdc61c0c1c8f4c94324f956700d78d1ea9584a8' '.github/workflows/governance.yml': - - 'hyperpolymath/standards@75bd5994470661a099df562d6d5ad2fe744dbd66' + - 'metadatastician/standards@1fdc61c0c1c8f4c94324f956700d78d1ea9584a8' '.github/workflows/hypatia-scan.yml': - - 'hyperpolymath/standards@75bd5994470661a099df562d6d5ad2fe744dbd66' + - 'metadatastician/standards@1fdc61c0c1c8f4c94324f956700d78d1ea9584a8' '.github/workflows/instant-sync.yml': - 'peter-evans/repository-dispatch@v4.0.1' '.github/workflows/label-triage.yml': [] @@ -38,15 +39,15 @@ workflows: '.github/workflows/main-estate-audit.yml': - 'hyperpolymath/cicd-suite@4f5404281dd7092fa77cc18daec7237c4f1ee4ac' '.github/workflows/mirror.yml': - - 'hyperpolymath/standards@75bd5994470661a099df562d6d5ad2fe744dbd66' + - 'metadatastician/standards@1fdc61c0c1c8f4c94324f956700d78d1ea9584a8' '.github/workflows/push-email-notify.yml': - 'hyperpolymath/smtp-notify-action@v0.5.0' '.github/workflows/rust-ci.yml': - - 'hyperpolymath/standards@412a7031577112b31ee287cc6060179d638d6500' + - 'metadatastician/standards@412a7031577112b31ee287cc6060179d638d6500' '.github/workflows/scorecard.yml': - - 'hyperpolymath/standards@75bd5994470661a099df562d6d5ad2fe744dbd66' + - 'metadatastician/standards@1fdc61c0c1c8f4c94324f956700d78d1ea9584a8' '.github/workflows/secret-scanner.yml': - - 'hyperpolymath/standards@75bd5994470661a099df562d6d5ad2fe744dbd66' + - 'metadatastician/standards@1fdc61c0c1c8f4c94324f956700d78d1ea9584a8' dependencies: 'actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9': ref: 'v6.1.0' @@ -78,6 +79,11 @@ dependencies: commit: 'sha1-368f82528645a54fb793d4d04e342629a3f51346' owner_id: 44036562 repo_id: 438112499 + 'actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333': + ref: 'v8.0.2' + commit: 'sha1-9000827ccba6bdab643e8b6fd33ac0654aef8333' + owner_id: 44036562 + repo_id: 192626254 'actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f': ref: 'v7.0.0' commit: 'sha1-bbbca2ddaa5d8feaa63e36b76fdaad77386f024f' @@ -100,9 +106,9 @@ dependencies: commit: 'sha1-22d081ff2d3a40755e97629de92e3bcbfa7cf2ed' owner_id: 42048915 repo_id: 356423100 - 'dtolnay/rust-toolchain@e2a55d2ffb04f378e9626c28d38b36d230d1e12f': + 'dtolnay/rust-toolchain@683b588c487a69d01f6c6ddf40f036ff682cc2c8': ref: 'v1' - commit: 'sha1-e2a55d2ffb04f378e9626c28d38b36d230d1e12f' + commit: 'sha1-683b588c487a69d01f6c6ddf40f036ff682cc2c8' owner_id: 1940490 repo_id: 260749683 'dtolnay/rust-toolchain@v1': @@ -171,41 +177,37 @@ dependencies: commit: 'sha1-2155aa26a21758f2ba119f61bc7e0e1981c106fb' owner_id: 6759885 repo_id: 1275650185 - 'hyperpolymath/k9-ecosystem@main': - ref: 'main' - commit: 'sha1-20f6be5b5a14a48680b236955b5c4ad9033d00d4' - owner_id: 6759885 - repo_id: 1275650185 'hyperpolymath/smtp-notify-action@v0.5.0': ref: 'v0.5.0' commit: 'sha1-c1c9fa07992a02c1fd3d67a0dc1b08cccb852aef' owner_id: 6759885 repo_id: 1352485172 - 'hyperpolymath/standards@412a7031577112b31ee287cc6060179d638d6500': - ref: '412a7031577112b31ee287cc6060179d638d6500' - commit: 'sha1-412a7031577112b31ee287cc6060179d638d6500' - owner_id: 6759885 - repo_id: 1116521501 - 'hyperpolymath/standards@75bd5994470661a099df562d6d5ad2fe744dbd66': - ref: '75bd5994470661a099df562d6d5ad2fe744dbd66' - commit: 'sha1-75bd5994470661a099df562d6d5ad2fe744dbd66' - owner_id: 6759885 + 'metadatastician/standards@1fdc61c0c1c8f4c94324f956700d78d1ea9584a8': + ref: '1fdc61c0c1c8f4c94324f956700d78d1ea9584a8' + commit: 'sha1-1fdc61c0c1c8f4c94324f956700d78d1ea9584a8' + owner_id: 297579217 repo_id: 1116521501 uses: - 'actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9' - 'actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1' + - 'actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333' - 'actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9' - 'denoland/setup-deno@22d081ff2d3a40755e97629de92e3bcbfa7cf2ed' - - 'dtolnay/rust-toolchain@e2a55d2ffb04f378e9626c28d38b36d230d1e12f' + - 'dtolnay/rust-toolchain@683b588c487a69d01f6c6ddf40f036ff682cc2c8' - 'editorconfig-checker/action-editorconfig-checker@51f63319f592f97930c73d9c46184d20bd206393' - 'erlef/setup-beam@54075bcc5e249e4758d363f27d099f55d843f124' - 'github/codeql-action@b96794f015dfd88f77b49b1c93e0fa7110f94c63' - - 'ossf/scorecard-action@2d1146689b8cda280b9bc96326124645441f03bc' + - 'ossf/scorecard-action@fa7117db83c52d787ae5a89f350999701505d78e' - 'oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6' - 'webfactory/ssh-agent@e83874834305fe9a4a2997156cb26c5de65a8555' - 'ossf/scorecard-action@2d1146689b8cda280b9bc96326124645441f03bc': - ref: 'v2.4.4' - commit: 'sha1-2d1146689b8cda280b9bc96326124645441f03bc' + 'metadatastician/standards@412a7031577112b31ee287cc6060179d638d6500': + ref: '412a7031577112b31ee287cc6060179d638d6500' + commit: 'sha1-412a7031577112b31ee287cc6060179d638d6500' + owner_id: 297579217 + repo_id: 1116521501 + 'ossf/scorecard-action@fa7117db83c52d787ae5a89f350999701505d78e': + ref: 'v2.5.0' + commit: 'sha1-fa7117db83c52d787ae5a89f350999701505d78e' owner_id: 67707773 repo_id: 421101922 'oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6': diff --git a/.github/workflows/dogfood-gate.yml b/.github/workflows/dogfood-gate.yml index ecfaa6d5..90a1da90 100644 --- a/.github/workflows/dogfood-gate.yml +++ b/.github/workflows/dogfood-gate.yml @@ -63,55 +63,11 @@ jobs: echo "Scanned **${MANIFEST_COUNT}** .a2ml/.deed manifest(s). See step output for details." >> "$GITHUB_STEP_SUMMARY" fi - # --------------------------------------------------------------------------- - # Job 2: K9 contract validation - # --------------------------------------------------------------------------- - k9-validate: - name: Validate K9 contracts - runs-on: ubuntu-latest - timeout-minutes: 10 - - steps: - - name: Checkout repository - uses: actions/checkout@v7.0.1 - - - name: Check for K9 files - id: detect - run: | - COUNT=$(find . \( -name '*.k9' -o -name '*.k9.ncl' \) -not -path './.git/*' | wc -l) - CONFIG_COUNT=$(find . \( -name '*.toml' -o -name '*.yaml' -o -name '*.yml' -o -name '*.json' \) \ - -not -path './.git/*' -not -path './node_modules/*' -not -path './.deno/*' \ - -not -name 'package-lock.json' -not -name 'Cargo.lock' -not -name 'deno.lock' | wc -l) - echo "k9_count=$COUNT" >> "$GITHUB_OUTPUT" - echo "config_count=$CONFIG_COUNT" >> "$GITHUB_OUTPUT" - if [ "$COUNT" -eq 0 ] && [ "$CONFIG_COUNT" -gt 0 ]; then - echo "::warning::Found $CONFIG_COUNT config files but no K9 contracts. Run k9iser to generate contracts." - fi - - - name: Validate K9 contracts - if: steps.detect.outputs.k9_count > 0 - uses: hyperpolymath/k9-ecosystem/validate-action@main - with: - path: '.' - strict: 'false' - - - name: Write summary - run: | - K9_COUNT="${{ steps.detect.outputs.k9_count }}" - CFG_COUNT="${{ steps.detect.outputs.config_count }}" - if [ "$K9_COUNT" -eq 0 ]; then - cat <<'EOF' >> "$GITHUB_STEP_SUMMARY" - ## K9 Contract Validation - - :warning: **No .a2ml/.deed manifest files found.** Every RSR-compliant repo should have a repo deed (`_chora.deed`) at its root. - - Generate contracts with: `k9iser generate .` - EOF - else - echo "## K9 Contract Validation" >> "$GITHUB_STEP_SUMMARY" - echo "" >> "$GITHUB_STEP_SUMMARY" - echo "Validated **${K9_COUNT}** K9 contract(s) against **${CFG_COUNT}** config file(s)." >> "$GITHUB_STEP_SUMMARY" - fi + # K9 contract validation moved to fork-governance.yml, which runs the + # canonical K9-DEED conformance lane (metadatastician/standards + # k9-conformance.yml, contract v1.0.0) and reports it in the PR comment. + # The k9-ecosystem validate-action that ran here rejected the `K9!` + # envelope the contract requires, so the two could not both pass. # --------------------------------------------------------------------------- # Job 3: Empty-linter — invisible character detection @@ -244,7 +200,7 @@ jobs: name: Dogfooding compliance summary runs-on: ubuntu-latest timeout-minutes: 10 - needs: [deed-validate, k9-validate, empty-lint, groove-check] + needs: [deed-validate, empty-lint, groove-check] if: always() steps: diff --git a/.github/workflows/fork-governance.yml b/.github/workflows/fork-governance.yml new file mode 100644 index 00000000..afc31c8e --- /dev/null +++ b/.github/workflows/fork-governance.yml @@ -0,0 +1,74 @@ +# This workflow is managed by gh actions-lock. +# SPDX-License-Identifier: MPL-2.0 +# fork-governance.yml — one PR comment for the DEED, K9-DEED and Hypatia lanes. +# +# Adopted per metadatastician/standards 3-practice/fork-governance/ADOPTION.adoc. +# The three lanes produce verdicts and artifacts; the verdict job reads them +# from this workflow's `needs` graph (a reusable cannot see its caller's jobs) +# and posts one comment, updated in place on every push. +# +# All four `uses:` and `canon-ref` are the same standards commit: two refs that +# can drift are two renderers that can disagree. +# +# DEED: `require-files: false` — PanLL has no `.deed` files yet (its manifests +# are still `.a2ml`, pending the estate-wide `.a2ml` → `.deed` migration, +# standards #837), so the lane reports grey "not applicable" instead of red. +name: Fork governance + +on: + pull_request: + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + deed: + name: DEED syntax + uses: metadatastician/standards/.github/workflows/deed-conformance.yml@1fdc61c0c1c8f4c94324f956700d78d1ea9584a8 + permissions: + contents: read + with: + require-files: false + + k9: + name: K9-DEED conformance + uses: metadatastician/standards/.github/workflows/k9-conformance.yml@1fdc61c0c1c8f4c94324f956700d78d1ea9584a8 + permissions: + contents: read + pull-requests: read + with: + require-files: true + fail-on-indeterminate: false + + hypatia: + name: Hypatia verdict grid + uses: metadatastician/standards/.github/workflows/hypatia-scan-reusable.yml@1fdc61c0c1c8f4c94324f956700d78d1ea9584a8 + permissions: + actions: read + contents: read + security-events: write + secrets: + HYPATIA_SCAN_PAT: ${{ secrets.HYPATIA_SCAN_PAT }} + + verdict: + name: Post the fork governance summary + needs: [deed, k9, hypatia] + if: ${{ always() && github.event_name == 'pull_request' }} + uses: metadatastician/standards/.github/workflows/fork-governance-reusable.yml@1fdc61c0c1c8f4c94324f956700d78d1ea9584a8 + permissions: + contents: read + actions: read + pull-requests: write + with: + pr-number: ${{ github.event.pull_request.number }} + deed-result: ${{ needs.deed.result }} + k9-result: ${{ needs.k9.result }} + k9-verdict: ${{ needs.k9.outputs.verdict }} + hypatia-result: ${{ needs.hypatia.result }} + hypatia-verdict: ${{ needs.hypatia.outputs.verdict }} + canon-ref: 1fdc61c0c1c8f4c94324f956700d78d1ea9584a8 diff --git a/.github/workflows/governance.yml b/.github/workflows/governance.yml index 95eafe72..1d006dc5 100644 --- a/.github/workflows/governance.yml +++ b/.github/workflows/governance.yml @@ -1,7 +1,7 @@ # This workflow is managed by gh actions-lock. # SPDX-License-Identifier: MPL-2.0 # governance.yml — single wrapper calling the shared estate governance bundle -# in hyperpolymath/standards instead of carrying per-repo copies. +# in metadatastician/standards instead of carrying per-repo copies. # # Replaces the per-repo governance scaffolding removed in the same commit: # quality.yml, guix-nix-policy.yml, npm-bun-blocker.yml, ts-blocker.yml, @@ -33,6 +33,6 @@ permissions: jobs: governance: - uses: hyperpolymath/standards/.github/workflows/governance-reusable.yml@75bd5994470661a099df562d6d5ad2fe744dbd66 + uses: metadatastician/standards/.github/workflows/governance-reusable.yml@1fdc61c0c1c8f4c94324f956700d78d1ea9584a8 secrets: HYPATIA_SCAN_PAT: ${{ secrets.HYPATIA_SCAN_PAT }} diff --git a/.github/workflows/hypatia-scan.yml b/.github/workflows/hypatia-scan.yml index 06edf194..74c202b1 100644 --- a/.github/workflows/hypatia-scan.yml +++ b/.github/workflows/hypatia-scan.yml @@ -1,15 +1,18 @@ # This workflow is managed by gh actions-lock. # SPDX-License-Identifier: MPL-2.0 -# Thin wrapper around hyperpolymath/standards hypatia-scan-reusable.yml. +# Thin wrapper around metadatastician/standards hypatia-scan-reusable.yml. # See standards#191 for the reusable's purpose and design. +# +# Pull requests run the Hypatia lane through fork-governance.yml, which posts +# its verdict grid in the PR comment. This workflow has no pull_request +# trigger so a PR does not scan twice and upload duplicate code-scanning +# results (standards 3-practice/fork-governance/ADOPTION.adoc §3). name: Hypatia Security Scan on: push: branches: [main, master, develop] - pull_request: - branches: [main, master] schedule: - cron: '0 0 * * 0' workflow_dispatch: @@ -27,6 +30,6 @@ permissions: jobs: hypatia: - uses: hyperpolymath/standards/.github/workflows/hypatia-scan-reusable.yml@75bd5994470661a099df562d6d5ad2fe744dbd66 + uses: metadatastician/standards/.github/workflows/hypatia-scan-reusable.yml@1fdc61c0c1c8f4c94324f956700d78d1ea9584a8 secrets: HYPATIA_SCAN_PAT: ${{ secrets.HYPATIA_SCAN_PAT }} diff --git a/.github/workflows/mirror.yml b/.github/workflows/mirror.yml index 6182ddfd..ececb1e1 100644 --- a/.github/workflows/mirror.yml +++ b/.github/workflows/mirror.yml @@ -13,7 +13,7 @@ permissions: jobs: mirror: - uses: hyperpolymath/standards/.github/workflows/mirror-reusable.yml@75bd5994470661a099df562d6d5ad2fe744dbd66 + uses: metadatastician/standards/.github/workflows/mirror-reusable.yml@1fdc61c0c1c8f4c94324f956700d78d1ea9584a8 secrets: GITLAB_SSH_KEY: ${{ secrets.GITLAB_SSH_KEY }} BITBUCKET_SSH_KEY: ${{ secrets.BITBUCKET_SSH_KEY }} diff --git a/.github/workflows/rust-ci.yml b/.github/workflows/rust-ci.yml index ca53233b..99cf5433 100644 --- a/.github/workflows/rust-ci.yml +++ b/.github/workflows/rust-ci.yml @@ -1,7 +1,7 @@ # This workflow is managed by gh actions-lock. # SPDX-License-Identifier: MPL-2.0 # Rust CI — thin wrapper calling the shared estate reusable in -# hyperpolymath/standards. Configure once, propagate everywhere. +# metadatastician/standards. Configure once, propagate everywhere. # See: docs/CI-REUSABLE-WORKFLOWS.adoc in standards. name: Rust CI @@ -16,4 +16,4 @@ permissions: jobs: rust-ci: - uses: hyperpolymath/standards/.github/workflows/rust-ci-reusable.yml@412a7031577112b31ee287cc6060179d638d6500 + uses: metadatastician/standards/.github/workflows/rust-ci-reusable.yml@412a7031577112b31ee287cc6060179d638d6500 diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 47ce57a7..5acbc445 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -16,4 +16,4 @@ jobs: contents: read security-events: write id-token: write - uses: hyperpolymath/standards/.github/workflows/scorecard-reusable.yml@75bd5994470661a099df562d6d5ad2fe744dbd66 + uses: metadatastician/standards/.github/workflows/scorecard-reusable.yml@1fdc61c0c1c8f4c94324f956700d78d1ea9584a8 diff --git a/.github/workflows/secret-scanner.yml b/.github/workflows/secret-scanner.yml index f5e9834a..590c85a1 100644 --- a/.github/workflows/secret-scanner.yml +++ b/.github/workflows/secret-scanner.yml @@ -21,4 +21,4 @@ jobs: contents: read pull-requests: write actions: read - uses: hyperpolymath/standards/.github/workflows/secret-scanner-reusable.yml@75bd5994470661a099df562d6d5ad2fe744dbd66 + uses: metadatastician/standards/.github/workflows/secret-scanner-reusable.yml@1fdc61c0c1c8f4c94324f956700d78d1ea9584a8 From e5b52dbfcb8111b05697d49d9f1df67e8289c748 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 27 Oct 2026 01:32:07 +0000 Subject: [PATCH 03/21] k9: untrack generated/k9iser/*.k9 These are k9iser output, already ignored by .gitignore. The previous commit meant to untrack them but the removal was dropped while the change was split into commits, so the K9-DEED lane still scanned them and failed (no SPDX header, not Nickel). Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01BCQeL169ZnUirUVaDXQLA3 --- generated/k9iser/clade-portal-config.k9 | 18 ------------------ generated/k9iser/contract-security.k9 | 13 ------------- generated/k9iser/contract-workspace.k9 | 13 ------------- generated/k9iser/panel-harness.k9 | 13 ------------- generated/k9iser/pcc-cargo.k9 | 14 -------------- generated/k9iser/rescript-compiler.k9 | 16 ---------------- 6 files changed, 87 deletions(-) delete mode 100644 generated/k9iser/clade-portal-config.k9 delete mode 100644 generated/k9iser/contract-security.k9 delete mode 100644 generated/k9iser/contract-workspace.k9 delete mode 100644 generated/k9iser/panel-harness.k9 delete mode 100644 generated/k9iser/pcc-cargo.k9 delete mode 100644 generated/k9iser/rescript-compiler.k9 diff --git a/generated/k9iser/clade-portal-config.k9 b/generated/k9iser/clade-portal-config.k9 deleted file mode 100644 index 5a6b44b5..00000000 --- a/generated/k9iser/clade-portal-config.k9 +++ /dev/null @@ -1,18 +0,0 @@ -# Auto-generated K9 contract for clade-portal-config -# Safety tier: yard - -[must] -app.security.sandbox.enabled : string { == true } -app.ipc.timeout : string { > 0 } -app.ipc.maxMessageSize : string { > 0 } -bundle.license : string { == 'MPL-2.0' } -ephapax.linearVerification : string { == true } - -[trust] -signed-by = "ci-pipeline" - -[dust] -# (no dust rules defined) - -[intend] -production-ready = true diff --git a/generated/k9iser/contract-security.k9 b/generated/k9iser/contract-security.k9 deleted file mode 100644 index 67ab104c..00000000 --- a/generated/k9iser/contract-security.k9 +++ /dev/null @@ -1,13 +0,0 @@ -# Auto-generated K9 contract for contract-security -# Safety tier: yard - -[must] - -[trust] -signed-by = "ci-pipeline" - -[dust] -# (no dust rules defined) - -[intend] -panel-contract-valid = true diff --git a/generated/k9iser/contract-workspace.k9 b/generated/k9iser/contract-workspace.k9 deleted file mode 100644 index ed3021e0..00000000 --- a/generated/k9iser/contract-workspace.k9 +++ /dev/null @@ -1,13 +0,0 @@ -# Auto-generated K9 contract for contract-workspace -# Safety tier: yard - -[must] - -[trust] -signed-by = "ci-pipeline" - -[dust] -# (no dust rules defined) - -[intend] -panel-contract-valid = true diff --git a/generated/k9iser/panel-harness.k9 b/generated/k9iser/panel-harness.k9 deleted file mode 100644 index 9e3baabf..00000000 --- a/generated/k9iser/panel-harness.k9 +++ /dev/null @@ -1,13 +0,0 @@ -# Auto-generated K9 contract for panel-harness -# Safety tier: yard - -[must] - -[trust] -signed-by = "ci-pipeline" - -[dust] -# (no dust rules defined) - -[intend] -production-ready = true diff --git a/generated/k9iser/pcc-cargo.k9 b/generated/k9iser/pcc-cargo.k9 deleted file mode 100644 index 4bf2b2c7..00000000 --- a/generated/k9iser/pcc-cargo.k9 +++ /dev/null @@ -1,14 +0,0 @@ -# Auto-generated K9 contract for pcc-cargo -# Safety tier: yard - -[must] -package.edition : string { == '2021' } - -[trust] -signed-by = "ci-pipeline" - -[dust] -# (no dust rules defined) - -[intend] -production-ready = true diff --git a/generated/k9iser/rescript-compiler.k9 b/generated/k9iser/rescript-compiler.k9 deleted file mode 100644 index c886ffdb..00000000 --- a/generated/k9iser/rescript-compiler.k9 +++ /dev/null @@ -1,16 +0,0 @@ -# Auto-generated K9 contract for rescript-compiler -# Safety tier: yard - -[must] -name : string { == 'panll' } -package-specs[0].module : string { == 'esmodule' } -package-specs[0].in-source : string { == true } - -[trust] -signed-by = "ci-pipeline" - -[dust] -remove = ["deprecated suffix .js (use .res.js)"] - -[intend] -production-ready = true From 74e34e6d9770ba8bee75f0461b532bc1f7222dcc Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 27 Oct 2026 01:37:59 +0000 Subject: [PATCH 04/21] ci(hypatia-scan): least-privilege token permissions Top-level permissions drop to contents: read; the job gets actions: read, contents: read and security-events: write, which is what hypatia-scan-reusable declares. pull-requests: write goes: the workflow no longer runs on pull_request. Addresses the Scorecard Token-Permissions alert on this file that fork-governance's Hypatia lane grades. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01BCQeL169ZnUirUVaDXQLA3 --- .github/workflows/hypatia-scan.yml | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/.github/workflows/hypatia-scan.yml b/.github/workflows/hypatia-scan.yml index 74c202b1..022a2283 100644 --- a/.github/workflows/hypatia-scan.yml +++ b/.github/workflows/hypatia-scan.yml @@ -22,14 +22,18 @@ concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true +# Read-only at the top level; the job gets the one write it needs (uploading +# SARIF), which is all the reusable declares. No pull_request trigger, so no +# pull-requests permission. permissions: - actions: read contents: read - security-events: write - pull-requests: write jobs: hypatia: - uses: metadatastician/standards/.github/workflows/hypatia-scan-reusable.yml@1fdc61c0c1c8f4c94324f956700d78d1ea9584a8 + permissions: + actions: read + contents: read + security-events: write + uses:metadatastician/standards/.github/workflows/hypatia-scan-reusable.yml@1fdc61c0c1c8f4c94324f956700d78d1ea9584a8 secrets: HYPATIA_SCAN_PAT: ${{ secrets.HYPATIA_SCAN_PAT }} From 85e71b2bf0f398e1456f02f6bac003440386aac3 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 27 Oct 2026 01:38:12 +0000 Subject: [PATCH 05/21] ci(hypatia-scan): restore the space after uses: The previous commit dropped it, so the workflow did not parse. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01BCQeL169ZnUirUVaDXQLA3 --- .github/workflows/hypatia-scan.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/hypatia-scan.yml b/.github/workflows/hypatia-scan.yml index 022a2283..9fd288bf 100644 --- a/.github/workflows/hypatia-scan.yml +++ b/.github/workflows/hypatia-scan.yml @@ -34,6 +34,6 @@ jobs: actions: read contents: read security-events: write - uses:metadatastician/standards/.github/workflows/hypatia-scan-reusable.yml@1fdc61c0c1c8f4c94324f956700d78d1ea9584a8 + uses: metadatastician/standards/.github/workflows/hypatia-scan-reusable.yml@1fdc61c0c1c8f4c94324f956700d78d1ea9584a8 secrets: HYPATIA_SCAN_PAT: ${{ secrets.HYPATIA_SCAN_PAT }} From dc04df74584dd4dae93850f546ba866090551abb Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 27 Oct 2026 01:42:23 +0000 Subject: [PATCH 06/21] k9: render the deploy template, grant Hunt capabilities, keep push coverage Addresses three review findings on this PR: - panel-clades/container/deploy.k9.ncl.in is now the v1.0.0-shaped template ported from rsr-template-repo (K9! envelope, pedigree with capability grant, side effects and a signature block), adapted to this layout (container/, compose.toml). `just container-init` renders it to container/deploy.k9.ncl once no placeholder is left, as rsr-template-repo does; before, nothing produced the deploy.k9.ncl the docs use. Checked: rendered with sample values via `just container-init`, it passes k9-validate --strict --layer L2. - template-hunt.k9.ncl.in grants net.fetch, fs.write and process.spawn, which its allow_* flags request (K9-S007), and carries the Ed25519 presence block a Hunt component needs (K9-S009). A copy with its TODOs filled now passes L2. - dogfood-gate.yml runs the standards K9-DEED lane on pushes to main/master, so a direct push is still checked; pull requests keep running it once, in fork-governance.yml. actions.lock records the call. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01BCQeL169ZnUirUVaDXQLA3 --- .github/workflows/actions.lock | 1 + .github/workflows/dogfood-gate.yml | 23 +- .../svc/k9/template-hunt.k9.ncl.in | 13 + panel-clades/Justfile | 20 ++ panel-clades/container/README.adoc | 13 +- panel-clades/container/deploy.k9.ncl.in | 284 +++++++++--------- 6 files changed, 210 insertions(+), 144 deletions(-) diff --git a/.github/workflows/actions.lock b/.github/workflows/actions.lock index 4657e3d6..b8c13882 100644 --- a/.github/workflows/actions.lock +++ b/.github/workflows/actions.lock @@ -21,6 +21,7 @@ workflows: '.github/workflows/dogfood-gate.yml': - 'actions/checkout@v7.0.1' - 'hyperpolymath/deed-ecosystem@main' + - 'metadatastician/standards@1fdc61c0c1c8f4c94324f956700d78d1ea9584a8' '.github/workflows/e2e.yml': - 'actions/checkout@v7.0.1' - 'dtolnay/rust-toolchain@v1' diff --git a/.github/workflows/dogfood-gate.yml b/.github/workflows/dogfood-gate.yml index 90a1da90..f4810bdf 100644 --- a/.github/workflows/dogfood-gate.yml +++ b/.github/workflows/dogfood-gate.yml @@ -63,11 +63,24 @@ jobs: echo "Scanned **${MANIFEST_COUNT}** .a2ml/.deed manifest(s). See step output for details." >> "$GITHUB_STEP_SUMMARY" fi - # K9 contract validation moved to fork-governance.yml, which runs the - # canonical K9-DEED conformance lane (metadatastician/standards - # k9-conformance.yml, contract v1.0.0) and reports it in the PR comment. - # The k9-ecosystem validate-action that ran here rejected the `K9!` - # envelope the contract requires, so the two could not both pass. + # --------------------------------------------------------------------------- + # Job 2: K9-DEED conformance on pushes to main/master + # --------------------------------------------------------------------------- + # The canonical lane (metadatastician/standards k9-conformance.yml, contract + # v1.0.0). Pull requests run it in fork-governance.yml, which reports it in + # the PR comment, so it runs here on push only: a PR does not check twice, + # and a direct push to main is still checked. It replaces the k9-ecosystem + # validate-action, which rejected the `K9!` envelope the contract requires. + k9-conformance: + name: K9-DEED conformance (push) + if: github.event_name == 'push' + uses: metadatastician/standards/.github/workflows/k9-conformance.yml@1fdc61c0c1c8f4c94324f956700d78d1ea9584a8 + permissions: + contents: read + pull-requests: read + with: + require-files: true + fail-on-indeterminate: false # --------------------------------------------------------------------------- # Job 3: Empty-linter — invisible character detection diff --git a/.machine_readable/svc/k9/template-hunt.k9.ncl.in b/.machine_readable/svc/k9/template-hunt.k9.ncl.in index d36b97fc..6d577645 100644 --- a/.machine_readable/svc/k9/template-hunt.k9.ncl.in +++ b/.machine_readable/svc/k9/template-hunt.k9.ncl.in @@ -27,6 +27,13 @@ K9! allow_filesystem_write = true, allow_subprocess = true, signature_required = true, + # Default-deny (K9-S007): each flag above must be granted here. Drop the + # ones the component does not need, together with their flags. + capabilities = [ + "net.fetch", # allow_network + "fs.write", # allow_filesystem_write + "process.spawn", # allow_subprocess + ], }, metadata = { name = "TODO: component-name", @@ -45,6 +52,12 @@ K9! "TODO: List what commands this executes", "TODO: List what network access this requires", ], + # Ed25519, presence only (K9-S009). Signing (step 6 below) adds + # payload_hash and signature; verification is K9-C001 (L3). + signature = { + algorithm = "Ed25519", + key_id = "TODO: signing-key-id", + }, }, # Configuration with contracts (Yard-level validation) diff --git a/panel-clades/Justfile b/panel-clades/Justfile index ab9ca5d2..655cca82 100644 --- a/panel-clades/Justfile +++ b/panel-clades/Justfile @@ -529,6 +529,26 @@ container-init: fi done + # The k9-svc deploy component ships as a template (deploy.k9.ncl.in): a + # file with brace tokens is not a K9 component. Render it once nothing is + # left to fill (init fills PROJECT_NAME and VERSION), so the K9 lane + # validates the real thing. The bracket spellings keep just from reading + # the token pattern as an interpolation. + DEPLOY_IN="container/deploy.k9.ncl.in" + DEPLOY_OUT="${DEPLOY_IN%.in}" + if [ -f "$DEPLOY_IN" ]; then + LEFT=$(grep -oE '[{][{][A-Za-z_]+[}][}]' "$DEPLOY_IN" | sort -u | tr '\n' ' ' || true) + if [ -n "$LEFT" ]; then + echo "Kept $DEPLOY_IN: unfilled tokens ${LEFT}remain." + echo " Run 'just init', then 'just container-init' again to render it." + elif [ -e "$DEPLOY_OUT" ]; then + echo "Kept $DEPLOY_IN: $DEPLOY_OUT already exists, so it was not overwritten." + else + mv "$DEPLOY_IN" "$DEPLOY_OUT" + echo "Rendered $DEPLOY_OUT (K9 component, contract v1.0.0)." + fi + fi + echo "Container templates initialised." echo "" echo "Next steps:" diff --git a/panel-clades/container/README.adoc b/panel-clades/container/README.adoc index 85d1b620..ce062cea 100644 --- a/panel-clades/container/README.adoc +++ b/panel-clades/container/README.adoc @@ -60,9 +60,11 @@ All files use `container` tokens that are replaced by `just container-init` crash detection, resource thresholds, and log output. | `deploy.k9.ncl.in` -| **k9-svc** deployment component at Hunt trust level. Full pedigree - (L1--L5), environment configs (dev/staging/prod), container - configuration, and rolling deployment strategy. +| **k9-svc** deployment component template at Hunt trust level (K9 + contract v1.0.0): pedigree with capability grant, side effects and a + signature block, environment configs (dev/staging/prod), container + configuration, and rolling deployment strategy. `just container-init` + renders it to `deploy.k9.ncl`. | `0-AI-MANIFEST.a2ml` | AI-readable manifest describing the container directory, file @@ -154,8 +156,9 @@ For k9-svc managed deployments: [source,bash] ---- -# Validate the deployment component -nickel typecheck container/deploy.k9.ncl # after rendering deploy.k9.ncl.in +# `just container-init` renders deploy.k9.ncl.in to deploy.k9.ncl once +# `just init` and it have filled every placeholder. Then validate it: +k9-validate.sh --strict --layer L2 container/deploy.k9.ncl # Deploy (requires Hunt-level authorisation) k9-svc deploy container/deploy.k9.ncl --env production diff --git a/panel-clades/container/deploy.k9.ncl.in b/panel-clades/container/deploy.k9.ncl.in index b632c52d..38ed0be9 100644 --- a/panel-clades/container/deploy.k9.ncl.in +++ b/panel-clades/container/deploy.k9.ncl.in @@ -1,175 +1,191 @@ +K9! # SPDX-License-Identifier: MPL-2.0 # deploy.k9.ncl — {{PROJECT_NAME}} deployment component (Hunt level) # -# .k9.ncl.in — a TEMPLATE, not a component (K9 contract v1.0.0, D173). Its -# {{PLACEHOLDER}} tokens are not Nickel, so it must not claim the reserved -# `.k9.ncl` suffix: a template is never loaded or leashed. Fill every token, -# then bring the pedigree up to contract v1.0.0 (schema_version, -# component_type, security.leash = 'Hunt with a capability grant, -# side_effects and a signature block) before saving it as deploy.k9.ncl. -# rsr-template-repo's build/container/stapeln/deploy.k9.ncl.in is the -# v1.0.0-shaped version of this template. +# k9-svc deployment component, K9 contract v1.0.0 (D173). Shipped as +# deploy.k9.ncl.in because a template is not a component: `just init` fills +# PROJECT_NAME and VERSION, `just container-init` fills SERVICE_NAME, PORT and +# REGISTRY, and once no brace token is left `just container-init` renders it +# to container/deploy.k9.ncl. Ported from rsr-template-repo's +# build/container/stapeln/deploy.k9.ncl.in, with this template's layout +# (container/, compose.toml). # -# k9-svc deployment specification with full pedigree (L1-L5). -# Security Level: 'Hunt (requires cryptographic handshake for execution). +# WARNING: this component executes shell commands. It is 'Hunt, so a host +# runs it only with all five Hunt preconditions met, a verified signature +# among them. The signature block below is a claim until the owner signs the +# rendered file; until then the validator reports it 'Present_Unverified +# (K9-C001) and Hunt stays unauthorised. # -# WARNING: This component can execute shell commands! -# It requires explicit authorisation via the Leash system. -# -# Usage: -# nickel typecheck container/deploy.k9.ncl +# Usage (on the rendered file): +# k9-validate.sh --strict --layer L2 container/deploy.k9.ncl # k9-svc validate container/deploy.k9.ncl # k9-svc deploy container/deploy.k9.ncl --env production -# The component's pedigree (self-description across five layers) -let component_pedigree = { - # ───────────────────────────────────────────────────────────── - # L1: The Snout — Identity - # ───────────────────────────────────────────────────────────── - metadata = { - name = "{{SERVICE_NAME}}-deploy", - version = "{{VERSION}}", - breed = "application/vnd.k9+nickel", - magic_number = "K9!", - description = "{{PROJECT_NAME}} deployment component (Hunt level)", - }, - - # ───────────────────────────────────────────────────────────── - # L2: The Scent — Target Environment - # ───────────────────────────────────────────────────────────── - target = { - os = 'Linux, - is_edge = false, - requires_podman = true, - min_memory_mb = 256, - }, +{ + pedigree = { + schema_version = "1.0.0", + component_type = "deployment", + + metadata = { + name = "{{SERVICE_NAME}}-deploy", + version = "{{VERSION}}", + description = "{{PROJECT_NAME}} deployment component (Hunt level)", + }, - # ───────────────────────────────────────────────────────────── - # L3: The Leash — Security - # ───────────────────────────────────────────────────────────── - security = { - trust_level = 'Hunt, - allow_network = true, - allow_filesystem_write = true, - allow_subprocess = true, - # In production, replace with a real Ed25519 signature. - signature = "PLACEHOLDER-SIGNATURE-REQUIRED-FOR-HUNT", - }, + # Target environment (extension field; contract §5.5). + target = { + os = 'Linux, + is_edge = false, + requires_podman = true, + min_memory_mb = 256, + }, - # ───────────────────────────────────────────────────────────── - # L4: The Gut — Self-Validation - # ───────────────────────────────────────────────────────────── - validation = { - checksum = "sha256:placeholder", - pedigree_version = "1.0.0", - hunt_authorized = false, # Must be set true after handshake - }, + security = { + leash = 'Hunt, + trust_level = "controlled-execution", + allow_network = true, + allow_filesystem_write = true, + allow_subprocess = true, + signature_required = true, + # Default-deny: every flag above is paid for here (contract §8.4), plus + # the deploy and rollback actions the scripts below perform. + capabilities = [ + "net.fetch", + "fs.write", + "process.spawn", + "container.run", + "deploy.apply", + "rollback.apply", + ], + }, - # ───────────────────────────────────────────────────────────── - # L5: The Muscle — Deployment Recipes - # ───────────────────────────────────────────────────────────── - recipes = { - install = "just container-build", - validate = "just container-verify", - deploy = "just container-up", - migrate = "just container-build && just container-up", + warnings = [ + "This is a Hunt-level component: it executes shell commands and changes the system.", + "Review the deployment scripts before running it.", + "A signature block being present is not a signature being verified.", + "Authorise Hunt explicitly: k9-svc authorize container/deploy.k9.ncl", + ], + + side_effects = [ + "spawns container/ct-build.sh, which builds the image and packs and signs a .ctp bundle beside it", + "pulls and pushes {{REGISTRY}}/{{SERVICE_NAME}} images", + "starts, updates and stops the {{SERVICE_NAME}} stack with selur-compose, else podman compose over container/compose.toml", + "publishes the service on port {{PORT}}", + ], + + # Ed25519, presence only (K9-S009). The signing step adds payload_hash + # and signature to the rendered file; verification is K9-C001 (L3). + signature = { + algorithm = "Ed25519", + key_id = "primary", + }, }, -} in -# Deployment configuration -let deployment = { - # Target environments (dev / staging / production) - environments = { - dev = { - replicas = 1, - memory = "256Mi", - cpu = "100m", - image_tag = "dev", - }, - staging = { - replicas = 2, - memory = "512Mi", - cpu = "250m", - image_tag = "staging", + # Deployment configuration + deployment = { + # Target environments (dev / staging / production) + environments = { + dev = { + replicas = 1, + memory = "256Mi", + cpu = "100m", + image_tag = "dev", + }, + staging = { + replicas = 2, + memory = "512Mi", + cpu = "250m", + image_tag = "staging", + }, + production = { + replicas = 3, + memory = "1Gi", + cpu = "500m", + image_tag = "latest", + }, }, - production = { - replicas = 3, - memory = "1Gi", - cpu = "500m", - image_tag = "latest", - }, - }, - # Container configuration - container = { - image = "{{REGISTRY}}/{{SERVICE_NAME}}", - port = {{PORT}}, - health_check = "/health", - readiness_check = "/ready", - }, + # Container configuration + container = { + image = "{{REGISTRY}}/{{SERVICE_NAME}}", + port = {{PORT}}, + health_check = "/health", + readiness_check = "/ready", + }, - # Deployment strategy - strategy = { - type = "rolling", - max_surge = 1, - max_unavailable = 0, + # Deployment strategy + strategy = { + type = "rolling", + max_surge = 1, + max_unavailable = 0, + }, }, -} in -# Deployment scripts (executed at Hunt level) -let scripts = { - # Pre-deployment validation - pre_deploy = m%" + # Deployment scripts (executed at Hunt level) + scripts = { + # Pre-deployment validation + pre_deploy = m%" #!/bin/sh set -eu echo "K9: Pre-deployment validation for {{SERVICE_NAME}}..." -cd container && selur-compose verify || podman compose --file compose.toml config +cd container +if command -v selur-compose >/dev/null 2>&1; then + selur-compose verify +else + podman compose --file compose.toml config +fi echo "K9: Validation passed." "%, - # Deployment script - deploy = m%" + # Deployment script + deploy = m%" #!/bin/sh set -eu ENV="${1:-dev}" echo "K9: Deploying {{SERVICE_NAME}} to $ENV environment..." cd container ./ct-build.sh -selur-compose up --detach || podman compose --file compose.toml up --detach +if command -v selur-compose >/dev/null 2>&1; then + selur-compose up --detach +else + podman compose --file compose.toml up --detach +fi echo "K9: Deployment to $ENV complete." "%, - # Rollback script - rollback = m%" + # Rollback script + rollback = m%" #!/bin/sh set -eu echo "K9: Rolling back {{SERVICE_NAME}} deployment..." cd container -selur-compose down || podman compose --file compose.toml down +if command -v selur-compose >/dev/null 2>&1; then + selur-compose down +else + podman compose --file compose.toml down +fi echo "K9: Rollback complete." "%, -} in - -# Export the component -{ - pedigree = component_pedigree, - deployment = deployment, - scripts = scripts, - - # Security check: this component requires Hunt level - required_level = 'Hunt, - - # Warning for users - warning = m%" -WARNING: This is a Hunt-level component. - -It can execute shell commands and modify your system. -Before running, ensure you have: - -1. Reviewed the deployment scripts above -2. Verified the signature (when implemented) -3. Explicitly authorised Hunt-level execution + }, -Run with: k9-svc authorize container/deploy.k9.ncl && k9-svc deploy container/deploy.k9.ncl -"%, + # Just recipes: an execution surface, which is why the leash is 'Hunt + # (K9-S011). + recipes = { + install = { + description = "Build the container image", + commands = ["just container-build"], + }, + validate = { + description = "Verify the compose configuration", + commands = ["just container-verify"], + }, + deploy = { + description = "Start the container stack", + commands = ["just container-up"], + }, + migrate = { + description = "Rebuild the image and restart the stack", + commands = ["just container-build", "just container-up"], + }, + }, } From 882dd75ea10d9c16da79f02bfe18bb18abe97451 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 27 Oct 2026 01:43:24 +0000 Subject: [PATCH 07/21] docs(layouts): validate presets with K9-aware commands The presets now open with the K9! envelope, which the nickel CLI cannot read. Document k9-validate.sh for conformance and evaluating the Nickel body (tail -n +2, run from layouts/ so the relative import resolves) for the pedigree-layout contracts. Checked: logic-and-proofs exports, and a preset whose pane sizes do not sum to 100 is still rejected. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01BCQeL169ZnUirUVaDXQLA3 --- layouts/README.adoc | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/layouts/README.adoc b/layouts/README.adoc index 610983d1..dfc2369b 100644 --- a/layouts/README.adoc +++ b/layouts/README.adoc @@ -54,18 +54,31 @@ The `pedigree-layout.ncl` schema enforces: == Validating a Preset +Each preset is a K9 component: line 1 is the `K9!` envelope, which is not +Nickel, so the `nickel` CLI cannot read the file directly. Validate it with +the K9 validator, or evaluate the Nickel body (everything after line 1) from +inside `layouts/` so the `./pedigree-layout.ncl` import resolves: + [source,bash] ---- -nickel typecheck layouts/logic-and-proofs.k9.ncl -nickel export layouts/logic-and-proofs.k9.ncl --format json +# K9 contract v1.0.0 conformance (envelope, pedigree, and a body typecheck) +k9-validate.sh --strict --layer L2 layouts/logic-and-proofs.k9.ncl + +# Evaluate the preset against pedigree-layout.ncl's contracts +cd layouts +tail -n +2 logic-and-proofs.k9.ncl | nickel typecheck +tail -n +2 logic-and-proofs.k9.ncl | nickel export --format json ---- +`k9-validate.sh` is standards' `1-formats/k9/tools/k9-validate.sh`; CI runs +it on every PR through the K9-DEED conformance lane. + == Creating a Custom Layout . Copy an existing preset . Modify pane sizes (must sum to 100) . Change panel visibility and prover priority -. Run `nickel typecheck` to validate +. Validate it as above (`k9-validate.sh`, then `nickel export` on the body) Community-contributed presets are welcome — submit a PR with your `.k9.ncl` file and it will be validated against the pedigree schema automatically. From e6af8ebd7ec4828440604559f32547fc66460df9 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 4 Nov 2026 23:49:04 +0000 Subject: [PATCH 08/21] ci: repin standards to e064539; render the deploy template from init too Standards main now allows metadatastician/standards/.github/workflows/*@* in its canonical Actions allowlist, so pinning to it clears the governance Allowlist Preflight that has been red on main since the owner transfer. - Repin governance, hypatia-scan, scorecard, secret-scanner, mirror, fork-governance (all four lanes and canon-ref) and dogfood-gate's push K9 lane from 1fdc61c to e064539. Contracts checked: only hypatia-scan-reusable changed, and only by optional inputs; no job asks for a permission its caller does not grant. rust-ci stays on 412a703. - fork-governance passes hypatia-advisory to the summary, as standards' ADOPTION.adoc template now does. - actions.lock: the standards record is re-keyed; its nested uses are unchanged at e064539 and every leaf record matches standards' own lock. - panel-clades/Justfile: the deploy-template render moves to a private `_render-deploy-k9` recipe that both `init` and `container-init` call, since `init` already fills the container placeholders. After rendering it points container/0-AI-MANIFEST.a2ml at deploy.k9.ncl instead of the removed .in file. (Codex findings on hyperpolymath/panll#167.) Checked locally against standards e064539: all workflows parse; the duplicate-key, SPDX/permissions, lock gate, lock validator, verify-local and allowlist checks pass; K9 corpus 106 pass, 1 routed; `just init` and `just container-init` with scripted answers both render a deploy.k9.ncl that passes k9-validate --strict --layer L2 and rewrite the manifest. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01BCQeL169ZnUirUVaDXQLA3 --- .github/workflows/actions.lock | 20 +++++------ .github/workflows/dogfood-gate.yml | 2 +- .github/workflows/fork-governance.yml | 11 +++--- .github/workflows/governance.yml | 2 +- .github/workflows/hypatia-scan.yml | 2 +- .github/workflows/mirror.yml | 2 +- .github/workflows/scorecard.yml | 2 +- .github/workflows/secret-scanner.yml | 2 +- panel-clades/Justfile | 51 +++++++++++++++++---------- 9 files changed, 54 insertions(+), 40 deletions(-) diff --git a/.github/workflows/actions.lock b/.github/workflows/actions.lock index b8c13882..5ef9a75f 100644 --- a/.github/workflows/actions.lock +++ b/.github/workflows/actions.lock @@ -21,18 +21,18 @@ workflows: '.github/workflows/dogfood-gate.yml': - 'actions/checkout@v7.0.1' - 'hyperpolymath/deed-ecosystem@main' - - 'metadatastician/standards@1fdc61c0c1c8f4c94324f956700d78d1ea9584a8' + - 'metadatastician/standards@e064539116c2dcfdf282a4749dbd58c69961c7e6' '.github/workflows/e2e.yml': - 'actions/checkout@v7.0.1' - 'dtolnay/rust-toolchain@v1' - 'goto-bus-stop/setup-zig@v2.2.1' - 'swatinem/rust-cache@v2.9.3' '.github/workflows/fork-governance.yml': - - 'metadatastician/standards@1fdc61c0c1c8f4c94324f956700d78d1ea9584a8' + - 'metadatastician/standards@e064539116c2dcfdf282a4749dbd58c69961c7e6' '.github/workflows/governance.yml': - - 'metadatastician/standards@1fdc61c0c1c8f4c94324f956700d78d1ea9584a8' + - 'metadatastician/standards@e064539116c2dcfdf282a4749dbd58c69961c7e6' '.github/workflows/hypatia-scan.yml': - - 'metadatastician/standards@1fdc61c0c1c8f4c94324f956700d78d1ea9584a8' + - 'metadatastician/standards@e064539116c2dcfdf282a4749dbd58c69961c7e6' '.github/workflows/instant-sync.yml': - 'peter-evans/repository-dispatch@v4.0.1' '.github/workflows/label-triage.yml': [] @@ -40,15 +40,15 @@ workflows: '.github/workflows/main-estate-audit.yml': - 'hyperpolymath/cicd-suite@4f5404281dd7092fa77cc18daec7237c4f1ee4ac' '.github/workflows/mirror.yml': - - 'metadatastician/standards@1fdc61c0c1c8f4c94324f956700d78d1ea9584a8' + - 'metadatastician/standards@e064539116c2dcfdf282a4749dbd58c69961c7e6' '.github/workflows/push-email-notify.yml': - 'hyperpolymath/smtp-notify-action@v0.5.0' '.github/workflows/rust-ci.yml': - 'metadatastician/standards@412a7031577112b31ee287cc6060179d638d6500' '.github/workflows/scorecard.yml': - - 'metadatastician/standards@1fdc61c0c1c8f4c94324f956700d78d1ea9584a8' + - 'metadatastician/standards@e064539116c2dcfdf282a4749dbd58c69961c7e6' '.github/workflows/secret-scanner.yml': - - 'metadatastician/standards@1fdc61c0c1c8f4c94324f956700d78d1ea9584a8' + - 'metadatastician/standards@e064539116c2dcfdf282a4749dbd58c69961c7e6' dependencies: 'actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9': ref: 'v6.1.0' @@ -183,9 +183,9 @@ dependencies: commit: 'sha1-c1c9fa07992a02c1fd3d67a0dc1b08cccb852aef' owner_id: 6759885 repo_id: 1352485172 - 'metadatastician/standards@1fdc61c0c1c8f4c94324f956700d78d1ea9584a8': - ref: '1fdc61c0c1c8f4c94324f956700d78d1ea9584a8' - commit: 'sha1-1fdc61c0c1c8f4c94324f956700d78d1ea9584a8' + 'metadatastician/standards@e064539116c2dcfdf282a4749dbd58c69961c7e6': + ref: 'e064539116c2dcfdf282a4749dbd58c69961c7e6' + commit: 'sha1-e064539116c2dcfdf282a4749dbd58c69961c7e6' owner_id: 297579217 repo_id: 1116521501 uses: diff --git a/.github/workflows/dogfood-gate.yml b/.github/workflows/dogfood-gate.yml index f4810bdf..ae480ec8 100644 --- a/.github/workflows/dogfood-gate.yml +++ b/.github/workflows/dogfood-gate.yml @@ -74,7 +74,7 @@ jobs: k9-conformance: name: K9-DEED conformance (push) if: github.event_name == 'push' - uses: metadatastician/standards/.github/workflows/k9-conformance.yml@1fdc61c0c1c8f4c94324f956700d78d1ea9584a8 + uses: metadatastician/standards/.github/workflows/k9-conformance.yml@e064539116c2dcfdf282a4749dbd58c69961c7e6 permissions: contents: read pull-requests: read diff --git a/.github/workflows/fork-governance.yml b/.github/workflows/fork-governance.yml index afc31c8e..a2810da1 100644 --- a/.github/workflows/fork-governance.yml +++ b/.github/workflows/fork-governance.yml @@ -29,7 +29,7 @@ permissions: jobs: deed: name: DEED syntax - uses: metadatastician/standards/.github/workflows/deed-conformance.yml@1fdc61c0c1c8f4c94324f956700d78d1ea9584a8 + uses: metadatastician/standards/.github/workflows/deed-conformance.yml@e064539116c2dcfdf282a4749dbd58c69961c7e6 permissions: contents: read with: @@ -37,7 +37,7 @@ jobs: k9: name: K9-DEED conformance - uses: metadatastician/standards/.github/workflows/k9-conformance.yml@1fdc61c0c1c8f4c94324f956700d78d1ea9584a8 + uses: metadatastician/standards/.github/workflows/k9-conformance.yml@e064539116c2dcfdf282a4749dbd58c69961c7e6 permissions: contents: read pull-requests: read @@ -47,7 +47,7 @@ jobs: hypatia: name: Hypatia verdict grid - uses: metadatastician/standards/.github/workflows/hypatia-scan-reusable.yml@1fdc61c0c1c8f4c94324f956700d78d1ea9584a8 + uses: metadatastician/standards/.github/workflows/hypatia-scan-reusable.yml@e064539116c2dcfdf282a4749dbd58c69961c7e6 permissions: actions: read contents: read @@ -59,7 +59,7 @@ jobs: name: Post the fork governance summary needs: [deed, k9, hypatia] if: ${{ always() && github.event_name == 'pull_request' }} - uses: metadatastician/standards/.github/workflows/fork-governance-reusable.yml@1fdc61c0c1c8f4c94324f956700d78d1ea9584a8 + uses: metadatastician/standards/.github/workflows/fork-governance-reusable.yml@e064539116c2dcfdf282a4749dbd58c69961c7e6 permissions: contents: read actions: read @@ -71,4 +71,5 @@ jobs: k9-verdict: ${{ needs.k9.outputs.verdict }} hypatia-result: ${{ needs.hypatia.result }} hypatia-verdict: ${{ needs.hypatia.outputs.verdict }} - canon-ref: 1fdc61c0c1c8f4c94324f956700d78d1ea9584a8 + hypatia-advisory: ${{ needs.hypatia.outputs.advisory }} + canon-ref: e064539116c2dcfdf282a4749dbd58c69961c7e6 diff --git a/.github/workflows/governance.yml b/.github/workflows/governance.yml index 1d006dc5..f6fbd6fa 100644 --- a/.github/workflows/governance.yml +++ b/.github/workflows/governance.yml @@ -33,6 +33,6 @@ permissions: jobs: governance: - uses: metadatastician/standards/.github/workflows/governance-reusable.yml@1fdc61c0c1c8f4c94324f956700d78d1ea9584a8 + uses: metadatastician/standards/.github/workflows/governance-reusable.yml@e064539116c2dcfdf282a4749dbd58c69961c7e6 secrets: HYPATIA_SCAN_PAT: ${{ secrets.HYPATIA_SCAN_PAT }} diff --git a/.github/workflows/hypatia-scan.yml b/.github/workflows/hypatia-scan.yml index 9fd288bf..0cece4aa 100644 --- a/.github/workflows/hypatia-scan.yml +++ b/.github/workflows/hypatia-scan.yml @@ -34,6 +34,6 @@ jobs: actions: read contents: read security-events: write - uses: metadatastician/standards/.github/workflows/hypatia-scan-reusable.yml@1fdc61c0c1c8f4c94324f956700d78d1ea9584a8 + uses: metadatastician/standards/.github/workflows/hypatia-scan-reusable.yml@e064539116c2dcfdf282a4749dbd58c69961c7e6 secrets: HYPATIA_SCAN_PAT: ${{ secrets.HYPATIA_SCAN_PAT }} diff --git a/.github/workflows/mirror.yml b/.github/workflows/mirror.yml index ececb1e1..9a277c98 100644 --- a/.github/workflows/mirror.yml +++ b/.github/workflows/mirror.yml @@ -13,7 +13,7 @@ permissions: jobs: mirror: - uses: metadatastician/standards/.github/workflows/mirror-reusable.yml@1fdc61c0c1c8f4c94324f956700d78d1ea9584a8 + uses: metadatastician/standards/.github/workflows/mirror-reusable.yml@e064539116c2dcfdf282a4749dbd58c69961c7e6 secrets: GITLAB_SSH_KEY: ${{ secrets.GITLAB_SSH_KEY }} BITBUCKET_SSH_KEY: ${{ secrets.BITBUCKET_SSH_KEY }} diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 5acbc445..d30c1e18 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -16,4 +16,4 @@ jobs: contents: read security-events: write id-token: write - uses: metadatastician/standards/.github/workflows/scorecard-reusable.yml@1fdc61c0c1c8f4c94324f956700d78d1ea9584a8 + uses: metadatastician/standards/.github/workflows/scorecard-reusable.yml@e064539116c2dcfdf282a4749dbd58c69961c7e6 diff --git a/.github/workflows/secret-scanner.yml b/.github/workflows/secret-scanner.yml index 590c85a1..b165dfe5 100644 --- a/.github/workflows/secret-scanner.yml +++ b/.github/workflows/secret-scanner.yml @@ -21,4 +21,4 @@ jobs: contents: read pull-requests: write actions: read - uses: metadatastician/standards/.github/workflows/secret-scanner-reusable.yml@1fdc61c0c1c8f4c94324f956700d78d1ea9584a8 + uses: metadatastician/standards/.github/workflows/secret-scanner-reusable.yml@e064539116c2dcfdf282a4749dbd58c69961c7e6 diff --git a/panel-clades/Justfile b/panel-clades/Justfile index 655cca82..3bd7d363 100644 --- a/panel-clades/Justfile +++ b/panel-clades/Justfile @@ -226,6 +226,10 @@ init: sed -i "s|\[YOUR-REPO-NAME\]|${PROJECT_NAME}|g" 0-AI-MANIFEST.a2ml 2>/dev/null || true sed -i "s|\[YOUR-NAME/ORG\]|${OWNER}|g" 0-AI-MANIFEST.a2ml 2>/dev/null || true + # init fills every placeholder, the container ones included, so the deploy + # template can be rendered here too. + "{{just_executable()}}" --justfile "{{justfile()}}" _render-deploy-k9 + echo "" echo "── Validation ───────────────────────────────────" @@ -529,25 +533,7 @@ container-init: fi done - # The k9-svc deploy component ships as a template (deploy.k9.ncl.in): a - # file with brace tokens is not a K9 component. Render it once nothing is - # left to fill (init fills PROJECT_NAME and VERSION), so the K9 lane - # validates the real thing. The bracket spellings keep just from reading - # the token pattern as an interpolation. - DEPLOY_IN="container/deploy.k9.ncl.in" - DEPLOY_OUT="${DEPLOY_IN%.in}" - if [ -f "$DEPLOY_IN" ]; then - LEFT=$(grep -oE '[{][{][A-Za-z_]+[}][}]' "$DEPLOY_IN" | sort -u | tr '\n' ' ' || true) - if [ -n "$LEFT" ]; then - echo "Kept $DEPLOY_IN: unfilled tokens ${LEFT}remain." - echo " Run 'just init', then 'just container-init' again to render it." - elif [ -e "$DEPLOY_OUT" ]; then - echo "Kept $DEPLOY_IN: $DEPLOY_OUT already exists, so it was not overwritten." - else - mv "$DEPLOY_IN" "$DEPLOY_OUT" - echo "Rendered $DEPLOY_OUT (K9 component, contract v1.0.0)." - fi - fi + "{{just_executable()}}" --justfile "{{justfile()}}" _render-deploy-k9 echo "Container templates initialised." echo "" @@ -557,6 +543,33 @@ container-init: echo " 3. Review container/compose.toml — adjust services and volumes" echo " 4. Build: just container-build" +# Render container/deploy.k9.ncl.in to container/deploy.k9.ncl once no +# placeholder is left. Called by `init` and `container-init`. +[private] +_render-deploy-k9: + #!/usr/bin/env bash + set -euo pipefail + # The k9-svc deploy component ships as a template (deploy.k9.ncl.in): a + # file with brace tokens is not a K9 component. Render it once nothing is + # left to fill, so the K9 lane validates the real thing. The bracket + # spellings keep just from reading the token pattern as an interpolation. + DEPLOY_IN="container/deploy.k9.ncl.in" + DEPLOY_OUT="${DEPLOY_IN%.in}" + MANIFEST="container/0-AI-MANIFEST.a2ml" + [ -f "$DEPLOY_IN" ] || exit 0 + LEFT=$(grep -oE '[{][{][A-Za-z_]+[}][}]' "$DEPLOY_IN" | sort -u | tr '\n' ' ' || true) + if [ -n "$LEFT" ]; then + echo "Kept $DEPLOY_IN: unfilled tokens ${LEFT}remain." + echo " Run 'just init' (or 'just container-init') to fill them; it renders the file." + elif [ -e "$DEPLOY_OUT" ]; then + echo "Kept $DEPLOY_IN: $DEPLOY_OUT already exists, so it was not overwritten." + else + mv "$DEPLOY_IN" "$DEPLOY_OUT" + # The container manifest names the template; point it at the component. + [ -f "$MANIFEST" ] && sed -i 's|deploy\.k9\.ncl\.in|deploy.k9.ncl|g' "$MANIFEST" + echo "Rendered $DEPLOY_OUT (K9 component, contract v1.0.0)." + fi + # Build container image via cerro-torre pipeline container-build *args: #!/usr/bin/env bash From edf943549e8642ba928fea5648f3eea05b10d44d Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 4 Nov 2026 23:57:40 +0000 Subject: [PATCH 09/21] panel-clades: point the root manifest at the rendered deploy component _render-deploy-k9 rewrote only container/0-AI-MANIFEST.a2ml. The root 0-AI-MANIFEST.a2ml inventory also lists deploy.k9.ncl.in (line 102), so after rendering it named a file that no longer exists. Rewrite both manifests, and drop the "(template)" note from the inventory line. (Codex finding on hyperpolymath/panll#171.) Checked: `just init` and `just container-init` with scripted answers both render container/deploy.k9.ncl (passes k9-validate --strict --layer L2) and leave both manifests naming deploy.k9.ncl. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01BCQeL169ZnUirUVaDXQLA3 --- panel-clades/Justfile | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/panel-clades/Justfile b/panel-clades/Justfile index 3bd7d363..7be0388e 100644 --- a/panel-clades/Justfile +++ b/panel-clades/Justfile @@ -555,7 +555,6 @@ _render-deploy-k9: # spellings keep just from reading the token pattern as an interpolation. DEPLOY_IN="container/deploy.k9.ncl.in" DEPLOY_OUT="${DEPLOY_IN%.in}" - MANIFEST="container/0-AI-MANIFEST.a2ml" [ -f "$DEPLOY_IN" ] || exit 0 LEFT=$(grep -oE '[{][{][A-Za-z_]+[}][}]' "$DEPLOY_IN" | sort -u | tr '\n' ' ' || true) if [ -n "$LEFT" ]; then @@ -565,8 +564,11 @@ _render-deploy-k9: echo "Kept $DEPLOY_IN: $DEPLOY_OUT already exists, so it was not overwritten." else mv "$DEPLOY_IN" "$DEPLOY_OUT" - # The container manifest names the template; point it at the component. - [ -f "$MANIFEST" ] && sed -i 's|deploy\.k9\.ncl\.in|deploy.k9.ncl|g' "$MANIFEST" + # Both manifests name the template; point them at the component. The + # root inventory's line also loses its "(template)" note. + for MANIFEST in 0-AI-MANIFEST.a2ml container/0-AI-MANIFEST.a2ml; do + [ -f "$MANIFEST" ] && sed -i '/deploy\.k9\.ncl\.in/{s|deploy\.k9\.ncl\.in|deploy.k9.ncl|g; s| (template)||}' "$MANIFEST" + done echo "Rendered $DEPLOY_OUT (K9 component, contract v1.0.0)." fi From 92d68d7d585de5d09c927ed6ad8106068c9f55c8 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 9 Nov 2026 13:02:48 +0000 Subject: [PATCH 10/21] ci: repin standards to 9bdf864; add the fork-PR governance publisher MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Moves every metadatastician/standards caller (governance, hypatia-scan, scorecard, secret-scanner, mirror, the four fork-governance lanes and canon-ref, and dogfood-gate's push-only K9 lane) from e064539 to 9bdf864, current standards main. That commit carries standards#1408, which lets a fork pull request's governance comment be posted, plus the fail-closed secret-scanner history check (#1427), the reviewer-coverage fixes (#1417, #1418) and panic-attack scope excludes (#1404). Rust CI stays on 412a703. Adds .github/workflows/fork-governance-publish.yml per standards ADOPTION.adoc §2: a workflow_run caller of fork-governance-publish-reusable.yml at the same commit, with actions: read and pull-requests: write on the job only. It takes effect once merged, because workflow_run runs the default branch's copy. actions.lock is re-keyed with standards' scripts/relock-standards-pin.sh at 9bdf864, which checks that every workflow's lock entries name exactly the refs it pins. Rust CI's 412a703 pin was set aside for the run and restored. The nested uses of the standards record are unchanged, and the publisher's only action (download-artifact 9000827) was already listed. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01BCQeL169ZnUirUVaDXQLA3 --- .github/workflows/actions.lock | 32 ++++++++++--------- .github/workflows/dogfood-gate.yml | 2 +- .github/workflows/fork-governance-publish.yml | 31 ++++++++++++++++++ .github/workflows/fork-governance.yml | 10 +++--- .github/workflows/governance.yml | 2 +- .github/workflows/hypatia-scan.yml | 2 +- .github/workflows/mirror.yml | 2 +- .github/workflows/scorecard.yml | 2 +- .github/workflows/secret-scanner.yml | 2 +- 9 files changed, 59 insertions(+), 26 deletions(-) create mode 100644 .github/workflows/fork-governance-publish.yml diff --git a/.github/workflows/actions.lock b/.github/workflows/actions.lock index 5ef9a75f..d5008103 100644 --- a/.github/workflows/actions.lock +++ b/.github/workflows/actions.lock @@ -21,18 +21,20 @@ workflows: '.github/workflows/dogfood-gate.yml': - 'actions/checkout@v7.0.1' - 'hyperpolymath/deed-ecosystem@main' - - 'metadatastician/standards@e064539116c2dcfdf282a4749dbd58c69961c7e6' + - 'metadatastician/standards@9bdf864cc7f2c8e24b4df84bf00d04158eee3ad5' '.github/workflows/e2e.yml': - 'actions/checkout@v7.0.1' - 'dtolnay/rust-toolchain@v1' - 'goto-bus-stop/setup-zig@v2.2.1' - 'swatinem/rust-cache@v2.9.3' + '.github/workflows/fork-governance-publish.yml': + - 'metadatastician/standards@9bdf864cc7f2c8e24b4df84bf00d04158eee3ad5' '.github/workflows/fork-governance.yml': - - 'metadatastician/standards@e064539116c2dcfdf282a4749dbd58c69961c7e6' + - 'metadatastician/standards@9bdf864cc7f2c8e24b4df84bf00d04158eee3ad5' '.github/workflows/governance.yml': - - 'metadatastician/standards@e064539116c2dcfdf282a4749dbd58c69961c7e6' + - 'metadatastician/standards@9bdf864cc7f2c8e24b4df84bf00d04158eee3ad5' '.github/workflows/hypatia-scan.yml': - - 'metadatastician/standards@e064539116c2dcfdf282a4749dbd58c69961c7e6' + - 'metadatastician/standards@9bdf864cc7f2c8e24b4df84bf00d04158eee3ad5' '.github/workflows/instant-sync.yml': - 'peter-evans/repository-dispatch@v4.0.1' '.github/workflows/label-triage.yml': [] @@ -40,15 +42,15 @@ workflows: '.github/workflows/main-estate-audit.yml': - 'hyperpolymath/cicd-suite@4f5404281dd7092fa77cc18daec7237c4f1ee4ac' '.github/workflows/mirror.yml': - - 'metadatastician/standards@e064539116c2dcfdf282a4749dbd58c69961c7e6' + - 'metadatastician/standards@9bdf864cc7f2c8e24b4df84bf00d04158eee3ad5' '.github/workflows/push-email-notify.yml': - 'hyperpolymath/smtp-notify-action@v0.5.0' '.github/workflows/rust-ci.yml': - 'metadatastician/standards@412a7031577112b31ee287cc6060179d638d6500' '.github/workflows/scorecard.yml': - - 'metadatastician/standards@e064539116c2dcfdf282a4749dbd58c69961c7e6' + - 'metadatastician/standards@9bdf864cc7f2c8e24b4df84bf00d04158eee3ad5' '.github/workflows/secret-scanner.yml': - - 'metadatastician/standards@e064539116c2dcfdf282a4749dbd58c69961c7e6' + - 'metadatastician/standards@9bdf864cc7f2c8e24b4df84bf00d04158eee3ad5' dependencies: 'actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9': ref: 'v6.1.0' @@ -183,9 +185,14 @@ dependencies: commit: 'sha1-c1c9fa07992a02c1fd3d67a0dc1b08cccb852aef' owner_id: 6759885 repo_id: 1352485172 - 'metadatastician/standards@e064539116c2dcfdf282a4749dbd58c69961c7e6': - ref: 'e064539116c2dcfdf282a4749dbd58c69961c7e6' - commit: 'sha1-e064539116c2dcfdf282a4749dbd58c69961c7e6' + 'metadatastician/standards@412a7031577112b31ee287cc6060179d638d6500': + ref: '412a7031577112b31ee287cc6060179d638d6500' + commit: 'sha1-412a7031577112b31ee287cc6060179d638d6500' + owner_id: 297579217 + repo_id: 1116521501 + 'metadatastician/standards@9bdf864cc7f2c8e24b4df84bf00d04158eee3ad5': + ref: '9bdf864cc7f2c8e24b4df84bf00d04158eee3ad5' + commit: 'sha1-9bdf864cc7f2c8e24b4df84bf00d04158eee3ad5' owner_id: 297579217 repo_id: 1116521501 uses: @@ -201,11 +208,6 @@ dependencies: - 'ossf/scorecard-action@fa7117db83c52d787ae5a89f350999701505d78e' - 'oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6' - 'webfactory/ssh-agent@e83874834305fe9a4a2997156cb26c5de65a8555' - 'metadatastician/standards@412a7031577112b31ee287cc6060179d638d6500': - ref: '412a7031577112b31ee287cc6060179d638d6500' - commit: 'sha1-412a7031577112b31ee287cc6060179d638d6500' - owner_id: 297579217 - repo_id: 1116521501 'ossf/scorecard-action@fa7117db83c52d787ae5a89f350999701505d78e': ref: 'v2.5.0' commit: 'sha1-fa7117db83c52d787ae5a89f350999701505d78e' diff --git a/.github/workflows/dogfood-gate.yml b/.github/workflows/dogfood-gate.yml index ae480ec8..52742375 100644 --- a/.github/workflows/dogfood-gate.yml +++ b/.github/workflows/dogfood-gate.yml @@ -74,7 +74,7 @@ jobs: k9-conformance: name: K9-DEED conformance (push) if: github.event_name == 'push' - uses: metadatastician/standards/.github/workflows/k9-conformance.yml@e064539116c2dcfdf282a4749dbd58c69961c7e6 + uses: metadatastician/standards/.github/workflows/k9-conformance.yml@9bdf864cc7f2c8e24b4df84bf00d04158eee3ad5 permissions: contents: read pull-requests: read diff --git a/.github/workflows/fork-governance-publish.yml b/.github/workflows/fork-governance-publish.yml new file mode 100644 index 00000000..c9dc26af --- /dev/null +++ b/.github/workflows/fork-governance-publish.yml @@ -0,0 +1,31 @@ +# This workflow is managed by gh actions-lock. +# SPDX-License-Identifier: MPL-2.0 +# fork-governance-publish.yml — post the Fork governance comment on pull +# requests from forks. +# +# On a fork PR GitHub gives the "Fork governance" run a read-only token, so +# its verdict job uploads the rendered comment as an artifact instead of +# posting it. This workflow runs after every "Fork governance" run, from the +# default branch's copy with this repository's token, and posts that comment +# after checking it (metadatastician/standards +# 3-practice/fork-governance/ADOPTION.adoc §2). It skips same-repository runs, +# which post their own comment. It takes effect once merged to main. +# +# Same standards commit as fork-governance.yml: the renderer and the +# publisher must agree on the comment's marker and stamp. +name: Fork governance publish + +on: + workflow_run: + workflows: ["Fork governance"] + types: [completed] + +permissions: + contents: read + +jobs: + publish: + uses: metadatastician/standards/.github/workflows/fork-governance-publish-reusable.yml@9bdf864cc7f2c8e24b4df84bf00d04158eee3ad5 + permissions: + actions: read + pull-requests: write diff --git a/.github/workflows/fork-governance.yml b/.github/workflows/fork-governance.yml index a2810da1..556624fb 100644 --- a/.github/workflows/fork-governance.yml +++ b/.github/workflows/fork-governance.yml @@ -29,7 +29,7 @@ permissions: jobs: deed: name: DEED syntax - uses: metadatastician/standards/.github/workflows/deed-conformance.yml@e064539116c2dcfdf282a4749dbd58c69961c7e6 + uses: metadatastician/standards/.github/workflows/deed-conformance.yml@9bdf864cc7f2c8e24b4df84bf00d04158eee3ad5 permissions: contents: read with: @@ -37,7 +37,7 @@ jobs: k9: name: K9-DEED conformance - uses: metadatastician/standards/.github/workflows/k9-conformance.yml@e064539116c2dcfdf282a4749dbd58c69961c7e6 + uses: metadatastician/standards/.github/workflows/k9-conformance.yml@9bdf864cc7f2c8e24b4df84bf00d04158eee3ad5 permissions: contents: read pull-requests: read @@ -47,7 +47,7 @@ jobs: hypatia: name: Hypatia verdict grid - uses: metadatastician/standards/.github/workflows/hypatia-scan-reusable.yml@e064539116c2dcfdf282a4749dbd58c69961c7e6 + uses: metadatastician/standards/.github/workflows/hypatia-scan-reusable.yml@9bdf864cc7f2c8e24b4df84bf00d04158eee3ad5 permissions: actions: read contents: read @@ -59,7 +59,7 @@ jobs: name: Post the fork governance summary needs: [deed, k9, hypatia] if: ${{ always() && github.event_name == 'pull_request' }} - uses: metadatastician/standards/.github/workflows/fork-governance-reusable.yml@e064539116c2dcfdf282a4749dbd58c69961c7e6 + uses: metadatastician/standards/.github/workflows/fork-governance-reusable.yml@9bdf864cc7f2c8e24b4df84bf00d04158eee3ad5 permissions: contents: read actions: read @@ -72,4 +72,4 @@ jobs: hypatia-result: ${{ needs.hypatia.result }} hypatia-verdict: ${{ needs.hypatia.outputs.verdict }} hypatia-advisory: ${{ needs.hypatia.outputs.advisory }} - canon-ref: e064539116c2dcfdf282a4749dbd58c69961c7e6 + canon-ref: 9bdf864cc7f2c8e24b4df84bf00d04158eee3ad5 diff --git a/.github/workflows/governance.yml b/.github/workflows/governance.yml index f6fbd6fa..4f41c538 100644 --- a/.github/workflows/governance.yml +++ b/.github/workflows/governance.yml @@ -33,6 +33,6 @@ permissions: jobs: governance: - uses: metadatastician/standards/.github/workflows/governance-reusable.yml@e064539116c2dcfdf282a4749dbd58c69961c7e6 + uses: metadatastician/standards/.github/workflows/governance-reusable.yml@9bdf864cc7f2c8e24b4df84bf00d04158eee3ad5 secrets: HYPATIA_SCAN_PAT: ${{ secrets.HYPATIA_SCAN_PAT }} diff --git a/.github/workflows/hypatia-scan.yml b/.github/workflows/hypatia-scan.yml index 0cece4aa..2ec0e9a5 100644 --- a/.github/workflows/hypatia-scan.yml +++ b/.github/workflows/hypatia-scan.yml @@ -34,6 +34,6 @@ jobs: actions: read contents: read security-events: write - uses: metadatastician/standards/.github/workflows/hypatia-scan-reusable.yml@e064539116c2dcfdf282a4749dbd58c69961c7e6 + uses: metadatastician/standards/.github/workflows/hypatia-scan-reusable.yml@9bdf864cc7f2c8e24b4df84bf00d04158eee3ad5 secrets: HYPATIA_SCAN_PAT: ${{ secrets.HYPATIA_SCAN_PAT }} diff --git a/.github/workflows/mirror.yml b/.github/workflows/mirror.yml index b8159a9a..7d168193 100644 --- a/.github/workflows/mirror.yml +++ b/.github/workflows/mirror.yml @@ -16,7 +16,7 @@ jobs: # A manual dispatch from a feature branch has no local main ref to push. # Mirror only the canonical branch, as the push trigger already requires. if: github.ref == 'refs/heads/main' - uses: metadatastician/standards/.github/workflows/mirror-reusable.yml@e064539116c2dcfdf282a4749dbd58c69961c7e6 + uses: metadatastician/standards/.github/workflows/mirror-reusable.yml@9bdf864cc7f2c8e24b4df84bf00d04158eee3ad5 secrets: GITLAB_SSH_KEY: ${{ secrets.GITLAB_SSH_KEY }} CODEBERG_SSH_KEY: ${{ secrets.CODEBERG_SSH_KEY }} diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index d30c1e18..65511c8e 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -16,4 +16,4 @@ jobs: contents: read security-events: write id-token: write - uses: metadatastician/standards/.github/workflows/scorecard-reusable.yml@e064539116c2dcfdf282a4749dbd58c69961c7e6 + uses: metadatastician/standards/.github/workflows/scorecard-reusable.yml@9bdf864cc7f2c8e24b4df84bf00d04158eee3ad5 diff --git a/.github/workflows/secret-scanner.yml b/.github/workflows/secret-scanner.yml index b165dfe5..9e1e4f1e 100644 --- a/.github/workflows/secret-scanner.yml +++ b/.github/workflows/secret-scanner.yml @@ -21,4 +21,4 @@ jobs: contents: read pull-requests: write actions: read - uses: metadatastician/standards/.github/workflows/secret-scanner-reusable.yml@e064539116c2dcfdf282a4749dbd58c69961c7e6 + uses: metadatastician/standards/.github/workflows/secret-scanner-reusable.yml@9bdf864cc7f2c8e24b4df84bf00d04158eee3ad5 From c2685d1c3bb4a47473e42a4092d0713412aee702 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 9 Nov 2026 13:09:27 +0000 Subject: [PATCH 11/21] proofs: hold the ABI gate and its import cone to holes and totality; ledgers to PROOF-POLICY MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A review of every proof in the repository against standards 3-practice/PROOF-POLICY.adoc found the Idris2 proofs sound as stated and green on main (Build Validation 42408104327, panel-clades-abi), with these gaps, now closed or recorded. check-abi.sh - Fails on a hole (?name): idris2 --build accepts a module with holes. - Fails on partial or covering, and on a module without %default total (Idris2 defaults to covering, which skips termination checking). - Scans code only: comments, nested block comments and string literals are set aside, so prose such as "NO believe_me" does not trip it. - Holds the typed-wasm modules the proofs import (the import cone, PROOF-POLICY §1.1) to the same scan and prints the cone. The kernel was previously trusted unscanned. - Tested both ways: the real sources and cone pass; planted holes, partial, covering, believe_me, idris_crash and a missing %default total fail; the same words in comments and strings pass. bash -n and shellcheck -S warning are clean. PROOF-NEEDS.adoc - Closed §5 status tokens throughout, with notes kept separate. - Policy class: every obligation here is engineering, so no Proof Witness Grade applies (§8.1); the grade-algebra laws are theory owned by resource-algebra; where the Mathesis catalogue row stands. - A receipt for the last green run: checker, invocation, import cone, result. - New gaps from review: alignUpCorrect holds by construction while the rounding lower bound is unproved; CABICompliant certifies alignment only; the Nat and Bits32 tag tables are not tied together. - Kani (§4.1) is named for the unsafe and FFI seams. - A §4 exception record for the TLA+ provenance model: no receipt (TLC is not run in CI), its invariant holds by construction, and it has no forging action. A summary is in the model's header. docs/proof-debt.adoc - Adds main.rs's event-pump FFI calls (gossamer_emit, g_timeout_add), introduced by #164, to (d); records the new gate scope; names Kani. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01BCQeL169ZnUirUVaDXQLA3 --- PROOF-NEEDS.adoc | 248 ++++++++++++++++++++++++++---- docs/models/Provenance.tla | 12 ++ docs/proof-debt.adoc | 20 ++- panel-clades/scripts/check-abi.sh | 89 ++++++++++- 4 files changed, 336 insertions(+), 33 deletions(-) diff --git a/PROOF-NEEDS.adoc b/PROOF-NEEDS.adoc index cfadd97a..53eb6868 100644 --- a/PROOF-NEEDS.adoc +++ b/PROOF-NEEDS.adoc @@ -6,17 +6,47 @@ What PanLL proves today, what it deliberately does not, and what is still owed. Escape hatches and their dispositions are tracked separately in link:docs/proof-debt.adoc[`docs/proof-debt.adoc`]. -Last reconciled against the tree: 2026-10-26 (`main` at `1937c6b`). +Last reconciled against the tree: 2026-11-09 (`main` at `7d1feaa`). + +The policy is hyperpolymath/standards `3-practice/PROOF-POLICY.adoc`. +Statuses in this file use its closed §5 vocabulary: `PROVEN`, `TESTED`, +`ASSUMED`, `OPEN`. Anything else said about an item is in a separate +note, never in place of the token. toc::[] +== Policy class + +Every obligation PanLL owns is an *engineering* obligation (PROOF-POLICY §1): + +* interface and ABI contracts (types, layouts, tags, grade contracts) in + *Idris2*; +* first-party Rust in *Creusot* with *Pearlite* contracts, and *Verus* + (§1.2); +* Rust `unsafe` blocks and FFI seams additionally in *Kani*, reported as + `TESTED` with its bounds (§4.1). + +PanLL owns no theory theorem, so Proof Witness Grades do not apply here +(§8.1): no `PWG` is stated or owed. The laws of the grade algebras that +`Grades.idr` uses (occupancy, loss, max-plus) are theory. Their source of +truth is hyperpolymath/resource-algebra's Agda, and any grade for them is +recorded there. PanLL uses typed-wasm's Idris2 transcription of them as a +checked dependency (the import cone below); whether that transcription +states the same laws is the open item "Grade kernel identity" (#143). + +The Mathesis catalogue (hyperpolymath/mathesis, `docs/catalogue/`) lists +PanLL as an engineering repository whose status document is this file. +Its row comes from the census of 2026-10-26, so it does not yet count the +TLA+ model added since (`docs/models/`); Mathesis derives its rows from +the tree at each census and does not assign them. + == Current state The only machine-checked proofs live in the panel-clades ABI, `panel-clades/src/abi/PanelClades/ABI/` (Idris2 0.8.0, `%default total`, about 1,300 lines). As of the date above, no `.idr` file contains a hole, `believe_me`, `assert_total`, `assert_smaller`, `idris_crash`, -`unsafePerformIO` or `postulate`. +`unsafePerformIO`, `postulate`, `partial` or `covering`. The CI job `panel-clades-abi` in `.github/workflows/build-validation.yml` runs `panel-clades/scripts/check-abi.sh`, which: @@ -25,22 +55,65 @@ runs `panel-clades/scripts/check-abi.sh`, which: `panel-clades/scripts/fetch-siblings.sh` (`117bbf8`); * builds `panel-clades/src/abi/panel-clades-grades.ipkg` against it and fails on any `Error:` line, even when `idris2` exits 0; -* fails if any module under `PanelClades/` is missing from the ipkg, or - contains `believe_me`, `assert_total`, `assert_smaller`, `idris_crash`, - `unsafePerformIO` or `postulate` (a keyword scan; it does not look for - holes); +* fails if any module under `PanelClades/` is missing from the ipkg or + does not declare `%default total`, or if its code (comments and string + literals set aside) contains a hole (`?name`; `idris2 --build` accepts a + module with holes), `believe_me`, `assert_total`, `assert_smaller`, + `idris_crash`, `unsafePerformIO`, `postulate`, `partial` or `covering`; +* holds the typed-wasm modules those modules import (the import cone, + PROOF-POLICY §1.1) to the same scan, and prints the cone; * fails if `Budgets.idr` is stale against the clade configs (`scripts/gen-budgets.sh --check`); * requires each of the six fixtures in `panel-clades/tests/reject/` to be refused at its `invalid` declaration, with the expected message. -=== What is proved +=== Receipt + +The most recent receipt is the `panel-clades-abi` job of Build Validation +run +https://github.com/hyperpolymath/panll/actions/runs/42408104327[42408104327] +(job 128510101342), on `main` at +`7d1feaaf112b3d91fd504aaf871be5ae49840434`, 2026-11-07. [cols="1,3",options="header"] |=== -|Module |Proved +|Field |Value + +|Checker +|Idris2 0.8.0, built from idris-lang/Idris2 +`15a3e4e70843f7a34100f6470c04b791330788df` on Chez Scheme (Ubuntu +package) + +|Invocation +|`bash panel-clades/scripts/check-abi.sh`, which runs +`idris2 --build panel-clades-grades.ipkg` with the kernel's TTCs on +`IDRIS2_PATH`, then `idris2 --check` on each reject fixture + +|Import cone +|hyperpolymath/typed-wasm `117bbf895b513673ba06eb6c4ebb0e26e276e295`: +`TypedWasm.ABI.ResourceAlgebra`, `.Nat`, `.Loss`, `.MaxPlus`, `.HWM`, all +`%default total`, no escape hatch or hole. Below that, only Idris2's own +prelude and base (`Data.List`, `Data.Nat`, `Data.So`, `Data.Vect`, +`Data.Vect.Elem`, `Decidable.Equality`), which are the prover's trust base. + +|Result +|5 modules built, 0 errors; 6 of 6 fixtures refused at their `invalid` +declaration +|=== + +That run predates the import-cone and hole scans. The cone was scanned at +the pin by hand, with the same rules, and is clean. The first `main` run +after this change is the first receipt to include both. A receipt is +current only for its commit: rerun the job, never carry a result forward. + +=== What is proved + +[cols="1,1,3",options="header"] +|=== +|Module |Status |Proved |`Types.idr` +|`PROVEN` |The C tag encodings of `Result` and `CladeKind` round-trip, so they are injective (`resultRoundTrip`, `cladeKindRoundTrip`). Equality on both is decidable without a catch-all `absurd` (`decEqViaTag`). `allCladeKinds` @@ -48,12 +121,14 @@ lists every kind (`cladeKindExhaustive`) and has length `cladeKindCount`. A `Handle` is never null and a `CladeId` is never empty, by construction. |`Layout.idr` +|`PROVEN` |Rounding up to an alignment lands on a multiple of it (`alignUpCorrect`). Every computed struct size is a multiple of its alignment (`calcStructSizeAligned`). Field alignment is decided by an equation on `Nat` (`decDivides`, `proveFieldsAligned`), not by a Boolean test. |`Grades.idr` +|`PROVEN` |Clade resource contracts are graded in typed-wasm's kernel (transcribed from hyperpolymath/resource-algebra), not re-declared. Occupancy composes within a bound (`phasesWithin`). Loss adds across two crossings @@ -61,14 +136,19 @@ within a bound (`phasesWithin`). Loss adds across two crossings bound (`betterOfTwo`). Occupancy and cost are kept apart (`panelPhasesOccupancy` vs `panelPhasesCost`). The neurosym-bridge contract typechecks against its declared budget -(`neurosymBridgeContract`). +(`neurosymBridgeContract`). The kernel is in the checked import cone, so +it is not an assumption; whether its definitions match resource-algebra's +is #143. |`Budgets.idr` +|No obligation |Generated from the clades' `config.k9.ncl` resource blocks. It proves nothing itself; it is the input `Grades.idr` checks against. Only -neurosym-bridge declares a budget so far. +neurosym-bridge declares a budget so far. The budgets are DESIGNED values, +not measurements. |`Foreign.idr` +|No obligation |Typechecks only. It declares the Zig FFI surface; it proves nothing. |=== @@ -78,11 +158,28 @@ and `WasmPointerIsEightBytes`. === What is not proved -These limits are stated in the modules themselves. +The first item and the last two are stated in the modules themselves. The +other three were found reviewing the proofs on 2026-11-09. * *Struct size covers the last field.* `Layout.idr` does not prove that a computed size is at least the end of the last field; `verifyLayout` checks it at run time and refuses a layout that fails. +* *Rounding up reaches the size.* `alignUpCorrect` holds by construction: + `alignUp` is defined as a number of units times the alignment, so it is + always a multiple. The property that carries weight, that for an + alignment of at least 1 the result is at least the size and less than + size plus alignment, is not proved. It is the lemma the struct-size + lower bound needs. +* *C layout beyond alignment.* `CABICompliant` certifies that each + field's offset is a multiple of its alignment, and nothing else. It does + not prove that fields are in order and do not overlap, that alignments + are powers of two (an alignment of 0 is accepted), or that the struct's + alignment is its largest field alignment. +* *The two tag tables agree.* `Types.idr` defines each tag twice: as a + `Nat` (`resultTag`, `cladeKindTag`, used for decidable equality) and as + a `Bits32` (`resultToInt`, `cladeKindToInt`, the C encoding). Each table + is proved to round-trip on its own; nothing proves the two give the same + number. * *The Zig side matches.* Nothing checks that `panel-clades/ffi/zig/` exports the symbols `Foreign.idr` declares, with those types, or uses the sizes and tags `Types.idr` proves. `ffi/zig/src/main.zig` restates @@ -93,54 +190,88 @@ These limits are stated in the modules themselves. == What needs proving -[cols="2,3,2,1",options="header"] +Status is the PROOF-POLICY §5 token. The note says why, and what comes next. + +[cols="2,3,1,2,1",options="header"] |=== -|Item |What |Status |Tracked +|Item |What |Status |Note |Tracked |Struct size lower bound |Prove `calcStructSize` is at least the end of the last field, and drop the run-time check in `verifyLayout`. -|Not started. Induction over the field list plus a lemma about rounding -up. +|`OPEN` +|Not started. Induction over the field list, plus the rounding lemma +below. +|— + +|Rounding up reaches the size +|For an alignment `a ≥ 1`: `size ≤ alignUp size a < size + a`. +|`OPEN` +|Not started. Induction on the fuel of `unitsFor`. The struct-size lower +bound rests on it. +|— + +|C layout rules +|Fields in order and not overlapping, every alignment a power of two, and +the struct aligned to its largest field alignment, as `CABICompliant` +evidence. +|`OPEN` +|Not started. Today `CABICompliant` carries field alignment only. +|— + +|Tag tables agree +|`resultToInt r = cast (resultTag r)` for every `Result`, and the same for +`CladeKind`, or one table derived from the other. +|`OPEN` +|Not started. A case-by-case equation for each constructor. |— |Safe callback registration |A safe wrapper for `prim__registerCallback` in `Foreign.idr`, using Idris2's FFI callback mechanism (`cast` is banned). +|`OPEN` |Not started. |#149 |Zig/Idris FFI agreement |Check that the Zig exports match `Foreign.idr` and the proven sizes and -tags. This is a CI check, not a proof. -|Not started. +tags. +|`OPEN` +|Not started. A CI check, not a proof; once it runs it is `TESTED` +evidence, not `PROVEN`. |— |Grade kernel identity |Confirm typed-wasm's transcription matches resource-algebra's Agda `MaxPlus` kernel, which is now the family instance. -|Open question; PanLL already imports the kernel through typed-wasm. +|`OPEN` +|PanLL already imports the kernel through typed-wasm; the question is +whether the Idris2 and Agda statements are the same. |#143 |More clade budgets |As clade configs declare resource blocks, `gen-budgets.sh` emits them and their contracts need to typecheck. -|One clade so far (neurosym-bridge). +|`OPEN` +|One clade so far (neurosym-bridge, `PROVEN` against its declared +budget). |— |IPC dispatch is total |Every command the frontend invokes has a registered handler. -|Backend side covered by tests in `src-gossamer/src/ipc.rs`: +|`TESTED` (backend side); `OPEN` (frontend side) +|Tests in `src-gossamer/src/ipc.rs`: `every_backend_command_is_registered` fails if a public handler in a command module is reachable from neither the table nor `main.rs`, and the table and `main.rs` never register the same name twice. Checking the frontend side needs its `invoke` names, which wait on the AffineScript -port. +port. The Rust implementation obligation is in the next section. |#148 |PCC soundness |The PanLL Constraint Checker (`tools/pcc`) propagates constraints soundly and finds every violation. +|`OPEN` |Blocked: `tools/pcc`'s ReScript scanner is retired, because #107 removed the `.res` sources it read. Its frontend-wiring checks now report "retired" until an AffineScript scanner replaces them. The propagator @@ -150,6 +281,7 @@ the AffineScript port, and it needs a written specification first. |Verification Dashboard accuracy |The dashboard reports the real proof state of the repos it inspects. +|`OPEN` |Better served by fixture tests than by proof. Waits on the frontend port. |#148 @@ -163,21 +295,27 @@ a parent hash. It is serialised as canonical JSON, signed with ML-DSA-65 and witnessed, and verifies against keys published under `.well-known/keys/`. Today's engine (git blame plus Co-Authored-By trailers) is forgeable, so its output counts only as "claimed". +|`TESTED`; proofs `OPEN` |Implemented in `crates/provenance`: JCS records, ML-DSA-65 signing, approved public-key registry, independent witnesses, timestamp/content/chain -verification and mutation/property tests. Symbolic binding model: -`docs/models/Provenance.tla`; protocol and limits: -`docs/PROVENANCE-EXHIBIT-B.adoc`. This does not prove every action is recorded, -prevent tail truncation without a checkpoint, or prove cryptographic hardness. +verification and mutation/property tests (the `TESTED` evidence). +Symbolic binding model: `docs/models/Provenance.tla`, a §4 exception +whose record is below; protocol and limits: +`docs/PROVENANCE-EXHIBIT-B.adoc`. This does not prove every action is +recorded, prevent tail truncation without a checkpoint, or prove +cryptographic hardness. The implementation proofs are in the next +section. |#173 |Cartridge schema validation |Loading a cartridge validates it against `src/abi/cartridge-schema.json`. +|`OPEN` |Schema only; no validator to prove against yet. |— |Wiring Inspector |Wiring analysis finds every panel connection. +|`OPEN` |Waits on the frontend port. |#148 |=== @@ -230,6 +368,16 @@ identity approval and independent witness judgment are outside these proofs. specifications; retain current tests as TESTED evidence only. |PCC frontend scanning remains blocked on the AffineScript port. Neither verifier is silently excluded because that separate dependency is unfinished. +|Every `unsafe` block and FFI seam in `src-gossamer/`: the `libloading` +coprocessor calls, the Gossamer and GLib calls in `main.rs`'s event pump, +`gossamer_tray_destroy`, and the `libc::kill` / `libc::statvfs` calls +|Kani (PROOF-POLICY §4.1), alongside Creusot / Pearlite and Verus: OPEN +|Harnesses for each seam's stated `SAFETY` conditions (live handle, +NUL-terminated strings, matching foreign signatures, valid pid). +|Reported `TESTED` with unwinding and size bounds when run, never `PROVEN`. +The foreign side (Zig coprocessor, Gossamer, GLib, libc) is `ASSUMED` at +its declared signature. The sites, and their debt disposition, are listed +in link:docs/proof-debt.adoc[`docs/proof-debt.adoc`] (d). |=== Any future proof receipt must name source/tool/solver versions, invocation, @@ -237,11 +385,59 @@ discharged obligations and assumptions. Shared SMT solvers or specifications do not establish independent witnesses. Approved contributor keys, independent witnesses and trusted checkpoints remain separate operational requirements. +== Exceptions (PROOF-POLICY §4) + +=== TLA+ model of provenance binding + +[cols="1,3",options="header"] +|=== +|Field |Record + +|Artefact +|`docs/models/Provenance.tla`, with `docs/models/Provenance.cfg` + +|Obligation +|A record chain is reported verified only when every entry carries both an +author and a witness signature over its own payload, its content matches +the signed digest, and the second entry's parent names the first. + +|Tool +|TLA+, checked with TLC + +|Why not the mapped verifier +|The statement is about the protocol, symbolically, with signatures as +unforgeable sets. The mapped verifiers (Creusot / Pearlite and Verus) +check the shipped Rust instead; that obligation is the +`verify_entry` / `verify_witnesses` row above, and this model does not +discharge it. + +|Status +|`OPEN`. TLC is not run in CI and no run has been recorded, so the model +has no receipt. Once run, it is `TESTED` with its bounds: +`Values = {a, b}`, chains of exactly two entries. + +|Design limit +|The invariant `Binding` (`verified => Valid`) holds by construction: +`Check` sets `verified' = Valid`, `Supply` resets `verified` to `FALSE`, +and `Sign` only adds to the signed sets, which keeps `Valid` true. A TLC +pass would therefore show only that `verified` tracks `Valid`. The model +has no action that forges or replays a signature, and `Sign` adds the +author and witness signatures together, so it cannot tell an +author-signed record from a witnessed one. To carry weight it needs +separate author and witness signing, an adversary action, and a property +that is not the definition of `Valid`. +|=== + == Priority . Struct size lower bound and the safe callback wrapper (#149): small, - local, and they close the two gaps the modules admit to. + local, and they close the two gaps the modules admit to. The rounding + lemma comes first, since the lower bound rests on it. +. Tag tables agree: one equation per constructor, and it removes a + drift between the C encoding and the decidable-equality tags. . Zig/Idris FFI agreement check: cheap, and it removes the largest unchecked assumption under the proofs. . PCC soundness: the highest-value proof, but only after the AffineScript port (#148) gives it something to check. +. The TLA+ provenance model: restate the property so a pass means + something, and run TLC in CI so it has a receipt. diff --git a/docs/models/Provenance.tla b/docs/models/Provenance.tla index 6ac5f4e1..947f5df3 100644 --- a/docs/models/Provenance.tla +++ b/docs/models/Provenance.tla @@ -3,6 +3,18 @@ EXTENDS Naturals, Sequences, FiniteSets \* SPDX-License-Identifier: MPL-2.0 \* Symbolic unforgeability: an attacker may change fields/content/links or \* remove signatures, but cannot add a signature for a payload not signed. +\* +\* PROOF-POLICY §4 exception (standards 3-practice/PROOF-POLICY.adoc). Full +\* record: PROOF-NEEDS.adoc, "Exceptions", "TLA+ model of provenance binding". +\* Obligation: a chain is verified only if each entry is author- and +\* witness-signed over its own payload, its content matches the signed +\* digest, and entry 2's parent names entry 1. +\* Tool: TLA+ / TLC. Why: a symbolic protocol statement; Creusot/Pearlite +\* and Verus check the shipped Rust, and this model discharges none of it. +\* Status: OPEN (TLC not run in CI, no receipt). Once run: TESTED, bounds +\* Values = {a, b}, chains of two. +\* Limit: Binding holds by construction (Check sets verified' = Valid), and +\* there is no forging action; see the full record. CONSTANT Values VARIABLES chain, authorSigned, witnessSigned, content, verified vars == <> diff --git a/docs/proof-debt.adoc b/docs/proof-debt.adoc index a639b84e..10eaa817 100644 --- a/docs/proof-debt.adoc +++ b/docs/proof-debt.adoc @@ -6,12 +6,14 @@ Schema: hyperpolymath/standards soundness-relevant escape hatch and its disposition. What PanLL proves, and what it still needs to prove, is in link:../PROOF-NEEDS.adoc[`PROOF-NEEDS.adoc`]. -Last reconciled: 2026-10-26 (`main` at `1937c6b`). +Last reconciled: 2026-11-09 (`main` at `7d1feaa`). * *Idris2* (`panel-clades/src/abi/`): no `believe_me`, `assert_total`, - `assert_smaller`, `idris_crash`, `unsafePerformIO`, `postulate` or holes, - measured on the date above. `check-abi.sh` fails CI if any of those six - keywords appears; it does not scan for holes. + `assert_smaller`, `idris_crash`, `unsafePerformIO`, `postulate`, + `partial`, `covering` or holes, measured on the date above. `check-abi.sh` + fails CI if any of them appears in code (comments and string literals set + aside) or a module lacks `%default total`, in PanLL's modules and in the + typed-wasm modules they import (the import cone). * *Estate check*: standards' `scripts/check-trusted-base.sh` finds 2 escape-hatch signatures, both documented below as not proof debt. * *Rust*: the estate check does not scan Rust. The `unsafe` blocks and @@ -38,6 +40,10 @@ declaration, at an FFI or system-call boundary. None is annotated with `// TRUSTED:`, and none has a stated test budget, so none qualifies as (b) yet. +The verifier for these seams is Kani, under PROOF-POLICY §4.1, alongside +Creusot / Pearlite and Verus. The obligations are listed in +link:../PROOF-NEEDS.adoc[`PROOF-NEEDS.adoc`]; none has been run. + *Owner*: @hyperpolymath. *Deadline*: INDEFINITE — the disposition of each group (annotate and budget as (b), or remove the `unsafe`) is an owner decision. *Plan* for each: add a `// TRUSTED: ; ` annotation @@ -53,6 +59,12 @@ and the test that sets the budget, then move the entry to (b). * `src-gossamer/src/coprocessor/mod.rs:114`, `:117`, `:120`, `:123` — the `unsafe extern "C" fn` types for those symbols. These are declarations; they share the entry above. +* `src-gossamer/src/main.rs:203`, `:209` — the event pump: `gossamer_emit` + delivers queued backend events to the webview, and `g_timeout_add` + schedules the pump on the GTK main loop. The `extern "C"` declarations + they call are at `:175` and `:177`. +** *Kind*: extraction boundary (Rust → Gossamer C ABI, and GLib). Both have + a `SAFETY` comment. Added by #164. * `src-gossamer/src/llm_coding/commands.rs:197`, `:217`, `:237` — `libc::kill` sending `SIGSTOP` / `SIGCONT` / `SIGTERM` to a session's process. diff --git a/panel-clades/scripts/check-abi.sh b/panel-clades/scripts/check-abi.sh index 9a189b21..c1b905df 100755 --- a/panel-clades/scripts/check-abi.sh +++ b/panel-clades/scripts/check-abi.sh @@ -16,7 +16,9 @@ # # A missing toolchain is a failure, never a skip. Every module under # src/abi/PanelClades/ must be listed in the ipkg (an unlisted one would be -# skipped silently), and none may use an escape hatch. +# skipped silently), declare `%default total`, and use no escape hatch and no +# hole; the typed-wasm modules they import (the import cone) are held to the +# same scan, and the cone is printed as the run's record of it. set -uo pipefail cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." || exit 1 ROOT="$(pwd)" @@ -30,6 +32,74 @@ err() { printf 'FAIL: %s\n' "$1"; fail=1; } # Print the supplied failure message and terminate the gate with exit status 1. die() { printf 'FAIL: %s\n' "$1"; exit 1; } +# Print each .idr file's code with comments and string literals blanked, as +# FILE:LINE:TEXT, so a scan neither trips over prose ("NO believe_me") nor +# misses code. Handles nested {- -} block comments, `--` line comments (not +# operators such as -->), `|||` doc comments, and "..." / '.' literals. +code_only() { + awk ' + FNR == 1 { depth = 0 } + { + line = $0; out = ""; i = 1; n = length(line) + if (depth == 0 && line ~ /^[[:space:]]*\|\|\|/) { print FILENAME ":" FNR ":"; next } + while (i <= n) { + two = substr(line, i, 2); c = substr(line, i, 1) + if (two == "{-") { depth++; i += 2; continue } + if (depth > 0) { if (two == "-}") { depth--; i += 2 } else i++; continue } + if (two == "--" && substr(line, i + 2, 1) !~ /[!#$%&*+.\/<=>?@\\^|~:-]/) break + if (c == "\"") { i++; while (i <= n && substr(line, i, 1) != "\"") { if (substr(line, i, 1) == "\\") i++; i++ }; i++; out = out "\"\""; continue } + if (c == "'"'"'" && substr(line, i + 2, 1) == "'"'"'") { i += 3; out = out "'"'"''"'"'"; continue } + out = out c; i++ + } + print FILENAME ":" FNR ":" out + }' "$@" +} + +# Escape hatches and holes in code, plus any module that does not declare +# `%default total` (Idris2's default is `covering`, which does not check +# termination). Returns 0 and prints the offending lines if anything is found. +# believe_me assert_total assert_smaller idris_crash unsafePerformIO +# postulate -- trust or totality escapes +# ?name -- a hole: idris2 --build accepts a module with holes +# partial / covering -- a declaration opting out of %default total +scan_escapes() { + local found=1 f + if code_only "$@" | grep -E \ + -e ':[0-9]+:.*\b(believe_me|assert_total|assert_smaller|idris_crash|unsafePerformIO|postulate)\b' \ + -e ':[0-9]+:(.*[^A-Za-z0-9_'"'"'?])?\?[a-z_][A-Za-z0-9_'"'"']*' \ + -e ':[0-9]+:.*\b(partial|covering)\b'; then + found=0 + fi + for f in "$@"; do + grep -qE '^%default[[:space:]]+total[[:space:]]*$' "$f" || { printf '%s: no %%default total\n' "$f"; found=0; } + done + return "$found" +} + +# The modules a PanelClades module imports, transitively, that resolve to a +# source file under one of the given roots. Imports that resolve nowhere +# are Idris2's own prelude/base/contrib, the prover's trust base, and are +# listed separately. Output: one path per line (cone), and the unresolved +# module names in "$OUT/import-base". +import_cone() { + local -a queue=("$@") roots=(src/abi "$TW") seen=() + local f m r p hit + : > "$OUT/import-base" + while [ "${#queue[@]}" -gt 0 ]; do + f="${queue[0]}"; queue=("${queue[@]:1}") + case " ${seen[*]-} " in *" $f "*) continue ;; esac + seen+=("$f"); printf '%s\n' "$f" + while IFS= read -r m; do + hit="" + for r in "${roots[@]}"; do + p="$r/${m//.//}.idr" + [ -f "$p" ] && { hit="$p"; break; } + done + if [ -n "$hit" ]; then queue+=("$hit"); else printf '%s\n' "$m" >> "$OUT/import-base"; fi + done < <(sed -nE 's/^import[[:space:]]+(public[[:space:]]+)?([A-Za-z0-9_.]+).*/\2/p' "$f") + done +} + command -v idris2 >/dev/null || die "idris2 not on PATH (a gate that cannot run must not report OK)" iv="$(idris2 --version)"; echo "$iv" case "$iv" in *"version 0.8.0"*) ;; *) [ -n "${ALLOW_ANY_IDRIS2:-}" ] || die "Idris2 0.8.0 required (set ALLOW_ANY_IDRIS2=1 to try another)" ;; esac @@ -47,10 +117,10 @@ for f in "${abi_files[@]}"; do grep -qE "^[[:space:]]*(modules[[:space:]]*=|,)[[:space:]]*${mod//./\\.}[[:space:]]*$" "$IPKG" \ || err "$mod is not listed in $IPKG (it would never be checked)" done -if grep -nE '\b(believe_me|assert_total|assert_smaller|idris_crash|unsafePerformIO|postulate)\b' "${abi_files[@]}"; then +if scan_escapes "${abi_files[@]}"; then err "escape hatch in src/abi/PanelClades (lines above)" fi -[ "$fail" -eq 0 ] && pass "every PanelClades module is listed in the ipkg; no escape hatches" +[ "$fail" -eq 0 ] && pass "every PanelClades module is listed in the ipkg, declares %default total, and has no escape hatch or hole" if bash scripts/gen-budgets.sh --check; then pass "Budgets.idr is current with the clade configs" else @@ -68,6 +138,19 @@ fi pass "typed-wasm grade kernel built at $(git -C _build/dependencies/typed-wasm rev-parse --short HEAD)" KTTC="$ROOT/$TW/build/ttc" +# The import cone (standards PROOF-POLICY §1.1): every first-party module the +# proofs rest on, PanelClades and typed-wasm alike, is held to the same scan. +# A kernel module with a hole or an escape hatch would be trusted silently. +import_cone "${abi_files[@]}" > "$OUT/import-cone" +mapfile -t cone < <(grep -v '^src/abi/PanelClades/' "$OUT/import-cone") +[ "${#cone[@]}" -gt 0 ] || die "import cone has no typed-wasm module; Grades.idr imports the kernel" +if scan_escapes "${cone[@]}"; then + err "escape hatch or hole in the typed-wasm import cone (lines above)" +else + pass "import cone: ${#cone[@]} typed-wasm module(s), no escape hatch or hole; base: $(sort -u "$OUT/import-base" | tr '\n' ' ')" +fi +sed 's/^/ cone: /' "$OUT/import-cone" + rm -rf src/abi/build (cd src/abi && IDRIS2_PATH="$KTTC" idris2 --build panel-clades-grades.ipkg) > "$OUT/grades.log" 2>&1 st=$? From 5f92d07fbc2f8155f44350d27b0c6f5cff6d0ddc Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 9 Nov 2026 13:11:10 +0000 Subject: [PATCH 12/21] proofs(abi): the Nat and Bits32 tag tables agree Types.idr writes each Result and CladeKind tag twice: as a Nat (resultTag, cladeKindTag) behind decidable equality, and as a Bits32 (resultToInt, cladeKindToInt), the C encoding. Each table round-trips on its own, but nothing tied them together, so one could drift and both round-trip proofs would still pass. resultTagsAgree and cladeKindTagsAgree prove resultToInt r = cast (resultTag r) and cladeKindToInt k = cast (cladeKindTag k), one Refl per constructor. Checked both ways with Idris2 0.8.0 built from 15a3e4e: - check-abi.sh passes (5 modules, 0 errors, 6 of 6 rejections, import cone clean); - a planted drift (NullPointer encoded as 5, with resultFromInt changed to match, so the round-trip still holds) is refused at resultTagsAgree: "Mismatch between: 4 and 5". PROOF-NEEDS.adoc moves the item from the open gaps to what Types.idr proves, and records the local run (stating it is not a receipt). Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01BCQeL169ZnUirUVaDXQLA3 --- PROOF-NEEDS.adoc | 33 ++++++++----------- .../src/abi/PanelClades/ABI/Types.idr | 29 ++++++++++++++++ 2 files changed, 42 insertions(+), 20 deletions(-) diff --git a/PROOF-NEEDS.adoc b/PROOF-NEEDS.adoc index 53eb6868..8b700e9e 100644 --- a/PROOF-NEEDS.adoc +++ b/PROOF-NEEDS.adoc @@ -101,10 +101,13 @@ prelude and base (`Data.List`, `Data.Nat`, `Data.So`, `Data.Vect`, declaration |=== -That run predates the import-cone and hole scans. The cone was scanned at -the pin by hand, with the same rules, and is clean. The first `main` run -after this change is the first receipt to include both. A receipt is -current only for its commit: rerun the job, never carry a result forward. +That run predates the import-cone and hole scans and the tag-agreement +proofs. On 2026-11-09 the same `check-abi.sh`, with those additions, ran +locally with Idris2 0.8.0 built from the same commit and passed with the +same result, listing the cone above. A local run is not a receipt. The +first `main` run after this change is the first receipt to include them. +A receipt is current only for its commit: rerun the job, never carry a +result forward. === What is proved @@ -118,7 +121,11 @@ current only for its commit: rerun the job, never carry a result forward. injective (`resultRoundTrip`, `cladeKindRoundTrip`). Equality on both is decidable without a catch-all `absurd` (`decEqViaTag`). `allCladeKinds` lists every kind (`cladeKindExhaustive`) and has length `cladeKindCount`. -A `Handle` is never null and a `CladeId` is never empty, by construction. +Each tag is written twice, as a `Nat` for decidable equality and as a +`Bits32` for the C encoding, and the two give the same number +(`resultTagsAgree`, `cladeKindTagsAgree`), so the tables cannot drift +apart. A `Handle` is never null and a `CladeId` is never empty, by +construction. |`Layout.idr` |`PROVEN` @@ -159,7 +166,7 @@ and `WasmPointerIsEightBytes`. === What is not proved The first item and the last two are stated in the modules themselves. The -other three were found reviewing the proofs on 2026-11-09. +other two were found reviewing the proofs on 2026-11-09. * *Struct size covers the last field.* `Layout.idr` does not prove that a computed size is at least the end of the last field; `verifyLayout` @@ -175,11 +182,6 @@ other three were found reviewing the proofs on 2026-11-09. not prove that fields are in order and do not overlap, that alignments are powers of two (an alignment of 0 is accepted), or that the struct's alignment is its largest field alignment. -* *The two tag tables agree.* `Types.idr` defines each tag twice: as a - `Nat` (`resultTag`, `cladeKindTag`, used for decidable equality) and as - a `Bits32` (`resultToInt`, `cladeKindToInt`, the C encoding). Each table - is proved to round-trip on its own; nothing proves the two give the same - number. * *The Zig side matches.* Nothing checks that `panel-clades/ffi/zig/` exports the symbols `Foreign.idr` declares, with those types, or uses the sizes and tags `Types.idr` proves. `ffi/zig/src/main.zig` restates @@ -219,13 +221,6 @@ evidence. |Not started. Today `CABICompliant` carries field alignment only. |— -|Tag tables agree -|`resultToInt r = cast (resultTag r)` for every `Result`, and the same for -`CladeKind`, or one table derived from the other. -|`OPEN` -|Not started. A case-by-case equation for each constructor. -|— - |Safe callback registration |A safe wrapper for `prim__registerCallback` in `Foreign.idr`, using Idris2's FFI callback mechanism (`cast` is banned). @@ -433,8 +428,6 @@ that is not the definition of `Valid`. . Struct size lower bound and the safe callback wrapper (#149): small, local, and they close the two gaps the modules admit to. The rounding lemma comes first, since the lower bound rests on it. -. Tag tables agree: one equation per constructor, and it removes a - drift between the C encoding and the decidable-equality tags. . Zig/Idris FFI agreement check: cheap, and it removes the largest unchecked assumption under the proofs. . PCC soundness: the highest-value proof, but only after the AffineScript diff --git a/panel-clades/src/abi/PanelClades/ABI/Types.idr b/panel-clades/src/abi/PanelClades/ABI/Types.idr index c91db0f5..22a0eb90 100644 --- a/panel-clades/src/abi/PanelClades/ABI/Types.idr +++ b/panel-clades/src/abi/PanelClades/ABI/Types.idr @@ -144,6 +144,17 @@ public export DecEq Result where decEq = decEqViaTag resultTag resultFromTag resultTagRetract +||| The C encoding of a `Result` is its decidable-equality tag, as a +||| `Bits32`. The two tables are written out separately; this keeps them +||| from drifting apart. +export +resultTagsAgree : (r : Result) -> resultToInt r = cast (resultTag r) +resultTagsAgree Ok = Refl +resultTagsAgree Error = Refl +resultTagsAgree InvalidParam = Refl +resultTagsAgree OutOfMemory = Refl +resultTagsAgree NullPointer = Refl + -------------------------------------------------------------------------------- -- Opaque Handles -------------------------------------------------------------------------------- @@ -401,6 +412,24 @@ public export DecEq CladeKind where decEq = decEqViaTag cladeKindTag cladeKindFromTag cladeKindTagRetract +||| The C encoding of a `CladeKind` is its decidable-equality tag, as a +||| `Bits32`, so the two tables cannot drift apart. +export +cladeKindTagsAgree : (k : CladeKind) -> cladeKindToInt k = cast (cladeKindTag k) +cladeKindTagsAgree Directive = Refl +cladeKindTagsAgree Scanner = Refl +cladeKindTagsAgree Builder = Refl +cladeKindTagsAgree Database = Refl +cladeKindTagsAgree Network = Refl +cladeKindTagsAgree Viewer = Refl +cladeKindTagsAgree Ai = Refl +cladeKindTagsAgree Loader = Refl +cladeKindTagsAgree Meta = Refl +cladeKindTagsAgree Service = Refl +cladeKindTagsAgree Inspector = Refl +cladeKindTagsAgree Bridge = Refl +cladeKindTagsAgree Terminal = Refl + ||| The total number of clade kinds. public export cladeKindCount : Nat From 2e87e0e0b896e61bffe10496f78b5415db81cc38 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 9 Nov 2026 13:24:12 +0000 Subject: [PATCH 13/21] proofs(abi): tight rounding, struct size covers its fields, full C layout evidence Closes the two Idris2 obligations the review left OPEN (owner decision 2026-11-09: prove both now). Layout.idr - alignUpCovers / alignUpTight: for an alignment a >= 1, size <= alignUp size a < size + a, by induction on the fuel of unitsFor (alignUpCorrect, divisibility, held by construction and carried no weight on its own). - calcStructSizeCovers, fieldWithinEnd, fieldWithinStruct: a computed struct size covers every field. StructLayout now carries that coverage as an erased LTE proof instead of a Boolean So, and verifyLayout drops its run-time check: its only refusal is an alignment of 0. structEnd is now structural over Data.Nat's maximum (same values as the previous max fold) so the lemmas can reason about it. - CABICompliant now carries, besides FieldsAligned: FieldsOrdered (each field starts at or after the previous one ends, so none overlap), AlignmentsPow2 (IsPow2, decided by decPow2), and layout.alignment = maxAlignment fields. checkCABI decides all four with equations and LTE proofs; every Right carries real evidence. - The example layout carries all four parts, and exampleCheckPasses shows the decision procedure agrees on it. Reject fixtures (tests/reject/), each refused at its `invalid` line for the expected reason: - OverlappingFields: no FieldsOrdered for a field starting inside the previous one ("Can't find an implementation for So False"); - AlignmentNotPow2: decPow2 12 is Nothing ("Mismatch between: True and False"); - StructUnderAligned: a layout that is aligned, ordered and power-of-two but declares alignment 4 against a field of 8 is refused by checkCABI. Positive controls (run locally, not committed): the same layout at alignment 8 passes checkCABI; decPow2 accepts 1 and 16 and refuses 0; verifyLayout accepts a nonzero alignment; alignUp 9 8 = 16. Fixtures import Data.Nat, Data.So and Decidable.Equality directly: Idris2 did not reduce lte or the DecEq Nat instance through a transitive import, which made the first drafts fail for the wrong reason ("Can't solve constraint"); the EXPECT lines now pin the right one. check-abi.sh with Idris2 0.8.0 (15a3e4e), locally: 5 modules, 0 errors, 9 of 9 rejections, import cone clean. PROOF-NEEDS.adoc, src/abi/README.adoc and CHANGELOG.adoc record the new proofs; the remaining "not proved" items are the decision procedures' completeness, Zig agreement and runtime grades. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01BCQeL169ZnUirUVaDXQLA3 --- CHANGELOG.adoc | 22 ++ PROOF-NEEDS.adoc | 83 ++--- .../src/abi/PanelClades/ABI/Layout.idr | 305 ++++++++++++++++-- panel-clades/src/abi/README.adoc | 16 +- .../tests/reject/AlignmentNotPow2.idr | 19 ++ .../tests/reject/OverlappingFields.idr | 18 ++ .../tests/reject/StructUnderAligned.idr | 24 ++ 7 files changed, 395 insertions(+), 92 deletions(-) create mode 100644 panel-clades/tests/reject/AlignmentNotPow2.idr create mode 100644 panel-clades/tests/reject/OverlappingFields.idr create mode 100644 panel-clades/tests/reject/StructUnderAligned.idr diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index 4f23bd0d..63c9cc07 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -9,6 +9,28 @@ https://semver.org/spec/v2.0.0.html[Semantic Versioning]. === https://github.com/hyperpolymath/panll/compare/v0.1.0...HEAD[Unreleased] +==== Added (2026-11-09 — panel-clades layout proofs and a stricter ABI gate) + +* *Rounding up is tight.* For an alignment `+a ≥ 1+`, `+alignUp+` is at or +above the size and below size plus `+a+` (`+alignUpCovers+`, +`+alignUpTight+`). +* *Struct size covers every field.* `+calcStructSizeCovers+` and +`+fieldWithinStruct+` prove it, so `+verifyLayout+` drops its run-time +check and refuses only an alignment of 0. `+StructLayout+` now carries +that coverage as an `+LTE+` proof instead of a Boolean `+So+`. +* *C layout evidence.* `+CABICompliant+` now also carries fields in order +without overlap (`+FieldsOrdered+`), power-of-two alignments +(`+AlignmentsPow2+`, decided by `+decPow2+`), and a struct alignment equal +to the largest field alignment (`+maxAlignment+`). `+checkCABI+` decides +all four parts. +* *Tag tables agree.* `+resultTagsAgree+` and `+cladeKindTagsAgree+` tie +each `+Bits32+` C encoding to its `+Nat+` equality tag. +* Three more expected rejections: `+OverlappingFields+`, +`+AlignmentNotPow2+`, `+StructUnderAligned+`. +* `+check-abi.sh+` now also fails on holes, `+partial+`, `+covering+` and a +module without `+%default total+`, reads code only (not comments or +strings), and holds the typed-wasm import cone to the same scan. + ==== Removed (2026-11-05) * *The PCC ReScript scanner (`tools/pcc/src/rescript_scanner.rs`) is diff --git a/PROOF-NEEDS.adoc b/PROOF-NEEDS.adoc index 8b700e9e..1dd61afb 100644 --- a/PROOF-NEEDS.adoc +++ b/PROOF-NEEDS.adoc @@ -101,10 +101,11 @@ prelude and base (`Data.List`, `Data.Nat`, `Data.So`, `Data.Vect`, declaration |=== -That run predates the import-cone and hole scans and the tag-agreement -proofs. On 2026-11-09 the same `check-abi.sh`, with those additions, ran -locally with Idris2 0.8.0 built from the same commit and passed with the -same result, listing the cone above. A local run is not a receipt. The +That run predates the import-cone and hole scans, the tag-agreement +proofs, the layout bounds and the C layout evidence. On 2026-11-09 the +same `check-abi.sh`, with those additions, ran locally with Idris2 0.8.0 +built from the same commit and passed: 5 modules, 0 errors, 9 of 9 +fixtures refused, and the cone above clean. A local run is not a receipt. The first `main` run after this change is the first receipt to include them. A receipt is current only for its commit: rerun the job, never carry a result forward. @@ -129,10 +130,19 @@ construction. |`Layout.idr` |`PROVEN` -|Rounding up to an alignment lands on a multiple of it (`alignUpCorrect`). -Every computed struct size is a multiple of its alignment -(`calcStructSizeAligned`). Field alignment is decided by an equation on -`Nat` (`decDivides`, `proveFieldsAligned`), not by a Boolean test. +|Rounding up to an alignment `a ≥ 1` is tight: the result is a multiple +of `a` (`alignUpCorrect`), at or above the size (`alignUpCovers`) and +below size plus `a` (`alignUpTight`). A computed struct size is a +multiple of its alignment (`calcStructSizeAligned`) and covers every field +(`calcStructSizeCovers`, `fieldWithinEnd`, `fieldWithinStruct`), so +`verifyLayout` has no run-time size check and refuses only an alignment of +0. `CABICompliant` evidence carries every offset as a multiple of its +alignment (`FieldsAligned`), the fields in order with each starting at or +after the previous one's end (`FieldsOrdered`; no two overlap, since the +chain is ordered), every alignment a power of two (`AlignmentsPow2`), and +the struct aligned to its largest field alignment (`maxAlignment`). Each +part is decided by an equation or an `LTE` proof (`decDivides`, +`isLTE`, `decPow2`, `decEq`), not by a Boolean test. |`Grades.idr` |`PROVEN` @@ -159,29 +169,20 @@ not measurements. |Typechecks only. It declares the Zig FFI surface; it proves nothing. |=== -The reject fixtures pin down what the types refuse: `BudgetTooSmall`, -`CollapseWithinTolerance`, `MisalignedField`, `NullHandle`, `OverBudget` +The reject fixtures pin down what the types and the decision procedures +refuse: `AlignmentNotPow2`, `BudgetTooSmall`, +`CollapseWithinTolerance`, `MisalignedField`, `NullHandle`, `OverBudget`, +`OverlappingFields`, `StructUnderAligned` and `WasmPointerIsEightBytes`. === What is not proved -The first item and the last two are stated in the modules themselves. The -other two were found reviewing the proofs on 2026-11-09. - -* *Struct size covers the last field.* `Layout.idr` does not prove that a - computed size is at least the end of the last field; `verifyLayout` - checks it at run time and refuses a layout that fails. -* *Rounding up reaches the size.* `alignUpCorrect` holds by construction: - `alignUp` is defined as a number of units times the alignment, so it is - always a multiple. The property that carries weight, that for an - alignment of at least 1 the result is at least the size and less than - size plus alignment, is not proved. It is the lemma the struct-size - lower bound needs. -* *C layout beyond alignment.* `CABICompliant` certifies that each - field's offset is a multiple of its alignment, and nothing else. It does - not prove that fields are in order and do not overlap, that alignments - are powers of two (an alignment of 0 is accepted), or that the struct's - alignment is its largest field alignment. +These limits are stated in the modules themselves. + +* *The decision procedures are complete.* `decDivides`, `decPow2` and + `checkCABI` are sound: every `Just` or `Right` carries real evidence. A + `Nothing` or `Left` means only "not shown"; that the property is then + false is not proved. * *The Zig side matches.* Nothing checks that `panel-clades/ffi/zig/` exports the symbols `Foreign.idr` declares, with those types, or uses the sizes and tags `Types.idr` proves. `ffi/zig/src/main.zig` restates @@ -198,29 +199,6 @@ Status is the PROOF-POLICY §5 token. The note says why, and what comes next. |=== |Item |What |Status |Note |Tracked -|Struct size lower bound -|Prove `calcStructSize` is at least the end of the last field, and drop -the run-time check in `verifyLayout`. -|`OPEN` -|Not started. Induction over the field list, plus the rounding lemma -below. -|— - -|Rounding up reaches the size -|For an alignment `a ≥ 1`: `size ≤ alignUp size a < size + a`. -|`OPEN` -|Not started. Induction on the fuel of `unitsFor`. The struct-size lower -bound rests on it. -|— - -|C layout rules -|Fields in order and not overlapping, every alignment a power of two, and -the struct aligned to its largest field alignment, as `CABICompliant` -evidence. -|`OPEN` -|Not started. Today `CABICompliant` carries field alignment only. -|— - |Safe callback registration |A safe wrapper for `prim__registerCallback` in `Foreign.idr`, using Idris2's FFI callback mechanism (`cast` is banned). @@ -425,9 +403,8 @@ that is not the definition of `Valid`. == Priority -. Struct size lower bound and the safe callback wrapper (#149): small, - local, and they close the two gaps the modules admit to. The rounding - lemma comes first, since the lower bound rests on it. +. The safe callback wrapper (#149): small and local, and the last gap + `Foreign.idr` admits to. . Zig/Idris FFI agreement check: cheap, and it removes the largest unchecked assumption under the proofs. . PCC soundness: the highest-value proof, but only after the AffineScript diff --git a/panel-clades/src/abi/PanelClades/ABI/Layout.idr b/panel-clades/src/abi/PanelClades/ABI/Layout.idr index 0163ea50..b795c4eb 100644 --- a/panel-clades/src/abi/PanelClades/ABI/Layout.idr +++ b/panel-clades/src/abi/PanelClades/ABI/Layout.idr @@ -6,28 +6,85 @@ ||| Alignment and struct layout for the C-compatible structs of the ||| panel-clades ABI. ||| -||| What is proved: rounding up to an alignment always lands on a multiple -||| of it (`alignUpCorrect`, by construction), every computed struct size -||| is a multiple of its alignment (`calcStructSizeAligned`), and every -||| `Divides` / `FieldsAligned` witness is a real equation. Field -||| alignment is decided by an equation on Nat (`decDivides`), not by a -||| Boolean test followed by an unproved `Refl`. +||| What is proved: ||| -||| What is NOT proved: that a computed size is at least the end of the -||| last field. `verifyLayout` checks that at run time, and refuses a -||| layout that fails it. +||| * rounding up to an alignment a ≥ 1 is tight: it lands on a multiple +||| of a (`alignUpCorrect`), at or above the size (`alignUpCovers`) and +||| below size + a (`alignUpTight`); +||| * a computed struct size is a multiple of its alignment +||| (`calcStructSizeAligned`) and covers every field +||| (`calcStructSizeCovers`, `fieldWithinStruct`), so `verifyLayout` +||| needs no run-time size check; +||| * C layout evidence (`CABICompliant`) carries, for every field, an +||| offset that is a multiple of its alignment (`FieldsAligned`), fields +||| in order and not overlapping (`FieldsOrdered`), alignments that are +||| powers of two (`AlignmentsPow2`), and a struct alignment equal to +||| the largest field alignment (`maxAlignment`). Each is decided by an +||| equation or an `LTE` proof, never by a Boolean test followed by an +||| unproved `Refl`. +||| +||| What is NOT proved: that a Zig or C compiler lays a struct out this way. +||| The evidence says a layout follows the C rules; matching the Zig side is +||| a separate check (PROOF-NEEDS.adoc, "Zig/Idris FFI agreement"). ||| ||| @see https://en.wikipedia.org/wiki/Data_structure_alignment module PanelClades.ABI.Layout import PanelClades.ABI.Types +import Data.Either import Data.List +import Data.List.Elem +import Data.Nat import Data.So import Decidable.Equality %default total +-------------------------------------------------------------------------------- +-- Nat lemmas (local, so this module imports nothing beyond base) +-------------------------------------------------------------------------------- + +lteR : (n : Nat) -> LTE n n +lteR Z = LTEZero +lteR (S n) = LTESucc (lteR n) + +lteT : LTE a b -> LTE b c -> LTE a c +lteT LTEZero _ = LTEZero +lteT (LTESucc p) (LTESucc q) = LTESucc (lteT p q) + +lteEqL : a = b -> LTE b c -> LTE a c +lteEqL Refl p = p + +lteEqR : LTE a b -> b = c -> LTE a c +lteEqR p Refl = p + +||| k - u never exceeds k. +minusLteSelf : (k, u : Nat) -> LTE (k `minus` u) k +minusLteSelf Z _ = LTEZero +minusLteSelf (S k) Z = lteR (S k) +minusLteSelf (S k) (S u) = lteSuccRight (minusLteSelf k u) + +||| k fits in u plus what k has left over u. +lteMinusPlus : (k, u : Nat) -> LTE k (u + (k `minus` u)) +lteMinusPlus Z _ = LTEZero +lteMinusPlus (S k) Z = lteR (S k) +lteMinusPlus (S k) (S u) = LTESucc (lteMinusPlus k u) + +||| When k ≤ u, k - u is 0. +minusLteZero : (k, u : Nat) -> LTE k u -> k `minus` u = Z +minusLteZero Z _ _ = Refl +minusLteZero (S k) (S u) (LTESucc p) = minusLteZero k u p +minusLteZero (S _) Z LTEZero impossible + +||| Adding back what was taken off. +addBack : (u, k, t : Nat) -> LTE u k -> u + ((k `minus` u) + t) = k + t +addBack u k t le = + rewrite plusAssociative u (k `minus` u) t in + rewrite plusCommutative u (k `minus` u) in + rewrite plusMinusLte u k le in + Refl + -------------------------------------------------------------------------------- -- Alignment Utilities -------------------------------------------------------------------------------- @@ -69,11 +126,54 @@ paddingFor offset alignment = alignUp offset alignment `minus` offset ||| `alignUp` always produces a multiple of the alignment. This holds by ||| construction (alignUp is defined as units × alignment), for every -||| alignment including 0. +||| alignment including 0. `alignUpCovers` and `alignUpTight` are the +||| properties that carry weight. public export alignUpCorrect : (size : Nat) -> (align : Nat) -> Divides align (alignUp size align) alignUpCorrect size align = DivideBy (alignUnits size align) Refl +||| With enough fuel, the units cover the size. +unitsCover : (fuel, size, u : Nat) -> LTE size fuel + -> LTE size (unitsFor fuel size (S u) * S u) +unitsCover _ Z _ _ = LTEZero +unitsCover Z (S k) _ prf = void (succNotLTEzero prf) +unitsCover (S f) (S k) u prf = + let ih = unitsCover f (k `minus` u) u (lteT (minusLteSelf k u) (fromLteSucc prf)) + in LTESucc (lteT (lteMinusPlus k u) (plusLteMonotoneLeft u _ _ ih)) + +||| Units for nothing are none, whatever the fuel. +unitsZero : (fuel, unit : Nat) -> unitsFor fuel Z unit = Z +unitsZero Z _ = Refl +unitsZero (S _) _ = Refl + +||| With enough fuel, the units overshoot the size by less than one unit. +unitsTight : (fuel, size, u : Nat) -> LTE size fuel + -> LT (unitsFor fuel size (S u) * S u) (size + S u) +unitsTight Z Z _ _ = LTESucc LTEZero +unitsTight (S _) Z _ _ = LTESucc LTEZero +unitsTight Z (S k) _ prf = void (succNotLTEzero prf) +unitsTight (S f) (S k) u prf with (isLTE u k) + _ | Yes le = + let ih = unitsTight f (k `minus` u) u (lteT (minusLteSelf k u) (fromLteSucc prf)) + step = plusLteMonotoneLeft u _ _ ih + in LTESucc (lteEqL (plusSuccRightSucc u _) (lteEqR step (addBack u k (S u) le))) + _ | No gt = + rewrite minusLteZero k u (lteSuccLeft (notLTEImpliesGT gt)) in + rewrite unitsZero f (S u) in + LTESucc (lteEqL (cong S (plusZeroRightNeutral u)) + (lteEqR (lteAddRight (S u)) (plusCommutative (S u) k))) + +||| Rounding up to an alignment of at least 1 never lands below the size. +export +alignUpCovers : (size, u : Nat) -> LTE size (alignUp size (S u)) +alignUpCovers size u = unitsCover size size u (lteR size) + +||| Rounding up to an alignment a ≥ 1 adds less than a: it is the least +||| multiple of a at or above the size. +export +alignUpTight : (size, u : Nat) -> LT (alignUp size (S u)) (size + S u) +alignUpTight size u = unitsTight size size u (lteR size) + ||| Decide whether n divides m, returning the witness when it does. The ||| candidate quotient is the ceiling quotient (equal to the exact one ||| whenever n divides m), then the equation is checked with Nat's @@ -86,6 +186,25 @@ decDivides n m = Yes prf => Just (DivideBy (alignUnits m n) prf) No _ => Nothing +||| `n` is a power of two: 1, or twice a power of two. +public export +data IsPow2 : Nat -> Type where + Pow2One : IsPow2 1 + Pow2Double : IsPow2 n -> IsPow2 (n + n) + +||| Decide whether n is a power of two by doubling from 1. A `Just` +||| always carries a real `IsPow2` witness; `Nothing` means "not shown". +||| n + 1 doublings are more than enough fuel. +public export +decPow2 : (n : Nat) -> Maybe (IsPow2 n) +decPow2 n = go (S n) 1 Pow2One + where + go : (fuel : Nat) -> (p : Nat) -> IsPow2 p -> Maybe (IsPow2 n) + go Z _ _ = Nothing + go (S f) p pf = case decEq p n of + Yes same => Just (replace {p = IsPow2} same pf) + No _ => if lte p n then go f (p + p) (Pow2Double pf) else Nothing + -------------------------------------------------------------------------------- -- Struct Field Layout -------------------------------------------------------------------------------- @@ -110,9 +229,19 @@ nextFieldOffset : Field -> Nat nextFieldOffset f = alignUp (fieldEnd f) f.alignment ||| The greatest field end offset in bytes, or 0 for an empty list. +||| (Structural, with Data.Nat's `maximum`, so the lemmas below can reason +||| about it; it computes the same value as the earlier `max` fold.) public export structEnd : List Field -> Nat -structEnd = foldr (\f, acc => max (fieldEnd f) acc) 0 +structEnd [] = 0 +structEnd (f :: fs) = maximum (fieldEnd f) (structEnd fs) + +||| Every field ends at or before the struct's last field end. +export +fieldWithinEnd : (fs : List Field) -> Elem f fs -> LTE (fieldEnd f) (structEnd fs) +fieldWithinEnd (f :: fs) Here = maximumLeftUpperBound (fieldEnd f) (structEnd fs) +fieldWithinEnd (g :: fs) (There e) = + lteT (fieldWithinEnd fs e) (maximumRightUpperBound (fieldEnd g) (structEnd fs)) ||| Total struct size: the furthest field end, rounded up to the struct's ||| alignment. (Fields carry explicit offsets, so this is the C rule; the @@ -129,7 +258,14 @@ calcStructSizeAligned : (fields : List Field) -> (align : Nat) -> Divides align (calcStructSize fields align) calcStructSizeAligned fields align = alignUpCorrect (structEnd fields) align -||| A struct layout: its fields, its size and alignment, a check that the +||| With an alignment of at least 1, the computed struct size covers every +||| field. +export +calcStructSizeCovers : (fields : List Field) -> (u : Nat) + -> LTE (structEnd fields) (calcStructSize fields (S u)) +calcStructSizeCovers fields u = alignUpCovers (structEnd fields) u + +||| A struct layout: its fields, its size and alignment, a proof that the ||| size covers the fields, and a proof that the size is a multiple of the ||| alignment. public export @@ -138,9 +274,15 @@ record StructLayout where fields : List Field totalSize : Nat alignment : Nat - {auto 0 sizeCorrect : So (totalSize >= structEnd fields)} + 0 covers : LTE (structEnd fields) totalSize 0 aligned : Divides alignment totalSize +||| Every field of a layout lies within its total size. +export +0 fieldWithinStruct : (layout : StructLayout) -> Elem f layout.fields + -> LTE (fieldEnd f) layout.totalSize +fieldWithinStruct layout e = lteT (fieldWithinEnd layout.fields e) layout.covers + ||| Proof that every field's offset is a multiple of its alignment public export data FieldsAligned : List Field -> Type where @@ -152,29 +294,55 @@ data FieldsAligned : List Field -> Type where FieldsAligned rest -> FieldsAligned (f :: rest) -||| Build a layout whose size is computed from its fields; refused when -||| the computed size does not cover them. -||| Returns Right with the layout, or Left "Invalid struct size". -||| Field offsets are checked separately by `checkCABI`. +||| Build a layout whose size is computed from its fields. The size is +||| proved to cover the fields and to be a multiple of the alignment, so +||| the only refusal is an alignment of 0, which no C type has. +||| Field offsets and the C rules are checked separately by `checkCABI`. public export verifyLayout : (fields : List Field) -> (align : Nat) -> Either String StructLayout -verifyLayout fields align = - case choose (calcStructSize fields align >= structEnd fields) of - Left covers => - Right (MkStructLayout fields (calcStructSize fields align) align - {sizeCorrect = covers} (calcStructSizeAligned fields align)) - Right _ => Left "Invalid struct size" +verifyLayout _ Z = Left "Alignment 0 is not a C alignment" +verifyLayout fields (S u) = + Right (MkStructLayout fields (calcStructSize fields (S u)) (S u) + (calcStructSizeCovers fields u) (calcStructSizeAligned fields (S u))) -------------------------------------------------------------------------------- -- C ABI Compatibility -------------------------------------------------------------------------------- -||| Proof that a struct follows C ABI rules (every field aligned) +||| Fields in declaration order, each starting at or after the end of the +||| one before, so no two overlap. +public export +data FieldsOrdered : List Field -> Type where + OrdNil : FieldsOrdered [] + OrdOne : FieldsOrdered [f] + OrdCons : LTE (fieldEnd f) g.offset -> FieldsOrdered (g :: rest) + -> FieldsOrdered (f :: g :: rest) + +||| Every field's alignment is a power of two. +public export +data AlignmentsPow2 : List Field -> Type where + Pow2Nil : AlignmentsPow2 [] + Pow2Cons : IsPow2 f.alignment -> AlignmentsPow2 rest -> AlignmentsPow2 (f :: rest) + +||| The C struct alignment: the largest field alignment, or 1 for no fields. +public export +maxAlignment : List Field -> Nat +maxAlignment [] = 1 +maxAlignment (f :: fs) = maximum f.alignment (maxAlignment fs) + +||| Proof that a struct follows the C layout rules: every field offset is +||| a multiple of its alignment, the fields are in order and do not +||| overlap, every alignment is a power of two, and the struct is aligned +||| to its largest field alignment. (Size covering the fields, and being a +||| multiple of the alignment, are carried by `StructLayout` itself.) public export data CABICompliant : StructLayout -> Type where CABIOk : (layout : StructLayout) -> FieldsAligned layout.fields -> + FieldsOrdered layout.fields -> + AlignmentsPow2 layout.fields -> + layout.alignment = maxAlignment layout.fields -> CABICompliant layout ||| Attempt to prove all fields are correctly aligned. Returns `Left` @@ -192,14 +360,53 @@ proveFieldsAligned (f :: rest) = Left err => Left err Right restProof => Right (ConsField f rest here restProof) -||| Return a proof that every field offset is a multiple of its alignment, -||| or propagate the first field-alignment error from `proveFieldsAligned`. +||| Attempt to prove the fields are in order and do not overlap. Returns +||| `Left` naming the first field that starts before the previous one ends. +public export +proveFieldsOrdered : (fields : List Field) -> Either String (FieldsOrdered fields) +proveFieldsOrdered [] = Right OrdNil +proveFieldsOrdered [_] = Right OrdOne +proveFieldsOrdered (f :: g :: rest) = + case isLTE (fieldEnd f) g.offset of + No _ => Left ("Field " ++ g.name ++ " at offset " ++ show g.offset + ++ " overlaps " ++ f.name ++ ", which ends at " + ++ show (fieldEnd f)) + Yes fit => + case proveFieldsOrdered (g :: rest) of + Left err => Left err + Right restProof => Right (OrdCons fit restProof) + +||| Attempt to prove every field alignment is a power of two. +public export +proveAlignmentsPow2 : (fields : List Field) -> Either String (AlignmentsPow2 fields) +proveAlignmentsPow2 [] = Right Pow2Nil +proveAlignmentsPow2 (f :: rest) = + case decPow2 f.alignment of + Nothing => Left ("Field " ++ f.name ++ " has alignment " + ++ show f.alignment ++ ", not a power of two") + Just p => + case proveAlignmentsPow2 rest of + Left err => Left err + Right restProof => Right (Pow2Cons p restProof) + +||| Return C layout evidence for a layout, or the first rule it breaks. public export checkCABI : (layout : StructLayout) -> Either String (CABICompliant layout) checkCABI layout = case proveFieldsAligned layout.fields of - Right prf => Right (CABIOk layout prf) Left err => Left err + Right aligned => + case proveFieldsOrdered layout.fields of + Left err => Left err + Right ordered => + case proveAlignmentsPow2 layout.fields of + Left err => Left err + Right pow2 => + case decEq layout.alignment (maxAlignment layout.fields) of + No _ => Left ("Struct alignment " ++ show layout.alignment + ++ " is not the largest field alignment " + ++ show (maxAlignment layout.fields)) + Yes same => Right (CABIOk layout aligned ordered pow2 same) -------------------------------------------------------------------------------- -- Platform-Specific Layouts @@ -219,7 +426,7 @@ verifyAllPlatforms : Either String () verifyAllPlatforms layouts = go allPlatforms where - ||| Check the remaining platforms, stopping at the first alignment error. + ||| Check the remaining platforms, stopping at the first C-rule error. go : List Platform -> Either String () go [] = Right () go (p :: ps) = case checkCABI (layouts p) of @@ -230,6 +437,13 @@ verifyAllPlatforms layouts = go allPlatforms -- Example Layouts -------------------------------------------------------------------------------- +||| An LTE proof found by evaluation, for closed numbers: `lte m n` +||| reduces, so the `So` is found automatically, and numbers that do not +||| fit have no `So True` and fail the typecheck at the use site. +public export +fitsIn : (m, n : Nat) -> {auto ok : So (lte m n)} -> LTE m n +fitsIn m n {ok} = lteReflectsLTE m n (soToEq ok) + ||| The example struct's fields: a u32, then a u64 and a double, each on ||| its natural boundary (4 bytes of padding after x). public export @@ -240,10 +454,11 @@ exampleFields = , MkField "z" 16 8 8 -- Double at offset 16 ] -||| Example: simple struct layout, 24 bytes, 8-byte aligned (24 = 3 × 8). +||| Example: simple struct layout, 24 bytes, 8-byte aligned (24 = 3 × 8), +||| covering its last field end (16 + 8 = 24). public export exampleLayout : StructLayout -exampleLayout = MkStructLayout exampleFields 24 8 (DivideBy 3 Refl) +exampleLayout = MkStructLayout exampleFields 24 8 (fitsIn 24 24) (DivideBy 3 Refl) ||| The computed size agrees with the declared one. export @@ -263,10 +478,33 @@ exampleFieldsAligned = (DivideBy 2 Refl) NoFields)) -||| Prove that every field in the example layout is correctly aligned. +||| The example's fields are in order: x ends at 4 ≤ 8, y ends at 16 ≤ 16. +export +exampleFieldsOrdered : FieldsOrdered PanelClades.ABI.Layout.exampleFields +exampleFieldsOrdered = OrdCons (fitsIn 4 8) (OrdCons (fitsIn 16 16) OrdOne) + +||| The example's alignments are 4 = 2 × 2 and 8 = 2 × 4. +export +exampleAlignmentsPow2 : AlignmentsPow2 PanelClades.ABI.Layout.exampleFields +exampleAlignmentsPow2 = Pow2Cons four (Pow2Cons eight (Pow2Cons eight Pow2Nil)) + where + four : IsPow2 4 + four = Pow2Double (Pow2Double Pow2One) + eight : IsPow2 8 + eight = Pow2Double four + +||| The example layout follows the C rules: aligned, ordered, power-of-two +||| alignments, and struct alignment 8, its largest field alignment. export exampleLayoutValid : CABICompliant PanelClades.ABI.Layout.exampleLayout -exampleLayoutValid = CABIOk exampleLayout exampleFieldsAligned +exampleLayoutValid = + CABIOk exampleLayout exampleFieldsAligned exampleFieldsOrdered + exampleAlignmentsPow2 Refl + +||| The decision procedure agrees on the example. +export +exampleCheckPasses : isRight (checkCABI PanelClades.ABI.Layout.exampleLayout) = True +exampleCheckPasses = Refl -------------------------------------------------------------------------------- -- Field lookup @@ -279,7 +517,8 @@ lookupField : (layout : StructLayout) -> (fieldName : String) -> Maybe Field lookupField layout name = find (\f => f.name == name) layout.fields ||| Run-time check that a field's span `[offset, offset+size)` fits inside -||| the struct. +||| the struct. For a field of the layout this always holds +||| (`fieldWithinStruct`); it remains for fields from elsewhere. public export offsetInBounds : (layout : StructLayout) -> (f : Field) -> Bool offsetInBounds layout f = fieldEnd f <= layout.totalSize diff --git a/panel-clades/src/abi/README.adoc b/panel-clades/src/abi/README.adoc index 854b8d10..10cbd7a9 100644 --- a/panel-clades/src/abi/README.adoc +++ b/panel-clades/src/abi/README.adoc @@ -31,7 +31,8 @@ Handle, the C type universe (`+CType+`, `+cSizeOf+`, `+HasSize+`), CladeId, CladeKind |`+PanelClades/ABI/Layout.idr+` |Alignment and struct layout: `+Divides+`, -`+alignUp+`, `+FieldsAligned+`, `+CABICompliant+` +`+alignUp+` and its bounds, `+FieldsAligned+`, `+FieldsOrdered+`, +`+AlignmentsPow2+`, `+CABICompliant+` |`+PanelClades/ABI/Foreign.idr+` |FFI function declarations and their safe wrappers @@ -49,10 +50,10 @@ against the clade's declared budget (all five modules) |=== -=== Check status (2026-10-14) +=== Check status (2026-11-09) All five modules *typecheck* under Idris2 0.8.0 -(`+bash panel-clades/scripts/check-abi.sh+`), with six expected +(`+bash panel-clades/scripts/check-abi.sh+`), with nine expected rejections in `+tests/reject/+`. Before 2026-10-14, `+Types.idr+`, `+Layout.idr+` and `+Foreign.idr+` had @@ -104,9 +105,12 @@ says so. |`+HasSize+` admits only `+cSizeOf+`; C int is 4 bytes everywhere; pointers are 8 bytes on Linux and 4 on WASM; size_t is pointer-sized |`+Verify.*+`; `+tests/reject/WasmPointerIsEightBytes.idr+` |PROVEN / TESTED |`+alignUp+` gives a multiple of the alignment; every computed struct size is a multiple of its alignment |`+alignUpCorrect+`, `+calcStructSizeAligned+` |PROVEN |`+Divides+` witnesses are equations |`+tests/reject/MisalignedField.idr+` |TESTED -|The example layout is C-ABI compliant and its computed size is 24 |`+exampleLayoutValid+`, `+exampleSizeComputed+` |PROVEN -|A computed struct size covers every field |`+verifyLayout+` checks at run time |ASSUMED (not proved) -|`+decDivides+` returns `+Nothing+` only when the divisor does not divide |— |ASSUMED (not proved) +|Rounding up to an alignment a ≥ 1 is at or above the size and below size + a |`+alignUpCovers+`, `+alignUpTight+` |PROVEN +|A computed struct size covers every field, so every field of a layout lies within its total size |`+calcStructSizeCovers+`, `+fieldWithinEnd+`, `+fieldWithinStruct+` |PROVEN +|`+CABICompliant+` evidence: offsets aligned, fields in order without overlap, power-of-two alignments, struct alignment = largest field alignment |`+FieldsAligned+`, `+FieldsOrdered+`, `+AlignmentsPow2+`, `+maxAlignment+`; `+tests/reject/OverlappingFields.idr+`, `+AlignmentNotPow2.idr+`, `+StructUnderAligned.idr+` |PROVEN / TESTED +|The example layout is C-ABI compliant and its computed size is 24 |`+exampleLayoutValid+`, `+exampleSizeComputed+`, `+exampleCheckPasses+` |PROVEN +|The `+Nat+` and `+Bits32+` tag tables agree |`+resultTagsAgree+`, `+cladeKindTagsAgree+` |PROVEN +|`+decDivides+` returns `+Nothing+` only when the divisor does not divide; `+decPow2+` returns `+Nothing+` only for a non-power of two |— |ASSUMED (not proved) |A clade contract typechecks only if its config's declared peak fits its declared budget and its declared loss its tolerance |`+declaredContract+`; `+tests/reject/BudgetTooSmall.idr+`; `+gen-budgets.sh --check+` |PROVEN / TESTED |The neurosym-bridge budgets (peak 48, drift 16, budget 64; loss 2, tolerance 3) describe the panel |declared in `+clades/neurosym-bridge/config.k9.ncl+` |DESIGNED (declared, not measured) |The Zig FFI uses these tags, sizes and symbols |hand-restated in `+ffi/zig/src/main.zig+` |OPEN (no check) diff --git a/panel-clades/tests/reject/AlignmentNotPow2.idr b/panel-clades/tests/reject/AlignmentNotPow2.idr new file mode 100644 index 00000000..6e397717 --- /dev/null +++ b/panel-clades/tests/reject/AlignmentNotPow2.idr @@ -0,0 +1,19 @@ +-- SPDX-License-Identifier: MPL-2.0 +-- Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) +-- +-- MUST FAIL (panel-clades/scripts/check-abi.sh). +-- +-- C alignments are powers of two. decPow2 refuses 12, so a field aligned +-- to 12 has no AlignmentsPow2 evidence and checkCABI refuses its layout. +-- EXPECT: Mismatch between: True and False +module AlignmentNotPow2 + +import Data.Maybe +import Data.Nat +import Decidable.Equality +import PanelClades.ABI.Layout + +%default total + +invalid : isJust (decPow2 12) = True +invalid = Refl diff --git a/panel-clades/tests/reject/OverlappingFields.idr b/panel-clades/tests/reject/OverlappingFields.idr new file mode 100644 index 00000000..23c94f17 --- /dev/null +++ b/panel-clades/tests/reject/OverlappingFields.idr @@ -0,0 +1,18 @@ +-- SPDX-License-Identifier: MPL-2.0 +-- Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) +-- +-- MUST FAIL (panel-clades/scripts/check-abi.sh). +-- +-- Fields must be in order and must not overlap: b starts at 4, inside a, +-- which ends at 8, so no FieldsOrdered evidence exists for them. +-- EXPECT: Can't find an implementation for So False +module OverlappingFields + +import Data.So +import Data.Nat +import PanelClades.ABI.Layout + +%default total + +invalid : FieldsOrdered [MkField "a" 0 8 8, MkField "b" 4 4 4] +invalid = OrdCons (fitsIn 8 4) OrdOne diff --git a/panel-clades/tests/reject/StructUnderAligned.idr b/panel-clades/tests/reject/StructUnderAligned.idr new file mode 100644 index 00000000..ef2637ae --- /dev/null +++ b/panel-clades/tests/reject/StructUnderAligned.idr @@ -0,0 +1,24 @@ +-- SPDX-License-Identifier: MPL-2.0 +-- Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) +-- +-- MUST FAIL (panel-clades/scripts/check-abi.sh). +-- +-- A struct is aligned to its largest field alignment. This one declares +-- 4 while its field needs 8: aligned, ordered, power-of-two, and still +-- refused by checkCABI. +-- EXPECT: Mismatch between: True and False +module StructUnderAligned + +import Data.Either +import Data.So +import Data.Nat +import Decidable.Equality +import PanelClades.ABI.Layout + +%default total + +underAligned : StructLayout +underAligned = MkStructLayout [MkField "y" 0 8 8] 8 4 (fitsIn 8 8) (DivideBy 2 Refl) + +invalid : isRight (checkCABI StructUnderAligned.underAligned) = True +invalid = Refl From 7a7f2ea87562a38d0cf2887e0d9dc197661d7b5e Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 9 Nov 2026 13:50:43 +0000 Subject: [PATCH 14/21] proofs(provenance): rewrite the TLA+ model with an adversary; run TLC in CI both ways MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Owner decision 2026-11-09: rewrite the provenance model and run TLC in CI. The old model's invariant (verified => Valid) held by construction: Check set verified' = Valid, and nothing could forge, because one Sign action added the author and witness signatures together. docs/models/Provenance.tla - The genuine chain (R1, then R2 naming R1) is signed by the author and attested by the witness from the start. An adversary can sign any payload under a leaked key (ForgeAuthor, ForgeWitness) and present any two records with any content (Supply). - Valid mirrors crates/provenance verify_chain: author signature and witness attestation on every record, content matching its digest, the first record naming no parent and the second naming the first. - Genuine: a verified chain is exactly the genuine chain with its genuine content. It mentions the genuine chain, which the verifier never sees, so it is not Valid restated. Three configurations, run by scripts/check-provenance-model.sh in the new provenance-model job of build-validation.yml: - Provenance.cfg (author key leaked) and ProvenanceWitnessLeak.cfg (witness key leaked): no error, 591,872 distinct states each, depth 13; - ProvenanceBothLeak.cfg: TLC must report "Invariant Genuine is violated", and does, so the property is not vacuous. A mutation run that dropped the witness check from Valid was also caught: with only the author key leaked, TLC finds a forged chain. The script asserts the TLC version it ran (TLC2 Version 2.19 of 08 August 2024). Locally the whole check takes about 7 minutes. Toolchain (PROOF-POLICY §6 deviation, recorded in PROOF-NEEDS.adoc and in the job): tla2tools.jar v1.7.4, the official release asset, pinned by SHA-256 936a2620...0e88, because PanLL has no Guix manifest or proof container providing TLC. PROOF-NEEDS.adoc: the §4 exception record becomes TESTED with its bounds and assumptions (ML-DSA unforgeability, honest witness; timestamps, key registry and chains longer than two not modelled), plus the deviation record. docs/PROVENANCE-EXHIBIT-B.adoc describes the new model and marks the 2026-11-06 TLC receipt for the old one superseded. That receipt also corrects this PR's earlier note that the old model had no recorded run: it had a local one. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01BCQeL169ZnUirUVaDXQLA3 --- .github/workflows/build-validation.yml | 33 +++++++ CHANGELOG.adoc | 7 ++ PROOF-NEEDS.adoc | 71 +++++++++----- docs/PROVENANCE-EXHIBIT-B.adoc | 11 ++- docs/models/Provenance.cfg | 13 ++- docs/models/Provenance.tla | 131 ++++++++++++++++++------- docs/models/ProvenanceBothLeak.cfg | 12 +++ docs/models/ProvenanceWitnessLeak.cfg | 12 +++ scripts/check-provenance-model.sh | 58 +++++++++++ 9 files changed, 286 insertions(+), 62 deletions(-) create mode 100644 docs/models/ProvenanceBothLeak.cfg create mode 100644 docs/models/ProvenanceWitnessLeak.cfg create mode 100755 scripts/check-provenance-model.sh diff --git a/.github/workflows/build-validation.yml b/.github/workflows/build-validation.yml index 96cb85b1..746d6c2a 100644 --- a/.github/workflows/build-validation.yml +++ b/.github/workflows/build-validation.yml @@ -44,6 +44,39 @@ jobs: - name: Build Panel Contract Compiler run: cargo build --locked --release --manifest-path tools/pcc/Cargo.toml + # Provenance model (docs/models/Provenance.tla, a PROOF-POLICY §4 + # exception recorded in PROOF-NEEDS.adoc): TLC checks that a chain + # verifies only if it is the genuine one while either signing key is + # uncompromised, and must find the forgery when both are. + # + # PROOF-POLICY §6 deviation (also recorded in PROOF-NEEDS.adoc, state + # open): PanLL has no Guix manifest or proof container that provides TLC, + # so the official tla2tools.jar release asset is fetched and checked + # against the SHA-256 recorded here; the script asserts the TLC version + # it ran. Java is the runner image's. + provenance-model: + runs-on: ubuntu-latest + timeout-minutes: 30 + permissions: + contents: read + env: + TLA2TOOLS_URL: https://github.com/tlaplus/tlaplus/releases/download/v1.7.4/tla2tools.jar + TLA2TOOLS_SHA256: 936a262061c914694dfd669a543be24573c45d5aa0ff20a8b96b23d01e050e88 + steps: + - name: Checkout + uses: actions/checkout@v7.0.1 + with: + persist-credentials: false + + - name: Fetch TLA+ tools v1.7.4 (pinned by digest) + run: | + curl -sSfL --retry 3 -o "$RUNNER_TEMP/tla2tools.jar" "$TLA2TOOLS_URL" + echo "$TLA2TOOLS_SHA256 $RUNNER_TEMP/tla2tools.jar" | sha256sum -c - + java -version + + - name: Model-check the provenance binding (both ways) + run: bash scripts/check-provenance-model.sh "$RUNNER_TEMP/tla2tools.jar" + # panel-clades ABI: every PanelClades.ABI module typechecks against # typed-wasm's grade kernel at its pin, and the expected rejections are # refused. Idris2 publishes no binaries, so 0.8.0 is built from source, diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index 63c9cc07..59f57c77 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -30,6 +30,13 @@ each `+Bits32+` C encoding to its `+Nat+` equality tag. * `+check-abi.sh+` now also fails on holes, `+partial+`, `+covering+` and a module without `+%default total+`, reads code only (not comments or strings), and holds the typed-wasm import cone to the same scan. +* *Provenance model rewritten and run in CI.* `+docs/models/Provenance.tla+` +now has an adversary that signs under a leaked key, separate author and +witness signatures, and the invariant `+Genuine+`: a verified chain is +exactly the genuine one while either key is uncompromised. The previous +invariant held by construction. The `+provenance-model+` job +(`+build-validation.yml+`) runs TLC with each single key leaked (must hold) +and with both leaked (must find the forgery). ==== Removed (2026-11-05) diff --git a/PROOF-NEEDS.adoc b/PROOF-NEEDS.adoc index 1dd61afb..6d72ae23 100644 --- a/PROOF-NEEDS.adoc +++ b/PROOF-NEEDS.adoc @@ -272,7 +272,7 @@ trailers) is forgeable, so its output counts only as "claimed". |Implemented in `crates/provenance`: JCS records, ML-DSA-65 signing, approved public-key registry, independent witnesses, timestamp/content/chain verification and mutation/property tests (the `TESTED` evidence). -Symbolic binding model: `docs/models/Provenance.tla`, a §4 exception +Symbolic binding model: `docs/models/Provenance.tla`, `TESTED` in CI, a §4 exception whose record is below; protocol and limits: `docs/PROVENANCE-EXHIBIT-B.adoc`. This does not prove every action is recorded, prevent tail truncation without a checkpoint, or prove @@ -367,38 +367,60 @@ witnesses and trusted checkpoints remain separate operational requirements. |Field |Record |Artefact -|`docs/models/Provenance.tla`, with `docs/models/Provenance.cfg` +|`docs/models/Provenance.tla`, with three TLC configurations: +`Provenance.cfg` (author key leaked), `ProvenanceWitnessLeak.cfg` +(witness key leaked) and `ProvenanceBothLeak.cfg` (both leaked). They are +run by `scripts/check-provenance-model.sh` in the `provenance-model` job of +`.github/workflows/build-validation.yml`. |Obligation -|A record chain is reported verified only when every entry carries both an -author and a witness signature over its own payload, its content matches -the signed digest, and the second entry's parent names the first. +|A chain is verified only if it is exactly the genuine chain, in order and +with its genuine content, while at least one of the two signing keys +(author, independent witness) is uncompromised. `Valid` mirrors what +`crates/provenance` `verify_chain` checks: an author signature and a +witness attestation on every record, content matching its digest, and each +record naming its predecessor (the first naming none). The property +`Genuine` refers to the genuine chain, which the verifier never sees, so it +is not `Valid` restated. |Tool -|TLA+, checked with TLC +|TLA+, checked with TLC (`TLC2 Version 2.19 of 08 August 2024`, from +tla2tools v1.7.4) |Why not the mapped verifier |The statement is about the protocol, symbolically, with signatures as -unforgeable sets. The mapped verifiers (Creusot / Pearlite and Verus) -check the shipped Rust instead; that obligation is the +sets that only a key holder can add to. The mapped verifiers (Creusot / +Pearlite and Verus) check the shipped Rust instead; that obligation is the `verify_entry` / `verify_witnesses` row above, and this model does not discharge it. |Status -|`OPEN`. TLC is not run in CI and no run has been recorded, so the model -has no receipt. Once run, it is `TESTED` with its bounds: -`Values = {a, b}`, chains of exactly two entries. - -|Design limit -|The invariant `Binding` (`verified => Valid`) holds by construction: -`Check` sets `verified' = Valid`, `Supply` resets `verified` to `FALSE`, -and `Sign` only adds to the signed sets, which keeps `Valid` true. A TLC -pass would therefore show only that `verified` tracks `Valid`. The model -has no action that forges or replays a signature, and `Sign` adds the -author and witness signatures together, so it cannot tell an -author-signed record from a witnessed one. To carry weight it needs -separate author and witness signing, an adversary action, and a property -that is not the definition of `Valid`. +|`TESTED`, with bounds: two field/digest values (`A`, `B`) plus `None` +for the genesis parent, so 12 possible payloads; chains of exactly two +records; any subset of payloads forged under a leaked key. Checked both +ways. With one leaked key, either one, `Genuine` holds over all 591,872 +distinct reachable states (depth 13). With both keys leaked, TLC must find +the forgery and does, so the property is not vacuous. A mutation run that +dropped the witness check from `Valid` was caught too: one leaked author +key then forges a chain. Local run on 2026-11-09, about 7 minutes; the +`provenance-model` job's run on `main` is the receipt. + +|Assumed +|Signatures cannot be produced without the key (ML-DSA-65 unforgeability), +and the witness attests only genuine records. Timestamps, key +registration, chains longer than two and tail truncation are not modelled. + +|Deviation (PROOF-POLICY §6) +|*Leg:* this model check. *Tool:* tla2tools.jar v1.7.4, the +tlaplus/tlaplus GitHub release asset, SHA-256 +`936a262061c914694dfd669a543be24573c45d5aa0ff20a8b96b23d01e050e88`; TLC +reports `TLC2 Version 2.19 of 08 August 2024`. *Why:* PanLL has no Guix +manifest or proof container that provides TLC, so the job fetches the +release asset, checks it against the digest pinned in the workflow, and +the script fails on any other TLC version. The digest was taken from the +asset as downloaded over TLS on 2026-11-09; tlaplus publishes no checksum +file to compare it with. Java is the runner image's. *State:* `open`, +until TLC comes from a Guix manifest or a pinned container. |=== == Priority @@ -409,5 +431,6 @@ that is not the definition of `Valid`. unchecked assumption under the proofs. . PCC soundness: the highest-value proof, but only after the AffineScript port (#148) gives it something to check. -. The TLA+ provenance model: restate the property so a pass means - something, and run TLC in CI so it has a receipt. +. The provenance implementation proofs (Creusot / Pearlite and Verus on + `verify_entry`, `verify_witnesses`, `verify_chain`): the TLA+ model now + says what the protocol is worth, but not that the Rust implements it. diff --git a/docs/PROVENANCE-EXHIBIT-B.adoc b/docs/PROVENANCE-EXHIBIT-B.adoc index a364e1ce..1981730b 100644 --- a/docs/PROVENANCE-EXHIBIT-B.adoc +++ b/docs/PROVENANCE-EXHIBIT-B.adoc @@ -102,8 +102,12 @@ real ML-DSA signatures, every metadata field, content mutations, witness tampering, identity binding, validity/revocation and chain deletion/splicing. Property tests generate arbitrary content, metadata and parent-hash mutations. `docs/models/Provenance.tla` models signature binding and chain verification -symbolically. Its invariant is not a cryptographic proof of ML-DSA or SHA3, -nor a proof of all implementation paths. Production adoption requires an +symbolically, with an adversary that can sign under a leaked key. Its +invariant `Genuine` says a verified chain is exactly the genuine one while +either the author key or the witness key is uncompromised; TLC checks it in +CI (`provenance-model` job) with each single key leaked, and must find the +forgery when both are. It is not a cryptographic proof of ML-DSA or SHA3, +nor a proof of any implementation path (PROOF-NEEDS.adoc, "Exceptions"). Production adoption requires an approved contributor key and independent witness; historical records are never silently re-signed. @@ -130,3 +134,6 @@ distinct states, no invariant violation. SHA-256 of the checked model: `c379ece2393e2cacfffccd6bb0360184ad179c77ae72dc875948a022d123444a`. The model assumes signature/hash binding; it is not an implementation correspondence proof or a cryptographic hardness proof. +This receipt is superseded (2026-11-09): that model's invariant held by +construction and it had no forging action. The rewritten model and its CI +run replace it; see PROOF-NEEDS.adoc. diff --git a/docs/models/Provenance.cfg b/docs/models/Provenance.cfg index a67c6463..7d882d06 100644 --- a/docs/models/Provenance.cfg +++ b/docs/models/Provenance.cfg @@ -1,3 +1,12 @@ +\* SPDX-License-Identifier: MPL-2.0 +\* Provenance: AuthorKeyLeaked = TRUE, WitnessKeyLeaked = FALSE (see Provenance.tla). SPECIFICATION Spec -CONSTANT Values = {a, b} -INVARIANT Binding +CONSTANTS + A = A + B = B + None = None + AuthorKeyLeaked = TRUE + WitnessKeyLeaked = FALSE +INVARIANTS + TypeOK + Genuine diff --git a/docs/models/Provenance.tla b/docs/models/Provenance.tla index 947f5df3..47390ed3 100644 --- a/docs/models/Provenance.tla +++ b/docs/models/Provenance.tla @@ -1,43 +1,106 @@ --------------------------- MODULE Provenance --------------------------- -EXTENDS Naturals, Sequences, FiniteSets \* SPDX-License-Identifier: MPL-2.0 -\* Symbolic unforgeability: an attacker may change fields/content/links or -\* remove signatures, but cannot add a signature for a payload not signed. +\* +\* Symbolic model of witnessed provenance records (EXHIBIT-B-QUANTUM-SAFE.txt, +\* docs/PROVENANCE-EXHIBIT-B.adoc). Each record is signed by its author and +\* attested by an independent witness. The claim checked here: a chain that +\* verifies is exactly the genuine chain, in order, unless BOTH the author +\* key and the witness key have leaked. One leaked key is not enough. \* \* PROOF-POLICY §4 exception (standards 3-practice/PROOF-POLICY.adoc). Full \* record: PROOF-NEEDS.adoc, "Exceptions", "TLA+ model of provenance binding". -\* Obligation: a chain is verified only if each entry is author- and -\* witness-signed over its own payload, its content matches the signed -\* digest, and entry 2's parent names entry 1. +\* Obligation: verification accepts only the genuine chain while at least +\* one of the two signing keys is uncompromised. \* Tool: TLA+ / TLC. Why: a symbolic protocol statement; Creusot/Pearlite \* and Verus check the shipped Rust, and this model discharges none of it. -\* Status: OPEN (TLC not run in CI, no receipt). Once run: TESTED, bounds -\* Values = {a, b}, chains of two. -\* Limit: Binding holds by construction (Check sets verified' = Valid), and -\* there is no forging action; see the full record. -CONSTANT Values -VARIABLES chain, authorSigned, witnessSigned, content, verified -vars == <> -Payloads == [field: Values, digest: Values, parent: Values] -Init == /\ chain = <<>> /\ authorSigned = {} /\ witnessSigned = {} - /\ content = <<>> /\ verified = FALSE -Sign == \E p \in Payloads: - /\ authorSigned' = authorSigned \cup {p} - /\ witnessSigned' = witnessSigned \cup {p} - /\ UNCHANGED <> -\* Any ordered pair, including a splice or substituted contribution. -Supply == \E a,b \in Payloads, x,y \in Values: - /\ chain' = <> /\ content' = <> - /\ verified' = FALSE - /\ UNCHANGED <> -Valid == /\ Len(chain) = 2 - /\ \A i \in 1..2: /\ chain[i] \in authorSigned - /\ chain[i] \in witnessSigned - /\ chain[i].digest = content[i] - /\ chain[2].parent = chain[1].field -Check == /\ verified' = Valid - /\ UNCHANGED <> -Next == Sign \/ Supply \/ Check +\* Status: TESTED (bounded: two values, chains of two), from the +\* provenance-model job in build-validation.yml; see PROOF-NEEDS.adoc. +\* Assumed: signatures cannot be made without the key (ML-DSA-65 +\* unforgeability), and the witness attests only genuine records. +\* +\* Configurations (all run in CI): +\* Provenance.cfg author key leaked -> invariants hold +\* ProvenanceWitnessLeak.cfg witness key leaked -> invariants hold +\* ProvenanceBothLeak.cfg both keys leaked -> Genuine MUST be violated +\* The third is the check that the property is not vacuous: with both keys, +\* a forger does get a forged chain verified, and TLC must find it. +EXTENDS Naturals, Sequences + +CONSTANTS A, B, None, \* model values + AuthorKeyLeaked, \* BOOLEAN + WitnessKeyLeaked \* BOOLEAN + +Values == {A, B} +Payloads == [field: Values, digest: Values, parent: Values \cup {None}] + +\* The genuine chain: a genesis record and one record that names it. +R1 == [field |-> A, digest |-> A, parent |-> None] +R2 == [field |-> B, digest |-> B, parent |-> A] +Genuine2 == <> +Intended == {R1, R2} + +VARIABLES chain, \* the records presented for verification + content, \* the bytes presented beside each record + authorSigned, \* payloads carrying an author signature + witnessSigned, \* payloads carrying a witness attestation + verified \* the verifier's last verdict on (chain, content) +vars == <> + +TypeOK == + /\ chain \in Seq(Payloads) /\ Len(chain) <= 2 + /\ content \in Seq(Values) /\ Len(content) <= 2 + /\ authorSigned \subseteq Payloads + /\ witnessSigned \subseteq Payloads + /\ verified \in BOOLEAN + +\* The honest author signed the genuine records and the witness attested +\* them; nothing else carries a signature yet. +Init == + /\ chain = <<>> /\ content = <<>> + /\ authorSigned = Intended + /\ witnessSigned = Intended + /\ verified = FALSE + +\* With a leaked key the adversary can sign any payload under it. +ForgeAuthor == + /\ AuthorKeyLeaked + /\ \E p \in Payloads : authorSigned' = authorSigned \cup {p} + /\ UNCHANGED <> + +ForgeWitness == + /\ WitnessKeyLeaked + /\ \E p \in Payloads : witnessSigned' = witnessSigned \cup {p} + /\ UNCHANGED <> + +\* The adversary presents any two records with any content: a splice, a +\* reorder, a replay, a substituted contribution or a forgery. +Supply == + /\ \E r1, r2 \in Payloads, x, y \in Values : + /\ chain' = <> + /\ content' = <> + /\ verified' = FALSE + /\ UNCHANGED <> + +\* What the verifier checks (crates/provenance verify_chain): every record +\* author-signed and witnessed, content matching its digest, and each +\* record naming its predecessor (the first naming none). +Valid == + /\ Len(chain) = 2 /\ Len(content) = 2 + /\ \A i \in 1..2 : /\ chain[i] \in authorSigned + /\ chain[i] \in witnessSigned + /\ chain[i].digest = content[i] + /\ chain[1].parent = None + /\ chain[2].parent = chain[1].field + +Check == + /\ verified' = Valid + /\ UNCHANGED <> + +Next == ForgeAuthor \/ ForgeWitness \/ Supply \/ Check Spec == Init /\ [][Next]_vars -Binding == verified => Valid + +\* The property. It mentions the genuine chain, which the verifier never +\* sees, so it is not the definition of Valid restated: it says what Valid +\* is worth. +Genuine == verified => (chain = Genuine2 /\ content = <>) ============================================================================= diff --git a/docs/models/ProvenanceBothLeak.cfg b/docs/models/ProvenanceBothLeak.cfg new file mode 100644 index 00000000..90fbf6b3 --- /dev/null +++ b/docs/models/ProvenanceBothLeak.cfg @@ -0,0 +1,12 @@ +\* SPDX-License-Identifier: MPL-2.0 +\* ProvenanceBothLeak: AuthorKeyLeaked = TRUE, WitnessKeyLeaked = TRUE (see Provenance.tla). +SPECIFICATION Spec +CONSTANTS + A = A + B = B + None = None + AuthorKeyLeaked = TRUE + WitnessKeyLeaked = TRUE +INVARIANTS + TypeOK + Genuine diff --git a/docs/models/ProvenanceWitnessLeak.cfg b/docs/models/ProvenanceWitnessLeak.cfg new file mode 100644 index 00000000..c9631cee --- /dev/null +++ b/docs/models/ProvenanceWitnessLeak.cfg @@ -0,0 +1,12 @@ +\* SPDX-License-Identifier: MPL-2.0 +\* ProvenanceWitnessLeak: AuthorKeyLeaked = FALSE, WitnessKeyLeaked = TRUE (see Provenance.tla). +SPECIFICATION Spec +CONSTANTS + A = A + B = B + None = None + AuthorKeyLeaked = FALSE + WitnessKeyLeaked = TRUE +INVARIANTS + TypeOK + Genuine diff --git a/scripts/check-provenance-model.sh b/scripts/check-provenance-model.sh new file mode 100755 index 00000000..230526ed --- /dev/null +++ b/scripts/check-provenance-model.sh @@ -0,0 +1,58 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 +# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) +# +# check-provenance-model.sh — model-check docs/models/Provenance.tla with TLC, +# both ways: +# +# Provenance.cfg author key leaked -> no error (must pass) +# ProvenanceWitnessLeak.cfg witness key leaked -> no error (must pass) +# ProvenanceBothLeak.cfg both keys leaked -> invariant Genuine is +# violated (must be found) +# +# The third run is what keeps the property honest: if TLC stopped finding +# the both-keys forgery, the model would have become vacuous, and this +# script fails. A missing toolchain or an unexpected TLC version is a +# failure, never a skip. +# +# Usage: check-provenance-model.sh /path/to/tla2tools.jar +# The jar's version and digest are pinned in build-validation.yml +# (provenance-model job); the deviation record is in PROOF-NEEDS.adoc. +set -uo pipefail +JAR="${1:?usage: check-provenance-model.sh /path/to/tla2tools.jar}" +EXPECT_VERSION="TLC2 Version 2.19 of 08 August 2024" +cd -- "$(dirname -- "${BASH_SOURCE[0]}")/../docs/models" || exit 1 +command -v java >/dev/null || { echo "FAIL: java not on PATH"; exit 1; } +[ -f "$JAR" ] || { echo "FAIL: $JAR not found"; exit 1; } + +work="$(mktemp -d)"; trap 'rm -rf "$work"' EXIT +fail=0 + +run() { # run -> log in $work/.log, exit status in $? + java -XX:+UseParallelGC -cp "$JAR" tlc2.TLC -workers auto -deadlock \ + -metadir "$work/$1.states" -config "$1.cfg" Provenance.tla > "$work/$1.log" 2>&1 +} + +for cfg in Provenance ProvenanceWitnessLeak; do + run "$cfg"; st=$? + grep -qF "$EXPECT_VERSION" "$work/$cfg.log" \ + || { echo "FAIL: $cfg: TLC is not '$EXPECT_VERSION'"; head -5 "$work/$cfg.log"; fail=1; continue; } + if [ "$st" -eq 0 ] && grep -q '^Model checking completed. No error has been found.' "$work/$cfg.log"; then + echo "PASS: $cfg — $(grep -E '^[0-9]+ states generated' "$work/$cfg.log")" + else + tail -40 "$work/$cfg.log"; echo "FAIL: $cfg (exit $st): expected no error"; fail=1 + fi +done + +cfg=ProvenanceBothLeak +run "$cfg"; st=$? +if ! grep -qF "$EXPECT_VERSION" "$work/$cfg.log"; then + echo "FAIL: $cfg: TLC is not '$EXPECT_VERSION'"; head -5 "$work/$cfg.log"; fail=1 +elif [ "$st" -ne 0 ] && grep -q '^Error: Invariant Genuine is violated' "$work/$cfg.log"; then + echo "PASS: $cfg — TLC found the both-keys forgery, as it must" +else + tail -40 "$work/$cfg.log"; echo "FAIL: $cfg (exit $st): expected 'Invariant Genuine is violated'"; fail=1 +fi + +[ "$fail" -eq 0 ] || { echo "PROVENANCE MODEL CHECK FAILED"; exit 1; } +echo "OK: provenance model — Genuine holds with one leaked key, fails with two" From 60674440333e5892808b289d629fb098c6fc678d Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 9 Nov 2026 14:09:55 +0000 Subject: [PATCH 15/21] fix(proofs): address CodeRabbit and SonarCloud findings on the ABI gate and ledgers check-abi.sh / abi-scan-lib.sh - The scans move to panel-clades/scripts/abi-scan-lib.sh, sourced by check-abi.sh and by a new regression test. - %default total is required in comment-stripped code: a directive inside a block or line comment no longer counts (CodeRabbit). - import_cone reads comment-stripped code and accepts indented imports: a commented-out import is no longer a dependency, an indented one is (CodeRabbit, two findings). - The directive check reads to the end of the stream. With grep -q, grep exited at the first match, awk died of SIGPIPE and pipefail reported the directive missing on any long module; found by running the gate locally after the change, before pushing. - [[ ]] instead of [ ] (the SonarCloud findings behind the C security rating on this PR: check-abi.sh lines 88, 96, 98, 123, 146), also in scripts/check-provenance-model.sh. panel-clades/tests/scan/test-abi-scan.sh (new, 27 cases, run in the panel-clades-abi job before the Idris2 build): planted holes, escapes, partial/covering, missing or commented-out %default total, a 20,000-line module, and cone resolution of real, indented and commented-out imports. Checked both ways: restoring the old raw-file directive check fails the block-comment case, and restoring grep -q fails the long-module case. Ledgers - PROOF-NEEDS.adoc: Budgets.idr and Foreign.idr leave the status table (no obligation); IPC dispatch splits into a backend row (TESTED) and a frontend row (OPEN); the provenance row carries one token, TESTED, with the implementation proofs stated OPEN in its note (CodeRabbit). - Provenance.tla header and the exception record describe Creusot / Pearlite and Verus as the planned verifiers, still OPEN, not as current checks (CodeRabbit). fork-governance-publish.yml: comments on why workflow_run is used and what each permission is for (zizmor undocumented-permissions). check-abi.sh with Idris2 0.8.0, locally: 5 modules, 0 errors, 9 of 9 rejections, import cone clean. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01BCQeL169ZnUirUVaDXQLA3 --- .github/workflows/build-validation.yml | 5 + .github/workflows/fork-governance-publish.yml | 8 +- PROOF-NEEDS.adoc | 49 +++++----- docs/models/Provenance.tla | 5 +- panel-clades/scripts/abi-scan-lib.sh | 84 +++++++++++++++++ panel-clades/scripts/check-abi.sh | 91 +++---------------- panel-clades/tests/scan/test-abi-scan.sh | 91 +++++++++++++++++++ scripts/check-provenance-model.sh | 8 +- 8 files changed, 233 insertions(+), 108 deletions(-) create mode 100644 panel-clades/scripts/abi-scan-lib.sh create mode 100755 panel-clades/tests/scan/test-abi-scan.sh diff --git a/.github/workflows/build-validation.yml b/.github/workflows/build-validation.yml index 746d6c2a..05004fd3 100644 --- a/.github/workflows/build-validation.yml +++ b/.github/workflows/build-validation.yml @@ -95,6 +95,11 @@ jobs: with: persist-credentials: false + # The source scans check-abi.sh relies on, tested both ways; needs no + # Idris2, so it fails fast before the 8-minute toolchain build. + - name: ABI scan regression tests + run: bash panel-clades/tests/scan/test-abi-scan.sh + - name: Install Chez Scheme and GMP run: | sudo apt-get update -qq diff --git a/.github/workflows/fork-governance-publish.yml b/.github/workflows/fork-governance-publish.yml index c9dc26af..14a40f66 100644 --- a/.github/workflows/fork-governance-publish.yml +++ b/.github/workflows/fork-governance-publish.yml @@ -15,6 +15,10 @@ # publisher must agree on the comment's marker and stamp. name: Fork governance publish +# workflow_run, deliberately: it is the only way to write a comment for a +# fork PR. Nothing from the fork runs here. The reusable checks out no code, +# reads the artifact as data, and posts only to the open PR whose head is +# the commit the run built. on: workflow_run: workflows: ["Fork governance"] @@ -27,5 +31,5 @@ jobs: publish: uses: metadatastician/standards/.github/workflows/fork-governance-publish-reusable.yml@9bdf864cc7f2c8e24b4df84bf00d04158eee3ad5 permissions: - actions: read - pull-requests: write + actions: read # download the run's fork-governance-comment artifact + pull-requests: write # post or update the one governance comment diff --git a/PROOF-NEEDS.adoc b/PROOF-NEEDS.adoc index 6d72ae23..9427f7ae 100644 --- a/PROOF-NEEDS.adoc +++ b/PROOF-NEEDS.adoc @@ -157,18 +157,15 @@ contract typechecks against its declared budget it is not an assumption; whether its definitions match resource-algebra's is #143. -|`Budgets.idr` -|No obligation -|Generated from the clades' `config.k9.ncl` resource blocks. It proves -nothing itself; it is the input `Grades.idr` checks against. Only -neurosym-bridge declares a budget so far. The budgets are DESIGNED values, -not measurements. - -|`Foreign.idr` -|No obligation -|Typechecks only. It declares the Zig FFI surface; it proves nothing. |=== +Two modules carry no obligation and are not in the table. +`Budgets.idr` is generated from the clades' `config.k9.ncl` resource +blocks; it is the input `Grades.idr` checks against. Only neurosym-bridge +declares a budget so far, and the budgets are DESIGNED values, not +measurements. `Foreign.idr` declares the Zig FFI surface and only +typechecks. + The reject fixtures pin down what the types and the decision procedures refuse: `AlignmentNotPow2`, `BudgetTooSmall`, `CollapseWithinTolerance`, `MisalignedField`, `NullHandle`, `OverBudget`, @@ -230,15 +227,21 @@ and their contracts need to typecheck. budget). |— -|IPC dispatch is total -|Every command the frontend invokes has a registered handler. -|`TESTED` (backend side); `OPEN` (frontend side) +|IPC dispatch is total: backend side +|Every public handler in a backend command module is registered, once. +|`TESTED` |Tests in `src-gossamer/src/ipc.rs`: `every_backend_command_is_registered` fails if a public handler in a command module is reachable from neither the table nor `main.rs`, and -the table and `main.rs` never register the same name twice. Checking the -frontend side needs its `invoke` names, which wait on the AffineScript -port. The Rust implementation obligation is in the next section. +the table and `main.rs` never register the same name twice. The Rust +implementation obligation is in the next section. +|#148 + +|IPC dispatch is total: frontend side +|Every command the frontend invokes has a registered handler. +|`OPEN` +|Needs the frontend's `invoke` names, which wait on the AffineScript +port. |#148 |PCC soundness @@ -268,7 +271,7 @@ a parent hash. It is serialised as canonical JSON, signed with ML-DSA-65 and witnessed, and verifies against keys published under `.well-known/keys/`. Today's engine (git blame plus Co-Authored-By trailers) is forgeable, so its output counts only as "claimed". -|`TESTED`; proofs `OPEN` +|`TESTED` |Implemented in `crates/provenance`: JCS records, ML-DSA-65 signing, approved public-key registry, independent witnesses, timestamp/content/chain verification and mutation/property tests (the `TESTED` evidence). @@ -276,8 +279,8 @@ Symbolic binding model: `docs/models/Provenance.tla`, `TESTED` in CI, a §4 exce whose record is below; protocol and limits: `docs/PROVENANCE-EXHIBIT-B.adoc`. This does not prove every action is recorded, prevent tail truncation without a checkpoint, or prove -cryptographic hardness. The implementation proofs are in the next -section. +cryptographic hardness. The implementation proofs are `OPEN`, in the +next section. |#173 |Cartridge schema validation @@ -389,10 +392,10 @@ tla2tools v1.7.4) |Why not the mapped verifier |The statement is about the protocol, symbolically, with signatures as -sets that only a key holder can add to. The mapped verifiers (Creusot / -Pearlite and Verus) check the shipped Rust instead; that obligation is the -`verify_entry` / `verify_witnesses` row above, and this model does not -discharge it. +sets that only a key holder can add to. The mapped verifiers for the shipped Rust are Creusot / +Pearlite and Verus. They are planned, not yet run: the `verify_entry` / +`verify_witnesses` row above is `OPEN`, and this model does not discharge +it. |Status |`TESTED`, with bounds: two field/digest values (`A`, `B`) plus `None` diff --git a/docs/models/Provenance.tla b/docs/models/Provenance.tla index 47390ed3..ebb8e59a 100644 --- a/docs/models/Provenance.tla +++ b/docs/models/Provenance.tla @@ -11,8 +11,9 @@ \* record: PROOF-NEEDS.adoc, "Exceptions", "TLA+ model of provenance binding". \* Obligation: verification accepts only the genuine chain while at least \* one of the two signing keys is uncompromised. -\* Tool: TLA+ / TLC. Why: a symbolic protocol statement; Creusot/Pearlite -\* and Verus check the shipped Rust, and this model discharges none of it. +\* Tool: TLA+ / TLC. Why: a symbolic protocol statement. The shipped Rust is +\* for Creusot/Pearlite and Verus, planned and still OPEN; this model +\* discharges none of it. \* Status: TESTED (bounded: two values, chains of two), from the \* provenance-model job in build-validation.yml; see PROOF-NEEDS.adoc. \* Assumed: signatures cannot be made without the key (ML-DSA-65 diff --git a/panel-clades/scripts/abi-scan-lib.sh b/panel-clades/scripts/abi-scan-lib.sh new file mode 100644 index 00000000..d40d626d --- /dev/null +++ b/panel-clades/scripts/abi-scan-lib.sh @@ -0,0 +1,84 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 +# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) +# +# abi-scan-lib.sh — the source scans behind check-abi.sh, sourced by it and +# by tests/scan/test-abi-scan.sh. Every scan reads code only: comments and +# string literals are set aside first, so prose such as "NO believe_me" does +# not trip a scan, and a directive or import inside a comment does not count. + +# Print each .idr file's code with comments and string literals blanked, as +# FILE:LINE:TEXT. Handles nested {- -} block comments, `--` line comments +# (not operators such as -->), `|||` doc comments, and "..." / '.' literals. +code_only() { + awk ' + FNR == 1 { depth = 0 } + { + line = $0; out = ""; i = 1; n = length(line) + if (depth == 0 && line ~ /^[[:space:]]*\|\|\|/) { print FILENAME ":" FNR ":"; next } + while (i <= n) { + two = substr(line, i, 2); c = substr(line, i, 1) + if (two == "{-") { depth++; i += 2; continue } + if (depth > 0) { if (two == "-}") { depth--; i += 2 } else i++; continue } + if (two == "--" && substr(line, i + 2, 1) !~ /[!#$%&*+.\/<=>?@\\^|~:-]/) break + if (c == "\"") { i++; while (i <= n && substr(line, i, 1) != "\"") { if (substr(line, i, 1) == "\\") i++; i++ }; i++; out = out "\"\""; continue } + if (c == "'"'"'" && substr(line, i + 2, 1) == "'"'"'") { i += 3; out = out "'"'"''"'"'"; continue } + out = out c; i++ + } + print FILENAME ":" FNR ":" out + }' "$@" +} + +# Escape hatches and holes in code, plus any module whose code does not +# declare `%default total` (Idris2's default is `covering`, which does not +# check termination; a directive inside a comment does not count). +# Returns 0 and prints the offending lines if anything is found. +# believe_me assert_total assert_smaller idris_crash unsafePerformIO +# postulate -- trust or totality escapes +# ?name -- a hole: idris2 --build accepts a module with holes +# partial / covering -- a declaration opting out of %default total +scan_escapes() { + local found=1 f + if code_only "$@" | grep -E \ + -e ':[0-9]+:.*\b(believe_me|assert_total|assert_smaller|idris_crash|unsafePerformIO|postulate)\b' \ + -e ':[0-9]+:(.*[^A-Za-z0-9_'"'"'?])?\?[a-z_][A-Za-z0-9_'"'"']*' \ + -e ':[0-9]+:.*\b(partial|covering)\b'; then + found=0 + fi + for f in "$@"; do + if ! code_only "$f" | grep -E '^[^:]*:[0-9]+:[[:space:]]*%default[[:space:]]+total[[:space:]]*$' > /dev/null; then + printf '%s: no %%default total in code\n' "$f"; found=0 + fi + done + return "$found" +} + +# import_cone BASE_FILE ROOT... -- FILE... +# The modules the given files import, transitively, that resolve to a source +# file under one of the roots, one path per line (the files themselves +# first). Imports are read from code only, at any indentation. Imports +# that resolve under no root are Idris2's own prelude/base/contrib, the +# prover's trust base: their module names are written to BASE_FILE. +import_cone() { + local base="$1"; shift + local -a roots=() queue=() seen=() + while [[ $# -gt 0 && "$1" != "--" ]]; do roots+=("$1"); shift; done + [[ $# -gt 0 ]] && shift + queue=("$@") + local f m r p hit + : > "$base" + while [[ ${#queue[@]} -gt 0 ]]; do + f="${queue[0]}"; queue=("${queue[@]:1}") + case " ${seen[*]-} " in *" $f "*) continue ;; esac + seen+=("$f"); printf '%s\n' "$f" + while IFS= read -r m; do + hit="" + for r in "${roots[@]}"; do + p="$r/${m//.//}.idr" + if [[ -f "$p" ]]; then hit="$p"; break; fi + done + if [[ -n "$hit" ]]; then queue+=("$hit"); else printf '%s\n' "$m" >> "$base"; fi + done < <(code_only "$f" \ + | sed -nE 's/^[^:]*:[0-9]+:[[:space:]]*import[[:space:]]+(public[[:space:]]+)?([A-Za-z0-9_.]+).*/\2/p') + done +} diff --git a/panel-clades/scripts/check-abi.sh b/panel-clades/scripts/check-abi.sh index c1b905df..d40cb8ba 100755 --- a/panel-clades/scripts/check-abi.sh +++ b/panel-clades/scripts/check-abi.sh @@ -32,77 +32,14 @@ err() { printf 'FAIL: %s\n' "$1"; fail=1; } # Print the supplied failure message and terminate the gate with exit status 1. die() { printf 'FAIL: %s\n' "$1"; exit 1; } -# Print each .idr file's code with comments and string literals blanked, as -# FILE:LINE:TEXT, so a scan neither trips over prose ("NO believe_me") nor -# misses code. Handles nested {- -} block comments, `--` line comments (not -# operators such as -->), `|||` doc comments, and "..." / '.' literals. -code_only() { - awk ' - FNR == 1 { depth = 0 } - { - line = $0; out = ""; i = 1; n = length(line) - if (depth == 0 && line ~ /^[[:space:]]*\|\|\|/) { print FILENAME ":" FNR ":"; next } - while (i <= n) { - two = substr(line, i, 2); c = substr(line, i, 1) - if (two == "{-") { depth++; i += 2; continue } - if (depth > 0) { if (two == "-}") { depth--; i += 2 } else i++; continue } - if (two == "--" && substr(line, i + 2, 1) !~ /[!#$%&*+.\/<=>?@\\^|~:-]/) break - if (c == "\"") { i++; while (i <= n && substr(line, i, 1) != "\"") { if (substr(line, i, 1) == "\\") i++; i++ }; i++; out = out "\"\""; continue } - if (c == "'"'"'" && substr(line, i + 2, 1) == "'"'"'") { i += 3; out = out "'"'"''"'"'"; continue } - out = out c; i++ - } - print FILENAME ":" FNR ":" out - }' "$@" -} - -# Escape hatches and holes in code, plus any module that does not declare -# `%default total` (Idris2's default is `covering`, which does not check -# termination). Returns 0 and prints the offending lines if anything is found. -# believe_me assert_total assert_smaller idris_crash unsafePerformIO -# postulate -- trust or totality escapes -# ?name -- a hole: idris2 --build accepts a module with holes -# partial / covering -- a declaration opting out of %default total -scan_escapes() { - local found=1 f - if code_only "$@" | grep -E \ - -e ':[0-9]+:.*\b(believe_me|assert_total|assert_smaller|idris_crash|unsafePerformIO|postulate)\b' \ - -e ':[0-9]+:(.*[^A-Za-z0-9_'"'"'?])?\?[a-z_][A-Za-z0-9_'"'"']*' \ - -e ':[0-9]+:.*\b(partial|covering)\b'; then - found=0 - fi - for f in "$@"; do - grep -qE '^%default[[:space:]]+total[[:space:]]*$' "$f" || { printf '%s: no %%default total\n' "$f"; found=0; } - done - return "$found" -} - -# The modules a PanelClades module imports, transitively, that resolve to a -# source file under one of the given roots. Imports that resolve nowhere -# are Idris2's own prelude/base/contrib, the prover's trust base, and are -# listed separately. Output: one path per line (cone), and the unresolved -# module names in "$OUT/import-base". -import_cone() { - local -a queue=("$@") roots=(src/abi "$TW") seen=() - local f m r p hit - : > "$OUT/import-base" - while [ "${#queue[@]}" -gt 0 ]; do - f="${queue[0]}"; queue=("${queue[@]:1}") - case " ${seen[*]-} " in *" $f "*) continue ;; esac - seen+=("$f"); printf '%s\n' "$f" - while IFS= read -r m; do - hit="" - for r in "${roots[@]}"; do - p="$r/${m//.//}.idr" - [ -f "$p" ] && { hit="$p"; break; } - done - if [ -n "$hit" ]; then queue+=("$hit"); else printf '%s\n' "$m" >> "$OUT/import-base"; fi - done < <(sed -nE 's/^import[[:space:]]+(public[[:space:]]+)?([A-Za-z0-9_.]+).*/\2/p' "$f") - done -} +# The scans (code_only, scan_escapes, import_cone) live in abi-scan-lib.sh, +# which tests/scan/test-abi-scan.sh exercises both ways. +# shellcheck source=abi-scan-lib.sh +. scripts/abi-scan-lib.sh command -v idris2 >/dev/null || die "idris2 not on PATH (a gate that cannot run must not report OK)" iv="$(idris2 --version)"; echo "$iv" -case "$iv" in *"version 0.8.0"*) ;; *) [ -n "${ALLOW_ANY_IDRIS2:-}" ] || die "Idris2 0.8.0 required (set ALLOW_ANY_IDRIS2=1 to try another)" ;; esac +case "$iv" in *"version 0.8.0"*) ;; *) [[ -n "${ALLOW_ANY_IDRIS2:-}" ]] || die "Idris2 0.8.0 required (set ALLOW_ANY_IDRIS2=1 to try another)" ;; esac rm -rf "$OUT"; mkdir -p "$OUT" IPKG="src/abi/panel-clades-grades.ipkg" @@ -111,7 +48,7 @@ abi_list="$OUT/abi-files" find src/abi/PanelClades -type f -name '*.idr' -print0 > "$abi_list" \ || die "could not enumerate .idr files under src/abi/PanelClades" mapfile -d '' -t abi_files < "$abi_list" -[ "${#abi_files[@]}" -gt 0 ] || die "no .idr files found under src/abi/PanelClades" +[[ "${#abi_files[@]}" -gt 0 ]] || die "no .idr files found under src/abi/PanelClades" for f in "${abi_files[@]}"; do mod="$(printf '%s' "${f#src/abi/}" | sed 's|\.idr$||; s|/|.|g')" grep -qE "^[[:space:]]*(modules[[:space:]]*=|,)[[:space:]]*${mod//./\\.}[[:space:]]*$" "$IPKG" \ @@ -120,7 +57,7 @@ done if scan_escapes "${abi_files[@]}"; then err "escape hatch in src/abi/PanelClades (lines above)" fi -[ "$fail" -eq 0 ] && pass "every PanelClades module is listed in the ipkg, declares %default total, and has no escape hatch or hole" +[[ "$fail" -eq 0 ]] && pass "every PanelClades module is listed in the ipkg, declares %default total, and has no escape hatch or hole" if bash scripts/gen-budgets.sh --check; then pass "Budgets.idr is current with the clade configs" else @@ -141,9 +78,9 @@ KTTC="$ROOT/$TW/build/ttc" # The import cone (standards PROOF-POLICY §1.1): every first-party module the # proofs rest on, PanelClades and typed-wasm alike, is held to the same scan. # A kernel module with a hole or an escape hatch would be trusted silently. -import_cone "${abi_files[@]}" > "$OUT/import-cone" +import_cone "$OUT/import-base" src/abi "$TW" -- "${abi_files[@]}" > "$OUT/import-cone" mapfile -t cone < <(grep -v '^src/abi/PanelClades/' "$OUT/import-cone") -[ "${#cone[@]}" -gt 0 ] || die "import cone has no typed-wasm module; Grades.idr imports the kernel" +[[ "${#cone[@]}" -gt 0 ]] || die "import cone has no typed-wasm module; Grades.idr imports the kernel" if scan_escapes "${cone[@]}"; then err "escape hatch or hole in the typed-wasm import cone (lines above)" else @@ -154,7 +91,7 @@ sed 's/^/ cone: /' "$OUT/import-cone" rm -rf src/abi/build (cd src/abi && IDRIS2_PATH="$KTTC" idris2 --build panel-clades-grades.ipkg) > "$OUT/grades.log" 2>&1 st=$? -if [ "$st" -eq 0 ] && ! grep -q '^Error' "$OUT/grades.log"; then +if [[ "$st" -eq 0 ]] && ! grep -q '^Error' "$OUT/grades.log"; then pass "panel-clades-grades.ipkg builds against the kernel ($(grep -c ': Building ' "$OUT/grades.log") module(s), 0 errors)" else { grep -A12 '^Error' "$OUT/grades.log" || tail -40 "$OUT/grades.log"; } | head -40 @@ -164,21 +101,21 @@ PTTC="$ROOT/src/abi/build/ttc" n=0 for f in tests/reject/*.idr; do - [ -e "$f" ] || { err "no tests/reject/*.idr fixtures"; break; } + [[ -e "$f" ]] || { err "no tests/reject/*.idr fixtures"; break; } n=$((n + 1)); name="$(basename "$f" .idr)" expect="$(sed -n 's/^-- EXPECT: //p' "$f" | head -1)" decl="$(grep -nE '^invalid[[:space:]]*:' "$f" | head -1 | cut -d: -f1)" log="$OUT/reject-$name.log" (cd tests/reject && IDRIS2_PATH="$KTTC:$PTTC" idris2 --build-dir "$ROOT/$OUT/reject-build" --check "$name.idr") > "$log" 2>&1 loc="$(grep -oE "^${name}:[0-9]+:" "$log" | head -1 | cut -d: -f2)" - if [ -z "$expect" ] || [ -z "$decl" ]; then err "reject $name: no '-- EXPECT:' line or 'invalid :' declaration" + if [[ -z "$expect" ]] || [[ -z "$decl" ]]; then err "reject $name: no '-- EXPECT:' line or 'invalid :' declaration" elif ! grep -q '^Error' "$log"; then cat "$log"; err "reject $name: ACCEPTED" elif grep -qE "$WRONG_REASON" "$log"; then cat "$log"; err "reject $name: refused for the WRONG reason" - elif [ -z "$loc" ] || [ "$loc" -lt "$decl" ]; then cat "$log"; err "reject $name: error not at its invalid declaration" + elif [[ -z "$loc" ]] || [[ "$loc" -lt "$decl" ]]; then cat "$log"; err "reject $name: error not at its invalid declaration" elif ! grep -qE -- "$expect" "$log"; then cat "$log"; err "reject $name: not refused with: $expect" else pass "rejected $name at line $loc ($expect)"; fi done echo -[ "$fail" -eq 0 ] || { echo "PANEL-CLADES ABI CHECK FAILED"; exit 1; } +[[ "$fail" -eq 0 ]] || { echo "PANEL-CLADES ABI CHECK FAILED"; exit 1; } echo "OK: panel-clades ABI — builds against the kernel, ${n} rejection(s)" diff --git a/panel-clades/tests/scan/test-abi-scan.sh b/panel-clades/tests/scan/test-abi-scan.sh new file mode 100755 index 00000000..c086e0de --- /dev/null +++ b/panel-clades/tests/scan/test-abi-scan.sh @@ -0,0 +1,91 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 +# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) +# +# test-abi-scan.sh — regression tests for scripts/abi-scan-lib.sh, the scans +# behind check-abi.sh. Both ways: each planted escape, hole, missing +# directive or commented-out import must be caught or ignored as stated, and +# the same words in comments or strings must not trip a scan. Needs only +# bash and awk; it runs before the Idris2 build in CI. +set -uo pipefail +cd -- "$(dirname -- "${BASH_SOURCE[0]}")/../.." || exit 1 +# shellcheck source=../../scripts/abi-scan-lib.sh +. scripts/abi-scan-lib.sh + +work="$(mktemp -d)"; trap 'rm -rf "$work"' EXIT +fail=0; n=0 + +# mod BODY... : write a module T.idr whose lines are BODY (no %default added). +mod() { printf '%s\n' "module T" "$@" > "$work/T.idr"; } + +# expect found|clean DESCRIPTION BODY... : scan_escapes on a module with +# `%default total` plus BODY. +expect() { + local want="$1" what="$2"; shift 2 + mod "%default total" "$@" + local got=clean + if scan_escapes "$work/T.idr" > /dev/null; then got=found; fi + n=$((n + 1)) + if [[ "$got" == "$want" ]]; then echo "ok $want: $what"; else echo "FAIL got $got, want $want: $what"; fail=1; fi +} + +expect found "hole" 'f : Nat' 'f = ?todo' +expect found "hole as an argument" 'f : Nat -> Nat' 'f x = plus x ?hole_1' +expect found "believe_me" 'f = believe_me 1' +expect found "idris_crash" 'g = idris_crash ""' +expect found "assert_total" 'g = assert_total (f 1)' +expect found "postulate" 'postulate p : Nat' +expect found "partial declaration" 'partial' 'f : Nat' +expect found "export partial" 'export partial' 'f : Nat' +expect found "covering declaration" 'covering' 'f : Nat' +expect found "hole after an operator" 'x = a --> ?b' +expect clean "words in a line comment" '-- NO believe_me, NO postulate, ?hole here' +expect clean "words in a doc comment" '||| partial functions and ?holes are banned' +expect clean "nested block comment" '{- block' 'believe_me ?x' '{- nested -} still' '-}' 'f : Nat' +expect clean "words in a string" 'msg = "is it ?ok to say believe_me"' +expect clean "operator, not a comment" 'x = a --> b' +expect clean "primed identifier" "f' : Nat" "f' = 1" +expect clean "identifier containing partial" 'partialSum : Nat' + +# %default total: required in code, not in a comment. +directive() { # directive found|clean DESCRIPTION BODY... + local want="$1" what="$2"; shift 2 + mod "$@" + local got=clean + if scan_escapes "$work/T.idr" > /dev/null; then got=found; fi + n=$((n + 1)) + if [[ "$got" == "$want" ]]; then echo "ok $want: $what"; else echo "FAIL got $got, want $want: $what"; fail=1; fi +} +directive found "no %default total" 'f : Nat' +directive found "%default total only in a block comment" '{-' '%default total' '-}' 'f : Nat' +directive found "%default total only in a line comment" '-- %default total' 'f : Nat' +directive found "%default covering" '%default covering' 'f : Nat' +directive clean "%default total in code" '%default total' 'f : Nat' +# A long module: grep must read to the end, or SIGPIPE under pipefail reads +# as a missing directive. +mapfile -t long < <(for i in $(seq 1 20000); do printf 'v%d : Nat\n' "$i"; done) +directive clean "%default total in a 20,000-line module" '%default total' "${long[@]}" + +# import_cone: commented-out imports are not dependencies; indented ones are. +mkdir -p "$work/root/A" "$work/root/B" +printf '%s\n' "module A.Real" "%default total" > "$work/root/A/Real.idr" +printf '%s\n' "module A.Indented" "%default total" > "$work/root/A/Indented.idr" +printf '%s\n' "module B.Ghost" "%default total" > "$work/root/B/Ghost.idr" +printf '%s\n' "module Top" "%default total" \ + "import A.Real" " import public A.Indented" "import Data.Nat" \ + "{- import B.Ghost -}" "-- import B.Ghost" > "$work/Top.idr" +cone="$(import_cone "$work/base" "$work/root" -- "$work/Top.idr")" +check_cone() { # check_cone yes|no PATH DESCRIPTION + n=$((n + 1)) + if grep -qxF "$2" <<< "$cone"; then got=yes; else got=no; fi + if [[ "$got" == "$1" ]]; then echo "ok cone $1: $3"; else echo "FAIL cone got $got, want $1: $3"; fail=1; fi +} +check_cone yes "$work/root/A/Real.idr" "an import" +check_cone yes "$work/root/A/Indented.idr" "an indented import public" +check_cone no "$work/root/B/Ghost.idr" "imports inside comments" +n=$((n + 1)) +if grep -qxF Data.Nat "$work/base"; then echo "ok base: unresolved import listed"; else echo "FAIL base: Data.Nat not listed"; fail=1; fi + +echo +[[ "$fail" -eq 0 ]] || { echo "ABI SCAN TESTS FAILED"; exit 1; } +echo "OK: abi scan — $n cases" diff --git a/scripts/check-provenance-model.sh b/scripts/check-provenance-model.sh index 230526ed..4368379d 100755 --- a/scripts/check-provenance-model.sh +++ b/scripts/check-provenance-model.sh @@ -23,7 +23,7 @@ JAR="${1:?usage: check-provenance-model.sh /path/to/tla2tools.jar}" EXPECT_VERSION="TLC2 Version 2.19 of 08 August 2024" cd -- "$(dirname -- "${BASH_SOURCE[0]}")/../docs/models" || exit 1 command -v java >/dev/null || { echo "FAIL: java not on PATH"; exit 1; } -[ -f "$JAR" ] || { echo "FAIL: $JAR not found"; exit 1; } +[[ -f "$JAR" ]] || { echo "FAIL: $JAR not found"; exit 1; } work="$(mktemp -d)"; trap 'rm -rf "$work"' EXIT fail=0 @@ -37,7 +37,7 @@ for cfg in Provenance ProvenanceWitnessLeak; do run "$cfg"; st=$? grep -qF "$EXPECT_VERSION" "$work/$cfg.log" \ || { echo "FAIL: $cfg: TLC is not '$EXPECT_VERSION'"; head -5 "$work/$cfg.log"; fail=1; continue; } - if [ "$st" -eq 0 ] && grep -q '^Model checking completed. No error has been found.' "$work/$cfg.log"; then + if [[ "$st" -eq 0 ]] && grep -q '^Model checking completed. No error has been found.' "$work/$cfg.log"; then echo "PASS: $cfg — $(grep -E '^[0-9]+ states generated' "$work/$cfg.log")" else tail -40 "$work/$cfg.log"; echo "FAIL: $cfg (exit $st): expected no error"; fail=1 @@ -48,11 +48,11 @@ cfg=ProvenanceBothLeak run "$cfg"; st=$? if ! grep -qF "$EXPECT_VERSION" "$work/$cfg.log"; then echo "FAIL: $cfg: TLC is not '$EXPECT_VERSION'"; head -5 "$work/$cfg.log"; fail=1 -elif [ "$st" -ne 0 ] && grep -q '^Error: Invariant Genuine is violated' "$work/$cfg.log"; then +elif [[ "$st" -ne 0 ]] && grep -q '^Error: Invariant Genuine is violated' "$work/$cfg.log"; then echo "PASS: $cfg — TLC found the both-keys forgery, as it must" else tail -40 "$work/$cfg.log"; echo "FAIL: $cfg (exit $st): expected 'Invariant Genuine is violated'"; fail=1 fi -[ "$fail" -eq 0 ] || { echo "PROVENANCE MODEL CHECK FAILED"; exit 1; } +[[ "$fail" -eq 0 ]] || { echo "PROVENANCE MODEL CHECK FAILED"; exit 1; } echo "OK: provenance model — Genuine holds with one leaked key, fails with two" From fe2f6b0715569c3baffd338fcc307079888a4af6 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 9 Nov 2026 14:26:34 +0000 Subject: [PATCH 16/21] fix(ci,proofs): restrict the TLC download to HTTPS; name PanLL's Exhibit B profile MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit From the session that opened hyperpolymath/panll#181 (closed as a duplicate of this PR); both findings verified here. build-validation.yml, provenance-model job: the tla2tools.jar download now runs curl with --proto '=https' --proto-redir '=https' --tlsv1.2. Following redirects with no protocol restriction is the SonarCloud vulnerability behind "C Security Rating on New Code" on this PR; the same one-line change turned #181's quality gate green. The SHA-256 check is unchanged, and the release asset still matches 936a2620...0e88 when fetched with the new flags. PROOF-NEEDS.adoc, provenance row (CodeRabbit on hyperpolymath/panll#172, comment 4479623116): the row said records "follow EXHIBIT-B", but crates/provenance implements a narrower profile. Checked against EXHIBIT-B-QUANTUM-SAFE.txt and the crate: - ML-DSA-65 only; Exhibit B §2.1 and §4.1d also allow ML-DSA-87, SLH-DSA, FALCON or another approved alternative; - at least one independent witness for `verified` (verify_witnesses starts from `!declared.is_empty()`); witnesses are optional in Exhibit B §3.2; - keys only from the approved .well-known/keys/ registry; Exhibit B §5.1 also lists OpenPGP keyservers and personal websites. The row now names that profile: every record PanLL verifies satisfies Exhibit B, and some Exhibit B records verify only as `claimed`. The ASSUMED third-party primitives now carry their Cargo.lock versions: fips204 0.5.0 (exact pin), sha3 0.10.9, serde_jcs 0.2.0, chrono 0.4.45, base64 0.22.1. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01BCQeL169ZnUirUVaDXQLA3 --- .github/workflows/build-validation.yml | 2 +- PROOF-NEEDS.adoc | 22 +++++++++++++++------- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/.github/workflows/build-validation.yml b/.github/workflows/build-validation.yml index 05004fd3..57760159 100644 --- a/.github/workflows/build-validation.yml +++ b/.github/workflows/build-validation.yml @@ -70,7 +70,7 @@ jobs: - name: Fetch TLA+ tools v1.7.4 (pinned by digest) run: | - curl -sSfL --retry 3 -o "$RUNNER_TEMP/tla2tools.jar" "$TLA2TOOLS_URL" + curl -fsSL --proto '=https' --proto-redir '=https' --tlsv1.2 --retry 3 -o "$RUNNER_TEMP/tla2tools.jar" "$TLA2TOOLS_URL" echo "$TLA2TOOLS_SHA256 $RUNNER_TEMP/tla2tools.jar" | sha256sum -c - java -version diff --git a/PROOF-NEEDS.adoc b/PROOF-NEEDS.adoc index 9427f7ae..3e615116 100644 --- a/PROOF-NEEDS.adoc +++ b/PROOF-NEEDS.adoc @@ -265,11 +265,16 @@ port. |Provenance engine |Provenance tracking is complete (every action is recorded) and unforgeable. Owner decision (2026-11-05): "unforgeable" means each -provenance record follows `EXHIBIT-B-QUANTUM-SAFE.txt`. The record holds -the author identity, a timestamp, a SHA3-256 content hash and, optionally, -a parent hash. It is serialised as canonical JSON, signed with ML-DSA-65 -and witnessed, and verifies against keys published under -`.well-known/keys/`. Today's engine (git blame plus Co-Authored-By +provenance record follows PanLL's profile of `EXHIBIT-B-QUANTUM-SAFE.txt`. +The record holds the author identity, a timestamp, a SHA3-256 content hash +and, optionally, a parent hash, serialised as canonical JSON. The profile +is narrower than Exhibit B: ML-DSA-65 only (Exhibit B §2.1 and §4.1 also +allow ML-DSA-87, SLH-DSA, FALCON or another approved alternative); at +least one independent witness is required for `verified` (witnesses are +optional in Exhibit B §3.2); and keys come only from the verifier's +approved `.well-known/keys/` registry (Exhibit B §5.1 also lists OpenPGP +keyservers and personal websites). Every record PanLL verifies therefore +satisfies Exhibit B; some Exhibit B records verify only as `claimed`. Today's engine (git blame plus Co-Authored-By trailers) is forgeable, so its output counts only as "claimed". |`TESTED` |Implemented in `crates/provenance`: JCS records, ML-DSA-65 signing, @@ -335,8 +340,11 @@ to the shipped functions. No separate model counts as a proof of them. canonicalisation of `content_hash` and the fields bound by signatures and attestations. Allocate the obligations against the shipped functions. |Only third-party JCS, SHA3-256, ML-DSA-65, entropy and time-parsing primitives -and their specified behaviour are ASSUMED. Record exact locked versions and -extern contracts when proofs are introduced; cryptographic hardness, civil +and their specified behaviour are ASSUMED, at the versions `Cargo.lock` pins +today: `fips204` 0.5.0 (ML-DSA-65; exact pin `=0.5.0` in +`crates/provenance/Cargo.toml`), `sha3` 0.10.9, `serde_jcs` 0.2.0, `chrono` +0.4.45 and `base64` 0.22.1. Record extern contracts when proofs are +introduced; cryptographic hardness, civil identity approval and independent witness judgment are outside these proofs. |`src-gossamer/src/ipc.rs` dispatch and `tools/pcc` propagation |Creusot / Pearlite and Verus: OPEN From fb15dd8daa866e8947e6dcc17aecfa1a23e4084f Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 9 Nov 2026 15:55:49 +0000 Subject: [PATCH 17/21] proofs: first C field at offset 0; scan escaped char literals, raw and multi-line strings; %default total before declarations Three findings from Codex review on #180, each confirmed and fixed. - Layout.idr: CABICompliant now carries FirstAtZero. C allows padding between and after members, never before the first (C11 6.7.2.1p15), so a lone field at offset 8 is no C layout, yet checkCABI accepted it. proveFirstAtZero decides it with decEq; checkCABI is rewritten in Either's do-notation. New reject fixture FirstFieldNotAtZero (that exact layout; refused only by the new rule). - abi-scan-lib.sh code_only: a character literal with an escape ('\"', '\'', '\x41') was misread, so the quote opened a "string" that blanked the rest of the line and hid any escape hatch after it. Raw strings (#"..."#, where \ is not an escape) had the same hole, and multi-line """...""" strings were not tracked. All are lexed now; a primed identifier does not open a literal. - scan_escapes: %default total must precede the first declaration. Idris applies it only from where it appears, so a definition above it was checked only for coverage. The check reads its input to the end (an early exit SIGPIPEs code_only under pipefail). Verified: test-abi-scan.sh 38 cases pass; against the previous library 5 of the new cases fail. check-abi.sh with Idris2 0.8.0 (15a3e4e): 5 modules, 0 errors, 10 of 10 rejections, import cone clean. shellcheck -S warning clean. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01BCQeL169ZnUirUVaDXQLA3 --- CHANGELOG.adoc | 16 ++--- PROOF-NEEDS.adoc | 12 ++-- docs/proof-debt.adoc | 2 +- panel-clades/scripts/abi-scan-lib.sh | 50 ++++++++++++---- panel-clades/scripts/check-abi.sh | 3 +- .../src/abi/PanelClades/ABI/Layout.idr | 58 ++++++++++++------- panel-clades/src/abi/README.adoc | 8 +-- .../tests/reject/FirstFieldNotAtZero.idr | 25 ++++++++ panel-clades/tests/scan/test-abi-scan.sh | 11 ++++ 9 files changed, 135 insertions(+), 50 deletions(-) create mode 100644 panel-clades/tests/reject/FirstFieldNotAtZero.idr diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index 59f57c77..31f2222a 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -18,18 +18,20 @@ above the size and below size plus `+a+` (`+alignUpCovers+`, `+fieldWithinStruct+` prove it, so `+verifyLayout+` drops its run-time check and refuses only an alignment of 0. `+StructLayout+` now carries that coverage as an `+LTE+` proof instead of a Boolean `+So+`. -* *C layout evidence.* `+CABICompliant+` now also carries fields in order -without overlap (`+FieldsOrdered+`), power-of-two alignments +* *C layout evidence.* `+CABICompliant+` now also carries a first field at +offset 0 (`+FirstAtZero+`), fields in order without overlap +(`+FieldsOrdered+`), power-of-two alignments (`+AlignmentsPow2+`, decided by `+decPow2+`), and a struct alignment equal to the largest field alignment (`+maxAlignment+`). `+checkCABI+` decides -all four parts. +all five parts. * *Tag tables agree.* `+resultTagsAgree+` and `+cladeKindTagsAgree+` tie each `+Bits32+` C encoding to its `+Nat+` equality tag. -* Three more expected rejections: `+OverlappingFields+`, -`+AlignmentNotPow2+`, `+StructUnderAligned+`. +* Four more expected rejections: `+FirstFieldNotAtZero+`, +`+OverlappingFields+`, `+AlignmentNotPow2+`, `+StructUnderAligned+`. * `+check-abi.sh+` now also fails on holes, `+partial+`, `+covering+` and a -module without `+%default total+`, reads code only (not comments or -strings), and holds the typed-wasm import cone to the same scan. +module without `+%default total+` before its first declaration, reads +code only (not comments, strings, raw or multi-line strings, or character +literals), and holds the typed-wasm import cone to the same scan. * *Provenance model rewritten and run in CI.* `+docs/models/Provenance.tla+` now has an adversary that signs under a leaked key, separate author and witness signatures, and the invariant `+Genuine+`: a verified chain is diff --git a/PROOF-NEEDS.adoc b/PROOF-NEEDS.adoc index 3e615116..a3766b97 100644 --- a/PROOF-NEEDS.adoc +++ b/PROOF-NEEDS.adoc @@ -56,7 +56,8 @@ runs `panel-clades/scripts/check-abi.sh`, which: * builds `panel-clades/src/abi/panel-clades-grades.ipkg` against it and fails on any `Error:` line, even when `idris2` exits 0; * fails if any module under `PanelClades/` is missing from the ipkg or - does not declare `%default total`, or if its code (comments and string + does not declare `%default total` before its first declaration (the + directive applies only from where it appears), or if its code (comments and string literals set aside) contains a hole (`?name`; `idris2 --build` accepts a module with holes), `believe_me`, `assert_total`, `assert_smaller`, `idris_crash`, `unsafePerformIO`, `postulate`, `partial` or `covering`; @@ -104,7 +105,7 @@ declaration That run predates the import-cone and hole scans, the tag-agreement proofs, the layout bounds and the C layout evidence. On 2026-11-09 the same `check-abi.sh`, with those additions, ran locally with Idris2 0.8.0 -built from the same commit and passed: 5 modules, 0 errors, 9 of 9 +built from the same commit and passed: 5 modules, 0 errors, 10 of 10 fixtures refused, and the cone above clean. A local run is not a receipt. The first `main` run after this change is the first receipt to include them. A receipt is current only for its commit: rerun the job, never carry a @@ -137,9 +138,10 @@ multiple of its alignment (`calcStructSizeAligned`) and covers every field (`calcStructSizeCovers`, `fieldWithinEnd`, `fieldWithinStruct`), so `verifyLayout` has no run-time size check and refuses only an alignment of 0. `CABICompliant` evidence carries every offset as a multiple of its -alignment (`FieldsAligned`), the fields in order with each starting at or -after the previous one's end (`FieldsOrdered`; no two overlap, since the -chain is ordered), every alignment a power of two (`AlignmentsPow2`), and +alignment (`FieldsAligned`), the first field at offset 0, since C allows +no padding before it (`FirstAtZero`), the fields in order with each +starting at or after the previous one's end (`FieldsOrdered`; no two +overlap, since the chain is ordered), every alignment a power of two (`AlignmentsPow2`), and the struct aligned to its largest field alignment (`maxAlignment`). Each part is decided by an equation or an `LTE` proof (`decDivides`, `isLTE`, `decPow2`, `decEq`), not by a Boolean test. diff --git a/docs/proof-debt.adoc b/docs/proof-debt.adoc index 10eaa817..4966ce6b 100644 --- a/docs/proof-debt.adoc +++ b/docs/proof-debt.adoc @@ -12,7 +12,7 @@ Last reconciled: 2026-11-09 (`main` at `7d1feaa`). `assert_smaller`, `idris_crash`, `unsafePerformIO`, `postulate`, `partial`, `covering` or holes, measured on the date above. `check-abi.sh` fails CI if any of them appears in code (comments and string literals set - aside) or a module lacks `%default total`, in PanLL's modules and in the + aside) or a module lacks `%default total` before its first declaration, in PanLL's modules and in the typed-wasm modules they import (the import cone). * *Estate check*: standards' `scripts/check-trusted-base.sh` finds 2 escape-hatch signatures, both documented below as not proof debt. diff --git a/panel-clades/scripts/abi-scan-lib.sh b/panel-clades/scripts/abi-scan-lib.sh index d40d626d..c8139948 100644 --- a/panel-clades/scripts/abi-scan-lib.sh +++ b/panel-clades/scripts/abi-scan-lib.sh @@ -9,29 +9,50 @@ # Print each .idr file's code with comments and string literals blanked, as # FILE:LINE:TEXT. Handles nested {- -} block comments, `--` line comments -# (not operators such as -->), `|||` doc comments, and "..." / '.' literals. +# (not operators such as -->), `|||` doc comments, "..." strings, raw +# strings #"..."# (any number of #, where \ is not an escape), multi-line +# """...""" strings, and character literals including escapes ('\'', +# '\"', '\x41'). A primed identifier (f') does not open a literal. code_only() { awk ' - FNR == 1 { depth = 0 } + FNR == 1 { depth = 0; term = ""; ml = 0 } { line = $0; out = ""; i = 1; n = length(line) - if (depth == 0 && line ~ /^[[:space:]]*\|\|\|/) { print FILENAME ":" FNR ":"; next } + if (depth == 0 && term == "" && line ~ /^[[:space:]]*\|\|\|/) { print FILENAME ":" FNR ":"; next } while (i <= n) { + if (term != "") { + if (esc && substr(line, i, 1) == "\\") { i += 2; continue } + if (substr(line, i, length(term)) == term) { i += length(term); term = ""; ml = 0; continue } + i++; continue + } two = substr(line, i, 2); c = substr(line, i, 1) if (two == "{-") { depth++; i += 2; continue } if (depth > 0) { if (two == "-}") { depth--; i += 2 } else i++; continue } if (two == "--" && substr(line, i + 2, 1) !~ /[!#$%&*+.\/<=>?@\\^|~:-]/) break - if (c == "\"") { i++; while (i <= n && substr(line, i, 1) != "\"") { if (substr(line, i, 1) == "\\") i++; i++ }; i++; out = out "\"\""; continue } - if (c == "'"'"'" && substr(line, i + 2, 1) == "'"'"'") { i += 3; out = out "'"'"''"'"'"; continue } + k = 0; while (substr(line, i + k, 1) == "#") k++ + if (substr(line, i + k, 1) == "\"") { + h = substr(line, i, k) + if (substr(line, i + k, 3) == "\"\"\"") { term = "\"\"\"" h; ml = 1; i += k + 3 } + else { term = "\"" h; ml = 0; i += k + 1 } + esc = (k == 0); out = out "\"\""; continue + } + if (c == SQ && (i == 1 || substr(line, i - 1, 1) !~ /[A-Za-z0-9_]/ && substr(line, i - 1, 1) != SQ)) { + if (substr(line, i + 1, 1) == "\\") { + j = i + 3; while (j <= n && j < i + 12 && substr(line, j, 1) != SQ) j++ + if (substr(line, j, 1) == SQ) { i = j + 1; out = out SQ SQ; continue } + } else if (substr(line, i + 2, 1) == SQ) { i += 3; out = out SQ SQ; continue } + } out = out c; i++ } + if (!ml) term = "" print FILENAME ":" FNR ":" out - }' "$@" + }' SQ="'" "$@" } # Escape hatches and holes in code, plus any module whose code does not -# declare `%default total` (Idris2's default is `covering`, which does not -# check termination; a directive inside a comment does not count). +# declare `%default total` before its first declaration (Idris2's default is +# `covering`, which does not check termination, and %default applies only +# from where it appears; a directive inside a comment does not count). # Returns 0 and prints the offending lines if anything is found. # believe_me assert_total assert_smaller idris_crash unsafePerformIO # postulate -- trust or totality escapes @@ -45,9 +66,18 @@ scan_escapes() { -e ':[0-9]+:.*\b(partial|covering)\b'; then found=0 fi + # Only the module header, imports and other % directives may precede it. + # The awk reads to the end: exiting early would SIGPIPE code_only, which + # pipefail reads as a missing directive. for f in "$@"; do - if ! code_only "$f" | grep -E '^[^:]*:[0-9]+:[[:space:]]*%default[[:space:]]+total[[:space:]]*$' > /dev/null; then - printf '%s: no %%default total in code\n' "$f"; found=0 + if ! code_only "$f" | awk ' + done { next } + { t = $0; sub(/^[^:]*:[0-9]+:/, "", t) } + t ~ /^[[:space:]]*%default[[:space:]]+total[[:space:]]*$/ { ok = 1; done = 1; next } + t ~ /^[[:space:]]*$/ || t ~ /^[[:space:]]*(module|import)([[:space:]]|$)/ || t ~ /^[[:space:]]*%/ { next } + { done = 1 } + END { exit !ok }'; then + printf '%s: no %%default total before the first declaration\n' "$f"; found=0 fi done return "$found" diff --git a/panel-clades/scripts/check-abi.sh b/panel-clades/scripts/check-abi.sh index d40cb8ba..f979a5bd 100755 --- a/panel-clades/scripts/check-abi.sh +++ b/panel-clades/scripts/check-abi.sh @@ -16,7 +16,8 @@ # # A missing toolchain is a failure, never a skip. Every module under # src/abi/PanelClades/ must be listed in the ipkg (an unlisted one would be -# skipped silently), declare `%default total`, and use no escape hatch and no +# skipped silently), declare `%default total` before its first declaration +# (the directive applies only from where it appears), and use no escape hatch and no # hole; the typed-wasm modules they import (the import cone) are held to the # same scan, and the cone is printed as the run's record of it. set -uo pipefail diff --git a/panel-clades/src/abi/PanelClades/ABI/Layout.idr b/panel-clades/src/abi/PanelClades/ABI/Layout.idr index b795c4eb..9fa3ccb9 100644 --- a/panel-clades/src/abi/PanelClades/ABI/Layout.idr +++ b/panel-clades/src/abi/PanelClades/ABI/Layout.idr @@ -16,8 +16,8 @@ ||| (`calcStructSizeCovers`, `fieldWithinStruct`), so `verifyLayout` ||| needs no run-time size check; ||| * C layout evidence (`CABICompliant`) carries, for every field, an -||| offset that is a multiple of its alignment (`FieldsAligned`), fields -||| in order and not overlapping (`FieldsOrdered`), alignments that are +||| offset that is a multiple of its alignment (`FieldsAligned`), a +||| first field at offset 0 (`FirstAtZero`), fields in order and not overlapping (`FieldsOrdered`), alignments that are ||| powers of two (`AlignmentsPow2`), and a struct alignment equal to ||| the largest field alignment (`maxAlignment`). Each is decided by an ||| equation or an `LTE` proof, never by a Boolean test followed by an @@ -309,6 +309,13 @@ verifyLayout fields (S u) = -- C ABI Compatibility -------------------------------------------------------------------------------- +||| The first field starts at offset 0. C allows padding between members +||| and after the last, never before the first (C11 6.7.2.1p15). +public export +data FirstAtZero : List Field -> Type where + ZeroNil : FirstAtZero [] + ZeroHead : f.offset = 0 -> FirstAtZero (f :: rest) + ||| Fields in declaration order, each starting at or after the end of the ||| one before, so no two overlap. public export @@ -331,8 +338,8 @@ maxAlignment [] = 1 maxAlignment (f :: fs) = maximum f.alignment (maxAlignment fs) ||| Proof that a struct follows the C layout rules: every field offset is -||| a multiple of its alignment, the fields are in order and do not -||| overlap, every alignment is a power of two, and the struct is aligned +||| a multiple of its alignment, the first field is at offset 0, the fields +||| are in order and do not overlap, every alignment is a power of two, and the struct is aligned ||| to its largest field alignment. (Size covering the fields, and being a ||| multiple of the alignment, are carried by `StructLayout` itself.) public export @@ -340,6 +347,7 @@ data CABICompliant : StructLayout -> Type where CABIOk : (layout : StructLayout) -> FieldsAligned layout.fields -> + FirstAtZero layout.fields -> FieldsOrdered layout.fields -> AlignmentsPow2 layout.fields -> layout.alignment = maxAlignment layout.fields -> @@ -360,6 +368,16 @@ proveFieldsAligned (f :: rest) = Left err => Left err Right restProof => Right (ConsField f rest here restProof) +||| Attempt to prove the first field is at offset 0. +public export +proveFirstAtZero : (fields : List Field) -> Either String (FirstAtZero fields) +proveFirstAtZero [] = Right ZeroNil +proveFirstAtZero (f :: _) = + case decEq f.offset 0 of + No _ => Left ("First field " ++ f.name ++ " is at offset " + ++ show f.offset ++ ", not 0") + Yes zero => Right (ZeroHead zero) + ||| Attempt to prove the fields are in order and do not overlap. Returns ||| `Left` naming the first field that starts before the previous one ends. public export @@ -392,21 +410,16 @@ proveAlignmentsPow2 (f :: rest) = ||| Return C layout evidence for a layout, or the first rule it breaks. public export checkCABI : (layout : StructLayout) -> Either String (CABICompliant layout) -checkCABI layout = - case proveFieldsAligned layout.fields of - Left err => Left err - Right aligned => - case proveFieldsOrdered layout.fields of - Left err => Left err - Right ordered => - case proveAlignmentsPow2 layout.fields of - Left err => Left err - Right pow2 => - case decEq layout.alignment (maxAlignment layout.fields) of - No _ => Left ("Struct alignment " ++ show layout.alignment - ++ " is not the largest field alignment " - ++ show (maxAlignment layout.fields)) - Yes same => Right (CABIOk layout aligned ordered pow2 same) +checkCABI layout = do + aligned <- proveFieldsAligned layout.fields + first <- proveFirstAtZero layout.fields + ordered <- proveFieldsOrdered layout.fields + pow2 <- proveAlignmentsPow2 layout.fields + case decEq layout.alignment (maxAlignment layout.fields) of + No _ => Left ("Struct alignment " ++ show layout.alignment + ++ " is not the largest field alignment " + ++ show (maxAlignment layout.fields)) + Yes same => Right (CABIOk layout aligned first ordered pow2 same) -------------------------------------------------------------------------------- -- Platform-Specific Layouts @@ -493,13 +506,14 @@ exampleAlignmentsPow2 = Pow2Cons four (Pow2Cons eight (Pow2Cons eight Pow2Nil)) eight : IsPow2 8 eight = Pow2Double four -||| The example layout follows the C rules: aligned, ordered, power-of-two +||| The example layout follows the C rules: aligned, first field at 0, +||| ordered, power-of-two ||| alignments, and struct alignment 8, its largest field alignment. export exampleLayoutValid : CABICompliant PanelClades.ABI.Layout.exampleLayout exampleLayoutValid = - CABIOk exampleLayout exampleFieldsAligned exampleFieldsOrdered - exampleAlignmentsPow2 Refl + CABIOk exampleLayout exampleFieldsAligned (ZeroHead Refl) + exampleFieldsOrdered exampleAlignmentsPow2 Refl ||| The decision procedure agrees on the example. export diff --git a/panel-clades/src/abi/README.adoc b/panel-clades/src/abi/README.adoc index 10cbd7a9..4d493725 100644 --- a/panel-clades/src/abi/README.adoc +++ b/panel-clades/src/abi/README.adoc @@ -31,7 +31,7 @@ Handle, the C type universe (`+CType+`, `+cSizeOf+`, `+HasSize+`), CladeId, CladeKind |`+PanelClades/ABI/Layout.idr+` |Alignment and struct layout: `+Divides+`, -`+alignUp+` and its bounds, `+FieldsAligned+`, `+FieldsOrdered+`, +`+alignUp+` and its bounds, `+FieldsAligned+`, `+FirstAtZero+`, `+FieldsOrdered+`, `+AlignmentsPow2+`, `+CABICompliant+` |`+PanelClades/ABI/Foreign.idr+` |FFI function declarations and their safe @@ -53,7 +53,7 @@ against the clade's declared budget === Check status (2026-11-09) All five modules *typecheck* under Idris2 0.8.0 -(`+bash panel-clades/scripts/check-abi.sh+`), with nine expected +(`+bash panel-clades/scripts/check-abi.sh+`), with ten expected rejections in `+tests/reject/+`. Before 2026-10-14, `+Types.idr+`, `+Layout.idr+` and `+Foreign.idr+` had @@ -107,7 +107,7 @@ says so. |`+Divides+` witnesses are equations |`+tests/reject/MisalignedField.idr+` |TESTED |Rounding up to an alignment a ≥ 1 is at or above the size and below size + a |`+alignUpCovers+`, `+alignUpTight+` |PROVEN |A computed struct size covers every field, so every field of a layout lies within its total size |`+calcStructSizeCovers+`, `+fieldWithinEnd+`, `+fieldWithinStruct+` |PROVEN -|`+CABICompliant+` evidence: offsets aligned, fields in order without overlap, power-of-two alignments, struct alignment = largest field alignment |`+FieldsAligned+`, `+FieldsOrdered+`, `+AlignmentsPow2+`, `+maxAlignment+`; `+tests/reject/OverlappingFields.idr+`, `+AlignmentNotPow2.idr+`, `+StructUnderAligned.idr+` |PROVEN / TESTED +|`+CABICompliant+` evidence: offsets aligned, first field at offset 0, fields in order without overlap, power-of-two alignments, struct alignment = largest field alignment |`+FieldsAligned+`, `+FirstAtZero+`, `+FieldsOrdered+`, `+AlignmentsPow2+`, `+maxAlignment+`; `+tests/reject/FirstFieldNotAtZero.idr+`, `+OverlappingFields.idr+`, `+AlignmentNotPow2.idr+`, `+StructUnderAligned.idr+` |PROVEN / TESTED |The example layout is C-ABI compliant and its computed size is 24 |`+exampleLayoutValid+`, `+exampleSizeComputed+`, `+exampleCheckPasses+` |PROVEN |The `+Nat+` and `+Bits32+` tag tables agree |`+resultTagsAgree+`, `+cladeKindTagsAgree+` |PROVEN |`+decDivides+` returns `+Nothing+` only when the divisor does not divide; `+decPow2+` returns `+Nothing+` only for a non-power of two |— |ASSUMED (not proved) @@ -118,7 +118,7 @@ says so. === Invariants -* `+%default total+` in every module. +* `+%default total+` in every module, before its first declaration. * No `+believe_me+`, `+assert_total+`, `+assert_smaller+`, `+idris_crash+` or `+unsafePerformIO+`. `+scripts/check-abi.sh+` greps for them and fails. diff --git a/panel-clades/tests/reject/FirstFieldNotAtZero.idr b/panel-clades/tests/reject/FirstFieldNotAtZero.idr new file mode 100644 index 00000000..180d822f --- /dev/null +++ b/panel-clades/tests/reject/FirstFieldNotAtZero.idr @@ -0,0 +1,25 @@ +-- SPDX-License-Identifier: MPL-2.0 +-- Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) +-- +-- MUST FAIL (panel-clades/scripts/check-abi.sh). +-- +-- C allows no padding before the first member, so a struct whose only +-- field sits at offset 8 has no C layout. The field is aligned, the +-- struct covers it and is aligned to 4: refused by checkCABI because the +-- first field is not at offset 0. +-- EXPECT: Mismatch between: True and False +module FirstFieldNotAtZero + +import Data.Either +import Data.So +import Data.Nat +import Decidable.Equality +import PanelClades.ABI.Layout + +%default total + +padded : StructLayout +padded = MkStructLayout [MkField "x" 8 4 4] 12 4 (fitsIn 12 12) (DivideBy 3 Refl) + +invalid : isRight (checkCABI FirstFieldNotAtZero.padded) = True +invalid = Refl diff --git a/panel-clades/tests/scan/test-abi-scan.sh b/panel-clades/tests/scan/test-abi-scan.sh index c086e0de..5cd51505 100755 --- a/panel-clades/tests/scan/test-abi-scan.sh +++ b/panel-clades/tests/scan/test-abi-scan.sh @@ -46,6 +46,15 @@ expect clean "words in a string" 'msg = "is it ?ok to say believe_me"' expect clean "operator, not a comment" 'x = a --> b' expect clean "primed identifier" "f' : Nat" "f' = 1" expect clean "identifier containing partial" 'partialSum : Nat' +expect found "escape after an escaped-quote char literal" "f = ('\\\"', believe_me v)" +expect found "escape after an escaped-apostrophe char" "f = ('\\'', believe_me v)" +expect found "escape after a quote char literal" "f = ('\"', believe_me v)" +expect found "escape after a hex char literal" "f = ('\\x41', ?h)" +expect found "escape after a raw string with a backslash" 'f = #"\"# ++ believe_me v' +expect found "escape after a closed multi-line string" 's = """' 'text' '"""' 'g = believe_me 1' +expect clean "words in a raw string" 'msg = #"say "believe_me" and ?ok"#' +expect clean "words in a multi-line string" 's = """' 'believe_me ?x partial' '"""' +expect clean "primed identifier then a char literal" "f' = g' 'a'" # %default total: required in code, not in a comment. directive() { # directive found|clean DESCRIPTION BODY... @@ -61,6 +70,8 @@ directive found "%default total only in a block comment" '{-' '%default total' ' directive found "%default total only in a line comment" '-- %default total' 'f : Nat' directive found "%default covering" '%default covering' 'f : Nat' directive clean "%default total in code" '%default total' 'f : Nat' +directive found "%default total after a declaration" 'loop : Nat -> Nat' 'loop n = loop n' '%default total' +directive clean "imports and directives before %default total" 'import Data.Nat' '%hide Prelude.id' '%default total' 'f : Nat' # A long module: grep must read to the end, or SIGPIPE under pipefail reads # as a missing directive. mapfile -t long < <(for i in $(seq 1 20000); do printf 'v%d : Nat\n' "$i"; done) From 146895e9eb79af1f9bdb38499973ca59b0eccc49 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 9 Nov 2026 16:18:24 +0000 Subject: [PATCH 18/21] =?UTF-8?q?proofs:=20each=20single-key=20TLC=20run?= =?UTF-8?q?=20must=20exercise=20its=20leak;=20'=3D=3F'=20is=20an=20operato?= =?UTF-8?q?r,=20not=20a=20hole;=20state=20the=20model's=20bounds=20and=20w?= =?UTF-8?q?itness=20assumption?= CodeRabbit review of fe2f6b0 on #180. - check-provenance-model.sh: TLC now runs with -coverage, and each single-key configuration must show the leaked key's forge action reaching a state and the other forge action none, so a configuration that leaks no key (or the wrong one) cannot pass vacuously. A relative jar path is resolved before the script changes directory. - abi-scan-lib.sh: a ? after an operator character is part of that operator (Idris2 lexes a =?b as the operator =?), so it is not reported as a hole. A hole after an operator and a space is still caught. - test-abi-scan.sh: assert_smaller, unsafePerformIO, the =? cases, a %default total that appears only inside a multi-line string, and a transitive import in the cone. - PROVENANCE-EXHIBIT-B.adoc: states the model's assumption (the witness attests only genuine records) and its bounds (two-record chains over 12 symbolic payloads), as the model header and PROOF-NEEDS.adoc already do. PROOF-NEEDS.adoc records the new leak-coverage check. Verified: test-abi-scan.sh 44 cases pass; against the previous library the =?b case fails. check-provenance-model.sh with TLC 2.19, given a relative jar path: all three configurations pass, ForgeAuthor and ForgeWitness each reach states only in their own run. With Provenance.cfg mutated to leak no key, the script fails at the coverage check. shellcheck -S warning clean. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01BCQeL169ZnUirUVaDXQLA3 --- PROOF-NEEDS.adoc | 6 +++++- docs/PROVENANCE-EXHIBIT-B.adoc | 11 ++++++++--- panel-clades/scripts/abi-scan-lib.sh | 4 +++- panel-clades/tests/scan/test-abi-scan.sh | 9 ++++++++- scripts/check-provenance-model.sh | 23 ++++++++++++++++++++++- 5 files changed, 46 insertions(+), 7 deletions(-) diff --git a/PROOF-NEEDS.adoc b/PROOF-NEEDS.adoc index a3766b97..b2414ed1 100644 --- a/PROOF-NEEDS.adoc +++ b/PROOF-NEEDS.adoc @@ -413,7 +413,11 @@ for the genesis parent, so 12 possible payloads; chains of exactly two records; any subset of payloads forged under a leaked key. Checked both ways. With one leaked key, either one, `Genuine` holds over all 591,872 distinct reachable states (depth 13). With both keys leaked, TLC must find -the forgery and does, so the property is not vacuous. A mutation run that +the forgery and does, so the property is not vacuous. Each single-key run +must also exercise its leak: `check-provenance-model.sh` fails unless TLC's +coverage report shows the leaked key's forge action reaching a state and +the other forge action none, so a configuration that leaks no key cannot +pass. A mutation run that dropped the witness check from `Valid` was caught too: one leaked author key then forges a chain. Local run on 2026-11-09, about 7 minutes; the `provenance-model` job's run on `main` is the receipt. diff --git a/docs/PROVENANCE-EXHIBIT-B.adoc b/docs/PROVENANCE-EXHIBIT-B.adoc index 1981730b..fec25f57 100644 --- a/docs/PROVENANCE-EXHIBIT-B.adoc +++ b/docs/PROVENANCE-EXHIBIT-B.adoc @@ -104,9 +104,14 @@ Property tests generate arbitrary content, metadata and parent-hash mutations. `docs/models/Provenance.tla` models signature binding and chain verification symbolically, with an adversary that can sign under a leaked key. Its invariant `Genuine` says a verified chain is exactly the genuine one while -either the author key or the witness key is uncompromised; TLC checks it in -CI (`provenance-model` job) with each single key leaked, and must find the -forgery when both are. It is not a cryptographic proof of ML-DSA or SHA3, +either the author key or the witness key is uncompromised. It assumes that +signatures cannot be made without the key and that the witness attests only +genuine records: a witness persuaded to sign a forged record is outside the +model. TLC checks it in CI (`provenance-model` job) with each single key +leaked, and must find the forgery when both are. The check is bounded: +two-record chains, built from 12 symbolic payloads (two field +and digest values, and a parent that is one of them or `None`). Longer +chains and larger domains are not checked. It is not a cryptographic proof of ML-DSA or SHA3, nor a proof of any implementation path (PROOF-NEEDS.adoc, "Exceptions"). Production adoption requires an approved contributor key and independent witness; historical records are never silently re-signed. diff --git a/panel-clades/scripts/abi-scan-lib.sh b/panel-clades/scripts/abi-scan-lib.sh index c8139948..a2f6b5e5 100644 --- a/panel-clades/scripts/abi-scan-lib.sh +++ b/panel-clades/scripts/abi-scan-lib.sh @@ -57,12 +57,14 @@ code_only() { # believe_me assert_total assert_smaller idris_crash unsafePerformIO # postulate -- trust or totality escapes # ?name -- a hole: idris2 --build accepts a module with holes +# (a ? after an operator character is part of +# that operator, as in a =?b, not a hole) # partial / covering -- a declaration opting out of %default total scan_escapes() { local found=1 f if code_only "$@" | grep -E \ -e ':[0-9]+:.*\b(believe_me|assert_total|assert_smaller|idris_crash|unsafePerformIO|postulate)\b' \ - -e ':[0-9]+:(.*[^A-Za-z0-9_'"'"'?])?\?[a-z_][A-Za-z0-9_'"'"']*' \ + -e ':[0-9]+:(.*[^A-Za-z0-9_'"'"'?!#$%&*+./<=>@\^|~:-])?\?[a-z_][A-Za-z0-9_'"'"']*' \ -e ':[0-9]+:.*\b(partial|covering)\b'; then found=0 fi diff --git a/panel-clades/tests/scan/test-abi-scan.sh b/panel-clades/tests/scan/test-abi-scan.sh index 5cd51505..aed57185 100755 --- a/panel-clades/tests/scan/test-abi-scan.sh +++ b/panel-clades/tests/scan/test-abi-scan.sh @@ -34,6 +34,8 @@ expect found "hole as an argument" 'f : Nat -> Nat' 'f x = plus x ?hole_1 expect found "believe_me" 'f = believe_me 1' expect found "idris_crash" 'g = idris_crash ""' expect found "assert_total" 'g = assert_total (f 1)' +expect found "assert_smaller" 'g = assert_smaller x y' +expect found "unsafePerformIO" 'g = unsafePerformIO action' expect found "postulate" 'postulate p : Nat' expect found "partial declaration" 'partial' 'f : Nat' expect found "export partial" 'export partial' 'f : Nat' @@ -44,6 +46,8 @@ expect clean "words in a doc comment" '||| partial functions and ?holes are expect clean "nested block comment" '{- block' 'believe_me ?x' '{- nested -} still' '-}' 'f : Nat' expect clean "words in a string" 'msg = "is it ?ok to say believe_me"' expect clean "operator, not a comment" 'x = a --> b' +expect clean "operator ending in ?, not a hole" 'x = a =?b' +expect found "hole after an operator and a space" 'x = a =? ?b' expect clean "primed identifier" "f' : Nat" "f' = 1" expect clean "identifier containing partial" 'partialSum : Nat' expect found "escape after an escaped-quote char literal" "f = ('\\\"', believe_me v)" @@ -69,6 +73,7 @@ directive found "no %default total" 'f : Nat' directive found "%default total only in a block comment" '{-' '%default total' '-}' 'f : Nat' directive found "%default total only in a line comment" '-- %default total' 'f : Nat' directive found "%default covering" '%default covering' 'f : Nat' +directive found "%default total only in a multi-line string" 's : String' 's = """' '%default total' '"""' directive clean "%default total in code" '%default total' 'f : Nat' directive found "%default total after a declaration" 'loop : Nat -> Nat' 'loop n = loop n' '%default total' directive clean "imports and directives before %default total" 'import Data.Nat' '%hide Prelude.id' '%default total' 'f : Nat' @@ -79,7 +84,8 @@ directive clean "%default total in a 20,000-line module" '%default total' "${lon # import_cone: commented-out imports are not dependencies; indented ones are. mkdir -p "$work/root/A" "$work/root/B" -printf '%s\n' "module A.Real" "%default total" > "$work/root/A/Real.idr" +printf '%s\n' "module A.Real" "%default total" "import A.Deep" > "$work/root/A/Real.idr" +printf '%s\n' "module A.Deep" "%default total" > "$work/root/A/Deep.idr" printf '%s\n' "module A.Indented" "%default total" > "$work/root/A/Indented.idr" printf '%s\n' "module B.Ghost" "%default total" > "$work/root/B/Ghost.idr" printf '%s\n' "module Top" "%default total" \ @@ -92,6 +98,7 @@ check_cone() { # check_cone yes|no PATH DESCRIPTION if [[ "$got" == "$1" ]]; then echo "ok cone $1: $3"; else echo "FAIL cone got $got, want $1: $3"; fail=1; fi } check_cone yes "$work/root/A/Real.idr" "an import" +check_cone yes "$work/root/A/Deep.idr" "a transitive import" check_cone yes "$work/root/A/Indented.idr" "an indented import public" check_cone no "$work/root/B/Ghost.idr" "imports inside comments" n=$((n + 1)) diff --git a/scripts/check-provenance-model.sh b/scripts/check-provenance-model.sh index 4368379d..f189e624 100755 --- a/scripts/check-provenance-model.sh +++ b/scripts/check-provenance-model.sh @@ -20,6 +20,8 @@ # (provenance-model job); the deviation record is in PROOF-NEEDS.adoc. set -uo pipefail JAR="${1:?usage: check-provenance-model.sh /path/to/tla2tools.jar}" +# Resolve a relative jar path before the cd below. +case "$JAR" in /*) ;; *) JAR="$PWD/$JAR" ;; esac EXPECT_VERSION="TLC2 Version 2.19 of 08 August 2024" cd -- "$(dirname -- "${BASH_SOURCE[0]}")/../docs/models" || exit 1 command -v java >/dev/null || { echo "FAIL: java not on PATH"; exit 1; } @@ -29,11 +31,24 @@ work="$(mktemp -d)"; trap 'rm -rf "$work"' EXIT fail=0 run() { # run -> log in $work/.log, exit status in $? - java -XX:+UseParallelGC -cp "$JAR" tlc2.TLC -workers auto -deadlock \ + java -XX:+UseParallelGC -cp "$JAR" tlc2.TLC -workers auto -deadlock -coverage 600 \ -metadir "$work/$1.states" -config "$1.cfg" Provenance.tla > "$work/$1.log" 2>&1 } +# reached : the distinct-state count TLC's coverage report +# gives the action (-coverage prints it once, at the end of the run). +reached() { + sed -nE "s/^<$2 line [0-9]+, col [0-9]+ to line [0-9]+, col [0-9]+ of module Provenance>: ([0-9]+):[0-9]+\$/\\1/p" "$1" | head -n 1 +} + +# Each single-key run must actually exercise its leak: the leaked key's +# forge action reaches a state and the other forge action reaches none, so a +# configuration that leaks no key (or the wrong one) cannot pass vacuously. for cfg in Provenance ProvenanceWitnessLeak; do + case "$cfg" in + Provenance) leaked=ForgeAuthor sound=ForgeWitness ;; + ProvenanceWitnessLeak) leaked=ForgeWitness sound=ForgeAuthor ;; + esac run "$cfg"; st=$? grep -qF "$EXPECT_VERSION" "$work/$cfg.log" \ || { echo "FAIL: $cfg: TLC is not '$EXPECT_VERSION'"; head -5 "$work/$cfg.log"; fail=1; continue; } @@ -42,6 +57,12 @@ for cfg in Provenance ProvenanceWitnessLeak; do else tail -40 "$work/$cfg.log"; echo "FAIL: $cfg (exit $st): expected no error"; fail=1 fi + n_leaked="$(reached "$work/$cfg.log" "$leaked")"; n_sound="$(reached "$work/$cfg.log" "$sound")" + if [[ "${n_leaked:-0}" -gt 0 && "$n_sound" == 0 ]]; then + echo "PASS: $cfg exercises its leak — $leaked reached $n_leaked distinct states, $sound none" + else + echo "FAIL: $cfg: expected $leaked > 0 and $sound = 0 distinct states, got $leaked=${n_leaked:-missing} $sound=${n_sound:-missing}"; fail=1 + fi done cfg=ProvenanceBothLeak From 29c5098cff76a20a22bf757154cd084ff1ac369b Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 9 Nov 2026 16:24:04 +0000 Subject: [PATCH 19/21] proofs: scan string interpolations as code, match uppercase holes, refuse empty C structs Codex review of fb15dd8 on #180, three findings, each confirmed. - abi-scan-lib.sh code_only: Idris runs the code in a string interpolation, but the scanner blanked the whole string, so "\{believe_me ...}" or "\{?h}" passed. Interpolations (\{...} in a plain or multi-line string, \#{...} in a raw string opened with one #, and so on) are now kept and scanned to their matching }, with nested braces counted and strings nested inside handled the same way. \{ in a raw string is still text. - scan_escapes: the hole pattern matched only a lowercase or _ start, so ?Todo passed. It now matches any identifier start. - Layout.idr: FirstAtZero has no evidence for [], so CABICompliant needs at least one field (C11 6.7.2.1p8: a struct with no named member is undefined); proveFirstAtZero refuses the empty list. New reject fixture EmptyStruct, refused only by that rule. Verified: test-abi-scan.sh 54 cases pass; against the previous library 6 of the new cases fail. The PanLL sources and the typed-wasm cone scan clean. check-abi.sh with Idris2 0.8.0 (15a3e4e): 5 modules, 0 errors, 11 of 11 rejections. shellcheck -S warning clean. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01BCQeL169ZnUirUVaDXQLA3 --- CHANGELOG.adoc | 8 +++--- PROOF-NEEDS.adoc | 17 +++++++----- docs/proof-debt.adoc | 2 +- panel-clades/scripts/abi-scan-lib.sh | 27 ++++++++++++++----- .../src/abi/PanelClades/ABI/Layout.idr | 23 +++++++++------- panel-clades/src/abi/README.adoc | 4 +-- panel-clades/tests/reject/EmptyStruct.idr | 25 +++++++++++++++++ panel-clades/tests/scan/test-abi-scan.sh | 10 +++++++ 8 files changed, 85 insertions(+), 31 deletions(-) create mode 100644 panel-clades/tests/reject/EmptyStruct.idr diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index 31f2222a..a6fea9e6 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -18,20 +18,20 @@ above the size and below size plus `+a+` (`+alignUpCovers+`, `+fieldWithinStruct+` prove it, so `+verifyLayout+` drops its run-time check and refuses only an alignment of 0. `+StructLayout+` now carries that coverage as an `+LTE+` proof instead of a Boolean `+So+`. -* *C layout evidence.* `+CABICompliant+` now also carries a first field at -offset 0 (`+FirstAtZero+`), fields in order without overlap +* *C layout evidence.* `+CABICompliant+` now also carries at least one +field, the first at offset 0 (`+FirstAtZero+`), fields in order without overlap (`+FieldsOrdered+`), power-of-two alignments (`+AlignmentsPow2+`, decided by `+decPow2+`), and a struct alignment equal to the largest field alignment (`+maxAlignment+`). `+checkCABI+` decides all five parts. * *Tag tables agree.* `+resultTagsAgree+` and `+cladeKindTagsAgree+` tie each `+Bits32+` C encoding to its `+Nat+` equality tag. -* Four more expected rejections: `+FirstFieldNotAtZero+`, +* Five more expected rejections: `+EmptyStruct+`, `+FirstFieldNotAtZero+`, `+OverlappingFields+`, `+AlignmentNotPow2+`, `+StructUnderAligned+`. * `+check-abi.sh+` now also fails on holes, `+partial+`, `+covering+` and a module without `+%default total+` before its first declaration, reads code only (not comments, strings, raw or multi-line strings, or character -literals), and holds the typed-wasm import cone to the same scan. +literals, though string interpolations are scanned as code), and holds the typed-wasm import cone to the same scan. * *Provenance model rewritten and run in CI.* `+docs/models/Provenance.tla+` now has an adversary that signs under a leaked key, separate author and witness signatures, and the invariant `+Genuine+`: a verified chain is diff --git a/PROOF-NEEDS.adoc b/PROOF-NEEDS.adoc index b2414ed1..ef033d38 100644 --- a/PROOF-NEEDS.adoc +++ b/PROOF-NEEDS.adoc @@ -57,10 +57,12 @@ runs `panel-clades/scripts/check-abi.sh`, which: fails on any `Error:` line, even when `idris2` exits 0; * fails if any module under `PanelClades/` is missing from the ipkg or does not declare `%default total` before its first declaration (the - directive applies only from where it appears), or if its code (comments and string - literals set aside) contains a hole (`?name`; `idris2 --build` accepts a - module with holes), `believe_me`, `assert_total`, `assert_smaller`, - `idris_crash`, `unsafePerformIO`, `postulate`, `partial` or `covering`; + directive applies only from where it appears), or if its code contains + a hole (`?name`, any case; `idris2 --build` accepts a module with + holes), `believe_me`, `assert_total`, `assert_smaller`, + `idris_crash`, `unsafePerformIO`, `postulate`, `partial` or `covering`. + Comments, string literals and character literals are set aside, but an + interpolation (`\{...}`) inside a string is code and is scanned; * holds the typed-wasm modules those modules import (the import cone, PROOF-POLICY §1.1) to the same scan, and prints the cone; * fails if `Budgets.idr` is stale against the clade configs @@ -105,7 +107,7 @@ declaration That run predates the import-cone and hole scans, the tag-agreement proofs, the layout bounds and the C layout evidence. On 2026-11-09 the same `check-abi.sh`, with those additions, ran locally with Idris2 0.8.0 -built from the same commit and passed: 5 modules, 0 errors, 10 of 10 +built from the same commit and passed: 5 modules, 0 errors, 11 of 11 fixtures refused, and the cone above clean. A local run is not a receipt. The first `main` run after this change is the first receipt to include them. A receipt is current only for its commit: rerun the job, never carry a @@ -138,8 +140,9 @@ multiple of its alignment (`calcStructSizeAligned`) and covers every field (`calcStructSizeCovers`, `fieldWithinEnd`, `fieldWithinStruct`), so `verifyLayout` has no run-time size check and refuses only an alignment of 0. `CABICompliant` evidence carries every offset as a multiple of its -alignment (`FieldsAligned`), the first field at offset 0, since C allows -no padding before it (`FirstAtZero`), the fields in order with each +alignment (`FieldsAligned`), at least one field with the first at +offset 0, since C requires a member and allows no padding before the +first (`FirstAtZero`), the fields in order with each starting at or after the previous one's end (`FieldsOrdered`; no two overlap, since the chain is ordered), every alignment a power of two (`AlignmentsPow2`), and the struct aligned to its largest field alignment (`maxAlignment`). Each diff --git a/docs/proof-debt.adoc b/docs/proof-debt.adoc index 4966ce6b..597ae4ea 100644 --- a/docs/proof-debt.adoc +++ b/docs/proof-debt.adoc @@ -12,7 +12,7 @@ Last reconciled: 2026-11-09 (`main` at `7d1feaa`). `assert_smaller`, `idris_crash`, `unsafePerformIO`, `postulate`, `partial`, `covering` or holes, measured on the date above. `check-abi.sh` fails CI if any of them appears in code (comments and string literals set - aside) or a module lacks `%default total` before its first declaration, in PanLL's modules and in the + aside, string interpolations scanned) or a module lacks `%default total` before its first declaration, in PanLL's modules and in the typed-wasm modules they import (the import cone). * *Estate check*: standards' `scripts/check-trusted-base.sh` finds 2 escape-hatch signatures, both documented below as not proof debt. diff --git a/panel-clades/scripts/abi-scan-lib.sh b/panel-clades/scripts/abi-scan-lib.sh index a2f6b5e5..22db523b 100644 --- a/panel-clades/scripts/abi-scan-lib.sh +++ b/panel-clades/scripts/abi-scan-lib.sh @@ -13,14 +13,21 @@ # strings #"..."# (any number of #, where \ is not an escape), multi-line # """...""" strings, and character literals including escapes ('\'', # '\"', '\x41'). A primed identifier (f') does not open a literal. +# Interpolations are code, not text: \{...} in a string (\#{...} in a raw +# string opened with one #, and so on) is kept and scanned, to its +# matching }, with strings nested inside it handled the same way. code_only() { awk ' - FNR == 1 { depth = 0; term = ""; ml = 0 } + FNR == 1 { depth = 0; term = ""; ml = 0; sp = 0 } { line = $0; out = ""; i = 1; n = length(line) - if (depth == 0 && term == "" && line ~ /^[[:space:]]*\|\|\|/) { print FILENAME ":" FNR ":"; next } + if (depth == 0 && term == "" && sp == 0 && line ~ /^[[:space:]]*\|\|\|/) { print FILENAME ":" FNR ":"; next } while (i <= n) { if (term != "") { + if (substr(line, i, 1) == "\\" && substr(line, i + 1, length(hk) + 1) == hk "{") { + sp++; st[sp] = term; se[sp] = esc; sm[sp] = ml; sh[sp] = hk; sb[sp] = 0 + term = ""; i += length(hk) + 2; out = out " "; continue + } if (esc && substr(line, i, 1) == "\\") { i += 2; continue } if (substr(line, i, length(term)) == term) { i += length(term); term = ""; ml = 0; continue } i++; continue @@ -31,9 +38,9 @@ code_only() { if (two == "--" && substr(line, i + 2, 1) !~ /[!#$%&*+.\/<=>?@\\^|~:-]/) break k = 0; while (substr(line, i + k, 1) == "#") k++ if (substr(line, i + k, 1) == "\"") { - h = substr(line, i, k) - if (substr(line, i + k, 3) == "\"\"\"") { term = "\"\"\"" h; ml = 1; i += k + 3 } - else { term = "\"" h; ml = 0; i += k + 1 } + hk = substr(line, i, k) + if (substr(line, i + k, 3) == "\"\"\"") { term = "\"\"\"" hk; ml = 1; i += k + 3 } + else { term = "\"" hk; ml = 0; i += k + 1 } esc = (k == 0); out = out "\"\""; continue } if (c == SQ && (i == 1 || substr(line, i - 1, 1) !~ /[A-Za-z0-9_]/ && substr(line, i - 1, 1) != SQ)) { @@ -42,9 +49,15 @@ code_only() { if (substr(line, j, 1) == SQ) { i = j + 1; out = out SQ SQ; continue } } else if (substr(line, i + 2, 1) == SQ) { i += 3; out = out SQ SQ; continue } } + if (sp > 0 && c == "{") { sb[sp]++; out = out c; i++; continue } + if (sp > 0 && c == "}") { + if (sb[sp] > 0) { sb[sp]--; out = out c; i++; continue } + term = st[sp]; esc = se[sp]; ml = sm[sp]; hk = sh[sp]; sp--; out = out " "; i++; continue + } out = out c; i++ } - if (!ml) term = "" + if (term != "" && !ml) term = "" + if (sp > 0 && !sm[1]) sp = 0 print FILENAME ":" FNR ":" out }' SQ="'" "$@" } @@ -64,7 +77,7 @@ scan_escapes() { local found=1 f if code_only "$@" | grep -E \ -e ':[0-9]+:.*\b(believe_me|assert_total|assert_smaller|idris_crash|unsafePerformIO|postulate)\b' \ - -e ':[0-9]+:(.*[^A-Za-z0-9_'"'"'?!#$%&*+./<=>@\^|~:-])?\?[a-z_][A-Za-z0-9_'"'"']*' \ + -e ':[0-9]+:(.*[^A-Za-z0-9_'"'"'?!#$%&*+./<=>@\^|~:-])?\?[A-Za-z_][A-Za-z0-9_'"'"']*' \ -e ':[0-9]+:.*\b(partial|covering)\b'; then found=0 fi diff --git a/panel-clades/src/abi/PanelClades/ABI/Layout.idr b/panel-clades/src/abi/PanelClades/ABI/Layout.idr index 9fa3ccb9..a1abcd2d 100644 --- a/panel-clades/src/abi/PanelClades/ABI/Layout.idr +++ b/panel-clades/src/abi/PanelClades/ABI/Layout.idr @@ -16,8 +16,9 @@ ||| (`calcStructSizeCovers`, `fieldWithinStruct`), so `verifyLayout` ||| needs no run-time size check; ||| * C layout evidence (`CABICompliant`) carries, for every field, an -||| offset that is a multiple of its alignment (`FieldsAligned`), a -||| first field at offset 0 (`FirstAtZero`), fields in order and not overlapping (`FieldsOrdered`), alignments that are +||| offset that is a multiple of its alignment (`FieldsAligned`), at +||| least one field, the first at offset 0 (`FirstAtZero`), fields in +||| order and not overlapping (`FieldsOrdered`), alignments that are ||| powers of two (`AlignmentsPow2`), and a struct alignment equal to ||| the largest field alignment (`maxAlignment`). Each is decided by an ||| equation or an `LTE` proof, never by a Boolean test followed by an @@ -309,11 +310,12 @@ verifyLayout fields (S u) = -- C ABI Compatibility -------------------------------------------------------------------------------- -||| The first field starts at offset 0. C allows padding between members -||| and after the last, never before the first (C11 6.7.2.1p15). +||| The struct has at least one field, and the first starts at offset 0. +||| A C struct needs a member (C11 6.7.2.1p8: one with no named member is +||| undefined), and C allows padding between members and after the last, +||| never before the first (6.7.2.1p15). There is no evidence for `[]`. public export data FirstAtZero : List Field -> Type where - ZeroNil : FirstAtZero [] ZeroHead : f.offset = 0 -> FirstAtZero (f :: rest) ||| Fields in declaration order, each starting at or after the end of the @@ -338,9 +340,10 @@ maxAlignment [] = 1 maxAlignment (f :: fs) = maximum f.alignment (maxAlignment fs) ||| Proof that a struct follows the C layout rules: every field offset is -||| a multiple of its alignment, the first field is at offset 0, the fields -||| are in order and do not overlap, every alignment is a power of two, and the struct is aligned -||| to its largest field alignment. (Size covering the fields, and being a +||| a multiple of its alignment, there is a field and the first is at +||| offset 0, the fields are in order and do not overlap, every alignment is +||| a power of two, and the struct is aligned to its largest field +||| alignment. (Size covering the fields, and being a ||| multiple of the alignment, are carried by `StructLayout` itself.) public export data CABICompliant : StructLayout -> Type where @@ -368,10 +371,10 @@ proveFieldsAligned (f :: rest) = Left err => Left err Right restProof => Right (ConsField f rest here restProof) -||| Attempt to prove the first field is at offset 0. +||| Attempt to prove there is a field and the first is at offset 0. public export proveFirstAtZero : (fields : List Field) -> Either String (FirstAtZero fields) -proveFirstAtZero [] = Right ZeroNil +proveFirstAtZero [] = Left "A C struct needs at least one member" proveFirstAtZero (f :: _) = case decEq f.offset 0 of No _ => Left ("First field " ++ f.name ++ " is at offset " diff --git a/panel-clades/src/abi/README.adoc b/panel-clades/src/abi/README.adoc index 4d493725..b0177312 100644 --- a/panel-clades/src/abi/README.adoc +++ b/panel-clades/src/abi/README.adoc @@ -53,7 +53,7 @@ against the clade's declared budget === Check status (2026-11-09) All five modules *typecheck* under Idris2 0.8.0 -(`+bash panel-clades/scripts/check-abi.sh+`), with ten expected +(`+bash panel-clades/scripts/check-abi.sh+`), with eleven expected rejections in `+tests/reject/+`. Before 2026-10-14, `+Types.idr+`, `+Layout.idr+` and `+Foreign.idr+` had @@ -107,7 +107,7 @@ says so. |`+Divides+` witnesses are equations |`+tests/reject/MisalignedField.idr+` |TESTED |Rounding up to an alignment a ≥ 1 is at or above the size and below size + a |`+alignUpCovers+`, `+alignUpTight+` |PROVEN |A computed struct size covers every field, so every field of a layout lies within its total size |`+calcStructSizeCovers+`, `+fieldWithinEnd+`, `+fieldWithinStruct+` |PROVEN -|`+CABICompliant+` evidence: offsets aligned, first field at offset 0, fields in order without overlap, power-of-two alignments, struct alignment = largest field alignment |`+FieldsAligned+`, `+FirstAtZero+`, `+FieldsOrdered+`, `+AlignmentsPow2+`, `+maxAlignment+`; `+tests/reject/FirstFieldNotAtZero.idr+`, `+OverlappingFields.idr+`, `+AlignmentNotPow2.idr+`, `+StructUnderAligned.idr+` |PROVEN / TESTED +|`+CABICompliant+` evidence: offsets aligned, at least one field with the first at offset 0, fields in order without overlap, power-of-two alignments, struct alignment = largest field alignment |`+FieldsAligned+`, `+FirstAtZero+`, `+FieldsOrdered+`, `+AlignmentsPow2+`, `+maxAlignment+`; `+tests/reject/EmptyStruct.idr+`, `+FirstFieldNotAtZero.idr+`, `+OverlappingFields.idr+`, `+AlignmentNotPow2.idr+`, `+StructUnderAligned.idr+` |PROVEN / TESTED |The example layout is C-ABI compliant and its computed size is 24 |`+exampleLayoutValid+`, `+exampleSizeComputed+`, `+exampleCheckPasses+` |PROVEN |The `+Nat+` and `+Bits32+` tag tables agree |`+resultTagsAgree+`, `+cladeKindTagsAgree+` |PROVEN |`+decDivides+` returns `+Nothing+` only when the divisor does not divide; `+decPow2+` returns `+Nothing+` only for a non-power of two |— |ASSUMED (not proved) diff --git a/panel-clades/tests/reject/EmptyStruct.idr b/panel-clades/tests/reject/EmptyStruct.idr new file mode 100644 index 00000000..2b6738c7 --- /dev/null +++ b/panel-clades/tests/reject/EmptyStruct.idr @@ -0,0 +1,25 @@ +-- SPDX-License-Identifier: MPL-2.0 +-- Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) +-- +-- MUST FAIL (panel-clades/scripts/check-abi.sh). +-- +-- A C struct needs at least one member (C11 6.7.2.1p8). This layout has +-- none: size 0 covers its (no) fields and is a multiple of its alignment 1, +-- which is the largest field alignment of an empty list, so only the +-- member rule refuses it in checkCABI. +-- EXPECT: Mismatch between: True and False +module EmptyStruct + +import Data.Either +import Data.So +import Data.Nat +import Decidable.Equality +import PanelClades.ABI.Layout + +%default total + +empty : StructLayout +empty = MkStructLayout [] 0 1 (fitsIn 0 0) (DivideBy 0 Refl) + +invalid : isRight (checkCABI EmptyStruct.empty) = True +invalid = Refl diff --git a/panel-clades/tests/scan/test-abi-scan.sh b/panel-clades/tests/scan/test-abi-scan.sh index aed57185..4225dfba 100755 --- a/panel-clades/tests/scan/test-abi-scan.sh +++ b/panel-clades/tests/scan/test-abi-scan.sh @@ -59,6 +59,16 @@ expect found "escape after a closed multi-line string" 's = """' 'text' '"""' expect clean "words in a raw string" 'msg = #"say "believe_me" and ?ok"#' expect clean "words in a multi-line string" 's = """' 'believe_me ?x partial' '"""' expect clean "primed identifier then a char literal" "f' = g' 'a'" +expect found "uppercase hole" 'f = ?Todo' +expect found "escape inside an interpolation" 'bad = "\{believe_me {a=Nat} {b=String} 0}"' +expect found "hole inside an interpolation" 'f = "x \{?h} y"' +expect found "escape inside a raw-string interpolation" 'f = #"\#{believe_me v}"#' +expect found "escape inside a multi-line interpolation" 's = """' ' \{believe_me v}' '"""' +expect found "escape after nested braces in an interpolation" 'f = "\{g {x = 1} y}" ++ believe_me v' +expect found "escape in a string nested in an interpolation, then code" 'f = "\{g "}" ++ believe_me v} z"' +expect clean "words beside an interpolation" 'msg = "believe_me \{show n} partial"' +expect clean "text after nested braces in an interpolation" 'f = "\{g {x = 1} y} believe_me"' +expect clean "\\{ in a raw string is text" 'msg = #"\{believe_me}"#' # %default total: required in code, not in a comment. directive() { # directive found|clean DESCRIPTION BODY... From a04c5fcf690b6034b5e94ee4ceaa47b1ccc07f0d Mon Sep 17 00:00:00 2001 From: "coderabbitai[bot]" <136622811+coderabbitai[bot]@users.noreply.github.com> Date: Mon, 9 Nov 2026 21:19:30 +0000 Subject: [PATCH 20/21] docs(panel-clades): document ABI proof helpers and clarify layout checks and import scanning --- panel-clades/scripts/abi-scan-lib.sh | 4 ++-- panel-clades/src/abi/PanelClades/ABI/Layout.idr | 16 +++++++++++++--- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/panel-clades/scripts/abi-scan-lib.sh b/panel-clades/scripts/abi-scan-lib.sh index 22db523b..9e528790 100644 --- a/panel-clades/scripts/abi-scan-lib.sh +++ b/panel-clades/scripts/abi-scan-lib.sh @@ -102,8 +102,8 @@ scan_escapes() { # The modules the given files import, transitively, that resolve to a source # file under one of the roots, one path per line (the files themselves # first). Imports are read from code only, at any indentation. Imports -# that resolve under no root are Idris2's own prelude/base/contrib, the -# prover's trust base: their module names are written to BASE_FILE. +# that resolve under no root are written as module names to BASE_FILE, which +# is cleared first. These may include Idris2's prelude/base/contrib modules. import_cone() { local base="$1"; shift local -a roots=() queue=() seen=() diff --git a/panel-clades/src/abi/PanelClades/ABI/Layout.idr b/panel-clades/src/abi/PanelClades/ABI/Layout.idr index a1abcd2d..f0858de3 100644 --- a/panel-clades/src/abi/PanelClades/ABI/Layout.idr +++ b/panel-clades/src/abi/PanelClades/ABI/Layout.idr @@ -46,17 +46,21 @@ import Decidable.Equality -- Nat lemmas (local, so this module imports nothing beyond base) -------------------------------------------------------------------------------- +||| Every natural number is less than or equal to itself. lteR : (n : Nat) -> LTE n n lteR Z = LTEZero lteR (S n) = LTESucc (lteR n) +||| Compose two less-than-or-equal proofs. lteT : LTE a b -> LTE b c -> LTE a c lteT LTEZero _ = LTEZero lteT (LTESucc p) (LTESucc q) = LTESucc (lteT p q) +||| Substitute an equal lower bound in a less-than-or-equal proof. lteEqL : a = b -> LTE b c -> LTE a c lteEqL Refl p = p +||| Substitute an equal upper bound in a less-than-or-equal proof. lteEqR : LTE a b -> b = c -> LTE a c lteEqR p Refl = p @@ -333,7 +337,8 @@ data AlignmentsPow2 : List Field -> Type where Pow2Nil : AlignmentsPow2 [] Pow2Cons : IsPow2 f.alignment -> AlignmentsPow2 rest -> AlignmentsPow2 (f :: rest) -||| The C struct alignment: the largest field alignment, or 1 for no fields. +||| The largest field alignment in bytes, with a minimum of 1 (also for an +||| empty list or fields whose alignments are all 0). public export maxAlignment : List Field -> Nat maxAlignment [] = 1 @@ -372,6 +377,7 @@ proveFieldsAligned (f :: rest) = Right restProof => Right (ConsField f rest here restProof) ||| Attempt to prove there is a field and the first is at offset 0. +||| Returns `Left` for an empty list or a non-zero first offset. public export proveFirstAtZero : (fields : List Field) -> Either String (FirstAtZero fields) proveFirstAtZero [] = Left "A C struct needs at least one member" @@ -397,7 +403,8 @@ proveFieldsOrdered (f :: g :: rest) = Left err => Left err Right restProof => Right (OrdCons fit restProof) -||| Attempt to prove every field alignment is a power of two. +||| Attempt to prove every field alignment is a power of two. Returns +||| `Left` naming the first field whose alignment is not shown to be one. public export proveAlignmentsPow2 : (fields : List Field) -> Either String (AlignmentsPow2 fields) proveAlignmentsPow2 [] = Right Pow2Nil @@ -410,7 +417,10 @@ proveAlignmentsPow2 (f :: rest) = Left err => Left err Right restProof => Right (Pow2Cons p restProof) -||| Return C layout evidence for a layout, or the first rule it breaks. +||| Return C layout evidence, or the first error encountered while checking +||| field alignment, the first offset, field order, power-of-two alignments +||| and struct alignment, in that order. A field alignment error means the +||| required evidence was not found. public export checkCABI : (layout : StructLayout) -> Either String (CABICompliant layout) checkCABI layout = do From 2326df485a78d02e28553dc3d1db55d812f11f88 Mon Sep 17 00:00:00 2001 From: "coderabbitai[bot]" <136622811+coderabbitai[bot]@users.noreply.github.com> Date: Mon, 9 Nov 2026 21:19:41 +0000 Subject: [PATCH 21/21] fix(ci): render fork governance artifacts as escaped untrusted text and add regression tests --- .github/workflows/fork-governance-publish.yml | 162 +++++++++++++++++- tests/fork_governance_publish_test.mjs | 54 ++++++ 2 files changed, 208 insertions(+), 8 deletions(-) create mode 100644 tests/fork_governance_publish_test.mjs diff --git a/.github/workflows/fork-governance-publish.yml b/.github/workflows/fork-governance-publish.yml index 14a40f66..031f610e 100644 --- a/.github/workflows/fork-governance-publish.yml +++ b/.github/workflows/fork-governance-publish.yml @@ -1,5 +1,6 @@ # This workflow is managed by gh actions-lock. # SPDX-License-Identifier: MPL-2.0 +# SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell (hyperpolymath) # fork-governance-publish.yml — post the Fork governance comment on pull # requests from forks. # @@ -7,16 +8,16 @@ # its verdict job uploads the rendered comment as an artifact instead of # posting it. This workflow runs after every "Fork governance" run, from the # default branch's copy with this repository's token, and posts that comment -# after checking it (metadatastician/standards -# 3-practice/fork-governance/ADOPTION.adoc §2). It skips same-repository runs, +# as escaped, explicitly untrusted text. It skips same-repository runs, # which post their own comment. It takes effect once merged to main. # -# Same standards commit as fork-governance.yml: the renderer and the -# publisher must agree on the comment's marker and stamp. +# Publisher vendored from metadatastician/standards at +# 9bdf864cc7f2c8e24b4df84bf00d04158eee3ad5, with safe artifact rendering. +# Preserve the shared comment marker and publisher stamp protocol. name: Fork governance publish # workflow_run, deliberately: it is the only way to write a comment for a -# fork PR. Nothing from the fork runs here. The reusable checks out no code, +# fork PR. Nothing from the fork runs here. This job checks out no code, # reads the artifact as data, and posts only to the open PR whose head is # the commit the run built. on: @@ -29,7 +30,152 @@ permissions: jobs: publish: - uses: metadatastician/standards/.github/workflows/fork-governance-publish-reusable.yml@9bdf864cc7f2c8e24b4df84bf00d04158eee3ad5 + name: Publish the fork governance summary (fork PR) + # Only a completed pull_request run from a fork has anything to publish; + # every other run posted its own comment. + if: >- + ${{ github.event_name == 'workflow_run' + && github.event.workflow_run.event == 'pull_request' + && github.event.workflow_run.head_repository.full_name != github.repository }} + runs-on: ubuntu-latest + timeout-minutes: 5 + # One publisher at a time per fork branch, so the read-compare-write of + # the comment below never interleaves with another publisher's. + concurrency: + group: fork-governance-publish-${{ github.event.workflow_run.head_repository.full_name }}-${{ github.event.workflow_run.head_branch }} + cancel-in-progress: false permissions: - actions: read # download the run's fork-governance-comment artifact - pull-requests: write # post or update the one governance comment + actions: read + pull-requests: write + steps: + - name: Download the rendered comment + id: download + continue-on-error: true + uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2 + with: + name: fork-governance-comment + path: ${{ runner.temp }}/fork-governance-comment + run-id: ${{ github.event.workflow_run.id }} + github-token: ${{ github.token }} + + # A run may leave no artifact only when it was replaced: a newer attempt + # of the same run, or a newer push to the fork branch (a newer run at a + # different commit; a push re-runs every pull request from the branch, + # so each one gets its own successor). Anything else is a missing report + # and fails here, including a run cancelled by hand with no successor. + - name: Fail on a missing comment + if: ${{ steps.download.outcome != 'success' }} + env: + GH_TOKEN: ${{ github.token }} + RUN_ID: ${{ github.event.workflow_run.id }} + RUN_ATTEMPT: ${{ github.event.workflow_run.run_attempt }} + WORKFLOW_ID: ${{ github.event.workflow_run.workflow_id }} + RUN_HEAD_SHA: ${{ github.event.workflow_run.head_sha }} + RUN_HEAD_REPO: ${{ github.event.workflow_run.head_repository.full_name }} + RUN_HEAD_BRANCH: ${{ github.event.workflow_run.head_branch }} + RUN_CONCLUSION: ${{ github.event.workflow_run.conclusion }} + RUN_URL: ${{ github.event.workflow_run.html_url }} + run: | + set -euo pipefail + pushed=$(gh api -X GET "repos/$GITHUB_REPOSITORY/actions/workflows/$WORKFLOW_ID/runs" \ + -f event=pull_request -f branch="$RUN_HEAD_BRANCH" -f per_page=100 \ + | jq --arg repo "$RUN_HEAD_REPO" --arg sha "$RUN_HEAD_SHA" --argjson id "$RUN_ID" \ + '[.workflow_runs[] | select(.head_repository.full_name == $repo and .id > $id and .head_sha != $sha)] | length') + attempt=$(gh api "repos/$GITHUB_REPOSITORY/actions/runs/$RUN_ID" --jq '.run_attempt') + if [ "$pushed" -gt 0 ] || [ "$attempt" -gt "$RUN_ATTEMPT" ]; then + echo "$RUN_URL left no comment, but a newer push or attempt replaced it; that run publishes." + exit 0 + fi + echo "::error::$RUN_URL ($RUN_CONCLUSION) uploaded no fork-governance-comment artifact and nothing replaced it, so its pull request has no governance comment" + exit 1 + + - name: Publish the comment + if: ${{ steps.download.outcome == 'success' }} + env: + GH_TOKEN: ${{ github.token }} + GRID_AUTHOR: github-actions[bot] + RUN_ID: ${{ github.event.workflow_run.id }} + RUN_ATTEMPT: ${{ github.event.workflow_run.run_attempt }} + RUN_HEAD_SHA: ${{ github.event.workflow_run.head_sha }} + RUN_HEAD_REPO: ${{ github.event.workflow_run.head_repository.full_name }} + RUN_HEAD_BRANCH: ${{ github.event.workflow_run.head_branch }} + RUN_URL: ${{ github.event.workflow_run.html_url }} + run: | + set -euo pipefail + dir="$RUNNER_TEMP/fork-governance-comment" + body="$dir/body.md" + meta="$dir/meta.json" + [ -f "$body" ] && [ -f "$meta" ] || { echo "::error::artifact is missing body.md or meta.json"; exit 1; } + + pr=$(jq -r '.pr_number // ""' "$meta") + if ! [[ "$pr" =~ ^[1-9][0-9]{0,9}$ ]]; then + echo "::error::artifact pr_number is not a pull request number"; exit 1 + fi + + # The pull request must be this run's own: open, at the commit the + # run built, from the fork and branch the run came from (header). + # Captured first, so a failed lookup fails the step under `set -e` + # instead of reading as "not open". + pr_info=$(gh api "repos/$GITHUB_REPOSITORY/pulls/$pr" \ + --jq '[.state, .head.sha, (.head.repo.full_name // "-"), .head.ref] | join(" ")') + # A git ref cannot contain whitespace, so the branch is the last field. + read -r pr_state pr_sha pr_repo pr_branch <<< "$pr_info" + if [ "$pr_state" != open ]; then + echo "Pull request #$pr is $pr_state; nothing to publish."; exit 0 + fi + if [ "$pr_sha" != "$RUN_HEAD_SHA" ]; then + echo "Pull request #$pr has moved past $RUN_HEAD_SHA; its newer run publishes."; exit 0 + fi + if [ "$pr_repo" != "$RUN_HEAD_REPO" ] || [ "$pr_branch" != "$RUN_HEAD_BRANCH" ]; then + echo "::error::artifact names #$pr, which is not from $RUN_HEAD_REPO:$RUN_HEAD_BRANCH"; exit 1 + fi + + if ! head -n 1 "$body" | grep -q '^\n' + printf '## Untrusted fork governance report\n\n' + printf 'The following text was supplied by the fork. It is not a verified bot verdict. Consult the lane checks for results.\n\n' + printf '
\n'
+            jq -Rrs '@html' "$body"
+            printf '
\n' + printf '\nRendered by the fork pull request run %s and published by fork-governance-publish.\n' "$RUN_URL" + printf '\n' "$RUN_ID" "$RUN_ATTEMPT" + } > "$dir/published.md" + # GitHub's limit is 65536 characters, not bytes; the glyphs are + # multibyte, so count characters. + if [ "$(LC_ALL=C.UTF-8 wc -m < "$dir/published.md")" -gt 65000 ]; then + echo "::error::body exceeds the 65000-character comment limit"; exit 1 + fi + + existing=$(gh api --paginate --slurp "repos/$GITHUB_REPOSITORY/issues/$pr/comments" \ + | jq -c --arg a "$GRID_AUTHOR" \ + '[.[][] | select((.user.login // "") == $a and ((.body // "") | contains("$/\1 \2/p' | tail -n 1) + if [ -n "$stamp" ]; then + read -r prev_id prev_attempt <<< "$stamp" + if [ "$prev_id" -gt "$RUN_ID" ] || { [ "$prev_id" -eq "$RUN_ID" ] && [ "$prev_attempt" -gt "$RUN_ATTEMPT" ]; }; then + echo "#$pr already carries the comment from newer run $prev_id.$prev_attempt; leaving it."; exit 0 + fi + fi + + jq -n --rawfile body "$dir/published.md" '{body: $body}' > "$dir/comment.json" + if [ -n "$comment_id" ]; then + gh api -X PATCH "repos/$GITHUB_REPOSITORY/issues/comments/$comment_id" \ + --input "$dir/comment.json" >/dev/null + echo "updated the fork governance comment on #$pr ($comment_id)" + else + gh api -X POST "repos/$GITHUB_REPOSITORY/issues/$pr/comments" \ + --input "$dir/comment.json" >/dev/null + echo "posted the fork governance comment on #$pr" + fi diff --git a/tests/fork_governance_publish_test.mjs b/tests/fork_governance_publish_test.mjs new file mode 100644 index 00000000..0ac0d1f1 --- /dev/null +++ b/tests/fork_governance_publish_test.mjs @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: MPL-2.0 +import assert from 'node:assert/strict'; +import { readFileSync, writeFileSync, mkdtempSync, rmSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; +import { spawnSync } from 'node:child_process'; +import test from 'node:test'; + +const workflow = readFileSync(new URL('../.github/workflows/fork-governance-publish.yml', import.meta.url), 'utf8'); +// Exercise the actual rendering and size guard, without GitHub writes. +const start = workflow.indexOf(' if ! head -n 1'); +const end = workflow.indexOf(' existing=', start); +assert.ok(start > 0 && end > start); +const render = workflow.slice(start, end).replace(/^ /gm, ''); + +function publish(body) { + const dir = mkdtempSync(join(tmpdir(), 'fork-publish-')); + try { + writeFileSync(join(dir, 'body.md'), body); + const result = spawnSync('bash', ['-c', 'set -euo pipefail\ndir="$REPORT_DIR"\nbody="$dir/body.md"\n' + render], { + encoding: 'utf8', + env: { ...process.env, REPORT_DIR: dir, RUN_URL: 'https://github.com/example/repo/actions/runs/123', RUN_ID: '123', RUN_ATTEMPT: '2' }, + }); + return { status: result.status, output: result.status === 0 ? readFileSync(join(dir, 'published.md'), 'utf8') : '' }; + } finally { + rmSync(dir, { recursive: true, force: true }); + } +} + +const marker = '\n'; +for (const payload of [ + '## PASS — all checks approved\n[trusted result](https://example.invalid)\n@someone', + '', +]) { + test(`artifact remains literal text: ${JSON.stringify(payload)}`, () => { + const { status, output } = publish(marker + payload); + assert.equal(status, 0); + const escape = text => text.replaceAll('&', '&').replaceAll('<', '<').replaceAll('>', '>').replaceAll("'", ''').replaceAll('"', '"'); + const expected = '\n' + + '## Untrusted fork governance report\n\n' + + 'The following text was supplied by the fork. It is not a verified bot verdict. Consult the lane checks for results.\n\n' + + '
\n' + escape(marker + payload) + '\n
\n' + + '\nRendered by the fork pull request run https://github.com/example/repo/actions/runs/123 and published by fork-governance-publish.\n' + + '\n'; + assert.equal(output, expected); + }); +} +test('missing marker is rejected', () => assert.notEqual(publish('PASS').status, 0)); +test('size is checked after HTML escaping', () => assert.notEqual(publish(marker + '&'.repeat(14000)).status, 0)); +test('ordinary Unicode report is accepted', () => assert.equal(publish(marker + '🟢 résumé').status, 0));