diff --git a/.github/workflows/actions.lock b/.github/workflows/actions.lock index 4657e3d..b8c1388 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 90a1da9..f4810bd 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 d36b97f..6d57764 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/layouts/README.adoc b/layouts/README.adoc index 610983d..dfc2369 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. diff --git a/panel-clades/Justfile b/panel-clades/Justfile index ab9ca5d..655cca8 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 85d1b62..ce062ce 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 b632c52..38ed0be 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"], + }, + }, }