nebari-dev / nebari-dev/nebari-frames
Frame Spec v0.3 compliance: stop rejecting unrecognized content, then split name into title and identifier
Nobody has claimed this yet.
- Dominant language
- Go
- Stars
- 2
- Forks
- 1
- Avg merge
- 10h 55m
- Merged PRs (30d)
- 9
Description
Context
openteams-ai/frame-spec#28 proposes Frame Spec v0.3: a Frame data model with Markdown, YAML, and JSON encodings. v0.2 remains the released, normative spec and this registry is compliant with it. This issue tracks what would change here if v0.3 lands.
The proposal's own interop table is the motivation:
| Direction | Result |
|---|---|
This registry's example Frames, checked by the spec's validate_frames.py |
4 of 4 pass |
| The spec repository's 18 example Frames, read by this registry's importer | 0 of 18 accepted |
The failures are unknown section "## Purpose", unknown field status, and human-readable names like "Editorial Style Guide" where we demand a lowercase slug.
The theme
Our parser is strict-reject. v0.3 is preserve-and-degrade.
backend/internal/frames/framemd.go states our policy directly: strict about "unknown keys, unknown section headings, malformed bullets", because "guessing would silently mangle an author's content". v0.3 forbids rejecting on all three. Its answer is not that guessing is fine, but that there is a third option we did not take: do not guess, do not reject, keep the content as guidance.
Most of the work below follows from that one difference.
Phase 1: stop rejecting what we do not recognize
Independent of Phase 2, much cheaper, and it is what unblocks importing the spec repository's examples. Recommend doing this first and re-measuring the interop table.
- Unknown top-level keys.
Parseinschema.gousesKnownFields(true). v0.3 §4.7: readers MUST preserve unknown elements and MUST NOT reject a Frame for carrying them, and writers MUST re-emit them so a round trip through a reader that does not understand an element is lossless.Docneeds somewhere to hold unrecognized keys, and both the YAML and.frame.mdwriters need to emit them. - Unknown section headings.
framemd.gorejects any##heading not inSlotTable. v0.3 §6.2: "A reader MUST NOT treat an unrecognized heading as an error." An unrecognized heading's content becomesguidance. Note the sharp edge: §6.2 also says readers MUST NOT match labels by prefix, suffix, or similarity, so "Rules of the Game" is guidance, not the Rules section. - Malformed terminology bullets.
framemd.gorejects a bullet that is not- **term**: definition. v0.3 §6.2: "A reader MUST NOT fail on the shape of a list item"; non-matching content is unstructured terminology content. §4.4 generalises this: a reader that cannot extract a refinement's structured form MUST keep the value as that refinement's content, and MUST NOT demote it toguidanceon that account. - Unregistered
visibilityvalues.validate.gorejects anything outside the four values. v0.3: a reader MUST preserve an unregistered value and MUST NOT reject the Frame for carrying one; it MAY warn. - Security rule that comes with this. §4.7: unknown elements are metadata, not content, and a reader MUST NOT present an unrecognized element's value to an AI system as guidance. The MCP composer needs to exclude preserved unknowns from what it hands the model.
Phase 2: name is doing two jobs
Breaking, needs a migration, deserves its own design pass.
v0.3 separates the human-readable title from the stable identifier, and is explicit that our current practice is non-conformant:
Implementations MUST NOT constrain
titleto an identifier syntax such as a slug; constraining it to such a syntax contradicts the term's meaning rather than narrowing it.
nameRe in validate.go is ^[a-z0-9][a-z0-9-]{0,63}$, and it is why every one of the spec repository's 18 examples fails our importer.
- Add
identifieras the stable key; maketitlefree text and drop the slug constraint. - Migrate
frames.nameand itsUNIQUE (org_id, name). - Update everything keyed on the slug:
/frames/:org/:name,nebari-frame://URIs, CLI refs, andextendsrefs. - §3.2: when a registry assigns a Frame a different identifier on import, it MUST record the prior one in
derivedFrom. This lands on the import tool in #52.
One mercy: §6.2 keeps name as an alias for title in the Markdown encoding, so existing .frame.md files do not break. It is our constraint that has to go, not the key.
New elements
identifier, status, license, canonicalSource, derivedFrom, previousVersion, issued, and guards.
statushas registered values (draft,review,approved,deprecated,revoked) under the same preserve-do-not-reject rule asvisibility. Note we already have astatuscolumn onframe_versionsserving a different purpose (the review-gate hook); these are not the same thing.guardscarries a real composition rule: values accumulate, and a Guard declared by any Frame in the composed set applies to the result, so a child cannot drop a parent's Guard by omitting it.changelogprobably maps toversionNotes, andframe_versions.digesttochecksum.
Also owed
- A conformance profile. §7 makes publishing one mandatory. It must declare whether we resolve composition transitively, and it must declare our
mergeTermsreplace-by-key behaviour: §5.1 rule 6 permits that narrowing but only if declared. - A JSON encoding (§6.4), plus the media types in §10.
Already compliant
Worth knowing how much we get for free:
- §5.1 rule 7, an unresolvable parent must not be silently ignored:
ErrParentUnreadablealready does this. - §5.1 rule 8, cycle detection:
CycleError. - §5.1 rule 5, elements describing the Frame itself are never inherited:
resolver.goalready carriesName,Description,Version,Visibility,ScopeandMaintainerstraight through rather than merging them from parents. maintaineralready matches the schema.org term the draft points at.- Our ten slots are the whitepaper's ten categories, which is where §4.4's section labels come from.
Out of scope
Frame templates. They are an authoring affordance local to this implementation and deliberately not a spec concept (see docs/adr/0001-frame-templates-are-not-frames.md), so v0.3 does not constrain the template model, its field rules, or its levels.
Caveat
v0.3 is a working draft and openteams-ai/frame-spec#28 is open. Nothing here should be built against it until it settles. The design reasoning behind the draft is in #83.
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 with backend/internal/frames/framemd.go, schema.go, and validate.go, then read the working draft in openteams-ai/frame-spec#28 and the reasoning in issue #83. The first separable milestone is Phase 1: preserve unknown keys, headings, terminology content, and visibility values without exposing unknown metadata to the composer, then rerun the spec repository's interop check. The full issue is done only after the migration, encodings, conformance profile, and related references are designed and implemented.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- go
- Domain
- backend, databases
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100