Skip to content

Commit f23ffb4

Browse files
hyperpolymathclaude
andcommitted
feat(abi): S1 additive Verb discriminant — promote ASSERT/DECLARE/RETRACT
Add `data Verb = VSelect|VInspect|VVerify|VAssert|VDeclare|VRetract` and an additive `verb` field to both `Grammar.idr record Statement` (proof corpus) and the Rust `ast.rs` mirror. The parser promotes ASSERT/DECLARE/RETRACT as verb tags over the existing relational body; INSPECT/VERIFY already aliased SELECT; MERGE/SPLIT/NORMALISE stay fail-closed (S2 — multi-subject / result-less semantics). Additive-extension property demonstrated: ZERO proof edits. All L0..L10 soundness proofs and the WireConformance Refls stay green (the predicates access Statement by name and ignore `verb`); only the 9 construction/pattern sites needed the extra arg. `verb` is not carried on the wire — it defaults to VSelect on decode and in the conformance fixtures, so the byte format and the Refls are unchanged. Also closes the gate.rs silent ASSERT->INSPECT rewrite: the admitted fixture now parses the contract's actual ASSERT example (vclt-gate-contract.adoc:86) and asserts verb==Assert; adds s1_verb_tags_and_fail_closed. Verified: idris2 --build vclut-core.ipkg exit 0; cargo test 7/7 suites; honesty-guard Clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 156f6d7 commit f23ffb4

11 files changed

Lines changed: 107 additions & 18 deletions

File tree

src/core/Checker.idr

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ mutual
172172
||| payload — itself smaller than the enclosing `Expr`).
173173
public export
174174
statementFieldRefs : Statement -> List FieldRef
175-
statementFieldRefs (MkStatement sel _ whr grp hav ord _ _ _ _ _ _ _ _) =
175+
statementFieldRefs (MkStatement sel _ whr grp hav ord _ _ _ _ _ _ _ _ _) =
176176
let selRefs : List FieldRef
177177
selRefs = selItemsFieldRefs sel
178178
whereRefs : List FieldRef

src/core/Composition.idr

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,7 @@ composeJoin q1 q2 = MkStatement
187187
(joinLinear (linearAnnot q1) (linearAnnot q2))
188188
(joinEpistemic (epistemicClause q1) (epistemicClause q2))
189189
(requestedLevel q1)
190+
(verb q1) -- join inherits the left verb
190191

191192
||| Two queries are composable if they target the same source octad.
192193
public export

src/core/Grammar.idr

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,13 @@ mutual
298298
-- Statement (top-level query)
299299
-- ═══════════════════════════════════════════════════════════════════════
300300

301+
||| The VCL statement verb (consonance surface, S1). The read verbs
302+
||| (VSelect/VInspect/VVerify) carry the existing relational semantics;
303+
||| the mutating verbs (VAssert/VDeclare/VRetract) are parsed as tags over
304+
||| the same body. MERGE/SPLIT/NORMALISE stay unmodelled (fail-closed).
305+
public export
306+
data Verb = VSelect | VInspect | VVerify | VAssert | VDeclare | VRetract
307+
301308
||| A complete VCL-total query statement.
302309
public export
303310
record Statement where
@@ -319,6 +326,9 @@ mutual
319326
epistemicClause : Maybe EpistemicClause -- Level 10: epistemic safety
320327
-- Metadata
321328
requestedLevel : SafetyLevel
329+
-- VCL consonance verb (S1). Every L0..L10 predicate accesses Statement
330+
-- by named field and ignores `verb`, so adding it is proof-preserving.
331+
verb : Verb
322332

323333
-- ═══════════════════════════════════════════════════════════════════════
324334
-- Injection-safety primitive (canonical single source of truth)
@@ -364,9 +374,9 @@ data HasSelectItems : Statement -> Type where
364374
||| Proof that a statement has a valid source.
365375
public export
366376
data HasSource : Statement -> Type where
367-
OctadSource : HasSource (MkStatement _ (SrcOctad _) _ _ _ _ _ _ _ _ _ _ _ _)
368-
FederationSource : HasSource (MkStatement _ (SrcFederation _) _ _ _ _ _ _ _ _ _ _ _ _)
369-
StoreSource : HasSource (MkStatement _ (SrcStore _) _ _ _ _ _ _ _ _ _ _ _ _)
377+
OctadSource : HasSource (MkStatement _ (SrcOctad _) _ _ _ _ _ _ _ _ _ _ _ _ _)
378+
FederationSource : HasSource (MkStatement _ (SrcFederation _) _ _ _ _ _ _ _ _ _ _ _ _ _)
379+
StoreSource : HasSource (MkStatement _ (SrcStore _) _ _ _ _ _ _ _ _ _ _ _ _ _)
370380

371381
||| Proof that a statement requesting Level 6+ has a LIMIT clause.
372382
public export

src/interface/WireConformance.idr

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ expected1 =
5050
MkStatement [SelStar] (SrcStore "main")
5151
Nothing [] Nothing [] Nothing Nothing
5252
Nothing Nothing Nothing Nothing Nothing
53-
SchemaBound
53+
SchemaBound VSelect
5454

5555
conform1 : fromWire WireConformance.golden1 = Right WireConformance.expected1
5656
conform1 = Refl
@@ -82,7 +82,7 @@ expected2 =
8282
(Just (EpClause
8383
[AgEngine, AgProver "lean4"]
8484
[EpReqKnows AgEngine (ELiteral (LitBool True) TAny)]))
85-
EpistemicSafe
85+
EpistemicSafe VSelect
8686

8787
conform2 : fromWire WireConformance.golden2 = Right WireConformance.expected2
8888
conform2 = Refl
@@ -98,7 +98,7 @@ expected3 =
9898
(Just (ELiteral (LitFloat 2.5) TAny))
9999
[] Nothing [] Nothing Nothing
100100
Nothing Nothing Nothing Nothing Nothing
101-
ParseSafe
101+
ParseSafe VSelect
102102

103103
conform3 : fromWire WireConformance.golden3 = Right WireConformance.expected3
104104
conform3 = Refl

src/interface/WireDecode.idr

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -618,7 +618,9 @@ mutual
618618
(la, r12) <- decOpt (\x => decLinear x) r11
619619
(ep, r13) <- decOpt (decEpiClause k) r12
620620
(lvl, r14) <- decSafety r13
621-
Right (MkStatement sel src wc gb hav ob lim off pf ef vc la ep lvl, r14)
621+
-- `verb` is not carried on the wire (S1): decode defaults to VSelect, the
622+
-- read sense, keeping the byte format and the WireConformance Refls stable.
623+
Right (MkStatement sel src wc gb hav ob lim off pf ef vc la ep lvl VSelect, r14)
622624
623625
-- ═══════════════════════════════════════════════════════════════════════
624626
-- Header + entry point

src/interface/parse/src/ast.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,20 @@ pub enum SafetyLevel {
207207
EpistemicSafe = 10,
208208
}
209209

210+
/// `Grammar.idr`: `data Verb`. Read verbs (`Select`/`Inspect`/`Verify`) carry
211+
/// the existing relational semantics; the mutating verbs (`Assert`/`Declare`/
212+
/// `Retract`) are parsed as tags over the same body (S1). `Merge`/`Split`/
213+
/// `Normalise` stay unmodelled (fail-closed in the parser).
214+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
215+
pub enum Verb {
216+
Select,
217+
Inspect,
218+
Verify,
219+
Assert,
220+
Declare,
221+
Retract,
222+
}
223+
210224
/// `Grammar.idr`: `record Statement` (`orderBy` is `(field, ascending?)`;
211225
/// Idris `Nat` limit/offset -> `u64`).
212226
#[derive(Debug, Clone, PartialEq)]
@@ -225,4 +239,6 @@ pub struct Statement {
225239
pub linear_annot: Option<LinearAnnotation>,
226240
pub epistemic_clause: Option<EpistemicClause>,
227241
pub requested_level: SafetyLevel,
242+
/// VCL consonance verb (S1) — mirrors `Grammar.idr`'s additive `verb`.
243+
pub verb: Verb,
228244
}

src/interface/parse/src/parser.rs

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -110,12 +110,30 @@ pub fn parse(input: &str) -> Result<Statement, ParseError> {
110110
}
111111

112112
fn parse_statement(p: &mut P) -> Result<Statement, ParseError> {
113-
// Accept SELECT (legacy) and INSPECT (VCL epistemic read request) interchangeably.
114-
if p.is_kw("INSPECT") || p.is_kw("VERIFY") {
113+
// VCL consonance verb (S1). The read verbs (SELECT legacy / INSPECT /
114+
// VERIFY) and the promoted mutating verbs (ASSERT / DECLARE / RETRACT) all
115+
// parse as a tag over the same relational body. MERGE / SPLIT / NORMALISE
116+
// are deliberately NOT accepted, so they fail-closed (their semantics are
117+
// S2, multi-subject / result-less).
118+
let verb = if p.is_kw("INSPECT") {
115119
p.bump();
120+
Verb::Inspect
121+
} else if p.is_kw("VERIFY") {
122+
p.bump();
123+
Verb::Verify
124+
} else if p.is_kw("ASSERT") {
125+
p.bump();
126+
Verb::Assert
127+
} else if p.is_kw("DECLARE") {
128+
p.bump();
129+
Verb::Declare
130+
} else if p.is_kw("RETRACT") {
131+
p.bump();
132+
Verb::Retract
116133
} else {
117134
p.eat_kw("SELECT")?;
118-
}
135+
Verb::Select
136+
};
119137
let select_items = parse_select_items(p)?;
120138
p.eat_kw("FROM")?;
121139
let source = parse_source(p)?;
@@ -179,6 +197,7 @@ fn parse_statement(p: &mut P) -> Result<Statement, ParseError> {
179197
linear_annot: None,
180198
epistemic_clause: None,
181199
requested_level: SafetyLevel::ParseSafe,
200+
verb,
182201
};
183202
stmt.requested_level = infer_requested_level(&stmt);
184203
Ok(stmt)

src/interface/parse/src/wire.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -845,6 +845,9 @@ fn dec_stmt(d: &mut D) -> Result<Statement, WireError> {
845845
linear_annot,
846846
epistemic_clause,
847847
requested_level,
848+
// `verb` is not carried on the wire (S1): default to the read sense,
849+
// matching `WireDecode.idr`, so the byte format stays stable.
850+
verb: Verb::Select,
848851
})
849852
}
850853

src/interface/parse/tests/conformance_emit.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ fn f1() -> Statement {
9494
linear_annot: None,
9595
epistemic_clause: None,
9696
requested_level: SafetyLevel::SchemaBound,
97+
verb: Verb::Select,
9798
}
9899
}
99100

@@ -138,6 +139,7 @@ fn f2() -> Statement {
138139
)],
139140
}),
140141
requested_level: SafetyLevel::EpistemicSafe,
142+
verb: Verb::Select,
141143
}
142144
}
143145

@@ -157,6 +159,7 @@ fn f3() -> Statement {
157159
linear_annot: None,
158160
epistemic_clause: None,
159161
requested_level: SafetyLevel::ParseSafe,
162+
verb: Verb::Select,
160163
}
161164
}
162165

src/interface/parse/tests/gate.rs

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
)]
1717

1818
use vcltotal_parse::{
19-
ast::Modality,
19+
ast::{Modality, Verb},
2020
certified_level, parse,
2121
schema::{FieldDef, ModalitySchema, OctadSchema, VqlType},
2222
};
@@ -49,13 +49,13 @@ fn schema_with_graph_fields(fields: Vec<FieldDef>) -> OctadSchema {
4949

5050
// ── Fixture 1: Admitted ────────────────────────────────────────────────────
5151
//
52-
// Contract §"Worked examples" — Admitted:
53-
// INSPECT GRAPH.knows FROM HEXAD 'e-1' WHERE depth < 3 LIMIT 10
52+
// Contract §"Worked examples" — Admitted (vclt-gate-contract.adoc:86):
53+
// ASSERT GRAPH.knows FROM HEXAD 'e-1' WHERE depth < 3 LIMIT 10
5454
// schema: graph.depth TInt non-null, graph.knows TOctad non-null
5555
// expected: admissible=true, certified_level=6, levels 1-6 all pass, exit 0.
5656

5757
#[test]
58-
fn fixture_admitted_inspect_with_limit() {
58+
fn fixture_admitted_assert_with_limit() {
5959
let schema = schema_with_graph_fields(vec![
6060
FieldDef {
6161
name: "depth".to_string(),
@@ -70,10 +70,14 @@ fn fixture_admitted_inspect_with_limit() {
7070
indexed: true,
7171
},
7272
]);
73-
// P5a parser uses SELECT/INSPECT; field refs need MODALITY.name qualification;
74-
// bare `depth` is parsed as Expr::Param (schema-unresolved) — passes L1 vacuously.
75-
let stmt = parse("INSPECT GRAPH.knows FROM HEXAD 'e-1' WHERE depth < 3 LIMIT 10")
73+
// S1: the contract's worked example uses the ASSERT consonance verb, which
74+
// now parses as a verb tag over the relational body (previously this fixture
75+
// had to rewrite ASSERT->INSPECT because the parser could not parse ASSERT).
76+
// Field refs need MODALITY.name qualification; bare `depth` parses as
77+
// Expr::Param (schema-unresolved) — passes L1 vacuously.
78+
let stmt = parse("ASSERT GRAPH.knows FROM HEXAD 'e-1' WHERE depth < 3 LIMIT 10")
7679
.expect("fixture 1 must parse");
80+
assert_eq!(stmt.verb, Verb::Assert, "the ASSERT verb tag must be captured");
7781
let cert = certified_level(&stmt, &schema);
7882
assert_eq!(
7983
cert, 6,
@@ -160,3 +164,30 @@ fn parse_error_on_empty_input() {
160164
fn parse_error_on_garbage() {
161165
assert!(parse("'); DROP TABLE --").is_err());
162166
}
167+
168+
// ── S1: consonance-verb discriminant ────────────────────────────────────────
169+
// The read verbs (SELECT/INSPECT/VERIFY) and the promoted mutating verbs
170+
// (ASSERT/DECLARE/RETRACT) parse as a verb tag over the same relational body;
171+
// MERGE/SPLIT/NORMALISE are deliberately fail-closed until their semantics (S2).
172+
#[test]
173+
fn s1_verb_tags_and_fail_closed() {
174+
let body = "GRAPH.knows FROM STORE main";
175+
for (kw, want) in [
176+
("SELECT", Verb::Select),
177+
("INSPECT", Verb::Inspect),
178+
("VERIFY", Verb::Verify),
179+
("ASSERT", Verb::Assert),
180+
("DECLARE", Verb::Declare),
181+
("RETRACT", Verb::Retract),
182+
] {
183+
let stmt = parse(&format!("{kw} {body}"))
184+
.unwrap_or_else(|e| panic!("{kw} should parse: {e:?}"));
185+
assert_eq!(stmt.verb, want, "{kw} must carry the {want:?} tag");
186+
}
187+
for kw in ["MERGE", "SPLIT", "NORMALISE"] {
188+
assert!(
189+
parse(&format!("{kw} {body}")).is_err(),
190+
"{kw} must fail-closed (S2, multi-subject / result-less semantics)"
191+
);
192+
}
193+
}

0 commit comments

Comments
 (0)