Repository navigation
Make error handling order-independent and fail closed (#267) - #268
Merged
tonyseale merged 2 commits intoOct 29, 2026
Merged
Conversation
|
@matthiasautrata is attempting to deploy a commit to the EKGF Team on Vercel. A member of the Team first needs to authorize it. |
This was referenced Oct 12, 2026
matthiasautrata
added a commit
to matthiasautrata/dprod
that referenced
this pull request
Oct 13, 2026
…EKGF#271) Resolves EKGF#271. §7.2 Step 1 collected a duty only if `matches(n, request)`, i.e. if the duty's own action and asset matched the request. A duty states what the bearer must do, not what is requested, so a request to display ex:marketData never collected the duty to report on ex:usageStats, and §12.3's example returned no duties. Step 2 then partitioned by `d.subject = grantor` / `d.subject = grantee`, so a duty without dprod:subjectOfDuty belonged to neither party, though specification §3.2 and term-mapping describe consumer duties without a subject. The ontology comment on dprod:subjectOfDuty contradicted those documents: it defaulted the bearer to the data product owner, which is the provider. - §3.2: before evaluation, a duty without a subject takes the policy's grantee, or the requesting agent in a policy without one. `d.subject` is then the bearer everywhere, including `performed`. A duty borne by the requesting agent stands for one duty per agent, tracked separately in Σ, so one agent's missed duty is not held against another. - §7.2: duties are collected from every policy with a permission in force for the request; grantor duties are those borne by the grantor, and all others are grantee duties. §12.2 matches. - Collecting every duty of a granting policy makes a provider's missed delivery reachable from every consumer request. Violated is terminal, so denying on it would lock the consumer out permanently. Step 6 now denies only on violations by the grantee's side; the grantor's are reported in `violations` on the Permit. - dprod:subjectOfDuty comment, specification §4.10, and term-mapping state the default. - Add tests/test_duty_bearer.py, which fails on the previous text. Rebased onto EKGF#268 (order-independent errors): a policy grants only if a permission is active (= true), and a permission in error in a policy that does not otherwise grant (E4) gives Failure on the Permit path, since resolving it could engage that policy's duties. Section 9.7 now also keeps the grantor violations reported with a Permit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 13, 2026
matthiasautrata
marked this pull request as ready for review
October 13, 2026 23:50
matthiasautrata
requested review from
FroehlichMarcel,
jgeluk,
joshcornejo,
rivettp and
tonyseale
as code owners
October 13, 2026 23:50
Resolves EKGF#267. §6.1 evaluated And/Or left to right with short-circuit, and a resolution error aborted evaluation. The outcome then depended on operand and rule order: And(false, error) was false, And(error, false) was Failure, and in §7.2 an error in a duty's activation condition (Step 3) aborted before an active prohibition (Step 4) could deny. §6.2 made incomparable operands `false`, so Not(x < 5) on a string was true and a prohibition guarded by an ill-typed comparison failed open. - §6.1: conditions are three-valued (true, false, Err(E)); And, Or, Not are strong Kleene. Each connective depends only on the set of operand values, and replacing an error by a Boolean never changes a Boolean result. Errors still never become `false`. - §6.2: every operator requires operands of one type, and ordering operators require ordered values; otherwise ResolutionError (Incomparable). This covers `neq` and `isNoneOf`, which would otherwise fail open the same way. - §7.1: EvaluationError is the set of errors, not the first one found. - §7.2: an active prohibition denies whatever else is in error; an error in a prohibition or in a policy's applicability gives Failure only if no prohibition is active; an error in a permission matters only if no permission is active; duty states are updated after the decision steps, and an unknown activation gives Failure only on the Permit path. §8.2 matches. - §9.6 (order independence) and §9.7 (error soundness: a Success decision, and the duties reported with a Permit, are the same under every resolution of the errors; Failure may be conservative). §13.2's Dafny sketch uses Kleene connectives; S1, S5, S7 in §13.3. - README and overview: "Deterministic left-to-right" becomes "Order-independent; errors never fail open". Specification §4.8 no longer says "hard" errors. - Add tests/test_error_semantics.py: a reference check of the truth tables, and documentation checks that fail on the previous text.
Moving the duty-state update after the prohibition and permission checks made a duty activate, and be found violated, only on a permitted request, which shifted activatedAt and relative deadlines. Error soundness does not need the move: a duty whose activation condition is in error stays Pending, and its error still gives Failure only on the Permit path. Resolves: EKGF#267 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
jgeluk
pushed a commit
to matthiasautrata/dprod
that referenced
this pull request
Oct 21, 2026
…EKGF#271) Resolves EKGF#271. §7.2 Step 1 collected a duty only if `matches(n, request)`, i.e. if the duty's own action and asset matched the request. A duty states what the bearer must do, not what is requested, so a request to display ex:marketData never collected the duty to report on ex:usageStats, and §12.3's example returned no duties. Step 2 then partitioned by `d.subject = grantor` / `d.subject = grantee`, so a duty without dprod:subjectOfDuty belonged to neither party, though specification §3.2 and term-mapping describe consumer duties without a subject. The ontology comment on dprod:subjectOfDuty contradicted those documents: it defaulted the bearer to the data product owner, which is the provider. - §3.2: before evaluation, a duty without a subject takes the policy's grantee, or the requesting agent in a policy without one. `d.subject` is then the bearer everywhere, including `performed`. A duty borne by the requesting agent stands for one duty per agent, tracked separately in Σ, so one agent's missed duty is not held against another. - §7.2: duties are collected from every policy with a permission in force for the request; grantor duties are those borne by the grantor, and all others are grantee duties. §12.2 matches. - Collecting every duty of a granting policy makes a provider's missed delivery reachable from every consumer request. Violated is terminal, so denying on it would lock the consumer out permanently. Step 6 now denies only on violations by the grantee's side; the grantor's are reported in `violations` on the Permit. - dprod:subjectOfDuty comment, specification §4.10, and term-mapping state the default. - Add tests/test_duty_bearer.py, which fails on the previous text. Rebased onto EKGF#268 (order-independent errors): a policy grants only if a permission is active (= true), and a permission in error in a policy that does not otherwise grant (E4) gives Failure on the Permit path, since resolving it could engage that policy's duties. Section 9.7 now also keeps the grantor violations reported with a Permit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
jgeluk
added a commit
to matthiasautrata/dprod
that referenced
this pull request
Oct 21, 2026
[Carried forward onto develop for EKGF#275 by Matthias Autrata: conflicts resolved against EKGF#244/EKGF#245/EKGF#262/EKGF#268/EKGF#270/EKGF#272, the namespace changed from https://www.omg.org/spec/DPROD/dprod/ to https://ekgf.org/dprod/spec/develop/, and EKGF#244's duty-only shape and test migrated from dprod:recurrence to dprod:schedule, which is not duty-only.]
jgeluk
pushed a commit
to matthiasautrata/dprod
that referenced
this pull request
Oct 21, 2026
…EKGF#271) Resolves EKGF#271. §7.2 Step 1 collected a duty only if `matches(n, request)`, i.e. if the duty's own action and asset matched the request. A duty states what the bearer must do, not what is requested, so a request to display ex:marketData never collected the duty to report on ex:usageStats, and §12.3's example returned no duties. Step 2 then partitioned by `d.subject = grantor` / `d.subject = grantee`, so a duty without dprod:subjectOfDuty belonged to neither party, though specification §3.2 and term-mapping describe consumer duties without a subject. The ontology comment on dprod:subjectOfDuty contradicted those documents: it defaulted the bearer to the data product owner, which is the provider. - §3.2: before evaluation, a duty without a subject takes the policy's grantee, or the requesting agent in a policy without one. `d.subject` is then the bearer everywhere, including `performed`. A duty borne by the requesting agent stands for one duty per agent, tracked separately in Σ, so one agent's missed duty is not held against another. - §7.2: duties are collected from every policy with a permission in force for the request; grantor duties are those borne by the grantor, and all others are grantee duties. §12.2 matches. - Collecting every duty of a granting policy makes a provider's missed delivery reachable from every consumer request. Violated is terminal, so denying on it would lock the consumer out permanently. Step 6 now denies only on violations by the grantee's side; the grantor's are reported in `violations` on the Permit. - dprod:subjectOfDuty comment, specification §4.10, and term-mapping state the default. - Add tests/test_duty_bearer.py, which fails on the previous text. Rebased onto EKGF#268 (order-independent errors): a policy grants only if a permission is active (= true), and a permission in error in a policy that does not otherwise grant (E4) gives Failure on the Permit path, since resolving it could engage that policy's duties. Section 9.7 now also keeps the grantor violations reported with a Permit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
jgeluk
added a commit
to matthiasautrata/dprod
that referenced
this pull request
Oct 21, 2026
[Carried forward onto develop for EKGF#275 by Matthias Autrata: conflicts resolved against EKGF#244/EKGF#245/EKGF#262/EKGF#268/EKGF#270/EKGF#272, the namespace changed from https://www.omg.org/spec/DPROD/dprod/ to https://ekgf.org/dprod/spec/develop/, and EKGF#244's duty-only shape and test migrated from dprod:recurrence to dprod:schedule, which is not duty-only.]
jgeluk
pushed a commit
to matthiasautrata/dprod
that referenced
this pull request
Oct 21, 2026
…EKGF#271) Resolves EKGF#271. §7.2 Step 1 collected a duty only if `matches(n, request)`, i.e. if the duty's own action and asset matched the request. A duty states what the bearer must do, not what is requested, so a request to display ex:marketData never collected the duty to report on ex:usageStats, and §12.3's example returned no duties. Step 2 then partitioned by `d.subject = grantor` / `d.subject = grantee`, so a duty without dprod:subjectOfDuty belonged to neither party, though specification §3.2 and term-mapping describe consumer duties without a subject. The ontology comment on dprod:subjectOfDuty contradicted those documents: it defaulted the bearer to the data product owner, which is the provider. - §3.2: before evaluation, a duty without a subject takes the policy's grantee, or the requesting agent in a policy without one. `d.subject` is then the bearer everywhere, including `performed`. A duty borne by the requesting agent stands for one duty per agent, tracked separately in Σ, so one agent's missed duty is not held against another. - §7.2: duties are collected from every policy with a permission in force for the request; grantor duties are those borne by the grantor, and all others are grantee duties. §12.2 matches. - Collecting every duty of a granting policy makes a provider's missed delivery reachable from every consumer request. Violated is terminal, so denying on it would lock the consumer out permanently. Step 6 now denies only on violations by the grantee's side; the grantor's are reported in `violations` on the Permit. - dprod:subjectOfDuty comment, specification §4.10, and term-mapping state the default. - Add tests/test_duty_bearer.py, which fails on the previous text. Rebased onto EKGF#268 (order-independent errors): a policy grants only if a permission is active (= true), and a permission in error in a policy that does not otherwise grant (E4) gives Failure on the Permit path, since resolving it could engage that policy's duties. Section 9.7 now also keeps the grantor violations reported with a Permit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
jgeluk
force-pushed
the
issue/267-order-independent-errors
branch
from
October 21, 2026 16:37
5c766bc to
b6a67eb
Compare
tonyseale
approved these changes
Oct 29, 2026
tonyseale
pushed a commit
to matthiasautrata/dprod
that referenced
this pull request
Oct 29, 2026
…EKGF#271) Resolves EKGF#271. §7.2 Step 1 collected a duty only if `matches(n, request)`, i.e. if the duty's own action and asset matched the request. A duty states what the bearer must do, not what is requested, so a request to display ex:marketData never collected the duty to report on ex:usageStats, and §12.3's example returned no duties. Step 2 then partitioned by `d.subject = grantor` / `d.subject = grantee`, so a duty without dprod:subjectOfDuty belonged to neither party, though specification §3.2 and term-mapping describe consumer duties without a subject. The ontology comment on dprod:subjectOfDuty contradicted those documents: it defaulted the bearer to the data product owner, which is the provider. - §3.2: before evaluation, a duty without a subject takes the policy's grantee, or the requesting agent in a policy without one. `d.subject` is then the bearer everywhere, including `performed`. A duty borne by the requesting agent stands for one duty per agent, tracked separately in Σ, so one agent's missed duty is not held against another. - §7.2: duties are collected from every policy with a permission in force for the request; grantor duties are those borne by the grantor, and all others are grantee duties. §12.2 matches. - Collecting every duty of a granting policy makes a provider's missed delivery reachable from every consumer request. Violated is terminal, so denying on it would lock the consumer out permanently. Step 6 now denies only on violations by the grantee's side; the grantor's are reported in `violations` on the Permit. - dprod:subjectOfDuty comment, specification §4.10, and term-mapping state the default. - Add tests/test_duty_bearer.py, which fails on the previous text. Rebased onto EKGF#268 (order-independent errors): a policy grants only if a permission is active (= true), and a permission in error in a policy that does not otherwise grant (E4) gives Failure on the Permit path, since resolving it could engage that policy's duties. Section 9.7 now also keeps the grantor violations reported with a Permit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
tonyseale
pushed a commit
to matthiasautrata/dprod
that referenced
this pull request
Oct 29, 2026
[Carried forward onto develop for EKGF#275 by Matthias Autrata: conflicts resolved against EKGF#244/EKGF#245/EKGF#262/EKGF#268/EKGF#270/EKGF#272, the namespace changed from https://www.omg.org/spec/DPROD/dprod/ to https://ekgf.org/dprod/spec/develop/, and EKGF#244's duty-only shape and test migrated from dprod:recurrence to dprod:schedule, which is not duty-only.]
tonyseale
pushed a commit
that referenced
this pull request
Oct 29, 2026
…#271) (#272) Resolves #271. §7.2 Step 1 collected a duty only if `matches(n, request)`, i.e. if the duty's own action and asset matched the request. A duty states what the bearer must do, not what is requested, so a request to display ex:marketData never collected the duty to report on ex:usageStats, and §12.3's example returned no duties. Step 2 then partitioned by `d.subject = grantor` / `d.subject = grantee`, so a duty without dprod:subjectOfDuty belonged to neither party, though specification §3.2 and term-mapping describe consumer duties without a subject. The ontology comment on dprod:subjectOfDuty contradicted those documents: it defaulted the bearer to the data product owner, which is the provider. - §3.2: before evaluation, a duty without a subject takes the policy's grantee, or the requesting agent in a policy without one. `d.subject` is then the bearer everywhere, including `performed`. A duty borne by the requesting agent stands for one duty per agent, tracked separately in Σ, so one agent's missed duty is not held against another. - §7.2: duties are collected from every policy with a permission in force for the request; grantor duties are those borne by the grantor, and all others are grantee duties. §12.2 matches. - Collecting every duty of a granting policy makes a provider's missed delivery reachable from every consumer request. Violated is terminal, so denying on it would lock the consumer out permanently. Step 6 now denies only on violations by the grantee's side; the grantor's are reported in `violations` on the Permit. - dprod:subjectOfDuty comment, specification §4.10, and term-mapping state the default. - Add tests/test_duty_bearer.py, which fails on the previous text. - Rebased onto #268 (order-independent errors). A policy grants only if one of its permissions is active (`= true`). A permission in error in a policy that does not otherwise grant (E₄) gives Failure on the Permit path, because resolving it could engage that policy's duties. §9.7 also keeps the grantor violations reported with a Permit. **Stacked on #268.** Until #268 merges, this PR also shows #268's two commits; review only the last one. After #268 is squash-merged: `git rebase --onto origin/develop 5c766bc issue/271-duty-collection-and-bearer` (or squash-merge directly: #268's changes are identical on both sides). Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
tonyseale
pushed a commit
to matthiasautrata/dprod
that referenced
this pull request
Oct 29, 2026
[Carried forward onto develop for EKGF#275 by Matthias Autrata: conflicts resolved against EKGF#244/EKGF#245/EKGF#262/EKGF#268/EKGF#270/EKGF#272, the namespace changed from https://www.omg.org/spec/DPROD/dprod/ to https://ekgf.org/dprod/spec/develop/, and EKGF#244's duty-only shape and test migrated from dprod:recurrence to dprod:schedule, which is not duty-only.]
tonyseale
pushed a commit
that referenced
this pull request
Oct 29, 2026
Resolves #184. Supersedes #234. Step 7 of #275. Carries Jacobus Geluk's reusable schedule model (#234) forward onto `develop`. His two commits are kept with their authorship (`047dcb4`, `81777dd`); each has a note recording what changed while carrying it forward. Following Tony's and Jacobus's decision on #184, the anchor rule is: **the schedule's DTSTART/TZ sets the occurrence times, and activation only limits the range.** ## Stacking This branch is built on #268 → #272, plus a merge of #270 (`687ea77`), because the schedule errors use #268's error model and §3.2 combines with #270's `asset`. Until those three merge, this PR also shows their commits. The schedule work is the last four commits: - `047dcb4` Implement reusable schedule model (Jacobus) - `81777dd` Address review findings on the schedule model (Jacobus) - `7c67d39` Migrate the spec and examples from `dprod:recurrence` to `dprod:schedule` - `257e65c` Bump the contracts modification dates **Merge after #270, #268 and #272.** Then squash-merge directly (the shared changes are identical) or first rebase the four commits onto `develop`. ## Changes compared with #234 - **No frozen-archive changes, and no duplicate of #244.** #244's duty-only shape and test now cover `dprod:deadline`, `dprod:subjectOfDuty` and `dprod:objectOfDuty`. `dprod:schedule` is not duty-only. The obsolete `dprod:recurrence` is rejected by `RejectObsoleteRecurrenceShape` on every rule type, and there are tests for both. - **Develop namespace.** `https://www.omg.org/spec/DPROD/dprod/` is replaced throughout, including the TZID-conflict SPARQL. The namespace move stays at step 10 of #275. - **Schedule errors fit #267's fail-closed model.** There is no `errors` field on the Result. A schedule that can't be processed adds its error to a new set E₅ (`occurrences(d, Σ) = Error(e)`, computed in Step 3), and Step 7 returns `Failure(E₃ ∪ E₄ ∪ E₅)`. D-FREEZE: a duty whose schedule is in error makes no transition, so only the Permit path is affected; Deny and NotApplicable are not. `EvaluationError = Set<ResolutionError | ScheduleError>`, and the §9.7 completion argument also replaces each schedule error with some set of occurrence times. - **The workflow change is dropped.** #262 already runs `validate.py` in CI. - **`ScheduleIdentifierShape` is renamed `RejectAnonymousScheduleShape`.** The spec generator renders every NodeShape that has a `sh:targetClass` as a class except `Reject*` shapes, so the old name appeared as a spurious "ScheduleIdentifier" class. - **The core spec and examples move to `dprod:schedule`.** The data-offer-and-contract, lifecycle-and-versioning, provider-duties and odcs-mapping examples, plus the `respec/template.html` contract example, now use identified `Schedule` nodes with an explicit `DTSTART;TZID=…` and an RRULE. The overview diagram is regenerated, and `tests/test_spec_contract_sections.py` expects `Schedule` and `ScheduleFormat` classes. - **The modification dates are bumped** in the contracts docs and in `dct:modified` on the profile, shapes and vocabulary. All 9 review findings on #234 were checked and are addressed. They are: crontab whitespace, the RFC 5545 regex (EXDATE/RDATE order, duplicate FREQ), the TZ conflict and `carriesTimeZone`, how ScheduleError is consumed and totality, the `expand` signature, bare-config tests, undefined guide examples, the identifier message on its own shape, the RRULE table, and the recurrence sweep of specification.md. ## Tests - This branch: core 26 OK, contracts 98 OK, `validate.py` clean. - Integrated with #264, #266, #270, #268 and #272: core 26 OK, contracts 102 OK, `validate.py` clean. Every JSON-LD example that uses a schedule conforms to the contracts shapes. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Jacobus Geluk <jacobus.geluk@ekgf.org> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
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.
Resolves #267.
§6.1 evaluated And/Or left to right with short-circuit, and a resolution
error aborted evaluation. The outcome then depended on operand and rule
order: And(false, error) was false, And(error, false) was Failure, and
in §7.2 an error in a duty's activation condition (Step 3) aborted
before an active prohibition (Step 4) could deny. §6.2 made incomparable
operands
false, so Not(x < 5) on a string was true and a prohibitionguarded by an ill-typed comparison failed open.
are strong Kleene. Each connective depends only on the set of operand
values, and replacing an error by a Boolean never changes a Boolean
result. Errors still never become
false.operators require ordered values; otherwise ResolutionError
(Incomparable). This covers
neqandisNoneOf, which wouldotherwise fail open the same way.
in a prohibition or in a policy's applicability gives Failure only if
no prohibition is active; an error in a permission matters only if no
permission is active; duty states are still updated before the
decision steps (Step 3), a duty whose activation is in error stays
Pending, and that unknown activation gives Failure only on the Permit
path. §8.2 matches.
decision, and the duties reported with a Permit, are the same under
every resolution of the errors; Failure may be conservative). §13.2's
Dafny sketch uses Kleene connectives; S1, S5, S7 in §13.3.
"Order-independent; errors never fail open". Specification §4.8 no
longer says "hard" errors.
tables, and documentation checks that fail on the previous text.
One of five independent fixes to the formal semantics, each with its own issue and PR. The PRs overlap only in
formal-semantics.md§7.2 (Steps 5-8, between this one and the error-handling and duty-collection PRs); rebase ontodevelopafter whichever merges first.