diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
index e152a86..ee15d90 100644
--- a/.github/workflows/codeql.yml
+++ b/.github/workflows/codeql.yml
@@ -8,12 +8,49 @@ on:
branches: [main, master]
schedule:
- cron: '0 6 * * 1'
+# Estate guardrail: cancel superseded runs so re-pushes don't pile up
+# queued runs across the estate. Safe here because this workflow only
+# performs read-only checks/lint/test/scan with no publish or mutation.
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
permissions:
contents: read
jobs:
+ # The estate is heterogeneous (Rust, Idris2, Agda, Elixir, ReScript,
+ # occasional JS/TS/Python). A hard-coded `javascript-typescript` matrix
+ # made CodeQL exit with a "no source / configuration error" on every
+ # non-JS/TS repo — a permanent false-red `analyze` on most repos' main.
+ # Detect the languages the repo ACTUALLY contains and only analyse the
+ # CodeQL-supported, buildless-safe ones; skip entirely when none apply.
+ detect:
+ runs-on: ubuntu-latest
+ outputs:
+ langs: ${{ steps.pick.outputs.langs }}
+ steps:
+ - name: Pick CodeQL languages from repo language stats
+ id: pick
+ env:
+ GH_TOKEN: ${{ github.token }}
+ run: |
+ stats=$(gh api "repos/${{ github.repository }}/languages" --jq 'keys[]' 2>/dev/null || echo "")
+ out=""
+ add() { out="$out $1"; }
+ echo "$stats" | grep -qix 'Rust' && add rust
+ echo "$stats" | grep -qixE 'JavaScript|TypeScript' && add javascript-typescript
+ echo "$stats" | grep -qix 'Python' && add python
+ echo "$stats" | grep -qix 'Ruby' && add ruby
+ echo "$stats" | grep -qix 'Go' && add go
+ arr=$(printf '%s\n' $out | grep . | sort -u | jq -R . | jq -s -c .)
+ [ -z "$arr" ] && arr='[]'
+ echo "Detected CodeQL languages: $arr"
+ echo "langs=$arr" >> "$GITHUB_OUTPUT"
+
analyze:
+ needs: detect
+ if: needs.detect.outputs.langs != '[]'
runs-on: ubuntu-latest
permissions:
contents: read
@@ -21,9 +58,7 @@ jobs:
strategy:
fail-fast: false
matrix:
- include:
- - language: javascript-typescript
- build-mode: none
+ language: ${{ fromJSON(needs.detect.outputs.langs) }}
steps:
- name: Checkout
@@ -33,7 +68,7 @@ jobs:
uses: github/codeql-action/init@0d579ffd059c29b07949a3cce3983f0780820c98 # v3.28.1
with:
languages: ${{ matrix.language }}
- build-mode: ${{ matrix.build-mode }}
+ build-mode: none
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@0d579ffd059c29b07949a3cce3983f0780820c98 # v3.28.1
diff --git a/examples/SafeDOMExample.res b/examples/SafeDOMExample.res
deleted file mode 100644
index 2c1b5b3..0000000
--- a/examples/SafeDOMExample.res
+++ /dev/null
@@ -1,109 +0,0 @@
-// SPDX-License-Identifier: PMPL-1.0-or-later
-// Example: Using SafeDOM for formally verified DOM mounting
-
-open SafeDOM
-
-// Example 1: Basic mounting with error handling
-let mountApp = () => {
- mountSafe(
- "#app",
- "
Hello, World!
Mounted safely with proofs.
",
- ~onSuccess=el => {
- Console.log("✓ App mounted successfully!")
- Console.log("Element:", el)
- },
- ~onError=err => {
- Console.error("✗ Mount failed:", err)
- }
- )
-}
-
-// Example 2: Wait for DOM ready before mounting
-let mountWhenDOMReady = () => {
- mountWhenReady(
- "#app",
- "App Title
",
- ~onSuccess=_ => Console.log("✓ Mounted after DOM ready"),
- ~onError=err => Console.error("✗ Failed:", err)
- )
-}
-
-// Example 3: Batch mounting (atomic - all or nothing)
-let mountMultiple = () => {
- let specs = [
- {selector: "#header", html: ""},
- {selector: "#nav", html: ""},
- {selector: "#main", html: "Content here
"},
- {selector: "#footer", html: ""}
- ]
-
- switch mountBatch(specs) {
- | Ok(elements) => {
- Console.log(`✓ Successfully mounted ${Array.length(elements)} elements`)
- elements->Array.forEach(el => Console.log(" -", el))
- }
- | Error(err) => {
- Console.error("✗ Batch mount failed:", err)
- Console.error(" (None were mounted - atomic operation)")
- }
- }
-}
-
-// Example 4: Explicit validation before mounting
-let mountWithValidation = () => {
- // Validate selector first
- switch ProvenSelector.validate("#my-app") {
- | Error(e) => Console.error(`Invalid selector: ${e}`)
- | Ok(validSelector) => {
- // Validate HTML
- switch ProvenHTML.validate("Content
") {
- | Error(e) => Console.error(`Invalid HTML: ${e}`)
- | Ok(validHtml) => {
- // Now mount with proven safety
- switch mount(validSelector, validHtml) {
- | Mounted(el) => Console.log("✓ Mounted with validated inputs:", el)
- | MountPointNotFound(s) => Console.error(`✗ Element not found: ${s}`)
- | InvalidSelector(_) => Console.error("Impossible - already validated")
- | InvalidHTML(_) => Console.error("Impossible - already validated")
- }
- }
- }
- }
-}
-
-// Example 5: Integration with TEA
-module MyApp = {
- type model = {message: string}
- type msg = NoOp
-
- let init = () => {message: "Hello from TEA"}
- let update = (model, _msg) => model
- let view = model => `${model.message}
`
-}
-
-let mountTEAApp = () => {
- let model = MyApp.init()
- let html = MyApp.view(model)
-
- mountWhenReady(
- "#tea-app",
- html,
- ~onSuccess=el => {
- Console.log("✓ TEA app mounted")
- // Set up event handlers, subscriptions here
- },
- ~onError=err => Console.error(`✗ TEA mount failed: ${err}`)
- )
-}
-
-// Entry point
-let main = () => {
- Console.log("SafeDOM Examples")
- Console.log("================\n")
-
- // Choose which example to run
- mountWhenDOMReady() // Run on DOM ready
-}
-
-// Auto-execute when module loads
-main()
diff --git a/examples/web-project-deno.json b/examples/web-project-deno.json
deleted file mode 100644
index 5ddd3bd..0000000
--- a/examples/web-project-deno.json
+++ /dev/null
@@ -1,20 +0,0 @@
-{
- "// NOTE": "Example deno.json for ReScript web projects",
- "tasks": {
- "build": "deno run -A npm:rescript",
- "clean": "deno run -A npm:rescript clean",
- "watch": "deno run -A npm:rescript -w",
- "serve": "deno run -A jsr:@std/http/file-server .",
- "test": "deno test --allow-all"
- },
- "imports": {
- "rescript": "^12.0.0",
- "@rescript/core": "npm:@rescript/core@^1.6.0",
- "safe-dom/": "https://raw.githubusercontent.com/hyperpolymath/rescript-dom-mounter/main/src/",
- "proven/": "../proven/bindings/rescript/src/"
- },
- "compilerOptions": {
- "allowJs": true,
- "checkJs": false
- }
-}
diff --git a/src/codegen/contract.rs b/src/codegen/contract.rs
index 741d3cc..2139a9f 100644
--- a/src/codegen/contract.rs
+++ b/src/codegen/contract.rs
@@ -15,9 +15,17 @@ use crate::codegen::parser::{ParsedEntry, ValueType};
///
/// The output format:
/// ```text
+/// K9!
+/// # SPDX-License-Identifier: PMPL-1.0-or-later
/// # Auto-generated K9 contract for
/// # Safety tier:
///
+/// pedigree = {
+/// schema_version = "1.0.0"
+/// metadata = { name = "" version = "1.0.0" }
+/// security = { leash = "" signature_required = false }
+/// }
+///
/// [must]
/// key : type { constraint, ... }
///
@@ -41,11 +49,31 @@ pub fn generate_k9_contract(
) -> String {
let mut output = String::new();
- // Header
+ // Header — `K9!` MUST be the first non-empty line and a `pedigree`
+ // block is mandatory, per the canonical hyperpolymath/k9-validate-action.
+ output.push_str("K9!\n");
+ output.push_str("# SPDX-License-Identifier: PMPL-1.0-or-later\n");
output.push_str(&format!("# Auto-generated K9 contract for {}\n", name));
output.push_str(&format!("# Safety tier: {}\n", safety_tier));
output.push('\n');
+ // [pedigree] — provenance + security leash. Required fields:
+ // metadata.name (error if absent), version/schema_version (warning),
+ // security.leash ∈ {kennel,yard,hunt} (warning if absent), and a
+ // signature field (error at hunt level if absent).
+ output.push_str("pedigree = {\n");
+ output.push_str(" schema_version = \"1.0.0\"\n");
+ output.push_str(" metadata = {\n");
+ output.push_str(&format!(" name = \"{}\"\n", name));
+ output.push_str(" version = \"1.0.0\"\n");
+ output.push_str(" }\n");
+ output.push_str(" security = {\n");
+ output.push_str(&format!(" leash = \"{}\"\n", safety_tier));
+ output.push_str(" signature_required = false\n");
+ output.push_str(" }\n");
+ output.push_str("}\n");
+ output.push('\n');
+
// [must] section — group rules by key, augment with type info from parsed entries
output.push_str("[must]\n");
if must_rules.is_empty() {
@@ -231,6 +259,12 @@ mod tests {
],
);
+ // K9! magic must be the first non-empty line (k9-validate-action).
+ assert_eq!(content.lines().next(), Some("K9!"));
+ // Mandatory pedigree block with name + version + leash.
+ assert!(content.contains("pedigree = {"));
+ assert!(content.contains("name = \"app-config\""));
+ assert!(content.contains("leash = \"kennel\""));
assert!(content.contains("# Auto-generated K9 contract for app-config"));
assert!(content.contains("# Safety tier: kennel"));
assert!(content.contains("[must]"));