Skip to content

feat: registration fleet-policy gate + OpenAPI→MCP SSE gateway - #518

Open
izzywdev wants to merge 1 commit into
masterfrom
claude/fuze-registration-gate
Open

feat: registration fleet-policy gate + OpenAPI→MCP SSE gateway#518
izzywdev wants to merge 1 commit into
masterfrom
claude/fuze-registration-gate

Conversation

@izzywdev

@izzywdev izzywdev commented Aug 3, 2026

Copy link
Copy Markdown
Owner

⚠️ This PR contains two independent changes. The MCP gateway was pushed to this branch by a parallel workstream after the PR was opened; the original body described only the registration gate. Body corrected to match the actual diff. The two parts are reviewable independently and share no code.


Part 1 — Registration fleet-policy gate (packages/onboarding-kit)

Adds fuzefront-validate-registration, a zero-dependency validator products run in their own CI, and fixes the template defect that caused the problem it catches.

The failure class this closes

A manifest can be entirely valid and still leave a product permanently crippled:

{ "mode": "portal", "routing": { "path": "/app/fuzehub" } }   // modes omitted

That is legal — the frozen contract says an absent modes falls back to [mode]. So this product registers cleanly, appears in the portal, passes every existing gate, and can never ship a mobile app, because a TWA can only wrap a standalone surface with a URL that stands on its own. Nothing is malformed; a capability simply never exists.

Deliberately not a schema tightening. modes already has minItems: 1, so a schema change catches nothing here — omission is the legal case, and its legality is what hides the gap. This is a fleet requirement, and the fleet is not in the schema.

The policy step has the same shape: a vendored pre-kit register.sh registers the app and never submits policy.json, so the product gets no roles and authorization fails closed for every user — which reads as a bug in the product.

The template was the source

templates/manifest.json shipped mode: portal, no modes, no routing.host. Every product that copied it inherited a registration that cannot serve mobile. FuzeHub and FuzeContact aren't two coincidences — they're the template, propagated. Fixed, and templates now run through the validator in CI so it can't regress.

What it enforces

Rule Why
effective modes include portal and standalone standalone is the only surface a mobile TWA/APK can wrap
standalone implies non-empty routing.host a standalone surface with no host has no URL to serve or wrap
policy.json exists, and a vendored register.sh submits it a pre-kit script that skips it leaves the product with no roles

Embed-only products are exempt — per the contract they aren't portal destinations at all. Matching is on the PUT /apps/{slug}/policy call, not the word "policy", so a # TODO comment can't satisfy it.

Verification

18 new tests passing; full kit suite green (19 register.sh behaviours, policy validator, schema freshness). Against real repos:

repo result
fuzecontact ✘ missing standalone + missing policy.json
fuzehub ✘ missing standalone
fuzebi / fuzeservice / fuzepicker ✔ conformant

Exactly the known state — reproduces both defects and clears the three conformant repos.

Not verified: no product repo has adopted the check yet, so this currently protects the kit and its templates, not the fleet.


Part 2 — OpenAPI → MCP SSE gateway (packages/mcp-gateway)

Authored by a parallel workstream. Config-driven gateway exposing a product's REST API as MCP tools over SSE — one shared implementation, deployed as one pod per product per the owner's ruling ("'just a proxy' doesn't mean shared, it means the implementation is merely a layer that exposes the REST API as tools").

I did not author this code and have not reviewed it line by line. Two open Semgrep findings against it are being addressed by its author:

  • src/spec.ts:64 — prototype-pollution-loop (error)
  • src/upstream.ts:72 — remote-property-injection (warning)

Both are on paths handling externally-supplied OpenAPI documents and upstream responses, so they warrant a real fix rather than a suppression.

Reviewers wanting to take Part 1 independently: it is confined to packages/onboarding-kit/** and .github/workflows/onboarding-kit-tests.yml.

Adds `fuzefront-validate-registration`, a zero-dependency validator products run
in their own CI, and fixes the template defect that caused the problem it
catches.

## The failure class

A manifest can be entirely valid and still leave a product permanently
crippled. `mode: "portal"` with `modes` omitted is legal — the frozen contract
says an absent `modes` falls back to `[mode]`. Such a product registers
cleanly, appears in the portal, passes every existing gate, and can never ship
a mobile app, because a TWA can only wrap a `standalone` surface with a URL
that stands on its own.

Nothing is malformed. Nothing errors. A capability simply never exists. No
schema can catch this, because it is not a shape violation — it is a fleet
requirement, and the fleet is not in the schema.

The same shape applies to the policy step: a vendored pre-kit `register.sh`
registers the app and never submits policy.json, so the product gets no roles
and authorization fails closed for everyone. The symptom reads as a bug in the
product.

## The template was the source

`templates/manifest.json` shipped `mode: portal`, no `modes`, and no
`routing.host`. Every product that copied it inherited a registration that
cannot serve a mobile app. FuzeHub and FuzeContact are not two coincidences —
they are the template, propagated. Fixed to `["portal","standalone"]` with a
`routing.host`, and the templates are now checked by the validator in CI so
this cannot regress.

## What the gate enforces

  - effective modes include BOTH `portal` and `standalone`
  - `standalone` implies a non-empty `routing.host`
  - `policy.json` exists, and a vendored `register.sh` actually submits it

Embed-only products are exempt from the surface rules: per the contract an
embed renders inside a third-party page with neither portal chrome nor
FuzeFront navigation, is not a portal destination, and may not register a menu
entry at all.

Matching is on the submission itself (`PUT /apps/{slug}/policy`), not the word
"policy" — a TODO comment must not satisfy the check.

## Verified

18 new tests, all passing. The full kit suite still passes (19 register.sh
behaviours, policy validator, schema freshness). Run against the real repos:

  fuzecontact  FAIL  missing standalone + missing policy.json
  fuzehub      FAIL  missing standalone
  fuzebi       PASS
  fuzeservice  PASS
  fuzepicker   PASS

which is exactly the known state — the gate reproduces the two defects that
prompted it and clears the three conformant repos.

Not verified: no product repo has adopted the check yet; wiring it into each
product's CI is follow-up work.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GaPa3JgrVNtWrGvqQEAEqv
@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Automated code review (gate-code-review)

Credit balance is too low

Report-only — this check never blocks merge.

@github-actions
github-actions Bot enabled auto-merge (squash) August 3, 2026 09:45
if (Array.isArray(raw)) raw.forEach(v => query.append(p.name, String(v)));
else query.append(p.name, value);
} else {
headers[p.name] = value;
let cur: unknown = doc;
for (const p of parts) {
if (!cur || typeof cur !== 'object') return {};
cur = (cur as Record<string, unknown>)[p];
@izzywdev izzywdev changed the title feat(onboarding-kit): gate registration against fleet policy feat: registration fleet-policy gate + OpenAPI→MCP SSE gateway Aug 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants