OpenEnergyPlatform / OpenEnergyPlatform/oeplatform
Document and teach the OEKG REST API: generated reference, prose in two homes, two academy notebooks
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 65
- Forks
- 29
- Avg merge
- 15h 25m
- Merged PRs (30d)
- 32
Description
Spec produced by
/to-specfrom the closed documentation half of the Wayfinder Map — OEKG REST API from the shape (tickets WF-13, WF-14, WF-15, all resolved 2026-09-14). The map is now closed. The map, its fifteen tickets and this spec's sibling (the API contract, WF-01…WF-12) live in the maintainer's vault; this issue is the paste of the spec itself so the work is trackable here.The repository has no
ready-for-agentlabel, so this carriesdocumentationandOEKG-APIinstead — add the label if you want the convention fromCLAUDE.mdto be real.
Spec — OEKG REST API documentation
Produced by /to-spec from the documentation half of
Map - OEKG REST API from the shape (WF-13, WF-14, WF-15), which closed on 2026-09-14 and closed
the map with it. Each decision below is derived from a resolved ticket; the ticket holds the
measurements and the rejected alternatives.
The second of two specs. Spec - OEKG REST API from the shape covers the API contract itself
(WF-01 … WF-12) and is being implemented as the twelve slices in
Tickets - OEKG REST API from the shape. This one covers how that API is described and taught
— the destination the maintainer added on 2026-09-08: an API nobody can find their way into is not
finished.
Verified against the working tree on 2026-09-14, after PR #2398 (drf-spectacular) and PR #2452
(slice 7) merged. Three of WF-13's and WF-14's stated facts had moved and are corrected in place
below.
Problem Statement
The OEKG REST API is being built slice by slice, and nobody outside the team can find their way
into it.
A client author has no reference. The platform's OpenAPI description of its REST API is
hand-written and 60 KB, documents 47 paths against 65 routes, and has already lost the
Dataset CRUD (#1971, merged), Bulk Upload (shipped in v1.9.0) and everything OEKG. The one
OpenAPI file that does mention the OEKG describes the SPARQL passthrough, not the CRUD surface.
So the experiment between hand-written and generated has already been run in this repository, and
hand-written lost.
A reader who does find a page finds a navigation that has come apart. The scenario-bundle
material is split across two plausible homes and one page escaped both: it is indented one level too
shallow and renders as a sibling of Web-API's and Django Apps rather than as a child of OEKG
API. That same page documents a route the API replaces, while promising group-based shared editing
that was ruled out of scope and does not exist. Elsewhere a developer guide's table stated that two
dropdown lists were hardcoded when both had been made dynamic — wrong about two fields at once,
found by a reader rather than by a check.
And a researcher with a modelling pipeline — the person the whole API was built for, whose own
words seeded the map ("Die programmatische Befüllung hat keinen direkten Weg") — has no
tutorial at all. The academy teaches scenario bundles only through the UI, in a course still
marked under development.
Meanwhile the contract that is being built is full of behaviour a reader will otherwise discover by
hitting a 400: that a key absent from a payload means untouched, that delete-by-omission exists
on exactly one endpoint, that a write is judged by the violations it introduces so a create is
stricter than a patch, that a successful write can report its own audit gap.
Solution
Three layers, each with its own freshness regime, and the split between them is a rule rather than
a preference.
-
The reference is generated. One OpenAPI document for the whole
api/v0/surface, produced by
drf-spectacular from the code, committed as an artifact, rendered by the Swagger UI mechanism this
repository already uses twice, and kept honest by a CI guard that validates first and diffs
second. Every fact a signature can express —If-Match,?confirm=,?expand=, the
428/412/409table, RDF content negotiation — is an annotation, and therefore covered by that
guard. -
The prose is split by audience, across the two homes that already exist. The client author
gets the semantic rules and worked examples underweb-api/oekg-api/; the OEP developer
extending the API gets the module map on the scenario-bundles architecture guide. Prose carries
only what a signature cannot say: nine semantic rules. The developer half is generated with
mkdocstrings; the client half is protected by pointers that run from the code to the doc —
each pinning test names the page it describes, so a behaviour change fires at the moment the page
becomes wrong. -
The teaching is two notebooks in the OEP Academy, one for the researcher publishing a single
bundle and one for the pipeline author re-importing. They write to production under a per-reader
acronym namespace, clean up after themselves at both ends, and link the nine rules rather than
restating them.
User Stories
The reference (WF-13)
- As a client author, I want a single OpenAPI document covering the whole
api/v0/surface, so that
I do not have to guess which of several partial descriptions applies to the endpoint I am calling. - As a client author, I want the reference to be generated from the code, so that what it says about
a path is what the server actually does. - As a client author, I want every scenario-bundle endpoint to appear in the reference, so that the
OEKG API is discoverable at all. - As a client author, I want
If-Matchto appear as a documented header parameter with its
428/412/409responses, so that I learn the precondition protocol from the reference rather
than from a rejected request. - As a client author, I want
?confirm=<acronym>and?expand=labelsdocumented as query
parameters, so that the two-step delete and label resolution are visible without reading prose. - As a client author, I want RDF content negotiation on reads documented as a response content type,
so that I know the read side is lossless without being told in a paragraph. - As a reviewer, I want the reference to be a file in the repository, so that a pull request shows me
what the API's description became. - As a maintainer, I want CI to fail when the committed reference no longer matches the code, so that
drift becomes a red build on the pull request that caused it rather than a discovery months later. - As a maintainer, I want CI to reject an invalid schema before it compares for drift, so that a
wrong annotation cannot produce a stable, reproducibly invalid document that a pure diff passes
forever. - As a maintainer, I want the guard to tolerate the pre-existing generator warnings, so that it is
not red from its first commit for reasons this work did not cause and did not agree to fix. - As a contributor, I want regenerating the reference to be one documented command, so that fixing a
red drift check is obvious rather than archaeological. - As a docs reader, I want the Swagger page to load a committed artifact, so that the documentation
site builds and renders without a running Django.
Navigation and placement (WF-14)
- As a docs reader, I want the API reference to be its own navigation entry, so that it is a
findable destination rather than a scroll target on a landing page. - As a docs reader, I want the scenario-dataset page to sit under OEKG API where it belongs, so
that the Documentation tree stops showing it as a top-level entry beside whole sections. - As a docs reader, I want each prose rule to link into the reference at a stable tag anchor, so
that following a rule does not land me at the top of a 65-path document. - As a client author, I want the legacy scenario-dataset route and the new REST API to be
distinguishable in the reference, so that two accordions one letter apart do not read as one
domain split in half. - As a client author, I want the reference to say which of those two is superseded, so that I do not
build against the route that is going away. - As a docs reader, I want the OEKG landing page to be prose about what the API is, the token, and
the two surfaces, so that the section has an introduction rather than an embedded schema. - As a maintainer, I want the retired OEKG schema file's Python SPARQL example to survive as prose,
so that retiring a file does not delete the only example of using the passthrough.
The nine semantic rules (WF-14)
- As a client author, I want to be told that a key absent from a payload means untouched under
every verb, so that I do not send a full object to change one field and wipe what I omitted. - As a client author, I want to be told that
_metais read-only and ignored on write, so that I can
send back exactly what I read without stripping fields. - As a pipeline author, I want to be told that delete-by-omission exists on
replace/and nowhere
else, so that I understand why one endpoint is declarative and the others are not. - As a client author, I want to be told that a bundle
POSTaccepts nested sub-resources while a
PATCHdoes not, so that I do not discover the asymmetry as a validation error. - As a client author, I want the delete's two guards explained as covering two different accidents
— stale state and wrong uuid — so that neither reads as redundant ceremony. - As a client author, I want to be told that a write is judged by the violations it introduces,
and that a create is strict because it has no pre-state, so that a400on create where aPATCH
succeeded reads as design rather than as a bug. - As a client author, I want
ref: tabledocumented as reproducible andref: datasetas current,
so that I choose the one whose semantics I actually want. - As a client author, I want a dead dataset link documented as staying, read back as
resolvable: false, so that a false value reads as correct rather than as breakage. - As a pipeline author, I want to be told that the server mints the uid and that I re-identify a
bundle withGET ?acronym=— which also yields theETag— so that a stateless run can find last
month's bundle again. - As a client author, I want to be told that
_meta.history_recordedcan readfalseinside a
successful write, so that I recognise a named audit gap rather than a status flag.
The developer half (WF-14)
- As an OEP developer extending the API, I want the module map on the scenario-bundles architecture
guide, so that the feature's two write paths are documented in one place instead of two. - As an OEP developer, I want the module documentation generated from the source, so that it cannot
drift from the modules it describes. - As an OEP developer, I want that generation to be static analysis, so that documenting the API's
modules does not trigger the ontology parse the API itself is forbidden to cause. - As an OEP developer, I want the duplicated module block removed so that one page owns it, so that
a future correction does not have to be made twice. - As an OEP developer, I want a test that pins a documented behaviour to name the mkdocs page in
its docstring, so that the person changing the behaviour is told which page they have just made
wrong. - As an external contributor, I want that pointer to name a published page rather than a private
research note, so that I can act on it without access to the maintainer's vault.
The academy notebooks (WF-15)
- As a researcher, I want a notebook that walks me from a token to a published scenario bundle, so
that I can fill the OEKG programmatically without reverse-engineering the UI. - As a pipeline author, I want a second notebook that re-identifies a bundle by acronym and replaces
it, so that the re-import case the API was built for is taught somewhere. - As a reader, I want each notebook to be runnable on its own, so that I am not sent to another
notebook to produce a precondition. - As a reader, I want the notebook to obtain my token from an environment variable or a prompt, so
that the published page never contains a credential. - As a reader, I want the demo bundle to carry an acronym derived from my own identity, so that two
readers following along at the same time do not collide. - As a reader, I want every section to begin by removing a bundle left behind by an earlier run, so
that re-running the notebook after an interrupted attempt starts from a known state. - As a reader, I want the notebook to fail loudly if I have not set my own handle, so that it cannot
silently operate inside another reader's namespace. - As a reader, I want the notebook to delete its demo bundle at the end, so that following a tutorial
does not leave debris in the public knowledge graph. - As a reader, I want the two-step delete to be part of the lesson, so that I learn the protocol by
using it rather than by reading about it. - As a reader, I want the notebook to link the nine semantic rules rather than restate them, so that
a frozen page cannot teach me a rule that has since changed. - As a reader, I want each notebook to state the date and platform release it was verified against,
so that I can judge the age of what I am reading. - As a maintainer, I want re-running both notebooks to be a step in the release procedure, so that
the freeze has an owner. - As an academy maintainer, I want the notebooks to live in the academy repository alone, so that no
second copy can diverge from the one that is published. - As a platform user, I want the OEP documentation to link out to the academy tutorials through the
mechanism it already uses for every other tutorial, so that they are reachable from the platform.
Implementation Decisions
Where the description comes from (WF-13)
D1. Generated, committed, one document for all of api/v0/. A management command writes a
checked-in OpenAPI artifact that the existing Swagger page loads. This is the same pattern as the
shape fetch — one command producing a committed artifact — and it is forced by the docs build,
which runs the static site generator alone with no Django. A live-only schema route was rejected for
the same reason: dead in a local docs build, and nothing to read in a pull request.
A second, path-filtered document for the OEKG audience was declined: it needs a custom preprocessing
hook or a second URL conf, and gives a second thing to regenerate.
D2. If a fact CAN be an annotation, it MUST be. If-Match is a header parameter,
?confirm=<acronym> and ?expand=labels are query parameters, 428/412/409/400 are
responses, RDF negotiation is a response content type. All of it is annotated and therefore covered
by the drift guard, so the client-visible half of the contract cannot quietly go stale. Pushing the
semantic rules into long description strings was declined — narrative paragraphs read badly inside a
Swagger accordion, and Python files are the wrong home for them.
D3. The guard validates first, diffs second, and never fails on warnings. Measured on the merged
branch: a responses={} annotation emitted a literal empty Responses Object that the specification
forbids, and the annotation was less accurate than no annotation at all. A pure drift check would
have passed that forever. Equally measured: a full run reports 330 errors and 14 warnings, nearly
all pre-existing unable to guess serializer on plain views — the baseline this work explicitly does
not fix — so a guard that failed on warnings would be red from its first commit.
D4. The OEKG schema file retires; the hand-written OEDB schema does not. The SPARQL passthrough
view is a DRF view inside the generated document's conf, so the OEKG file is covered and goes, its
Python request example surviving as prose. The OEDB schema is a different matter: retiring it means
annotating the tables and advanced endpoints first, because a plain view with no serializer class
generates thinly. That annotation work belongs to #1971. Accepted transitional cost, named rather
than discovered: the OEDB is described twice — thinly in the generated artifact, richly in the
hand-written one, with different freshness guarantees.
Placement and the two homes (WF-14)
D5. Two audiences, two homes, stated as a rule. web-api/oekg-api/ serves the client author;
the scenario-bundles architecture guide serves the OEP developer extending the API. Picking one
audience was the wrong move — both homes already exist and each is right for one. Naming the rule is
what closes two plausible homes, not choosing a winner.
D6. A dedicated reference page with the generated artifact beside it. WF-13 decided one Swagger
page for the whole surface without placing it, leaving two loose ends: once the OEKG schema file
retires the landing page's embed points at nothing, and the generated artifact has no home. The embed
URL is relative, so placing the page places the artifact — and that is where the generating command
writes. The reference gets its own nav entry and is the stable link target the notebooks use. The
landing page drops its embed and becomes prose. The OEDB page keeps its own embed.
The page plan:
web-api/
index.md section prose unchanged
api-reference.md Swagger UI over the whole api/v0 NEW
openapi.yaml generated, CI-validated + drift-diffed NEW
oedb-rest-api/index.md keeps its hand-written schema embed unchanged
oekg-api/
index.md landing: what the OEKG API is, the token, the
two surfaces, SPARQL passthrough prose;
embed removed edited
scenario-bundles.md the nine rules + worked examples NEW
scenario-dataset.md legacy: banner, nav indent fixed,
"soon, groups" promise deleted — then DELETED
by this slice (see F1) edited, then removed
oekg.yaml retired removed
features/scenario-bundles/
index.md duplicate module block removed edited
architecture.md + "API write path" + generated module blocks edited
D7. The tag set is specified here, because the anchors are the prose's link targets. Measured:
only 33 tag declarations exist across the whole API, all on advanced and schema-meta endpoints —
nothing on datasets, nothing on OEKG, so every OEKG path takes a lowercase path-derived tag. And the
pair that looks like one domain split in half is not: scenario-bundle is the single legacy RPC
route, scenario-bundles is the entire new REST API. They render as adjacent accordions one letter
apart with nothing saying which supersedes which. The fix is explicit tags plus an ordered tag list in
the generator's settings carrying a one-line description each — and that description is where
"superseded, use Scenario Bundles" gets said, inside the reference, covered by the drift guard.
D8. Nine semantic rules, not five. WF-13's test is what a signature cannot say; four more rules
pass the same test, and each is a place where the reference is not merely silent but actively
misleading:
| rule | why a signature cannot say it | |
|---|---|---|
| 1 | a key absent from a payload means untouched, under every verb | |
| 2 | _meta is read-only and ignored on write |
|
| 3 | delete-by-omission exists on replace/ and nowhere else |
|
| 4 | a bundle POST accepts nested sub-resources; a PATCH does not |
|
| 5 | the delete's two guards cover two different accidents | |
| 6 | a write is judged by the violations it introduces; a create is strict because it has no pre-state | the asymmetry looks like an inconsistency someone would tidy away; a client seeing 400 on create but not on PATCH reads it as a bug |
| 7 | ref: table = reproducible, ref: dataset = current; a dead link stays and reads resolvable: false |
the field's type says nothing about which resolution semantics applies, nor that a false value is correct rather than broken |
| 8 | the server mints the uid; a stateless pipeline re-identifies with GET ?acronym=, which also yields the ETag |
a pipeline supplying its own identifier fails, and a 201 Location does not explain how to find the bundle again next run |
| 9 | _meta.history_recorded can read false inside a successful write |
a boolean in a 200 body looks like a status flag, not like a named audit gap chosen over a phantom entry |
D9. Two freshness regimes, and the code→doc pointer covers BOTH halves. The developer page is
generated with mkdocstrings over the API's modules — house style, four pages already do it, and the
handler is static, so documenting them imports nothing and cannot trigger the ontology parse. The
client page's nine rules stay prose, and each rule's pinning test names the mkdocs page in its
docstring.
The pointer runs from code to doc, not the reverse, because a citation in prose rots silently on a
rename while a pointer in the test fires at the moment the behaviour changes — the only moment the
page is wrong. It applies to both halves. WF-14 originally scoped it to the client half and a live
drift showed the scoping was too narrow: a table describing where data comes from is neither a
signature nor a semantic rule, so it fell through both regimes and stood wrong about two fields at
once until a reader happened to open the file. The mechanism to close it already existed in this
repository, in exactly one place — and that test pointed at a vault research note rather than at
the published page, which an external contributor cannot read.
The rule: a test that pins a behaviour a mkdocs page describes names that page in its
docstring — the page, not the vault note.
D10. The legacy scenario-dataset page is repaired and then deleted by this work. See F1: the
obligation WF-14 placed on the slice-7 pull request was not discharged, so it lands here. Until the
deletion, the page must not go on promising group-based shared editing, which was ruled out of scope
and does not exist.
The academy notebooks (WF-15)
D11. Two standalone notebooks, in their own academy folder. One for the researcher publishing a
single bundle; one for the pipeline author re-importing. Each is fully standalone — the pipeline
notebook creates its own throwaway bundle as scaffolding rather than inheriting one. This is not
tidiness: the academy's only existing chained pair breaks at exactly that seam, and the second
notebook there has never been executed.
They go in a folder of their own rather than into the existing API tutorials folder, which costs one
navigation edit in the academy — that navigation lists tutorial folders explicitly, so a new folder
does not auto-appear the way a file inside a listed folder does.
D12. A stable per-reader acronym, with no working default. Forced by the requirement that each
section check for a leftover from a prior run: a randomised acronym makes yesterday's leftover
unfindable. The handle is supplied the way the token is — environment variable, else a prompt —
because the API has no "who am I" route, so a notebook cannot derive the reader's identity from
the token. It must fail loudly when unset: a shared fallback literal plus a guard that deletes
leftovers would have two readers silently deleting each other's bundle.
D13. Every section opens by deleting the leftover and proceeding clean. Not adopt-and-continue
(which skips the create it means to teach and inherits an interrupted run's half-state) and not
refuse-and-halt. A re-run is then identical to the first run, so the frozen outputs stay a truthful
picture. It only ever destroys inside the tutorial's own acronym namespace. In the pipeline notebook
the guard is the lesson — the leftover check is literally the ?acronym= re-identification.
D14. Throwaway bundles, deleted at the end, on production. Matching the academy's established
practice, because the OEKG has no sandbox equivalent to the tables'. This makes the two-step delete
part of the lesson. Each notebook closes by saying now do it for real.
D15. Auth and transport are inherited, not decided. Plain HTTP requests against documented
endpoints; the token from an environment variable or a prompt, exactly as the existing upload
tutorial does, linking the existing things you only need to do once page. Same platform token.
D16. The notebooks live in the academy, single copy. Authored there as a pull request against its
deploy branch; the platform repository holds no copy and links out through the tutorial-URL mechanism
it already uses for every other academy tutorial.
D17. Against drift: nothing mechanical. Each notebook carries a dated banner naming the date and
the platform release it was verified against, and re-running both is a step in the release
procedure — that checklist line is the only thing carrying the chore, so it must exist or this
decision is nothing at all. See the recorded objection under Out of Scope.
Testing Decisions
What makes a good test here
Assert the artifact and the pointers, never the prose. A test that greps a page for a sentence
pins wording, breaks on an edit that improved it, and teaches contributors to work around the suite.
The three things worth asserting are mechanical: that the generated description is valid, that it
matches the code, and that every page a test claims to describe exists.
Do not assert rendered HTML, navigation ordering, or anything that requires building the
documentation site — that is a different seam and it was considered and not taken (see Out of
Scope).
The seam — one module in the existing suite
Everything testable in this work goes through the Django test suite that already exists, as one
new module. Not a bespoke CI step: a step that only runs in CI is a seam nobody exercises locally,
and the point is that a contributor who breaks the reference is told so before they push. The
existing automated-testing workflow already provisions Postgres, Fuseki and the shape artifacts and
runs the suite, so the guard needs no new infrastructure.
Three tests:
- The generated description is valid. Generate into a temporary location with validation
enabled; fail on a validation error. This runs before the drift comparison, because a wrong
annotation can produce a stable, reproducibly invalid document that a diff passes forever.
Warnings are not failures — the measured baseline is 330 errors and 14 warnings from
pre-existing unannotated views. - The committed artifact matches the code. Compare a fresh generation against the checked-in
file; on mismatch, fail with the exact command that regenerates it. The failure message is part of
the test's contract — a red drift check whose fix is not obvious is how drift gets normalised. - Every code→doc pointer names a page that exists. Collect the pointers from the pinning tests'
docstrings and assert each named documentation page is present in the tree. Without this, D9 aims
at nothing after the first rename — and pointing at a page that has moved is the exact failure the
rule exists to prevent.
What is deliberately NOT tested
The academy notebooks have no test in this repository, by decision. A pinning test per notebook —
walking each arc through the test client, with a docstring naming the academy page — was specified,
costed against the existing graph-test harness, offered with the evidence of the broken tutorial
attached, and declined. The notebooks are therefore protected by D17's banner and release
checklist alone. Recorded here as a decision with its objection, not as an oversight.
Prior art
- The existing pinning tests across the API's own test modules are where the docstring pointers
go; they exist already and the work is annotating them, not writing them. - The one test in the tree that already points at a document is the model for the pointer — and
its correction is the first application of D9, since it names a vault note instead of the published
page. - The shape-artifact tests are the prior art for a test that depends on a generated artifact and
states its skip reason rather than failing when the artifact is absent. - The REUSE check is the house precedent for a CI guard that fails a push on a mechanical property
of the tree, which is what makes the shape of this guard unremarkable.
Out of Scope
- Annotating the OEDB tables and advanced endpoints so the hand-written schema can retire. A plain
view with no serializer class generates thinly, so deleting the hand-written 60 KB document today
would make those endpoints' documentation worse. That work belongs to #1971. Accepted cost: the
OEDB is described twice until someone does it. - Client generation from the schema. The client library is a separate repository with zero
references here. The committed document enables it; building it is cross-repo work with its own
owner. - Building the documentation site in pull-request CI (
mkdocs build --strict). Offered as a third
seam and not taken. It would have caught exactly the class of defect WF-14 catalogued — the
navigation indent, an embed pointing at a retired file, a missing artifact. Consequence, stated
rather than discovered: the documentation site is still built only on a push to the integration
branch, so a broken navigation entry or a dead embed is found after the merge, by a reader. The
page plan above therefore has to be reviewed by eye. - An OEKG sandbox. The tables have one; the OEKG has no equivalent, so a reader following a
notebook writes to the real graph. D12–D14 mitigate this (own acronym namespace, throwaway bundle,
deleted at both ends) but do not close it. It is a platform feature with its own owner. - Converging the UI and the API onto one write path, and the group-based shared editing the legacy
page currently promises. Both ruled out on the map; D10 deletes the promise. - Fixing the 330 pre-existing generator errors. Named, measured, and left — they are the baseline
D3 tolerates.
Further Notes
F1 — an obligation that fell through, and lands here
WF-14 placed the deletion of the legacy scenario-dataset page and the migration of its databus prose
on the slice-7 pull request, on the reasoning that the route is not superseded until that slice
ships. Slice 7 has since merged and did not touch it: the page is still present, still promising
group-based shared editing, and the navigation indent that makes it a top-level entry is still there.
The route it documents is now genuinely superseded, so the condition WF-14 set is met and the work is
simply unowned. This spec takes it (D10): repair the page, then delete it and migrate its
databus prose into the new client page — nothing else in the documentation explains databus
registration.
F2 — corrections to WF-13's and WF-14's stated facts, measured 2026-09-14
- The generator's settings on the integration branch carry only title, description and version —
D7's tag list is not there yet, so the tag curation is entirely this work's. - The one test that points at a document still names the vault note; D9's first application has
not landed. WF-14 recorded the maintainer's call that it rides this slice rather than a one-line
pull request, provided it is in by the end of the implementation. - The duplicated module block is confirmed present on both pages.
F3 — a tenth rule is pending, and it is not this spec's to decide
Slice 7 measured a hole the replace slice inherits: a dataset link whose stored address is not a page
on this platform reads back with a null reference kind, and that body is not accepted back —
the payload cannot say an address of its own. It is pinned by a characterisation test and stated
through the read-only metadata rather than hidden. Whatever slice 12 decides becomes a tenth
semantic rule or an annotation, and the client page must gain it; the rule cannot be written before
the decision exists.
Two smaller candidates the implementing slice should judge against WF-13's test (can a signature say
it?): that dataset links have no PATCH by design because every triple follows from the payload,
and that a duplicate link is refused so a later delete cannot be ambiguous.
F4 — ordering
The reference comes first, because the prose links into it by tag anchor and the notebooks link the
prose. Within the prose, the tag set precedes the pages that anchor to it.
- The generated reference, its page, and the CI guard (D1–D4, D6).
- The tag set, including the superseded description (D7).
- The client page with the nine rules; the landing page's embed removed; the legacy page repaired and
deleted (D5, D8, D10). - The developer half: generated module blocks, the duplicate removed, the first code→doc pointer
corrected (D9). - The two academy notebooks, in the academy repository (D11–D17).
Step 5 is cross-repo and runs last for a second reason: the researcher notebook needs the delete
slice and the pipeline notebook needs the read side and replace/. Neither exists yet, so the
notebooks are the last thing to land in the whole effort, API slices included.
F5 — deployment
Nothing to deploy and no migration. The generated artifact is a committed file rendered by the
static documentation site; the notebooks are published by the academy's own deploy. The only
operational change is the release-checklist line D17 requires.
F6 — the cross-repo defect found while specifying
The academy's existing publish tutorial operates on a single shared production table rather than
on the reader's own throwaway — unlike its sibling, which randomises and cleans up. It has never
been executed (no outputs, no execution counts) and its feedback link is a placeholder. Its
existence is the measured argument for D11's standalone rule. A paste-ready issue sits in the map's
assets; it belongs to the academy repository and is not part of this work.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by reading the existing api/v0/ surface, the drf-spectacular and Swagger setup referenced by PR #2398, and the web-api/oekg-api and scenario-bundles documentation locations. Map the requirements to the generated reference, navigation and prose tests, then the two OEP Academy notebooks; done means the committed reference, linked documentation, freshness checks, and both teaching paths are present.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- django, openapi, python
- Domain
- api, backend, documentation, testing
- Issue type
- Documentation
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100