Repository navigation
Per-edge blocking policy (block|warn|hide) and OR-groups, once edges have identity #224
Description
Context
#222 / #223 made inference non-gating by note: a note whose depends_on came from a listing document's order carries depends_on_source: toc, and all of that note's edges are advisory. That is the coarse version of the problem, and it is correct for v0.5.x because the writer stamps one label per note.
It is too coarse in two ways that will bite as soon as the sources mix.
Gap 1 — the policy is per note, but it is a property of each edge
depends_on is string[]. A note can hold one authored prerequisite and one inferred predecessor at the same time, and today a single label decides the gating behaviour of the whole list. Three consequences:
- Hand-editing
depends_onon a note labelledtocleaves the label stale, so the learner's own new prerequisite is silently advisory. The remedy is deleting the label line — documented as a known limitation in fix(engine): stop letting inference gate a note #223, not fixed. - Phase 2: AI chain verification (palee verify --chain) — audit depends_on against note content, downgrade-only authority #206-style explicit prerequisite text ("requires knowledge of X") produces edges that should gate, and they would land on a note whose enumeration-derived label says they should not.
--chain-tiercomposition writesnumberedortocper note, so adding a third source means adding a third label value and a third all-or-nothing policy.
The model that fits is per-edge policy: each edge carries block | warn | hide, where warn is "show it in palee plan, do not lock the note" — which is what Moodle and Canvas surface for an unmet prerequisite — and hide remains today's behaviour for authored edges. Storage would move from a note-level depends_on_source to a per-edge shape, e.g.
depends_on:
- id: T-linear-algebra
via: authored
policy: block
- id: T-svd
via: toc
policy: warnwith today's plain string list remaining the canonical shorthand for policy: block, so existing vaults parse unchanged.
Gap 2 — AND-only composition
areDependenciesSatisfied requires every prerequisite. Real curricula are not AND-only:
- Waterloo's prerequisite grammar has "at least n of the following", which is a
min_count, not a set of independent edges. - OpenDSA models a lesson with fan-in where any one of several backgrounds suffices.
- LOM's
relationkinds distinguishRequiresfromIsPartOf; the second is membership, not a gate, and today both would be written asdepends_on. - Airflow solved the identical problem on a completely different domain with
trigger_rule(all_success/one_success/none_failed_min_one_success) on the task, not the edges.
The corresponding schema addition is an OR-group:
depends_on_any_of:
- [T-python-basics, T-julia-basics]
min_count: 1Precedents worth copying rather than reinventing
SCORM/SCORM 2004 sequencing already specifies this exactly: <imsss:preCondition> with <action>conditional</action> for soft gates versus <action>deny</action> for hard ones, and an <or> operator over <sequencingRule> trees. LRMI/LOM describe prerequisites as relation metadata rather than as blocking state. Whatever we land on should be expressible as a subset of the SCORM rule model rather than a bespoke grammar, in case vault interoperability ever matters.
Why deferred
This is a frontmatter schema addition, which means a palee_schema version question, a migration path for existing vaults, and an invariant update (INV-46/47 both speak about edges as though they were uniform). #222/#223 removed the harm the schema would have mitigated, by taking the gating away from inference entirely. Doing the schema first, before the hub work in the #205 plan settles which edges exist at all, would be designing a grammar for an edge set that is still moving.
Sequencing: after the hub rework (enumerating document becomes the predecessor, so edges get a stable identity and an authoring source), when per-edge metadata has something to attach to.
Environment
- PALEE CLI
@kuldeep2822k/palee0.5.2,fix/palee-gating-policy(#223) - Node 24, Windows 11 / Git Bash
- Related: auto-chain produces semantically false prerequisites (measured: 98.2% of edges on 10 real study vaults) - tiered fix design #205 (tiered design), auto-chain gates on inference it cannot justify; depends_on_source has a writer and no reader #222 (this gap's parent), feat: support hierarchical auto-chaining (--auto-chain) and wikilink dependency graph resolution #73 (auto-chain), Phase 2: AI chain verification (palee verify --chain) — audit depends_on against note content, downgrade-only authority #206 (prerequisite text, unclosed)
Activity
Kuldeep2822k commentedon Sep 30, 2026
Verification round - still genuinely blocked, but not on the blocker this issue names
Re-read against
feat/73-tier0-hygiene@0d222bf.The stated precondition was "once edges have identity", and
depends_on_sourcedoes not supply it. It is a note-level key (src/types.ts:568) written once per note (src/cli/adopt.ts:1021-1023), whiledepends_onremains astring[](src/types.ts:133,557) read as a whole-list short-circuit (src/engine/dependency.ts:771-789). Provenance is a property of the note's list, not of an edge, so per-edgeblock|warn|hidestill has nothing to attach to.Gap 1 is partly mooted rather than fixed: a declared edge replaces the inferred one (
src/cli/adopt.ts:677-690) because one label covers the whole list, and04f1910(#240) narrowed declaration reading to## Prerequisiteslinks only. Nopolicy,via, orblock|warn|hideexists anywhere insrc. Gap 2 (depends_on_any_of/min_count) is unimplemented and unchanged - every dependency is required atsrc/engine/dependency.ts:773-787.Two real blockers, and I would re-label the issue onto them:
palee_schemaversion decision. Edge objects with per-edge metadata cannot be read bySUPPORTED_SCHEMA_VERSION = 1(src/engine/topic-id.ts:16); anything else is rejected atsrc/validation/rules/valid-palee-schema.ts:73. That is a breaking-format ruling, not an implementation task.Keeping
status: blocked. Nothing in #214's stack unblocks this - the label work makes #224 legible, not implementable.Kuldeep2822k commentedon Oct 1, 2026
Unblocking this needs one ruling, not code. Here is the decision in the form it can be answered yes or no to.
What is actually blocked
Per-edge
block | warn | hideand OR-groups need an edge to carry a property. Todaydepends_onisstring[](src/types.ts:557area) with one note-leveldepends_on_source(:587) written once per note (src/cli/adopt.ts:1020-1024) and read as a whole-list short-circuit (src/engine/dependency.ts:771). Provenance is a property of the note's list, soblock|warn|hidehas nothing to attach to — and an OR-group cannot be expressed at all, because every entry is required.The blocker is therefore not "edges have identity" (that label work landed) but
palee_schemav2:SUPPORTED_SCHEMA_VERSION = 1(src/engine/topic-id.ts) and anything else is rejected atsrc/validation/rules/valid-palee-schema.ts. Edge objects are a frontmatter format change, and a format change is a release decision.The ruling, stated small
Adopt v2 as an additive read, exclusive write.
depends_on: [T-a]plus optionaldepends_on_sourceis interpreted as edges all sharing one source, which is exactly the current semantics. No migration required to open an existing vault.depends_on: [{ id, source, policy }]andpalee_schema: 2. A note whose edges are uniform keeps writing v1, so most vaults never change on disk.depends_onsees a union type — the loader's normalisation,validate's cycle and dangling rules,plan's readiness,roadmap's explicit-wins check, and the tests that deep-compare frontmatter. That is the real price, and it is why this is a release-sized change rather than a feature.policy(thewarntier this issue wants for enumeration edges, instead of today's all-or-nothing advisory set), anddepends_on_any_offor courses with interchangeable prerequisites.What I would not do
A parallel key (
depends_on_policy: {...}alongsidedepends_on) avoids the schema bump and buys nothing: two sources of truth for one edge, and every reader must consult both forever. The label field already shows how that goes — it had a writer and no reader for a release.If the answer is "not now"
Then this issue should say so and close, because nothing else here is pending:
block|warn|hideand OR-groups both wait on the format, and the current advisory set already covers the two cases that motivated the request (toc,tie). Leaving itstatus: blockedwith no ruling is the worst of the three outcomes.docs(adr): record the AI provider boundary and Phase-2 designs as ADR…
docs(adr): record the AI provider boundary and Phase-2 designs as ADR…
docs(adr): record the AI provider boundary and Phase-2 designs as ADR…
docs(adr): record the AI provider boundary and Phase-2 designs as ADR…
docs(adr): record the AI provider boundary and Phase-2 designs as ADR…
docs(adr): record the AI provider boundary and Phase-2 designs as ADR…
docs(adr): record the AI provider boundary and Phase-2 designs as ADR…
docs(adr): record the AI provider boundary and Phase-2 designs as ADR…