Add or update a framework
Adding an Academic Standards framework should normally be a data and configuration change, not a curriculum-specific Python change.
The backend is designed to apply the same package validation, catalog, search, graph, resource, prompt, and MCP behavior to each accepted package.
Before you start
This repository consumes already-created curriculum graph artifacts. It does not include the upstream PDF/document extraction or knowledge-graph construction pipeline.
You need accepted source artifacts that satisfy the delivery and detailed artifact contracts before the package builder can create a graph package.
Read these references first:
- Interpretation profiles
- Prompt configurations
- Graph package format
- Manifest contract
- Validation and lifecycle
- Rights and provenance
Decide what is changing
Use a new framework ID when the conceptual framework family is different.
Use a new snapshot when the source curriculum/version changes within the same framework family. Snapshot IDs are immutable and namespaced by framework ID.
Do not edit an already passed package in place. The current package builder supports
initial package revision 1; the current loader/validator also supports revision 1.
An update therefore should be represented as a new immutable snapshot rather than an
invented later package revision.
End-to-end workflow
%%{init: {"themeVariables": {"fontSize": "18px"}}}%%
flowchart TB
SOURCE[Accepted curriculum graph artifacts] --> PROFILE[Create/select interpretation profile]
PROFILE --> PROMPT[Optional prompt configuration]
PROMPT --> DRY[Build package --dry-run]
DRY --> BUILD[Create pending graph package]
BUILD --> READONLY[Validate read-only]
READONLY -->|valid| PERSIST[Persist validation]
PERSIST --> PASSED[Terminal passed package]
PASSED --> SMOKE[Repository STDIO smoke]
SMOKE --> DOCS[Update framework catalog/docs]
DOCS --> BUNDLE{Release bundle?}
BUNDLE -- Yes --> MCPB[Build + stage-smoke MCPB]
BUNDLE -- No --> DONE[Review]
MCPB --> DONE
1. Create or version the interpretation profile
Profiles live at:
The profile is the semantic contract between generic Python and the curriculum. It should express source-local behavior such as:
- framework and jurisdiction terminology;
- local grade labels and normalized discovery facets;
- source statement types and normalized classifications;
- hierarchy levels and allowed parent relationships;
- tree versus multi-parent DAG behavior;
- root/grouping semantics;
- code format, normalization, and prefix-search policy;
- language/terminology preservation;
- rights and derivative-use policy;
- known source anomalies; and
- required disclosures.
Do not solve a profile problem by adding if framework_id == ... branches to generic
runtime code.
If profile semantics change for an existing immutable snapshot/package, create a new appropriate version/snapshot relationship rather than silently changing the bytes that a passed package checksum already binds.
2. Add optional prompt guidance
Prompt configuration lives at:
Prompt overlays are optional. Use them for framework-local terminology, warnings, context, or output guidance—not for retrieval logic that belongs in ordinary services or profile semantics.
The prompt configuration must resolve against the same profile identity/version used by the package.
3. Prepare delivery artifacts
The package builder requires canonical node and relationship JSONL inputs:
Recognized detailed artifacts may also be supplied, including the retained standards framework/items, provenance, KG bundle, hierarchy relationships, unresolved items, and validation report described in Graph package format.
Treat source wording, identifiers, hierarchy, unresolved relationships, rights, and provenance as evidence to preserve rather than values to normalize away.
4. Dry-run the package build
The safest operator interface is a JSON build specification:
uv --directory backend run --locked --no-dev \
kgfegmcp-build-manifest \
--spec ./package-build-spec.json \
--dry-run
A minimal specification has this shape:
{
"jurisdictionType": "<operator-approved type>",
"nodes": "/absolute/path/to/as_nodes_XXX.jsonl",
"outputRoot": "/absolute/path/to/data/graph_packages",
"profileId": "<profile-id>",
"profileVersion": "<profile-version>",
"relationships": "/absolute/path/to/as_relationships_XXX.jsonl",
"versionToken": "<immutable source version token>"
}
Optional fields cover detailed artifacts, explicitly named additional artifacts, snapshot relations, a source document, and authoritative source publication date.
The builder creates/proposes a pending package. It does not replace complete package validation.
5. Create the pending package
After reviewing the dry run:
uv --directory backend run --locked --no-dev \
kgfegmcp-build-manifest --spec ./package-build-spec.json
The package is written beneath:
The builder is deterministic: an already-existing byte-identical pending target can be
reported as existing_identical; it will not overwrite a different or terminal package
as though it were the same build.
6. Validate read-only first
Resolve the generated framework and snapshot IDs from the builder result, then run:
uv --directory backend run --locked --no-dev \
kgfegmcp-validate-packages one \
--framework-id <framework-id> \
--snapshot-id <snapshot-id> \
--read-only
Review every finding. Validation covers package closure and checksums, profile binding, delivery decoding, graph semantics, topology, counts, capabilities, rights metadata, and other acceptance invariants.
Do not weaken validation merely to admit one framework. If the source genuinely needs a new generic capability, model that capability explicitly and test it across appropriate packages.
7. Persist the terminal state
When read-only validation is clean, persist the pending-to-passed transition:
uv --directory backend run --locked --no-dev \
kgfegmcp-validate-packages one \
--framework-id <framework-id> \
--snapshot-id <snapshot-id>
Only a terminal passed package that still passes catalog read-only revalidation is
eligible to be served.
A failed or quarantined package is terminal and excluded. A valid but still
pending package is also excluded.
8. Verify discovery and retrieval
Run the end-to-end smoke test:
Then verify the new framework through an MCP client or focused service/tool tests:
list_frameworksreturns the framework/snapshot;get_frameworkresolves it exactly;get_capabilitiesreports package-appropriate search/resource behavior;- representative text search returns source-grounded hits;
- code search behaves exactly as the profile permits;
- hierarchy context preserves all valid parent edges; and
- declared resource access follows rights policy.
For a multi-parent package, include a representative node whose complete root paths prove that all valid parent edges survive loading and traversal.
9. Update documentation
Update the generated or maintained framework catalog and any framework-specific caveats:
- Available frameworks
- examples that intentionally name current framework/snapshot IDs;
- search-code support matrices;
- rights/disclosure notes; and
- totals derived from accepted manifests.
Do not hand-copy source facts into many pages when they can instead be generated from the manifest/profile contracts.
10. Validate packaged distribution when applicable
If the new package is part of the MCPB release, build and retain a stage:
rm -rf ./dist/kgfegmcp-stage
uv --directory backend run --locked --no-dev kgfegmcp-build-mcpb \
--stage-output ./dist/kgfegmcp-stage
uv --directory backend run --locked --no-dev kgfegmcp-stdio-smoke \
--bundle-root ./dist/kgfegmcp-stage
The stage smoke proves that configuration, profile, data, and backend files were all included independently of the source checkout.
Updating only prompt guidance
If source/package semantics do not change but prompt guidance does, version the prompt configuration according to the repository's profile/prompt policy and verify prompt rendering without altering accepted source evidence.
Prompt changes never turn model-generated text into source assertions.
When Python changes are justified
A new framework should trigger generic Python changes only when it exposes a genuinely new reusable requirement—for example a new framework-independent topology capability, resource class, or search policy that the current typed contracts cannot represent.
In that case:
- extend the generic model/service first;
- keep framework-specific values in profiles/manifests;
- add positive and negative tests across representative packages;
- update capability reporting honestly; and
- update MCP/reference contracts only if the public surface changes.
Next: Testing and acceptance