release v4.1.3#1517
Open
bplatz wants to merge 13 commits into
Open
Conversation
First facet of the governance-model tooling (fluree model). Users
declare intent; the CLI compiles it to stored policies and transacts
them as ordinary data — no bespoke server API, so it works identically
against local ledgers, fluree-db-server, and hosted stacks.
fluree model access enable <dataset> --profile read|write|intake
--entity <class-iri> [--property <iri>...] [--allow-shared]
[--class-iri <iri>] [--dry-run] [--remote <r>]
fluree model access show <dataset>
Compilation: a policy class (the assignment unit grants/tokens carry),
a view policy (f:onClass — exact for reads), a property-whitelist
modify policy (f:onProperty + f:allow — class-targeted modify cannot
cover new-subject inserts and f:query evaluates pre-state), and a
declarative fm:AccessProfile node recording the intent so enable is
idempotent (upsert) and future sync/verify re-derive from it.
Property derivation: explicit → SHACL shape → observed data →
fail-closed. Uniqueness partition: properties used by other classes
are excluded by default with the blast radius disclosed
('also used by: Invoice'), included only with --allow-shared; every
run prints an exactness verdict (class-exact vs property-approximate)
and flags the rdf:type creation caveat.
Unit tests cover compilation shapes per profile, the parse gates, and
the IRI validation; verified end-to-end in local mode (enable → show →
identity+policy-class insert allowed → non-whitelisted write denied →
shared-property partition + disclosure).
…tacks Completes the enable flow end-to-end: after the policies land in the ledger (data plane), --space merges the compiled policy class into the space's grant on the dataset via the stack's grants API — the one system-plane touch in the compiler, since grants are router-owned invariants (scope validation, membership checks). - Reads existing grants first and merges classes (never clobbers); keeps the existing access level, upgrading read → write with a printed notice only when the profile requires writes. - Policies-then-grant ordering is deliberate: a partial failure leaves unused policies (harmless), never a grant naming absent classes. - Root URL derived from the remote's data-plane URL (…/v1/fluree); clear errors when the remote isn't a hosted stack or has no token. - --space without --remote is a usage error. Verified against a live stack: enable … --space wrote 4 policies + attached the grant in one command; a minted space app token then carried leads2 with the compiled class, a whitelisted insert passed, and a non-whitelisted write was rejected at the ledger.
Terminology only: user-facing output now prints 'Allowed: N properties', identifiers use allowed/allow_list, docs say property allow-list. No behavior change.
Second facet of the governance-model tooling. The entity shape is the
model's single source of truth: the property list is written ONCE here
and everything else derives — model access enable now prefers
shape-derivation for its allow-lists, transaction-time validation
enforces the shape (the shapes-exist heuristic activates reject mode),
and SDK types / form fields can be generated from it later.
fluree model entity define <dataset> --entity <iri> [--label <l>]
--property "<iri> [string|integer|decimal|boolean|date|datetime|iri]
[required] [in[v1,v2,...]]" ...
fluree model entity show <dataset>
Compiles to an rdfs:Class node + a sh:NodeShape targeting it, with
stable property-shape ids ({entity}/shape/{localname}) so re-define is
an idempotent upsert. The define output states plainly that validation
becomes ACTIVE (existing data is not retro-validated; fluree validate
reports) and prints the model access enable synergy hint.
Shared ledger-IO helpers (resolve_mode/query/upsert) promoted from
access.rs to model/mod.rs.
Verified locally end-to-end: define (no seed data) → access enable
reports 'Derivation: shacl-shape' (class-exact, 3 properties) → a
shape-violating insert is rejected by SHACL with a violation report →
a conforming insert through the compiled policy class commits → both
show views render.
Third tier of the access compiler: a SPARQL property-path subset
(sequence / and inverse ^ of angle-bracketed absolute IRIs), anchored
identity → path → subject, expanded into f:query where-patterns on the
view policy (f:query replaces f:allow — they are alternative decision
modes). The raw path is recorded as fm:connected on the profile node.
--connected "^<https://x/owner>" # I see what I own
--connected "<https://x/memberOf>/^<https://x/team>"
# I see entities whose team I'm in
v1 scope: --profile read only. Relationship gates evaluate against
pre-transaction state, so gating writes would deny every create (the
connection triples are not yet visible); staged-state evaluation at
the engine lifts this later. Alternatives (|) and transitive (+/*)
paths are rejected with a clear message until f:query supports them.
Verified live in local mode: two users on different teams, one enable
with --connected, and each user's query returns only their own team's
entities — graph-native row-level security from a single flag.
…nd hierarchy - 'model access verify' recompiles every stored fm:AccessProfile from its intent node and semantically compares against the policies in the ledger (compact/full form tolerant). Hand-edited allow-lists, flipped f:allow, retargeted classes, and missing/unexpected policies report as DRIFT with a nonzero exit for CI. - 'model access sync' re-derives profiles whose property surface came from a source that can change (SHACL shape, observed data) and recompiles on drift; explicit lists are never rewritten. The compiler now records fm:derivation and fm:allowShared on the intent node so sync honors the author's original choices; nodes written before these fields (or by other front ends) read as explicit — the safe default. - 'model class define/show' — rdfs:subClassOf hierarchy as governed data, with the entailment blast-radius note; show excludes compiler-minted policy classes from the domain vocabulary. Verified live against a hosted stack (verify caught a hand-widened allow-list; enable repaired it) and locally (shape grew -> sync applied '+score' -> verify clean).
Proves the storage form governance tooling uses for relationship gates: a stored, classed view policy whose f:query is one triple over an @path term (the author's path expression survives verbatim in the artifact), selected via policy-class with a bind-only identity. Two identities, one gate, each sees only entities connected via ex:memberOf/^ex:team.
…caffolder The engine's f:create/f:update/f:delete verbs (with pre∪post class targeting and class-scoped rdf:type matching) make thin class policies exact, so the entire workaround layer is deleted: no property allow-list derivation, no uniqueness partition / --allow-shared, no fm: intent vocabulary, no sync, no drift-verify (1,461 → ~700 lines). Profiles map directly: read → view; write → view + create/update/delete; intake → create-only. --property remains as optional column narrowing (f:onProperty conjunction). --connected now stores the SPARQL path VERBATIM via the engine's @path context term (readable + reversible in the artifact; show extracts it back). model entity define gains --closed (sh:closed + rdf:type carve-out): validation owns the property surface, policy owns state transitions. Contract proven by integration test scaffolder_write_profile_grants_class_ownership_only: the scaffolder's byte-exact output, stored + selected via policy-class with bind-only identity, allows create/update of the class and denies minting or editing other classes. @path gate test switched to the CLI's angle-bracket absolute-IRI storage form.
Property-merge upsert could leave a stale f:allow: true on a policy node
when re-running `model access enable --connected`; the policy loader
gives f:allow precedence over f:query, so the new relationship gate was
silently disabled and the policy stayed allow-all. Install compiled
artifacts through one update transaction that wildcard-deletes every
OWNED node id (delete-if-exists via optional patterns) and inserts the
fresh compilation.
- access: owns {class}/view and {class}/write on every run, so gating
switches are exact and profile switches on the same class revoke the
node the new profile no longer emits
- entity: owns the shape and its property-shape children (a dropped
sh:closed/sh:minCount/sh:in no longer survives a re-run); the entity
class node is shared authorship with `model class define` and stays
additive
- class show: recognize policy classes by the extra @type on
f:AccessPolicy nodes (the fm:AccessProfile intent vocabulary no longer
exists); correct the --subclass-of re-run hint (replaces, not extends)
- integration test pins the CLI-emitted replace transaction end-to-end:
an allow-all read profile switched to a --connected gate must narrow
Add docs/cli/model.md covering the three governance facets (access profiles, SHACL entity shapes, class hierarchy), their compile-to-data architecture, and the atomic replace semantics of re-running enable and define. Index it in docs/SUMMARY.md and the CLI command table.
The access command's target restriction compiles to f:onClass, but the flag was named --entity — which reads as subject-level targeting (f:onSubject) to RDF users, and clashed with `model class define --class` taking the SAME iri for the same concept. Rename: - --entity → --class (the governed class; what data) - --class-iri → --policy-class (the assignment unit grants/tokens carry; how a request selects its policy set — not a data restriction) `model entity define --entity` is unchanged: there the flag names the entity facet's own concept. Internal names and enable's output follow (Policy class: line), and the docs spell out the three class-shaped roles: --class restricts data, --property narrows columns, the policy class selects the policy set.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Bumps the workspace version to 4.1.3 (root + testsuite-sparql + testsuite-shacl, with refreshed Cargo.locks).
Branched from the tip of
feat/cli-model-access, so this release PR also carries thefluree modelgovernance tooling (#1516). After merge, tag the merge commitv4.1.3to trigger cargo-dist per docs/contributing/releasing.md.