Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
d5dbfcb
fix: require request-owned query and mutation intent
Oct 2, 2026
9910f0b
feat(runtime): align canonical SQL provenance and retain mutation ite…
Oct 2, 2026
1b0f7d7
feat(runtime): retain .NET graph-owned scopes and commit-only audit (…
Oct 2, 2026
afb17d3
fix(trace): verify generated graphs and emit every safe physical quer…
Oct 2, 2026
afa93af
fix(trace): scope graph ledger imports and skip clean nodes (#41)
Oct 2, 2026
ce24a42
fix(trace): detach mutable .NET query inputs at snapshot boundary (#41)
Oct 3, 2026
a409351
fix(trace): retain .NET count origin and isolate returned graphs (#41)
Oct 3, 2026
8498f56
fix(trace): capture streaming requests before cursor execution (#41)
Oct 3, 2026
19dd906
fix(trace): retain successful mutation readbacks and originating scop…
Oct 3, 2026
b329a36
fix: retain loaded scalar privacy through graph saves (#41)
Oct 3, 2026
f301ca8
test: prove loaded graph privacy across rollback and retry (#41)
Oct 3, 2026
ca5b4ff
fix(trace): preserve aggregate membership across relation hydration (…
Oct 3, 2026
a648005
feat: expose owned query projections with generated trace acceptance …
Oct 3, 2026
1f4fa50
fix(trace): retain .NET relation keys when references are filtered (#41)
Oct 3, 2026
fc97904
test: verify root intent gates before policy and graph IO (#41)
Oct 3, 2026
d9c8e0f
test: verify overlapping query graphs at SQLite statement boundary (#41)
Oct 3, 2026
57c9319
test: verify generated bootstrap lineage and commit visibility (#41)
Oct 3, 2026
d46284f
fix: preserve nested Facet SQL provenance and results (#41)
Oct 3, 2026
7f2538c
fix: exclude null keys from loaded Facet membership (#41)
Oct 3, 2026
644af1a
fix: preserve grouped partition counts and verify trace fallbacks (#41)
Oct 3, 2026
67aa94e
fix: retain empty loaded relation facet metadata (#41)
Oct 4, 2026
653d287
fix: materialize requested nested facets for empty membership (#41)
Oct 4, 2026
cce7744
fix: retain original LIKE operands for safe intent logging (#41)
Oct 4, 2026
05684c3
test: prove generated Checker overlap isolation (#41)
Oct 4, 2026
a7cd0a7
fix: retain per-item committed audit for SQL batch containers (#41)
Oct 4, 2026
a56c1d1
test: cover explicit blank typed mutation route tails (#41)
Oct 4, 2026
7ee362e
fix: keep filtered forward FK and NotLoaded details (#41)
Oct 4, 2026
a5648fa
test: assert root intent on every generated relation statement (#41)
Oct 4, 2026
e80df65
test: retain generated Facet counts and ordered ancestry in examples
Oct 4, 2026
f363f07
test: assert canonical generated three-relation SQL trace paths
Oct 4, 2026
a09596e
test: observe shared Context storage during live query graphs
Oct 4, 2026
4433714
test: require exact graph identities at all persistence boundaries
Oct 4, 2026
17b58bf
test: verify generated dotnet bootstrap command and SQL intent
Oct 5, 2026
72c825c
test: bind generated readbacks to originating mutation intent
Oct 5, 2026
42eb4c2
test: require complete loaded-private lineage and unchanged provider …
Oct 5, 2026
9cb555f
test: exercise generated aggregate membership with filtered forward d…
Oct 5, 2026
28b3dcd
test: verify blank typed tails at real SQL and committed audit sinks
Oct 6, 2026
84afb8f
test: assert allocated graph physical lineage with logging on and off
Oct 6, 2026
dd8c2bc
fix: include Core tests in source and release verification gates
Oct 6, 2026
18421b8
fix: stabilize grouped relation limits by group keys (#41)
Oct 6, 2026
f5a4b33
test: prove query trace-only input rejects before policy and provider…
Oct 6, 2026
2bc1684
test: enforce typed AuditReason nodes at generated graph sinks (#41)
Oct 6, 2026
bc1c505
ci: install ripgrep for complete example verification
Oct 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/example-gate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install example verification tools
run: sudo apt-get update && sudo apt-get install -y ripgrep
- uses: actions/setup-dotnet@v4
with:
dotnet-version: '8.0.x'
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ jobs:
expected="${GITHUB_REF_NAME#dotnet-v}"
actual="$(sed -n 's:.*<Version>\(.*\)</Version>.*:\1:p' Directory.Build.props)"
test "$expected" = "$actual"
- run: dotnet test TeaQL.sln -c Release -p:UseSharedCompilation=false
- run: dotnet test tests/TeaQL.Streaming.Tests/TeaQL.Streaming.Tests.csproj -c Release -p:UseSharedCompilation=false
- name: Verify every source test project (including Core)
run: bash scripts/verify-trace-chain-source-tests.sh artifacts/test-results -c Release -p:UseSharedCompilation=false
- name: Pack public projects
shell: bash
run: |
Expand Down
103 changes: 103 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,108 @@
# TeaQL .NET SDK

## Trace Chain on the feature branch

`feature/request-trace-chain` requires a request-owned non-blank Query Comment
and Purpose, and a non-blank Mutation Comment before policy/provider access.
Logging switches do not relax validation. This is a local-source checkpoint,
not a newly published package capability.

The [School bootstrap example](examples/school-management/README.md) also
checks generated root/constant saves without injected trace nodes: physical
SQL routes, assigned audit identities, idempotence, audited reconciliation and
caller restoration. Its script runs twice on each retained SQLite database with
SQL logs on/off. At audit delivery, an independent read-only connection must
already observe the committed version. See `scripts/verify-school-bootstrap-example.sh`.

The SQL path uses the Rust-baseline canonical algorithm: an operation names
the originating entity, relation names are local, qualified properties belong
in `TraceNode.Detail`, and provider/SQL nodes occur once. Intent stays in its
structured metadata fields, not duplicated in every physical frame. Query
snapshots privately preserve their origin across relation and aggregate
derivation; caller-supplied diagnostic TraceChain frames cannot forge provenance.

Related aggregation consumes scalar membership keys before forward-reference
hydration. Nested attachment captures original scalar keys before hydration,
including targets keyed by `code` rather than `id`. A reference filtered to null
does not detach its child from the parent list or prevent another sibling from
using that FK. These per-load keys never become result fields or ledger entries. Native
SQLite regression cases cover root/nested count results, original trace ancestry,
descendant-binding privacy, logging disabled and aggregate failure recovery.
Generated models now retain related-count aliases in a runtime-owned
`QueryProjectionSnapshot`: `QueryProjection(alias)` returns an isolated `Value`,
`HasQueryProjection(alias)` distinguishes presence, and missing throws
`KeyNotFoundException`. Projections are not modeled fields or ledger mutations.
Regenerate libraries to acquire the API. The trace example exercises generated
root/nested Q/E/save, private descendant intent, logging on/off and count failures.
Native SQL execution also evaluates nested Facets and Facets inside a loaded
relation. Membership COUNT and target materialization inherit the original
request root, qualified relation route and private-binding provenance. Facets
are returned through `QueryResult.Facets` / `SmartList<Record>.Facets`; loaded
relation records expose query-only `Record.QueryFacets`, outside scalar fields
and JSON. Loaded-relation Facets execute per distinct parent membership key.
SQL NULL is not a membership key. Root `IncludeAll` Facets still expose zero-count
candidates; empty loaded to-many collections have no per-collection Facet carrier.
The SQLite regression covers direct and transaction queries, logging on/off,
root-page-independent counts, multiple parents, caller snapshots and actual
materialization failure. This is native SPI evidence: generated Q/E Facet
consumption, broader query/provider shapes and immutable-artifact acceptance
remain separate gates.

`ExecutionMetadata.MutationLineage` separately preserves existing root/item
reasons, including partial batch failures and mutation readback. Default masking
projects both that lineage and physical paths before sinks; execution values
and original command trace lists are not changed. Frozen shared vectors and
real SQLite root/three-level failure tests are separate evidence.

Graph saves explicitly receive an operation-owned `GraphMutationSession`.
Persistent immutable parent scopes retain sibling and deletion reasons; typed
ledger traces are complete per-entity replacements. The generated adapter
allocates IDs through the transaction before constructing a scope. Context does
not hold a trace stack or implicitly join nested saves. Untagged active-graph
mutations, expired capabilities and borrowed parent scopes fail closed.

The graph transaction queues safe application audit until commit and discards it
on rollback. Post-commit errors are `GraphCommittedException` (`Committed=true`);
remaining cleanup/audits still run and the committed transaction is not rolled
back. Whole-graph preflight supplies invocation-local sibling redaction values.
The native SQLite tests exercise allocation, deletion, typed ledger replacement,
real UNIQUE rollback and two concurrent requests serialized on one Context.
Shared helper vectors remain separate from that execution proof.

Regenerate dependent libraries: the adapter callback is now
`ExecuteGraphSaveAsync(comment, async graph => ...)`, with `graph.Preflight`,
`graph.MutateAsync` and graph-owned completion callbacks. Public generated
`.AuditAs(...).SaveAsync(context)` is unchanged. School, Conformance and Order
libraries are regenerated from their retained models, not patched manually.

The [generated six-entity example](examples/trace-chain) observes actual provider
commands, safe SQL and committed audit through generated Q/E/Mutation APIs.
Run `bash scripts/verify-trace-chain-example.sh` for two executions on the same
database without cleanup and an unchanged library. It covers branch/deletion
reasons, assigned typed IDs, a three-level query, real UNIQUE rollback,
successful-write/readback-failure separation and concurrent generated saves.
The read-only provider SPI `MutationRequest.MutationLineage` is not a wire field.
The [generated School Facet example](examples/facet-trace) exercises 24
root/nested/loaded-relation combinations twice on a retained SQLite database,
with exact physical SQL/count numbers, ordered logical ancestry and returned
empty metadata. It also proves that Facet-bearing no-op saves emit no writes,
and that a real reverse child change saves through its root with one audit.
Run `bash examples/facet-trace/verify.sh`; the all-examples gate includes it.
Successful relation SQL is now logged at each physical execution boundary;
parent/descendant binding provenance protects intent before parent logs are
emitted, and root statements are not reported twice.

Prepared batch/item-index lineage, detached-child and late low-level allocation, complete
entry-point/privacy coverage and internal Registry replay remain open. A direct
legacy transaction wrapper is not proof of commit-bound audit. Run
`bash scripts/verify-trace-chain-source-tests.sh /tmp/teaql-dotnet-tests-<unique-run>`
and `bash scripts/verify-examples.sh` against this checkout before promoting an
internal candidate. The source-test gate discovers projects under both `src/`
and `tests/`, including `TeaQL.Core.Tests` (which is absent from `TeaQL.sln`).
It retains separate TRX files, refuses overwrite, and rejects zero, skipped,
failed or incompletely executed suites. Release CI uses this same gate; append
`-c Release -p:UseSharedCompilation=false` to exercise its release configuration.

## Sensitive log data

Runtime diagnostic logs redact payload values by default, before delivery to
Expand Down
15 changes: 14 additions & 1 deletion examples/conformance/GeneratedRuntimeModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,10 @@ public static class GeneratedRuntimeModule
["WorkItem"] = new List<RelationDescriptor> {
RelationDescriptor.New("Platform", "Platform").LocalKey("platform").ForeignKey("id")
}
}, new Dictionary<string, string>
{
["Platform"] = "platform_data",
["WorkItem"] = "work_item_data"
}).WireEntity(WireFields.CreateMetadata("Platform", ["id", "name", "version"], aliases: new Dictionary<string, IReadOnlyList<string>> { ["id"] = ["id"], ["name"] = ["name"], ["version"] = ["version"] })).WireEntity(WireFields.CreateMetadata("WorkItem", ["id", "title", "description", "platform", "version"], aliases: new Dictionary<string, IReadOnlyList<string>> { ["id"] = ["id"], ["title"] = ["title"], ["description"] = ["description"], ["platform"] = ["platform"], ["version"] = ["version"] })).GeneratedBootstrap(EnsureGeneratedBootstrapAsync);

private static async Task EnsureGeneratedBootstrapAsync(UserContext context)
Expand All @@ -103,7 +107,8 @@ private static async Task EnsureGeneratedBootstrapAsync(UserContext context)

private static async Task EnsureGeneratedBootstrapOnceAsync(UserContext context)
{
using var bootstrapScope = context.EnterGeneratedBootstrap("Platform", 1);
{
using var bootstrapScope = context.EnterGeneratedBootstrap("Platform", 1);
var domainRoot = await Q.Platforms().WithIdIs(1).Comment("what: locate generated Domain Root").Purpose("why: idempotent runtime bootstrap").ExecuteForOneAsync(context);
if (domainRoot == null)
{
Expand All @@ -113,6 +118,14 @@ private static async Task EnsureGeneratedBootstrapOnceAsync(UserContext context)
try { domainRoot = await created.AuditAs("create generated Domain Root Platform").SaveAsync(context); }
catch { domainRoot = await Q.Platforms().WithIdIs(1).Comment("what: recover concurrent Domain Root bootstrap").Purpose("why: make bootstrap idempotent").ExecuteForOneAsync(context); if (domainRoot == null) throw; }
}
}
context.WithActiveRoot("Platform", 1);
}


static GeneratedRuntimeModule()
{
Module.Entity(Module.Metadata.GetEntity("Platform")!.AuditMaskFields(new List<string> { }));
Module.Entity(Module.Metadata.GetEntity("WorkItem")!.AuditMaskFields(new List<string> { }));
}
}
Loading
Loading