Skip to content

Build the Scantle control plane, isolated candidate verifier, contract diff engine, and orders reference platform - #1

Merged
raphael merged 142 commits into
mainfrom
phase5-integration
Oct 31, 2026
Merged

raphael merged 142 commits into
mainfrom
phase5-integration

Conversation

@raphael

@raphael raphael commented Oct 30, 2026 •

Copy link
Copy Markdown
Member

A user submits a change through propose_change, and Scantle returns a Change Card with a summary, unresolved questions, concrete examples, and preserved behaviors. This PR adds the Goa-designed HTTP/MCP control plane and checkpoint viewer for reviewing that card. confirm_intent saves the owner's complete answers and example table against the current request_revision; stale submissions fail. The original prompt, summary, and preserved behaviors remain intact. Changed answers or examples produce a new revision and discard the earlier UI composition; repeating an unchanged confirmation returns the stored confirmed card. get_change lets the owner recover the current card after a stale-editor error.

preview_architecture evaluates a submitted design for the confirmed revision in a sandbox and returns the C4 graph, agent policies, tool contracts, and a proposed swarm roster. diff compares design and go.mod structure independently of a card. draft_override updates a prompt template when an embedding host provides Options.TargetUpdater; standalone serve rejects that update, and compiled-rule changes return requires_rebuild: true. These six tools share the ui://scantle/checkpoint MCP App resource. The server also exposes the Golden Path Markdown resource.

The checkpoint viewer renders model-authored OpenUI policy comparison tables, ambiguity choices, workflow timelines, and impact callouts alongside the full intent form. Both views edit the same CP1DraftContext: selecting an option in a comparison table changes the answer shown in the form and submitted to confirm_intent. A missing, stale, or invalid composition leaves the form usable. The viewer preserves unsaved edits across unrelated results and provides explicit recovery when a saved revision has changed. Model-proposed outcomes and local example edits are labeled accordingly. Architecture graphs, structural diffs, and technical details remain available on demand in light and dark layouts.

The OpenUI component definitions also generate the catalog embedded in the model prompt. The UI build derives its TypeScript wire types and runtime result checks from the Goa-generated tool result schemas, and fails when an unsupported schema constraint appears. Partial tool arguments can update the request preview. propose_change and preview_architecture send MCP progress updates as analysis, design evaluation, and roster planning start and finish, when the caller's request includes a progressToken and the host relays them.

Scantle also includes isolated design/candidate evaluation and verification packages, with verification exposed separately through the scantle verify operator CLI. Architecture preview returns a proposed team roster; the public control-plane flow does not launch builders or approve merges. The implementation pins prerelease Goa, Goa-AI, and Model commits in go.mod.

Published @openuidev/lang-core@1.0.2 can still silently ignore bare expressions, empty assignments, and trailing tokens. Integration proceeds with that known limitation. OpenUI #1525 adds parser-owned invalid-statement and trailing-tokens diagnostics in ParseResult.meta.discarded. After a fixed upstream release is selected in Scantle's lockfile, the existing check in validateCompletedOpenUI rejects programs with those diagnostics without viewer code changes. The 24-case validator pass used a local build of that parser patch; it does not establish that the published dependency reports discarded source.

Native Safari verification exercised the actual served viewer at 390px in light and dark modes, including shared drafts, stale-revision recovery, and preservation of saved example text. This used a controlled parent relay; compatibility with a named production MCP Apps host remains unverified.

The orders example kept every run, checkpoint and run record in goa-ai's
in-memory store, so restarting the orders process lost suspended refund
approvals and run history. The new runstore package implements
storage.Store and the host session methods on one SQLite file
(modernc.org/sqlite, WAL, busy timeout).

Each Store method runs in one SQLite transaction. Mutations begin with
BEGIN IMMEDIATE, so the write lock is held from the first invariant check
to commit, and several Store handles on one file serialize correctly.
The checks and writes port goa-ai's inmem store method by method; unique
keys and conditional updates enforce the single-winner rules inside the
transaction: one record per (run, event key) and position, one seed
record per (attempt, key) and position, one published attempt per run
(partial unique index), and one successor per suspension (conditional
UPDATE on the unresolved resolution).

The contract suite ports 71 of inmem's 75 tests to the public Store API
and runs each case against both inmem.New() and the SQLite store. The
four tests not ported corrupt inmem's private maps. SQLite-only tests
cover close/reopen durability and two handles racing a seed publication
and a continuation.

cmd/orders opens the store at --state-dir (default .scantle/state,
file orders-runtime.db) and closes it after the engine stops. The
orders-eval e2e test passes a temporary --state-dir.
A review against goa-ai's inmem store found inputs where the SQLite
store returned different values:

- Rendered prompt text with invalid UTF-8 was rewritten by JSON, so an
  exact BeginRunSeed retry conflicted. Prompts are now gob-encoded.
- Times were Unix nanoseconds, which cannot represent dates outside
  1678-2262 and dropped a record timestamp's zone offset. Time columns
  now hold time.Time's binary encoding.
- FindRunPreparation and SettleRunPreparation could return found=true
  with a database error; they now return found=false on any error.
- A rejection joined with a rollback failure kept only the database
  error; the joined error is now returned unwrapped.

A new contract case runs these inputs against both stores.
# Conflicts:
#	examples/orders-eval/e2e/e2e_test.go
confirm_intent rejected every card whose status was not
needs_confirmation, so a request that propose_change confirmed on its
own could never have its examples edited, and a confirmed card could
not take a corrected answer without a new change_id.

confirm_intent now accepts needs_confirmation or confirmed cards. The
owner sends the complete answers and examples against the card's
current request_revision; a stale revision is refused. Changed answers
or examples confirm the card under a new revision, so previews bound to
the old one are refused; unchanged ones produce the same revision and
return the card as it is. The prompt, summary and preserved behaviors
are never changed here.

The diff tool now links ui://scantle/checkpoint, so an MCP Apps host can
render diff results.
Now that confirm_intent can refine a confirmed card, two editors can
work on one card. The one that saves second is refused with a stale
request_revision, and nothing could read the card the other saved: an
app-initiated save need not appear in the chat, and propose_change
refuses an existing change_id.

get_change(change_id) returns the stored ChangeCardResult to its owner
(GET /changes/{change_id}, and an MCP tool linked to the checkpoint
viewer). It reads the existing card, never changes it, and fails for an
unknown change_id or another principal. A test runs the two-editor case:
B saves, A is refused, A reads B's card and resubmits against it.
controlplane.go had grown past 1,000 lines. The four Checkpoint 1
validators (validateCP1Analysis, validateAmbiguities,
validateAmbiguityAnswers, validateConfirmedExamples) move unchanged into
their own file; no behavior changes.
…rols in flight, and retain editor on pending notifications
The CP1 model can now return an optional ui_composition: an OpenUI Lang
program built from the decision component catalog that helps the
operator decide the open questions. ProposeChange returns it on the
Change Card as UICompositionItem{program, composed_for_revision}, bound
to the revision it was composed with but never hashed into it, so
request_revision, confirm_intent and verify stay independent of it.

The program refers to ambiguities and examples by their 1-based position
in the analysis, because the model never sees the change_id and the
server assigns <change>-ambiguity-NN and <change>-check-NN from that same
order after the response. A blank program is rejected.

A confirm_intent that changes answers or examples returns the new
revision without the composition, and get_change never restores it; an
unchanged submission returns the stored card, composition included.

ProposeChange and PreviewArchitecture report MCP progress around the
model call, the design evaluation and the swarm plan. The CP1 output
allowance rises from 4096 to 6144 tokens for the composition. The
catalog prompt is embedded from internal/goldenpath/decision_catalog.md,
which the checkpoint UI build writes; this commit adds a placeholder.
The SSE test now proposes a change whose analysis carries a composition
and asserts the whole stream: both progress notifications first, then
exactly one result answering request 1, carrying the card with its
ui_composition program byte for byte and composed_for_revision equal to
request_revision.
ProposeChange rejected a whitespace-only ui_composition, and the CP1
schema's minLength made the model client reject an empty one, so a
composition the viewer could not use cost the operator the whole
analysis: no questions, no examples, no card.

The composition is presentation only. The schema now accepts any
string, UICompositionItem.program has no length rule, and ProposeChange
carries whatever the model returned, byte for byte, bound to its
revision. The checkpoint viewer already validates programs and keeps the
rest of the card usable when one is invalid. Tests cover an absent,
empty, whitespace-only, unparsable and catalog program.
…osed on unknown schema keywords, and use plain-English status copy
@raphael
raphael merged commit d11559f into main Oct 31, 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.

1 participant