Skip to content

Commit 156f6d7

Browse files
hyperpolymathclaude
andcommitted
fix(ci): green the three required gates + redirect stale stance doc
- vclt-gate bin: replace the panicking .expect() in parse_vql_type with a match returning Err on the (impossible) empty-object case, and add #![deny(clippy::unwrap_used, clippy::expect_used)] so the bin meets the Aspect/SPARK gate's no-panic posture (the lib already carries it). #49 - echidna-client: declare an own [workspace] so it is a standalone root (its transitive echidna-core path-dep breaks the default standalone build); Backend-Matrix now runs it via --manifest-path instead of `-p`, which could not select a non-member package. - scorecard-enforcer: move the run: "Check minimum score" gate into a separate `enforce` job consuming the SARIF artifact, so the scorecard job stays uses-only (publish_results:true rejects mixed run/uses, 400). - docs/VERIFICATION-STANCE.adoc: replace the stale duplicate (claimed predicates vacuous / not end-to-end proven) with a redirect to the canonical verification/proofs/ copy; SPDX header preserved. Verified locally: corpus build exit 0, honesty-guard Clean, cargo clippy --bin vclt-gate -D expect_used -D unwrap_used rc=0, both workspaces resolve. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 0b770ef commit 156f6d7

5 files changed

Lines changed: 59 additions & 68 deletions

File tree

.github/workflows/backend-matrix.yml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,4 +102,9 @@ jobs:
102102
run: git clone --depth 1 https://github.com/hyperpolymath/echidna ../echidna
103103

104104
- name: Client wiremock suite (ECHIDNA_PROVER=${{ matrix.prover }})
105-
run: cargo test -p vcltotal-echidna-client --tests --locked
105+
# `vcltotal-echidna-client` is intentionally NOT a member of the root
106+
# workspace (v0.2.0 workspace cleanup: its transitive echidna-core
107+
# path-dep breaks standalone CI), so `-p` cannot select it. It is its
108+
# own standalone workspace root; run it by manifest path now that the
109+
# echidna sibling has been cloned above.
110+
run: cargo test --manifest-path src/interface/echidna-client/Cargo.toml --tests --locked

.github/workflows/scorecard-enforcer.yml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,28 @@ jobs:
4444
with:
4545
sarif_file: results.sarif
4646

47+
- name: Upload SARIF artifact
48+
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
49+
with:
50+
name: scorecard-results
51+
path: results.sarif
52+
retention-days: 5
53+
54+
# Gating job: kept separate so the scorecard job above remains
55+
# uses-only. GitHub rejects a publish_results: true job that also
56+
# contains run: steps with HTTP 400 ("job must only have steps with
57+
# uses"). The minimum-score gate therefore runs here against the
58+
# SARIF uploaded as an artifact by the scorecard job.
59+
enforce:
60+
runs-on: ubuntu-latest
61+
timeout-minutes: 15
62+
needs: scorecard
63+
steps:
64+
- name: Download SARIF artifact
65+
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4
66+
with:
67+
name: scorecard-results
68+
4769
- name: Check minimum score
4870
run: |
4971
# Parse score from results

docs/VERIFICATION-STANCE.adoc

Lines changed: 13 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,73 +1,23 @@
1-
= VCL-total Verification Stance
1+
= VCL-total Verification Stance (moved)
22
:toc:
33
:sectnums:
44

55
// SPDX-License-Identifier: MPL-2.0
66
// Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk>
77

8-
WARNING: vcl-ut is the interface between *hypatia* and *verisim*. Both sides
9-
trust safety claims made here. This document states *precisely* what is and is
10-
not currently proven, so that neither consumer over-trusts the boundary. It
11-
takes precedence over any "verified" / "type-safe" / "proven" phrasing in
12-
`README.adoc`, `PROOF-NEEDS.md`, or module doc-comments.
8+
NOTE: This file is a pointer only. The canonical, current verification stance
9+
for vcl-ut now lives at `verification/proofs/VERIFICATION-STANCE.adoc`.
1310

14-
== Summary
11+
The canonical document is proof-backed and supersedes this `docs/` copy. As of
12+
2026-06-14, the Idris2 proof corpus is machine-checked: all ten safety levels
13+
(L1–L10) carry genuine soundness, and
14+
`idris2 0.8.0 --build verification/proofs/vclut-core.ipkg` exits 0.
1515

16-
The 10-level safety story is, at present, *architecturally typed but not
17-
end-to-end proven*. The dependent-type apparatus exists but is *not connected*
18-
to the runtime checker or the FFI boundary that hypatia/verisim consume.
16+
The earlier text of this `docs/` file (which described the apparatus as
17+
architecturally typed but not end-to-end proven, with vacuous level predicates)
18+
is no longer accurate and has been removed. This copy is retained only as a
19+
redirect so existing links do not break.
1920

20-
== What IS genuinely established
21+
See `verification/proofs/VERIFICATION-STANCE.adoc` for the authoritative,
22+
detail-complete stance.
2123

22-
* `src/interface/abi/Types.idr` — `SafetyLevel`, `VclTotalError`, `QueryMode`
23-
are real ADTs with total `DecEq` instances and total int<->tag round-trip
24-
functions. The `Verify` namespace proves tag ranges with `So`. These are
25-
sound, non-vacuous proofs.
26-
* `QueryHandle` is a genuine opaque type with an `So (ptr /= 0)` non-null
27-
witness; it cannot be constructed null through the public API.
28-
* `src/core/Levels.idr` `SafetyCertificate` / `CanWeaken` define a real
29-
subsumption lattice *at the type level*.
30-
31-
== What is NOT proven (the honest gaps)
32-
33-
. *The certificate is disconnected from the checker.* `src/core/Checker.idr`
34-
decides every level with `Bool`-returning `checkLevelN` functions and never
35-
constructs a `SafetyCertificate`. A query reported "Level 10" carries *no*
36-
`Levels.idr` proof object. The dependent types are decorative w.r.t. the
37-
actual decision procedure.
38-
. *Several level predicates are vacuous.* `NoRawUserInput` has the single
39-
constructor `AllParameterised : NoRawUserInput stmt` (provable for *any*
40-
statement, including injection-bearing ones); `AllNullableFieldsGuarded`'s
41-
`GuardedNull` accepts *any* expression; `AllSelectItemsTyped`'s `ConsTyped`
42-
requires no actual per-item typing evidence. As written, Levels 3, 4 and 5
43-
prove nothing about the property they name.
44-
. *The FFI boundary is stringly/int-typed.* `src/interface/abi/Foreign.idr`
45-
returns a bare `Bits32` error tag; the `parse` wrapper is an explicit
46-
placeholder (returns `Left InternalError` on success). hypatia/verisim
47-
receive an *asserted* level integer, not a transported proof.
48-
. *Layout/size proofs are vacuous.* `HasSize` / `HasAlignment` constructors
49-
accept any `n`; `queryPlanHeaderSize : ... HasSize QueryPlanHeader 24` type-
50-
checks regardless of the real layout. The 24-byte/8-align claim is unverified.
51-
52-
== Consumer guidance
53-
54-
* *hypatia / verisim*: treat the FFI level integer as an *advisory claim*, not
55-
a proof carrier. Do not weaken your own checks on the strength of a VCL-total
56-
level tag until gap (1) and (3) are closed.
57-
* Do not cite vcl-ut as "formally verified query safety" in downstream docs.
58-
59-
== Closing the gaps
60-
61-
The high-value, low-risk path (per `PROOF-NEEDS.md`): make `checkLevelN` return
62-
`Dec (Ln_... )` instead of `Bool`, give the vacuous predicates real
63-
constructors carrying evidence, and have the checker emit a `SafetyCertificate`
64-
that the Zig FFI transports as an opaque proof token. Tracked under
65-
hyperpolymath/standards#124.
66-
67-
== Provenance
68-
69-
Audit 2026-05-18 (corpus grep + read; no proof-escape symbols
70-
`believe_me`/`assert_total`/`postulate`/`sorry` are *applied* anywhere in
71-
`src/**` — the only match is a self-attesting comment in `Composition.idr`).
72-
The risk here is *not* proof escapes; it is *absent* proof masquerading as
73-
present proof at a trusted interface.

src/interface/echidna-client/Cargo.toml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,16 @@
11
# SPDX-License-Identifier: MPL-2.0
2+
3+
# Standalone workspace root: this crate is deliberately NOT a member of the
4+
# parent virtual workspace (root `Cargo.toml`), because it depends on
5+
# `vcltotal-interface`, which carries an external path-dep
6+
# (`../../../echidna/crates/echidna-core`) that does not resolve on a
7+
# standalone checkout. Declaring an own `[workspace]` lets the estate e2e
8+
# CI job (`.github/workflows/backend-matrix.yml`) build/test it by
9+
# `--manifest-path` once the echidna sibling is present, without dragging
10+
# the unresolved path-dep into the default standalone build. Mirrors the
11+
# same pattern used by `src/interface/parse/Cargo.toml`.
12+
[workspace]
13+
214
[package]
315
name = "vcltotal-echidna-client"
416
version = "0.1.0"

src/interface/parse/src/bin/vclt-gate.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
//! Pure function of (statement, schema): no network, no filesystem,
1616
//! no clock, no env-dependent behaviour beyond stdin/stdout/stderr.
1717
18+
#![deny(clippy::unwrap_used, clippy::expect_used)]
19+
1820
use serde_json::{json, Value};
1921
use std::io::{self, Read};
2022
use vcltotal_parse::schema::{FieldDef, ModalitySchema, VqlType};
@@ -267,10 +269,10 @@ fn parse_field_def(v: &Value) -> Result<FieldDef, String> {
267269
fn parse_vql_type(v: &Value) -> Result<VqlType, String> {
268270
match v {
269271
Value::String(s) => parse_nullary_type(s),
270-
Value::Object(map) if map.len() == 1 => {
271-
let (key, inner) = map.iter().next().expect("len==1 checked");
272-
parse_parameterised_type(key, inner)
273-
}
272+
Value::Object(map) if map.len() == 1 => match map.iter().next() {
273+
Some((key, inner)) => parse_parameterised_type(key, inner),
274+
None => Err("expected single-key object for VqlType, found empty object".to_string()),
275+
},
274276
other => Err(format!(
275277
"expected string or single-key object for VqlType, got {other}"
276278
)),

0 commit comments

Comments
 (0)