From d8302c151eb6ba40149e8e793ac8876808598b68 Mon Sep 17 00:00:00 2001 From: klappy <118073+klappy@users.noreply.github.com> Date: Wed, 8 Jul 2026 13:31:51 -0400 Subject: [PATCH 1/5] canon: codify preflight as a hard, enforced takeoff gate Make the preflight checklist a pass/fail takeoff gate rather than a habit: five items (clock, canon reachable, tools present, tier correct, boarded) checked live every flight before any work, each green only when observed this flight. Fail any item -> abort with an honest 'cannot reach X -- aborting', never fly from cache. A cleared flight declares its preflight result at the top of its first substantive message; work reported without a passed, declared preflight is invalid. START gate; recording-as-landing is the END gate. - canon/constraints/preflight-checklist-takeoff-gate.md (tier-1, authoritative) - model-operating-contract.md: 'The Preflight -- The Hard Takeoff Gate' - generic-boarding-pass.md + boarding-pass.md: gate in project + account text - DISPATCH.md: binds every dispatched flight to the gate - scripts/validate-preflight-declaration.py + soft CI job (mechanical presence check; truth-of-declaration remains a spawned-agent judgment per audit-gates-are-spawned-agent-sessions) - governance markers: CHANGELOG 0.40.0 + release note Grounded in canon/bootstrap/model-operating-contract, flight-deck-model, oddkit_preflight, and governance-change-discipline. Preflight passed and declared; oddkit_challenge + oddkit_gate (PASS 2/2) run on the draft. --- .github/workflows/canon-quality.yml | 109 ++++++++++ DISPATCH.md | 34 ++++ canon/CHANGELOG.md | 22 +++ canon/bootstrap/boarding-pass.md | 6 +- canon/bootstrap/generic-boarding-pass.md | 19 ++ canon/bootstrap/model-operating-contract.md | 18 ++ .../preflight-checklist-takeoff-gate.md | 81 ++++++++ .../2026-07-08-preflight-takeoff-gate.md | 40 ++++ scripts/validate-preflight-declaration.py | 187 ++++++++++++++++++ 9 files changed, 514 insertions(+), 2 deletions(-) create mode 100644 DISPATCH.md create mode 100644 canon/constraints/preflight-checklist-takeoff-gate.md create mode 100644 docs/oddkit/release-notes/2026-07-08-preflight-takeoff-gate.md create mode 100644 scripts/validate-preflight-declaration.py diff --git a/.github/workflows/canon-quality.yml b/.github/workflows/canon-quality.yml index 82275432..5636ee56 100644 --- a/.github/workflows/canon-quality.yml +++ b/.github/workflows/canon-quality.yml @@ -624,3 +624,112 @@ jobs: with: header: canon-quality-surfacing path: /tmp/surface-comment.md + + preflight-declaration: + name: Preflight declaration on flight artifacts (soft) + runs-on: ubuntu-latest + timeout-minutes: 3 + # Soft-only report for the START-gate half of the flight airframe + # (klappy://canon/constraints/preflight-checklist-takeoff-gate). Pure-Python + # file parsing — deterministic, no oddkit call. Checks that any file which + # self-identifies as a flight artifact (marker ``) + # carries a well-formed preflight declaration: all five items + a + # disposition. This is the MECHANICAL half only (audit-gates-are-spawned- + # agent-sessions permits a pattern matcher for literal presence checks); + # whether the declared preflight was TRUE is a spawned-agent judgment, never + # this job. Ships soft; flips to hard after an observation cycle, same as the + # frontmatter and audit gates. The END-gate counterpart is recording-as-landing. + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.x' + + - name: Run preflight-declaration check + run: | + python3 scripts/validate-preflight-declaration.py --json > /tmp/pf-result.json || true + python3 - <<'PYEOF' + import json + d = json.load(open('/tmp/pf-result.json')) + print(f"scanned={d['scanned']} flight_artifacts={d['flight_artifacts']} " + f"status={d['status']} findings={len(d['findings'])}") + PYEOF + + - name: Upload findings artifact + if: always() + uses: actions/upload-artifact@v4 + with: + name: preflight-declaration-findings + path: /tmp/pf-result.json + retention-days: 14 + if-no-files-found: warn + + - name: Render PR comment + if: github.event_name == 'pull_request' + run: | + python3 - <<'PYEOF' + import json + d = json.load(open('/tmp/pf-result.json')) + findings = d['findings'] + lines = [] + icon = '\u2705' if not findings else '\u26a0\ufe0f' + lines.append(f"### Canon Quality \u2014 Preflight Declaration {icon}") + lines.append('') + lines.append(f"Soft report for the START gate " + f"(`klappy://canon/constraints/preflight-checklist-takeoff-gate`). " + f"{d['scanned']} file(s) scanned, {d['flight_artifacts']} flight " + f"artifact(s) checked. **Never blocks** \u2014 informational until the " + f"gate flips to hard after the observation cycle.") + lines.append('') + if not findings: + lines.append('All flight artifacts carry a well-formed preflight declaration ' + '(five items + disposition), or none are marked yet.') + else: + lines.append(f'**{len(findings)} finding(s)** on marked flight artifacts:') + lines.append('') + lines.append('| File | Rule | Message |') + lines.append('|---|---|---|') + for f in findings: + path = f['location']['path'] + msg = (f['message'] or '').replace('|', '\\|') + lines.append(f"| `{path}` | `{f['rule_id']}` | {msg} |") + lines.append('') + lines.append('> Mechanical presence check only. A green result means a declaration ' + 'exists and is well-formed \u2014 not that the declared preflight was ' + 'true. Verifying the declaration is honest is a spawned-agent judgment ' + '(`klappy://canon/constraints/audit-gates-are-spawned-agent-sessions`).') + lines.append('') + lines.append('Validator: `scripts/validate-preflight-declaration.py` \u00b7 ' + 'Constraint: `klappy://canon/constraints/preflight-checklist-takeoff-gate` \u00b7 ' + 'Run: [#${{ github.run_number }}](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})') + open('/tmp/pf-comment.md','w').write('\n'.join(lines)) + PYEOF + + - name: Sticky comment + if: github.event_name == 'pull_request' + uses: marocchino/sticky-pull-request-comment@v2 + with: + header: canon-quality-preflight-declaration + path: /tmp/pf-comment.md + + - name: Workflow step summary + if: always() + run: | + { + echo "## Canon Quality \u2014 Preflight Declaration (soft)" + echo "" + python3 - <<'PYEOF' + import json + try: + d = json.load(open('/tmp/pf-result.json')) + print(f"- **Status**: {d['status']}") + print(f"- **Scanned**: {d['scanned']}") + print(f"- **Flight artifacts checked**: {d['flight_artifacts']}") + print(f"- **Findings**: {len(d['findings'])}") + except Exception as e: + print(f"- **Result**: check did not produce output ({e})") + PYEOF + } >> "$GITHUB_STEP_SUMMARY" diff --git a/DISPATCH.md b/DISPATCH.md new file mode 100644 index 00000000..2c08f5ef --- /dev/null +++ b/DISPATCH.md @@ -0,0 +1,34 @@ +# DISPATCH.md — Dispatch Protocol for Flights in This Project + +> Every dispatched flight — an assistant-orchestrated sub-agent, an autonomous-trigger runtime, or a fresh session boarding cold — passes the same hard preflight gate a maintainer session does. Dispatch does not lower the bar; it is the most common place the bar gets skipped, because a dispatched flight often arrives with a partial toolset and no one watching it board. This file is the takeoff clearance for anything dispatched here. + +See `canon/methods/dispatch-paths.md` for the two dispatch classes (assistant-orchestrated vs. autonomous-trigger) and `canon/bootstrap/model-operating-contract.md` for the binding operating contract. The authoritative rule below is `klappy://canon/constraints/preflight-checklist-takeoff-gate`. + +--- + +## The takeoff gate — no dispatched flight works without passing it + +Before ANY work, every flight runs preflight and passes all five items. Each item is green only when **observed live this flight** — never from cache, memory, or inference. + +1. **Clock** — `oddkit_time` succeeds and returns `server_time`. +2. **Canon reachable** — fetch `klappy://canon/bootstrap/model-operating-contract` via oddkit and confirm it resolves. **Unreachable → ABORT.** This is the failure that grounded the fleet all week: dispatched Code flights carried zero MCP connectors, could not reach canon, and flew anyway on recall. +3. **Tools present** — the specific connectors this task needs (GitAuth to push, Shopify for store work, the AMS wire to reach a peer, etc.) are actually available. Missing → abort, or narrow scope to what the present tools support and say so explicitly. +4. **Tier correct** — the running model matches the task's required tier. +5. **Boarded** — the role's boarding doc (`canon/bootstrap/boarding-pass` or `generic-boarding-pass`) and the memory-mirror are read this flight. + +All five green → **cleared for takeoff.** + +## Declare the result before you work + +A cleared flight states its preflight result at the **top of its first substantive message**: which items passed and the observed evidence for the load-bearing ones (clock value, operating-contract content hash, connectors present, tier, boarding read). A flight that reports work without a passed, declared preflight is **invalid** — the work is unverified because the conditions to verify it were never confirmed. + +## Abort behavior — report, do not simulate + +Fail any item and the flight does not take off. Report the specific failure plainly — **"cannot reach `X` — aborting"** — and stop. Do not fall back to recalled governance and fly as if canon were read; do not fabricate a tool result; do not infer the date the clock did not give; do not silently narrow scope and report the full task as done. Aborting on a failed preflight is the gate working. Per axiom 3, an honest "I could not board, so I did not fly" costs far less than a confident "done" produced from memory. + +## Two gates bound every flight + +- **START gate:** this preflight, declared before work. +- **END gate:** recording-as-landing — the flight records itself as it lands (`scripts/validate-preflight-declaration.py` and the CI job in `.github/workflows/canon-quality.yml` check that flight artifacts carry the declaration). + +A flight missing either bound is incomplete: it took off without clearance, or it landed without logging. Both gates are hard. diff --git a/canon/CHANGELOG.md b/canon/CHANGELOG.md index c0c50af9..f8dc9846 100644 --- a/canon/CHANGELOG.md +++ b/canon/CHANGELOG.md @@ -18,6 +18,28 @@ This changelog tracks changes to the **Canon pack** as a whole. The Canon uses **pack-level versioning** (one version number) rather than per-file versioning. Per-file versions are intentionally omitted to reduce ceremony and prevent metadata rot. +## 0.40.0 — 2026-07-08 + +**Preflight Becomes a Hard Takeoff Gate (E0010)** + +A posture recalibration within Flight Crew, not a new epoch. E0010 named preflight as an instrument that "fires before work, every session, regardless of capability." This release makes it a **gate**: five items (clock, canon reachable, tools present, tier correct, boarded), checked every flight before any work, each green only when observed live — never from cache or memory. Fail any item and the flight does not take off; it aborts and reports the specific failure ("cannot reach X — aborting") rather than simulating the result from recall. A cleared flight declares its preflight result at the top of its first substantive message, and work reported without a passed, declared preflight is invalid. This closes the failure the operator observed all week: dispatched flights with zero MCP connectors that could not reach canon and flew anyway. The preflight is the START gate; recording-as-landing is the END gate; both are hard. + +### Added — Canon Surface + +- **Constraint: The Preflight Checklist — The Hard Takeoff Gate No Flight Skips** (`canon/constraints/preflight-checklist-takeoff-gate.md`) — Tier 1, neutral. The authoritative rule: the five items, the declaration requirement, the abort behavior, and the two-surface enforcement model (mechanical presence check + spawned-agent judgment). +- **Release note** (`docs/oddkit/release-notes/2026-07-08-preflight-takeoff-gate.md`) — Tier 3, neutral. +- **Dispatch protocol** (`DISPATCH.md`, repo root) — binds every dispatched flight to the same takeoff gate. +- **CI structural check** (`scripts/validate-preflight-declaration.py` + `preflight-declaration` job in `.github/workflows/canon-quality.yml`) — soft-first mechanical presence check for the declaration on flight artifacts, mirroring the recording-as-landing end-gate pattern. + +### Changed — Boarding Surface + +- `canon/bootstrap/model-operating-contract.md` — new "The Preflight — The Hard Takeoff Gate" section (the binding procedure). +- `canon/bootstrap/generic-boarding-pass.md` and `canon/bootstrap/boarding-pass.md` — the paste-ready project and account instructions now carry the five-item gate, the declaration requirement, and the abort behavior. Update discipline names the takeoff gate as boarding-relevant law; the maintainer re-pastes settings. + +### Governance + +- Markers per `canon/constraints/governance-change-discipline.md`: version bump (this entry), changelog entry, and release note. Behavior-affecting posture recalibration within E0010 — no epoch bump (who-initiates is unchanged; the change hardens an existing posture). + ## 0.39.0 — 2026-06-09 **Flight Crew (E0010)** diff --git a/canon/bootstrap/boarding-pass.md b/canon/bootstrap/boarding-pass.md index bfb2b1ba..726e2b8f 100644 --- a/canon/bootstrap/boarding-pass.md +++ b/canon/bootstrap/boarding-pass.md @@ -28,7 +28,7 @@ The model's persistent memory was deliberately cleared of all governance content ## Account-Level Text (User Preferences — applies to all conversations) ``` -Klappy and the model operate as flight crew (E0010): Klappy is captain; the model is first officer — its own judgment, flying under procedures fetched live from the knowledge base, never recalled from memory. Wherever oddkit is available, board before any work: fetch klappy://canon/bootstrap/model-operating-contract and treat it as binding. Nothing in Klappy's authorial voice is committed, pushed, or merged without his review of the exact text. The model holds ratified stewardship of klappy/outcomes-driven-development (odd://canon/governance/stewardship-charter — fetch it before touching that repo). Voices (Oddie 🦦, Orville 🦅) are performed from canon/voice/ when invoked, never adopted as identity. The authoritative copy of these instructions is klappy://canon/bootstrap/boarding-pass — if these settings and canon ever conflict, canon wins. +Klappy and the model operate as flight crew (E0010): Klappy is captain; the model is first officer — its own judgment, flying under procedures fetched live from the knowledge base, never recalled from memory. Wherever oddkit is available, board before any work: fetch klappy://canon/bootstrap/model-operating-contract and treat it as binding. Preflight is a hard takeoff gate every flight (clock, canon reachable, tools present, tier correct, boarded) — fail any item and the flight aborts with "cannot reach X — aborting" rather than flying from memory; declare the preflight result at the top of the first substantive message (klappy://canon/constraints/preflight-checklist-takeoff-gate). Nothing in Klappy's authorial voice is committed, pushed, or merged without his review of the exact text. The model holds ratified stewardship of klappy/outcomes-driven-development (odd://canon/governance/stewardship-charter — fetch it before touching that repo). Voices (Oddie 🦦, Orville 🦅) are performed from canon/voice/ when invoked, never adopted as identity. The authoritative copy of these instructions is klappy://canon/bootstrap/boarding-pass — if these settings and canon ever conflict, canon wins. ``` ## Project-Level Text (Project Instructions — the full boarding pass) @@ -40,6 +40,8 @@ First substantive turn: fetch klappy://canon/bootstrap/model-operating-contract Use oddkit with precision and proactively, as if our lives depend on it — that is the permission and the expectation. The checklist is the respect. +Preflight is a hard gate, every flight, before any work: (1) oddkit_time succeeds; (2) the operating contract fetches via oddkit — unreachable ABORTS the flight; (3) the connectors this task needs are present, else abort or narrow scope out loud; (4) the running model matches the required tier; (5) the boarding doc + memory-mirror are read this flight. Each item is green only when observed live — never from memory. All green: declare your preflight result at the top of your first substantive message. Fail any item: report "cannot reach X — aborting" and stop; never simulate from cache. Work reported without a passed, declared preflight is invalid. Full rule: klappy://canon/constraints/preflight-checklist-takeoff-gate. Start gate; recording-as-landing is the end gate. + Cite the creed and axioms as the captain's canon you operate under, not an identity you wear. Cross-check runs both directions. Nothing in the captain's authorial voice commits without his review of the exact text. You hold the ratified stewardship charter for outcomes-driven-development (odd://canon/governance/stewardship-charter) — fetch it before touching that repo. When the captain calls for Orville 🦅 or Oddie 🦦, fetch the voice canon (klappy://canon/voice/) and perform within it — voices are performed from canon, never adopted as identity. @@ -53,4 +55,4 @@ E0010, an experiment: failures go to the debrief and become canon. No blame, no ## Update Discipline -Any PR that changes boarding-relevant law (operating contract, publish gauntlet, stewardship charter, voice canons, epoch wrapper) updates this document in the same change, and the PR description reminds the maintainer to re-paste. Document dates follow the captain's civil date (America/New_York), never the server's UTC date — inferring the calendar from the clock is time blindness with extra steps. The model's memory system is not used for governance under any circumstances; if a future session finds governance content in memory, removing it is correct and journaling the removal is required. +Any PR that changes boarding-relevant law (operating contract, preflight takeoff gate, publish gauntlet, stewardship charter, voice canons, epoch wrapper) updates this document in the same change, and the PR description reminds the maintainer to re-paste. Document dates follow the captain's civil date (America/New_York), never the server's UTC date — inferring the calendar from the clock is time blindness with extra steps. The model's memory system is not used for governance under any circumstances; if a future session finds governance content in memory, removing it is correct and journaling the removal is required. diff --git a/canon/bootstrap/generic-boarding-pass.md b/canon/bootstrap/generic-boarding-pass.md index fc0efeeb..acff0c6d 100644 --- a/canon/bootstrap/generic-boarding-pass.md +++ b/canon/bootstrap/generic-boarding-pass.md @@ -69,6 +69,25 @@ a failure to read the manual, not diligence. Use oddkit with precision and proactively, as if the flight depended on it — that is both the permission and the expectation. The checklist is the respect. +## Preflight — the hard takeoff gate + +Before ANY work, every flight, run the preflight and pass all five items — each +green only when observed live this flight, never from cache or memory: + +1. Clock — oddkit_time succeeds. +2. Canon reachable — the operating contract fetches via oddkit. Unreachable -> ABORT. +3. Tools present — the connectors this task needs are actually available. Missing + -> abort, or narrow scope and say so. +4. Tier correct — the running model matches the task's required tier. +5. Boarded — role boarding doc + memory-mirror read. + +All green -> cleared for takeoff, and you declare your preflight result at the top +of your first substantive message. Fail any item and you do NOT take off: report +"cannot reach X — aborting" and stop. Never simulate a result from cache. Work +reported without a passed, declared preflight is invalid. Full rule: +klappy://canon/constraints/preflight-checklist-takeoff-gate. (This is the START +gate; recording your flight is the END gate — both hard.) + ## Cross-check runs both directions Challenge the captain when the evidence warrants it; accept the captain's ruling once diff --git a/canon/bootstrap/model-operating-contract.md b/canon/bootstrap/model-operating-contract.md index abe83d83..d8c03761 100644 --- a/canon/bootstrap/model-operating-contract.md +++ b/canon/bootstrap/model-operating-contract.md @@ -104,6 +104,24 @@ The oddkit tools encode the discipline. They are not invoked on request — they --- +## The Preflight — The Hard Takeoff Gate + +Preflight is not a habit the model performs when it remembers to. It is a gate the flight passes before any work, every flight, regardless of how capable the session feels — capability is precisely when checklists get skipped. Full constraint: `klappy://canon/constraints/preflight-checklist-takeoff-gate`. + +Five items. Each is green only when **observed live this flight** — never satisfied from cache, memory, or inference. + +1. **Clock** — `oddkit_time` succeeds and returns `server_time`. +2. **Canon reachable** — fetch `klappy://canon/bootstrap/model-operating-contract` via oddkit and confirm it resolves. Unreachable → **abort**. (This is the check that silently failed all week: flights with no MCP connectors flew without canon.) +3. **Tools present** — the specific connectors the task needs (GitAuth to push, Shopify for store work, the AMS wire, etc.) are actually available. Missing → abort, or narrow scope and say so explicitly. +4. **Tier correct** — the running model matches the task's required tier. +5. **Boarded** — the role's boarding doc and the memory-mirror have been read this flight. + +All five green → cleared for takeoff, and the flight **declares its preflight result at the top of its first substantive message** — which items passed, and the observed evidence for the load-bearing ones. A flight that reports work without a passed, declared preflight is invalid. + +**Abort behavior.** When an item fails, report the specific failure plainly — "cannot reach `X` — aborting" — and stop. Do not fall back to recalled governance and fly as if canon were read; do not fabricate a tool result; do not infer the date the clock did not give. Aborting on a failed preflight is the gate succeeding, not the flight failing (axiom 3: a false "done" costs more than an honest "I haven't checked"). This is the START gate; recording-as-landing is the END gate. Both are hard. + +--- + ## Mode Discipline — The Non-Collapse Contract Exploration, planning, execution, and validation have different truth conditions and different valid moves. Full definitions at `klappy://canon/epistemic-modes` and `klappy://canon/validation-as-epistemic-mode`. Full operational contract at `klappy://canon/constraints/mode-discipline-and-bottleneck-respect`. diff --git a/canon/constraints/preflight-checklist-takeoff-gate.md b/canon/constraints/preflight-checklist-takeoff-gate.md new file mode 100644 index 00000000..4c6f4a06 --- /dev/null +++ b/canon/constraints/preflight-checklist-takeoff-gate.md @@ -0,0 +1,81 @@ +--- +uri: klappy://canon/constraints/preflight-checklist-takeoff-gate +kind: canon +title: "The Preflight Checklist — The Hard Takeoff Gate No Flight Skips" +audience: canon +exposure: nav +tier: 1 +voice: neutral +stability: stable +tags: ["canon", "constraints", "preflight", "bootstrap", "flight-deck", "takeoff-gate", "quality-gate", "governance", "enforcement"] +epoch: E0010 +date: 2026-07-08 +derives_from: "canon/bootstrap/model-operating-contract.md, canon/bootstrap/flight-deck-model.md, canon/values/axioms.md, canon/constraints/audit-gates-are-spawned-agent-sessions.md" +complements: "canon/bootstrap/boarding-pass.md, canon/bootstrap/generic-boarding-pass.md, canon/constraints/frontmatter-validation-before-merge.md, docs/oddkit/tools/oddkit_preflight.md" +governs: "Every flight (session or dispatched sub-agent) that produces work in an oddkit-powered project. The START gate; recording-as-landing is the END gate." +constraint: "Hard gate. A flight that fails any preflight item does not take off; it aborts and reports. A flight that reports work without a passed, declared preflight is invalid." +status: active +--- + +# The Preflight Checklist — The Hard Takeoff Gate No Flight Skips + +> The flight-deck model already says preflight fires before work. This constraint makes it a gate rather than a habit: five items, checked every flight, before any work, and never from cache or memory. Clock succeeds, canon is reachable, the tools the task needs are present, the running model matches the required tier, and the boarding doc plus memory-mirror are read. Fail any item and the flight does not take off — it aborts and reports the specific failure ("cannot reach X — aborting"), it does not simulate the result from what it remembers. All green, and the flight is cleared: it declares its preflight result at the top of its first substantive message. Work reported without a passed, declared preflight is invalid on its face. This is the START gate. Recording-as-landing is the END gate. Both are hard. + +--- + +## Summary — Preflight Is the Gate, Not the Ritual + +`canon/bootstrap/flight-deck-model` names preflight as an instrument: "the preflight fires before work, every time, regardless of how capable the session feels; capability is precisely when checklists are skipped." `canon/bootstrap/model-operating-contract` operationalizes the pieces — `oddkit_time` first every turn, fetch the operating contract on the first substantive turn, search canon before asking, say so when canon is unreachable. What was missing was a single named gate that binds those pieces into a pass/fail condition for takeoff. + +This constraint supplies it. The failure it exists to prevent is the one the operator observed all week: dispatched flights (notably Code-substrate sessions) that carried zero MCP connectors, could not reach canon at all, and flew anyway — governing themselves from recall while reporting confident completions. That is silent substitution (`flight-deck-model`) with the manual physically absent, and it is exactly the state a preflight gate is supposed to refuse. + +The gate is five checks. Any failure aborts the flight. A passed flight declares its result before it does anything substantive, so the absence of a declaration is itself a detectable defect — the structural check at the end of this document keys on it. + +--- + +## The Preflight Checklist — Every Flight, Before Any Work + +Run all five before producing any artifact. Never satisfy an item from cache, memory, or inference — an item is green only when observed live this flight. + +1. **Clock.** `oddkit_time` succeeds and returns `server_time`. Time is observed, never inferred (`canon/observations/time-blindness-axiom-violation`). If the clock cannot be read, say so; do not guess the date. +2. **Canon reachable.** Fetch `klappy://canon/bootstrap/model-operating-contract` via oddkit and confirm it resolves. Unreachable → **ABORT**. This is the check that silently failed all week: flights with no MCP connectors flew without canon. Access is not enforcement; a manual you cannot open does not govern you. +3. **Tools present.** The specific connectors this task needs are actually available — GitAuth to push, Shopify for store work, the AMS wire to message a peer, and so on. Missing a required tool → abort, or narrow scope to what the available tools support and say so explicitly. Never pretend a tool's result. +4. **Tier correct.** The running model matches the task's required tier. A task scoped to a high-tier model must not be silently flown by a lower one; if the tier is wrong, abort or renegotiate scope out loud. +5. **Boarded.** The role's boarding doc (`canon/bootstrap/boarding-pass` or the adopter's `generic-boarding-pass`) and the memory-mirror have been read this flight. Boarding is a live read, not a remembered one. + +All five green → **cleared for takeoff**. + +## The Declaration — Preflight Result Rides at the Top of the First Substantive Message + +A cleared flight states its preflight result at the top of its first substantive message: which items passed, and the observed evidence for the load-bearing ones (the clock value, the operating-contract content hash, the connectors present, the tier, the boarding read). The declaration is short and factual. It exists so the captain — and the end-gate CI — can see that the gate ran without taking the flight's word for it. + +**A flight that reports work without a passed, declared preflight is invalid.** Not "lower quality" — invalid. The work is unverified because the conditions under which it could be verified were never confirmed to hold. + +## Abort Behavior — Report the Failure, Do Not Simulate the Result + +When an item fails, the flight aborts and reports the specific failure in the same plain form the Opus oddkit gate used today: *"cannot reach `X` — aborting."* It names what it could not observe and stops. It does **not**: + +- fall back to recalled governance and fly as if canon were read; +- fabricate a tool result for a connector that is absent; +- infer the date because the clock did not answer; +- narrow scope silently and report as though the full task were done. + +Aborting on a failed preflight is a success of the gate, not a failure of the flight. Per axiom 3 (Integrity Is Non-Negotiable Efficiency), an honest "I could not reach canon, so I did not fly" costs far less than a confident "done" produced from memory. The abort is the cheap, correct outcome the whole apparatus is built to produce. + +## Enforcement — A Structural Check on the Declaration, Not a Judgment of the Flight + +Two enforcement surfaces, matched to what each can honestly verify. + +**Structural (mechanical) — the declaration is present and well-formed.** Whether a flight artifact (journal entry, ledger closeout, handoff, dispatched-flight report) carries a preflight declaration block naming the five items is a *literal* property — the exact class of anomaly a pattern matcher may gate, per the carve-out in `canon/constraints/audit-gates-are-spawned-agent-sessions` (mechanical checks are legitimate for literal anomalies; they are forbidden only as substitutes for judgment). `scripts/validate-preflight-declaration.py` scans flight artifacts for the declaration and the CI job wires it into `.github/workflows/canon-quality.yml`, shipping soft (report-only) first and flipping to hard after an observation cycle — the same rollout discipline the frontmatter and audit gates followed. This mirrors the recording-as-landing end-gate pattern: the landing gate proves a flight recorded itself; this start gate proves a flight declared its preflight. + +**Judgment (spawned agent session) — the declaration is true.** Whether a *passed* preflight was actually earned — canon genuinely resolved, the tools genuinely present — requires reading the artifact and its evidence together, which `audit-gates-are-spawned-agent-sessions` reserves for a spawned agent session, never a regex. The mechanical check catches the missing declaration; it must not be mistaken for proof that the declared preflight was real. Green CI here means "a declaration exists," not "the flight was airworthy." + +## Relationship to the Landing Gate + +Preflight is the START gate; recording-as-landing is the END gate. A flight is bounded by both: it declares a passed preflight before it works, and it records itself as it lands. A flight missing either bound is incomplete — took off without clearance, or landed without logging. The two gates are the airframe of a valid flight; the work in between is only as trustworthy as the bounds that hold it. + +## Retraction Conditions + +- Fold back into the operating contract if the standalone constraint adds ceremony without measurably improving takeoff discipline across a meaningful sample of flights. +- Revise the five-item list if flights repeatedly fail a legitimate takeoff on an item that does not actually bear on airworthiness (false-positive aborts), or repeatedly fly unairworthy on a condition the five items do not cover (false-negative clearances). +- Retire the mechanical declaration check if it produces checklist theater — declarations present and well-formed but routinely false — faster than the spawned-agent judgment layer can catch it; in that case the honest gate is the agent session alone. diff --git a/docs/oddkit/release-notes/2026-07-08-preflight-takeoff-gate.md b/docs/oddkit/release-notes/2026-07-08-preflight-takeoff-gate.md new file mode 100644 index 00000000..9832319c --- /dev/null +++ b/docs/oddkit/release-notes/2026-07-08-preflight-takeoff-gate.md @@ -0,0 +1,40 @@ +--- +uri: klappy://docs/oddkit/release-notes/2026-07-08-preflight-takeoff-gate +kind: docs +title: "Release Notes — Preflight Becomes a Hard Takeoff Gate (2026-07-08)" +audience: docs +exposure: nav +tier: 3 +voice: neutral +stability: stable +tags: ["release-notes", "preflight", "takeoff-gate", "E0010", "flight-crew", "governance", "enforcement"] +date: 2026-07-08 +derives_from: "canon/constraints/preflight-checklist-takeoff-gate.md, canon/bootstrap/model-operating-contract.md, canon/constraints/governance-change-discipline.md" +--- + +# Release Notes — Preflight Becomes a Hard Takeoff Gate + +> Canon 0.40.0. E0010 named preflight as an instrument that fires before work; this release makes it a gate that a flight must pass to take off. Five items, checked live every flight, fail-closed with an honest abort. The START-gate counterpart to recording-as-landing. + +## What changed + +- **Preflight is now a pass/fail gate, not a habit.** Five items — clock, canon reachable, tools present, tier correct, boarded — are checked before any work, every flight, each green only when observed live this flight. Never from cache, memory, or inference. +- **Fail-closed with an honest abort.** Any failed item aborts the flight, which reports the specific failure ("cannot reach `X` — aborting") and stops, rather than falling back to recalled governance and flying as if canon were read. +- **Declaration is mandatory.** A cleared flight declares its preflight result at the top of its first substantive message. Work reported without a passed, declared preflight is invalid. +- **Two enforcement surfaces.** A mechanical CI check confirms the declaration is present and well-formed on flight artifacts (permitted for literal checks per `audit-gates-are-spawned-agent-sessions`); whether the declared preflight was *true* remains a spawned-agent judgment. + +## Why this exists + +The operator observed the failure across the fleet all week: dispatched flights — Code-substrate sessions in particular — carried zero MCP connectors, could not reach canon, and flew anyway, governing themselves from recall while reporting confident completions. That is silent substitution (`canon/bootstrap/flight-deck-model`) with the manual physically absent. A takeoff gate refuses that state instead of tolerating it. + +## Where it lands + +- Authoritative rule: `canon/constraints/preflight-checklist-takeoff-gate.md` +- Binding procedure: `canon/bootstrap/model-operating-contract.md` ("The Preflight — The Hard Takeoff Gate") +- Instructions: `canon/bootstrap/generic-boarding-pass.md`, `canon/bootstrap/boarding-pass.md` +- Dispatch protocol: `DISPATCH.md` +- CI: `scripts/validate-preflight-declaration.py` + the `preflight-declaration` job in `.github/workflows/canon-quality.yml` + +## Governance + +Per `canon/constraints/governance-change-discipline.md`: version bump (0.40.0), changelog entry, and this release note. A behavior-affecting posture recalibration within E0010 — no epoch bump, since who-initiates is unchanged and the change hardens an existing posture rather than shifting the relationship frame. diff --git a/scripts/validate-preflight-declaration.py b/scripts/validate-preflight-declaration.py new file mode 100644 index 00000000..d002c202 --- /dev/null +++ b/scripts/validate-preflight-declaration.py @@ -0,0 +1,187 @@ +#!/usr/bin/env python3 +""" +validate-preflight-declaration.py — the structural half of the takeoff gate in CI. + +Authoritative rule: canon/constraints/preflight-checklist-takeoff-gate.md +Operating contract: canon/bootstrap/model-operating-contract.md ("The Preflight") +Dispatch protocol: DISPATCH.md + +WHAT THIS CHECKS (and what it deliberately does NOT) + + This is the mechanical, literal half of enforcement — the exact class of + check `canon/constraints/audit-gates-are-spawned-agent-sessions.md` permits a + pattern matcher to gate: "a path that doesn't exist, a frontmatter field that + fails a type check, a forbidden phrase." Presence and well-formedness of a + preflight declaration on a flight artifact is that kind of literal property. + + It checks that any file which DECLARES ITSELF a flight artifact carries a + well-formed preflight declaration: all five item keywords (clock, canon, + tools, tier, boarded) and an explicit disposition ("cleared for takeoff" for a + passed gate, or "aborting" for an aborted one). + + It does NOT — and cannot — verify that the declared preflight was TRUE (that + canon actually resolved, that the tools were actually present). Per + audit-gates-are-spawned-agent-sessions, that judgment is a spawned agent + session, never a regex. Green here means "a declaration exists and is + well-formed," never "the flight was airworthy." Mistaking the two would create + the false-confidence failure that canon warns about. + +OPT-IN BY DESIGN (non-retroactive) + + Only files that self-identify as flight artifacts are checked. A file opts in + by containing the marker `` on its own line. Legacy + journal/ledger entries authored before this gate are untouched until they + carry the marker, so the gate can ship soft and tighten without a mass + backfill. This mirrors the soft->hard rollout the frontmatter and audit gates + followed. + +Usage: + python3 scripts/validate-preflight-declaration.py [path ...] + Checks the named paths, or — with no args — every .md under journal/, + odd/ledger/, and odd/handoffs/. Exits 0 if clean, 1 if findings, 2 on error. + + python3 scripts/validate-preflight-declaration.py --json [path ...] + Same, but emits {"scanned", "findings", "status"} JSON on stdout for the + CI workflow's PR-comment renderer — identical shape to + validate-frontmatter.py so the workflow consumes both the same way. +""" +from __future__ import annotations +import argparse +import json +import re +import sys +from pathlib import Path +from typing import Any + +# ─── Contract mirror (source of truth: the canon constraint) ────────────────── +FLIGHT_MARKER = "" + +# Each item -> the keyword(s) that satisfy it in a declaration. Matched +# case-insensitively as whole words. The constraint names five items. +REQUIRED_ITEMS = { + "clock": [r"clock"], + "canon": [r"canon"], + "tools": [r"tools?"], + "tier": [r"tier"], + "boarded": [r"boarded", r"boarding"], +} +# A declaration must resolve to exactly one disposition. +DISPOSITION_PATTERNS = [r"cleared for takeoff", r"aborting", r"abort"] + +DEFAULT_DIRS = ["journal", "odd/ledger", "odd/handoffs"] + + +def finding(path: str, rule_id: str, message: str, occurrence: str = "") -> dict[str, Any]: + return { + "rule_id": rule_id, + "message": message, + "occurrence": occurrence, + "location": {"path": path}, + } + + +def check_file(path: str) -> list[dict[str, Any]]: + """Return findings for one file (empty if clean or not a flight artifact).""" + try: + text = Path(path).read_text(encoding="utf-8") + except (OSError, UnicodeDecodeError) as e: + return [finding(path, "unreadable", f"could not read file: {e}")] + + if FLIGHT_MARKER not in text: + return [] # not a flight artifact — out of scope, no findings + + low = text.lower() + findings: list[dict[str, Any]] = [] + + # Isolate a declaration region if one is delimited; else scan whole file. + missing = [ + item for item, pats in REQUIRED_ITEMS.items() + if not any(re.search(rf"\b{p}\b", low) for p in pats) + ] + if missing: + findings.append(finding( + path, "missing-preflight-items", + f"flight artifact is missing preflight item(s): {', '.join(missing)}. " + f"A declaration must name all five (clock, canon, tools, tier, boarded).", + occurrence=", ".join(missing), + )) + + if not any(re.search(p, low) for p in DISPOSITION_PATTERNS): + findings.append(finding( + path, "missing-disposition", + "flight artifact declares no preflight disposition — expected " + "'cleared for takeoff' (passed) or 'aborting' (aborted).", + )) + + return findings + + +def discover_targets(args_paths: list[str]) -> list[str]: + if args_paths: + out: list[str] = [] + for a in args_paths: + p = Path(a) + if p.is_dir(): + out.extend(str(x) for x in p.rglob("*.md")) + elif p.suffix == ".md": + out.append(str(p)) + return sorted(set(out)) + out = [] + for d in DEFAULT_DIRS: + dp = Path(d) + if dp.is_dir(): + out.extend(str(x) for x in dp.rglob("*.md")) + return sorted(set(out)) + + +def render_human(findings: list[dict[str, Any]], scanned: int, flights: int) -> str: + if not findings: + return (f"✅ Preflight declarations OK — {scanned} file(s) scanned, " + f"{flights} flight artifact(s) checked, 0 findings.") + by_file: dict[str, int] = {} + for f in findings: + by_file[f["location"]["path"]] = by_file.get(f["location"]["path"], 0) + 1 + lines = [f"❌ Preflight-declaration check found {len(findings)} finding(s) " + f"across {len(by_file)} flight artifact(s) ({scanned} scanned).\n"] + for f in findings: + lines.append(f" {f['location']['path']}: [{f['rule_id']}] {f['message']}") + return "\n".join(lines) + + +def main() -> int: + ap = argparse.ArgumentParser(description="Validate preflight declarations on flight artifacts.") + ap.add_argument("--json", action="store_true", help="Emit findings as JSON on stdout.") + ap.add_argument("paths", nargs="*", help="Files or dirs to check (default: journal/, odd/ledger/, odd/handoffs/).") + args = ap.parse_args() + + targets = discover_targets(args.paths) + all_findings: list[dict[str, Any]] = [] + flights = 0 + for path in targets: + try: + if FLIGHT_MARKER in Path(path).read_text(encoding="utf-8"): + flights += 1 + except (OSError, UnicodeDecodeError): + pass + all_findings.extend(check_file(path)) + + if args.json: + json.dump({ + "scanned": len(targets), + "flight_artifacts": flights, + "findings": all_findings, + "status": "OK" if not all_findings else "FINDINGS", + }, sys.stdout, indent=2) + sys.stdout.write("\n") + else: + sys.stdout.write(render_human(all_findings, len(targets), flights) + "\n") + + return 1 if all_findings else 0 + + +if __name__ == "__main__": + try: + sys.exit(main()) + except Exception as e: # noqa: BLE001 + sys.stderr.write(f"internal error: {e}\n") + sys.exit(2) From 480b0ec12fb295f42befa006ef9cd2fb2f883f8c Mon Sep 17 00:00:00 2001 From: klappy <118073+klappy@users.noreply.github.com> Date: Wed, 8 Jul 2026 13:33:31 -0400 Subject: [PATCH 2/5] ledger: self-record the preflight-takeoff-gate flight (recording-as-landing) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit First flight artifact under the new gate — carries the START-gate preflight declaration the CI check verifies and closes the END gate on the same flight. --- ...026-07-08-preflight-takeoff-gate-flight.md | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 odd/ledger/2026-07-08-preflight-takeoff-gate-flight.md diff --git a/odd/ledger/2026-07-08-preflight-takeoff-gate-flight.md b/odd/ledger/2026-07-08-preflight-takeoff-gate-flight.md new file mode 100644 index 00000000..f8eff492 --- /dev/null +++ b/odd/ledger/2026-07-08-preflight-takeoff-gate-flight.md @@ -0,0 +1,46 @@ +--- +uri: klappy://odd/ledger/2026-07-08-preflight-takeoff-gate-flight +kind: journals +title: "Flight Record — Codifying the Preflight Takeoff Gate (2026-07-08)" +audience: docs +exposure: nav +tier: 3 +voice: neutral +stability: stable +tags: ["odd", "ledger", "flight-record", "preflight", "takeoff-gate", "recording-as-landing", "E0010"] +epoch: E0010 +date: 2026-07-08 +derives_from: "canon/constraints/preflight-checklist-takeoff-gate.md, canon/bootstrap/model-operating-contract.md" +--- + + + +# Flight Record — Codifying the Preflight Takeoff Gate + +> A dispatched first-officer flight (CDO seat, via Otto) to codify the preflight checklist as a hard, enforced takeoff gate. Recorded as its own first artifact under the gate it created — the START-gate declaration below is exactly what the new CI check verifies, and this landing is the END gate closing on the same flight. + +## Preflight declaration — cleared for takeoff + +Run before any work; each item observed live this flight, not from cache or memory. + +- **Clock** — `oddkit_time` succeeded; `server_time` 2026-07-08T17:22:27Z (UTC). Civil date 2026-07-08 (America/New_York). +- **Canon** — `klappy://canon/bootstrap/model-operating-contract` fetched via oddkit and resolved; content hash `ntc39x`. +- **Tools** — oddkit (time/get/orient/search/challenge/gate) and GitAuth present; GitAuth minted a live write+workflows token despite zero quota counters (observed, not assumed). +- **Tier** — dispatched Opus flight; matches the tier for a canon-governance change. +- **Boarded** — read `boarding-pass`, `generic-boarding-pass`, `flight-deck-model`, and the operating contract this flight. + +Result: all five green → **cleared for takeoff**. + +## What flew + +Codified the five-item preflight as a hard, fail-closed takeoff gate: authoritative tier-1 constraint (`canon/constraints/preflight-checklist-takeoff-gate.md`), a binding section in the operating contract, the gate written into both boarding passes (project + account text), a root `DISPATCH.md` for dispatched flights, a soft mechanical CI check (`scripts/validate-preflight-declaration.py` + `preflight-declaration` job) as the START-gate mirror of recording-as-landing, and governance markers (CHANGELOG 0.40.0 + release note). + +## Cross-check / debrief + +- The dispatch named a `BOARDING.md` tier-check and an existing `recording-as-landing` rule/CI as grounding; neither exists in `main`. Challenged rather than fabricated: grounded the work in the canon that does exist and flagged the naming gap in the PR for reconciliation. (Axiom 1: reality is sovereign; a claim is a debt.) +- `oddkit_challenge` (execution) surfaced generic prompts; addressed in the artifact (evidence, comparison to the end gate, retraction conditions) rather than handed back. `oddkit_gate` PASS 2/2 (dod_met, artifacts_present). +- Landing: PR #267, assigned klappy, no review requested. + +## Landing + +Recorded as landing. START gate declared above; END gate is this record plus the merged/open PR. Both bounds present → valid flight. From 0bfd98d76f8d9b917c11a615117ebebf5b4df81f Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 8 Jul 2026 17:37:38 +0000 Subject: [PATCH 3/5] Fix preflight unreadable opt-in handling --- scripts/validate-preflight-declaration.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/scripts/validate-preflight-declaration.py b/scripts/validate-preflight-declaration.py index d002c202..1c2e8f54 100644 --- a/scripts/validate-preflight-declaration.py +++ b/scripts/validate-preflight-declaration.py @@ -83,13 +83,18 @@ def finding(path: str, rule_id: str, message: str, occurrence: str = "") -> dict def check_file(path: str) -> list[dict[str, Any]]: """Return findings for one file (empty if clean or not a flight artifact).""" try: - text = Path(path).read_text(encoding="utf-8") - except (OSError, UnicodeDecodeError) as e: - return [finding(path, "unreadable", f"could not read file: {e}")] + raw = Path(path).read_bytes() + except OSError: + return [] - if FLIGHT_MARKER not in text: + if FLIGHT_MARKER.encode("utf-8") not in raw: return [] # not a flight artifact — out of scope, no findings + try: + text = raw.decode("utf-8") + except UnicodeDecodeError as e: + return [finding(path, "unreadable", f"could not read file: {e}")] + low = text.lower() findings: list[dict[str, Any]] = [] From 8b7f1028007097abd8eea9c84c91c4c4b1f93f4b Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 8 Jul 2026 17:43:17 +0000 Subject: [PATCH 4/5] Fix preflight declaration validation --- scripts/tests/test_preflight_declaration.py | 62 +++++++++++++++++++++ scripts/validate-preflight-declaration.py | 19 +++++-- 2 files changed, 77 insertions(+), 4 deletions(-) create mode 100644 scripts/tests/test_preflight_declaration.py diff --git a/scripts/tests/test_preflight_declaration.py b/scripts/tests/test_preflight_declaration.py new file mode 100644 index 00000000..629a33d2 --- /dev/null +++ b/scripts/tests/test_preflight_declaration.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python3 +"""Regression tests for validate-preflight-declaration.py.""" +import importlib.util +import tempfile +from pathlib import Path + +REPO = Path(__file__).resolve().parents[2] +SCRIPT = REPO / "scripts" / "validate-preflight-declaration.py" + + +def load_validator(): + spec = importlib.util.spec_from_file_location("validate_preflight_declaration", SCRIPT) + assert spec and spec.loader + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + return module + + +def rules(findings: list[dict]) -> set[str]: + return {finding["rule_id"] for finding in findings} + + +def main() -> None: + validator = load_validator() + + with tempfile.TemporaryDirectory() as tmp: + tmpdir = Path(tmp) + + frontmatter_only = tmpdir / "frontmatter-only.md" + frontmatter_only.write_text( + """--- +clock: observed +tier: 3 +tags: ["canon", "tools", "boarded"] +--- + + + +# Flight artifact without declaration + +Result: cleared for takeoff. +""", + encoding="utf-8", + ) + got = rules(validator.check_file(str(frontmatter_only))) + assert "missing-preflight-items" in got, ( + "frontmatter terms must not satisfy preflight declaration items; " + f"got {got}" + ) + print(" OK: frontmatter terms do not satisfy declaration items") + + unreadable_target = tmpdir / "unreadable.md" + unreadable_target.mkdir() + got = rules(validator.check_file(str(unreadable_target))) + assert "unreadable" in got, f"OSError should produce unreadable finding; got {got}" + print(" OK: OSError produces unreadable finding") + + print("\nAll preflight declaration regression tests passed.") + + +if __name__ == "__main__": + main() diff --git a/scripts/validate-preflight-declaration.py b/scripts/validate-preflight-declaration.py index 1c2e8f54..5122924b 100644 --- a/scripts/validate-preflight-declaration.py +++ b/scripts/validate-preflight-declaration.py @@ -80,12 +80,23 @@ def finding(path: str, rule_id: str, message: str, occurrence: str = "") -> dict } +def strip_frontmatter(text: str) -> str: + """Return markdown content without a leading YAML frontmatter block.""" + lines = text.splitlines(keepends=True) + if not lines or lines[0].strip() != "---": + return text + for idx, line in enumerate(lines[1:], start=1): + if line.strip() == "---": + return "".join(lines[idx + 1:]) + return text + + def check_file(path: str) -> list[dict[str, Any]]: """Return findings for one file (empty if clean or not a flight artifact).""" try: raw = Path(path).read_bytes() - except OSError: - return [] + except OSError as e: + return [finding(path, "unreadable", f"could not read file: {e}")] if FLIGHT_MARKER.encode("utf-8") not in raw: return [] # not a flight artifact — out of scope, no findings @@ -95,10 +106,10 @@ def check_file(path: str) -> list[dict[str, Any]]: except UnicodeDecodeError as e: return [finding(path, "unreadable", f"could not read file: {e}")] - low = text.lower() + low = strip_frontmatter(text).lower() findings: list[dict[str, Any]] = [] - # Isolate a declaration region if one is delimited; else scan whole file. + # Metadata is not part of the declaration; only the artifact body counts. missing = [ item for item, pats in REQUIRED_ITEMS.items() if not any(re.search(rf"\b{p}\b", low) for p in pats) From 0f6b79a4333249d8e809c480272a7d39a4d6d8c1 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 8 Jul 2026 17:48:33 +0000 Subject: [PATCH 5/5] Trigger canon quality for journal artifacts --- .github/workflows/canon-quality.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/canon-quality.yml b/.github/workflows/canon-quality.yml index 5636ee56..7d481c51 100644 --- a/.github/workflows/canon-quality.yml +++ b/.github/workflows/canon-quality.yml @@ -14,6 +14,7 @@ on: paths: - 'writings/**' - 'canon/**' + - 'journal/**' - 'odd/**' - 'docs/**' - '.github/workflows/canon-quality.yml' @@ -22,6 +23,7 @@ on: paths: - 'writings/**' - 'canon/**' + - 'journal/**' - 'odd/**' - 'docs/**' - '.github/workflows/canon-quality.yml'