nebari-dev / nebari-dev/nebari-frames

Frame Spec v0.3 compliance: stop rejecting unrecognized content, then split name into title and identifier

Open
#85 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

type: enhancement 💅🏼
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. Parse in schema.go uses KnownFields(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. Doc needs somewhere to hold unrecognized keys, and both the YAML and .frame.md writers need to emit them.
  • Unknown section headings. framemd.go rejects any ## heading not in SlotTable. v0.3 §6.2: "A reader MUST NOT treat an unrecognized heading as an error." An unrecognized heading's content becomes guidance. 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.go rejects 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 to guidance on that account.
  • Unregistered visibility values. validate.go rejects 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 title to 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 identifier as the stable key; make title free text and drop the slug constraint.
  • Migrate frames.name and its UNIQUE (org_id, name).
  • Update everything keyed on the slug: /frames/:org/:name, nebari-frame:// URIs, CLI refs, and extends refs.
  • §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.

  • status has registered values (draft, review, approved, deprecated, revoked) under the same preserve-do-not-reject rule as visibility. Note we already have a status column on frame_versions serving a different purpose (the review-gate hook); these are not the same thing.
  • guards carries 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.
  • changelog probably maps to versionNotes, and frame_versions.digest to checksum.

Also owed

  • A conformance profile. §7 makes publishing one mandatory. It must declare whether we resolve composition transitively, and it must declare our mergeTerms replace-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: ErrParentUnreadable already does this.
  • §5.1 rule 8, cycle detection: CycleError.
  • §5.1 rule 5, elements describing the Frame itself are never inherited: resolver.go already carries Name, Description, Version, Visibility, Scope and Maintainer straight through rather than merging them from parents.
  • maintainer already 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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.