Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 20 additions & 4 deletions PLAN.md
Original file line number Diff line number Diff line change
Expand Up @@ -236,10 +236,24 @@ artifact stream in devMode with the 24-file digest set; Access shows last-match-
`edge-console` leaf rule beating the line/site catch-alls). Console clean; all four core gates green
(workspace coverage 90.70%).

**Remaining slice-5 cuts — the write path (a decision for the user, not yet built).** Authoring and
**Remaining slice-5 cuts — the write path. UNBLOCKED 2026-07-24 (register #16).** Authoring and
branch/draft orchestration both require the server to *gain a write path* into Git — the read-only cuts
deliberately do not. Branch/draft orchestration additionally depends on **W8 (concurrent drafts), still
open on the register**; it should not be invented unilaterally. These are the next decision before slice 6.
deliberately do not. The concurrency story that gated branch/draft is now ruled: **optimistic
concurrency, no locks; semantic conflict detection at the effective-config level; continuous invisible
rebase; detect-and-surface, never auto-resolve; branches are not user-visible — a draft is a named change
and the vocabulary is propose → review → apply; apply is the host's PR merge gated by CODEOWNERS.**

Two consequences to carry into the build:

- **Credentials.** The Studio holds none today by design. The write path needs Git write access:
ship **(i)** a bot/App identity with the human in commit trailers, behind the same port as
**(ii)** per-user OAuth (acting-as-user), which is the enterprise-scale target. Design for (ii),
ship (i).
- **Authorization must not drift.** Authentication, when it lands, buys attribution, acting-as-user
and "you can/cannot approve this" — **not** a role system. Authorization stays CODEOWNERS + branch
protection enforced by the Git host (register #10).

Next: the authoring cut (designable now), then branch/draft orchestration, then slice 6.

**Design-repo drift from the profiles epic — closed (2026-07-23).** This repo's `schema/` and
`fixtures/dallas/` were still the pre-profiles **flat form** (the profiles epic updated the kernel copy
Expand Down Expand Up @@ -281,7 +295,9 @@ Mock verified: all nine pages render with zero console errors; wizard walks 4 st
- `design/REVIEW.md` §6: #5 (device layers / blocked overrides — recommendation authoring-side only),
#6 (render into existing config sources first), #8 (commit render snapshots at release boundaries).
#4 (dataflows) resolves with step 2.4.
- **W8** — concurrent drafts have no story; bites in the first multi-user week.
- ~~**W8** — concurrent drafts have no story; bites in the first multi-user week.~~ **RESOLVED
2026-07-24 (user), register #16** — optimistic concurrency, semantic output-level conflict detection,
no user-visible branches. Propagated to the deck's draft cards, the mock's draft chip, and DESIGN-cli §8.4.
- Dependency: the deck's Greengrass scenario needs the deferred IPC-primary `uns-bridge` variant
(ROADMAP A52) — a real blocker recorded in REVIEW W7.1.

Expand Down
12 changes: 8 additions & 4 deletions design/REVIEW-UI.md
Original file line number Diff line number Diff line change
Expand Up @@ -194,9 +194,11 @@ instance-leaf scope, blocked-override language) and one real missing feature (bl
(`fleet.html:193`). Under the decided model every node carries a *pair* (config release, artifact
release), each with its own drift state. The fleet roll-up needs both, or it will actively mislead the
first time a config release ships without an artifact release.
- **F2 — The draft list displays the exact hazard the design has no story for (W8).** Two drafts touch
line 7 (`fleet.html:231-232`) with no overlap/conflict indicator. Until W8 is decided, the minimum is a
conflict badge when two drafts modify the same layer file or definition object.
- **F2 — The draft list displays the exact hazard the design had no story for (W8).** Two drafts touched
one line with no overlap/conflict indicator. **Answered by register #16:** presence is *advisory, not a
claim* — the surface shows "N other open drafts touch this scope" (ruling 6), while a real conflict badge
is raised by **semantic** detection at submit and apply (ruling 3 — compare rendered outputs, not layer
text), never by textual overlap alone.
- **F3 — Components as tree leaves** (`fleet.html:54`) makes "Component" an eighth pseudo-level. Fine —
but state the rule: tree tiers = `hierarchy.levels` + two fixed leaf kinds (node, component).
- **F4 — "Open draft" in the header routes to Dataflows** (`fleet.html:40`) — an arbitrary landing. Draft
Expand Down Expand Up @@ -296,7 +298,9 @@ instance-leaf scope, blocked-override language) and one real missing feature (bl
- **M7 — Secret provider management.** The component editor picks refs (`component-editor.html:81-90`);
nothing defines providers.
- **M8 — Draft management (W8).** No draft detail view: dirty objects, base release, rebase state,
conflicts with sibling drafts.
conflicts with sibling drafts. **Scoped by register #16:** the view is keyed on the change's *name*
(never a ref), shows the consequence diff when the base has moved under it, and lists semantic
conflicts for a human to resolve — it never offers auto-resolution or a lock.
- **M9 — Day-two component addition.** Adding a component to an existing node lives only inside the wizard
(U1); the component editor has no add flow, and "Add component instance" on Dataflows (`definition-map.html:40`)
routes to an editor with no such capability.
Expand Down
66 changes: 60 additions & 6 deletions design/REVIEW.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ which is the failure mode this review exists to catch. They are now consistent:
bundle's `config:`. All corrected.

**W4.1 and W4.2 are deliberately untouched** — device-scope layers and blocked overrides are decision #5,
which is still the user's to make. **W8** (concurrent drafts) remains open and unaddressed.
which is still the user's to make. **W8** (concurrent drafts) is **resolved as of 2026-07-24 — register #16**
(optimistic concurrency, semantic output-level conflict detection, no user-visible branches).

**A separate adversarial review of the mock UI now exists: `REVIEW-UI.md` (2026-07-22)** — feature/function
review of all eight screens against the deck's ch. 10 contracts, the settled decisions, and the shipped
Expand Down Expand Up @@ -393,12 +394,18 @@ Separately, chapter 4's "moving toward full hierarchical config" (`index.html:31
the shipped state (§2). The design is stronger than its own current-state chapter says, and should claim that
credit accurately.

### W8 — Concurrent drafts have no story
### W8 — Concurrent drafts have no story — **RESOLVED 2026-07-24, register #16**

The fleet mock shows two open drafts touching the same line (`fleet.html:227-234`); the draft model is "a Git
branch that freezes the base release" (`app.js:735-738`). Two branches editing the same layer file merge
textually, not semantically; nothing addresses definition-level conflict detection, rebase semantics, or lock
scope. For a multi-owner site — which the ownership matrix implies — this will surface in week one of real use.
The fleet mock showed two open drafts touching the same line; the draft model was "a Git branch that freezes
the base release". Two branches editing the same layer file merge textually, not semantically; nothing
addressed definition-level conflict detection, rebase semantics, or lock scope. For a multi-owner site — which
the ownership matrix implies — this would surface in week one of real use.

**Answered by register #16:** optimistic concurrency, **no locks**; conflict detection is **semantic, at the
effective-config level** (render `base`, `draft`, `main-now`, `merge(draft, main-now)` and compare outputs, so
the no-textual-conflict-but-changed-output case is caught); rebase is continuous and surfaced as a consequence
diff rather than a Git conflict; conflicts are detected and surfaced, never auto-resolved; and **branches are
not user-visible** — a draft is a named change, the vocabulary is propose → review → apply.

---

Expand Down Expand Up @@ -751,6 +758,53 @@ immediately.
access-policy narrowing) acquires a driver — the cheap path then is publishing each component's
existing recipe in its release descriptor and overlaying version/artifact/config.

16. **Concurrent drafts (W8).** — **RESOLVED 2026-07-24 (user).**
*Decision:* **optimistic concurrency with semantic, output-level conflict detection, and no
user-visible branches.** Seven rulings:

1. **A draft is a named change; the branch is an implementation detail.** The author names the
change ("Add file-replicator to the filling line"); the Studio derives the ref
(`draft/<slug>-<id>`). The vocabulary is **propose → review → apply** — never create-branch or
merge-branch. A draft opens implicitly on the first edit to committed state. Git remains the
only durable state; the author simply never types a ref.
2. **Optimistic concurrency. No locks, at any granularity.** A lock is a second control plane over
Git: it goes stale, it blocks across teams at exactly the scale this product targets, and
enforcing it properly needs identity that is deliberately deferred. Rejected as the mechanism.
3. **Conflict detection is semantic, at the effective-config level — never textual.** Because
render is deterministic and pure, the Studio renders four points at submit and again
immediately before apply — `base`, `draft`, `main-now`, and `merge(draft, main-now)` — and
compares **outputs**. A merged render that differs from what the draft's own diff predicted is
a conflict, whether or not Git found one. Textual auto-merge is never trusted alone; it is a
proposal that must survive an output-level check. This catches the case Git cannot see: two
drafts touching *different* files (one re-parents a node, another edits the layer at its old
scope) with no textual conflict and a changed effective config.
4. **Rebase is continuous and invisible.** The Studio keeps a draft rebased on main. When main
moves underneath, the author sees "your change now produces a different result than when you
started" plus the consequence diff — not a Git conflict.
5. **Detect and surface; never auto-resolve.** A semantic conflict requires a human decision.
Scope deletion or rename across drafts is an unconditional conflict.
6. **Presence is advisory, not a claim.** "N other open drafts touch this scope" removes surprise
without blocking anyone — the fleet surface already depicts concurrent drafts on one line.
7. **Apply is the Git host's PR merge**, gated by CODEOWNERS (already built and rendering on the
gate). No parallel approval system — consistent with #10.

*Consequence — the write path forces a credential decision now.* The Studio deliberately holds no
credentials today (the Runner port holds target credentials; the Studio holds none). Writing to Git
requires write access, in one of two shapes: **(i)** the Studio acts as itself (bot/App identity,
human attributed in commit trailers) — simple, unblocks authoring, per-user permissions unenforced
but CODEOWNERS still gates the merge; or **(ii)** the Studio acts as the user (per-user OAuth,
GitHub App user-to-server) — the host enforces everything per-user, correct at enterprise scale,
needs the auth work. **Design for (ii), ship (i) behind the same port** so the swap is plumbing.

*Consequence — the authorization posture is already decided and must not drift.* When
authentication arrives it buys attribution, acting-as-user, and "you can/cannot approve this" —
**not** a role system. Authorization stays CODEOWNERS + branch protection, enforced by the host
(#10). Authenticate users; do not build a policy engine.

*Propagated in the same change:* this register, `PLAN.md`, the deck's draft-journey and anatomy
cards (which said the UI "creates a Git branch and locks the editing context" — both now wrong),
the mock's draft chip (branch name → change title), and `DESIGN-cli` §8.4.

---

## Open doc-sync item — CLOSED 2026-07-22
Expand Down
23 changes: 13 additions & 10 deletions design/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -795,7 +795,7 @@ <h3>10. Product surface: detailed UI flows, screens, and interaction model</h3>
<div>
<span>2</span>
<b>Open a draft</b>
<p>Create a Git branch and lock the editing context.</p>
<p>Name the change. Nothing is locked, and no ref is ever typed.</p>
</div>
<div>
<span>3</span>
Expand Down Expand Up @@ -864,8 +864,11 @@ <h4 id="ui-journeys">Primary user journeys</h4>
<article>
<b>Create a deployment draft</b>
<p>
Entry is the current release lock for a site, line, or set of nodes. The UI creates a Git branch,
freezes the base release, and shows dirty fields separately from rendered artifact changes.
Entry is the current release lock for a site, line, or set of nodes. A draft is a <b>named
change</b> — the author names it, the Studio derives the ref, and nothing is locked
(register #16). Dirty fields show separately from rendered artifact changes, and the draft
stays rebased on main: when the base moves, the author sees a consequence diff rather than a
Git conflict.
</p>
</article>
<article>
Expand Down Expand Up @@ -932,13 +935,13 @@ <h4>Open the mock as an app, not as a book widget.</h4>
</div>
<div class="mock-link-grid">
<a href="mock-app/index.html"><b>Workspace setup</b><span>Empty workspace wizard, ordered hierarchy levels, instance graph, target standard, component assignments.</span></a>
<a href="mock-app/fleet.html"><b>Fleet</b><span>Site tree, target standard, exceptions, runtime evidence, open drafts.</span></a>
<a href="mock-app/definition-map.html"><b>Dataflows</b><span>Component instances, dataflows, config bindings, runtime capabilities.</span></a>
<a href="mock-app/hierarchy-config.html"><b>Config layers</b><span>Scope selector, winning values, blocked overrides, raw/effective preview.</span></a>
<a href="mock-app/component-editor.html"><b>Components</b><span>Instance fields, commands, secret refs, selected-target impact.</span></a>
<a href="mock-app/render-review.html"><b>Render review</b><span>Selected-target artifacts, consequences, and evidence.</span></a>
<a href="mock-app/release-gate.html"><b>Releases</b><span>Evidence gates, approvals, apply mode, audit preview, rollback.</span></a>
<a href="mock-app/plan-drift.html"><b>Drift</b><span>Definition, artifact, target apply, and runtime drift classification.</span></a>
<a href="mock-app/index.html"><b>Fleet</b><span>Site tree, target standard, exceptions, runtime evidence, open drafts.</span></a>
<a href="mock-app/index.html#tab=Topology"><b>Dataflows</b><span>Component instances, dataflows, config bindings, runtime capabilities.</span></a>
<a href="mock-app/index.html#tab=Config"><b>Config layers</b><span>Scope selector, winning values, blocked overrides, raw/effective preview.</span></a>
<a href="mock-app/index.html#tab=Components"><b>Components</b><span>Instance fields, commands, secret refs, selected-target impact.</span></a>
<a href="mock-app/index.html#tab=Render"><b>Render review</b><span>Selected-target artifacts, consequences, and evidence.</span></a>
<a href="mock-app/index.html#area=Releases"><b>Releases</b><span>Evidence gates, approvals, apply mode, audit preview, rollback.</span></a>
<a href="mock-app/index.html#area=Operations"><b>Drift</b><span>Definition, artifact, target apply, and runtime drift classification.</span></a>
</div>
</div>

Expand Down
33 changes: 31 additions & 2 deletions design/mock-app/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,10 +94,12 @@ function renderContextBar() {
.map((k) => `<option value="${k}"${k === state.key ? ' selected' : ''}>${esc(window.MOCK_DATA[k].workspace)}</option>`)
.join('');

// Register #16 ruling 1: a draft is a *named change*; the ref is derived, never shown as the
// primary identity and never typed by the author.
const d = state.data.draft;
el('draft-chip').innerHTML = d
? `<span class="mock-chip mock-chip--draft" title="The branch this workspace is editing">draft <code>${esc(d.branch)}</code> · ${d.changed} files</span>`
: `<span class="mock-chip" title="No draft branch — the workspace is showing committed state">no draft · reading <code>main</code></span>`;
? `<span class="mock-chip mock-chip--draft" title="The change this workspace is editing (ref ${esc(d.branch)})">draft: ${esc(d.title)} · ${d.changed} files</span>`
: `<span class="mock-chip" title="No open change — the workspace is showing committed state">no draft · showing committed state</span>`;

// Evidence provenance is global and always visible; a degraded state is designed, not promised.
const e = state.data.evidence;
Expand Down Expand Up @@ -443,11 +445,38 @@ function render() {
renderPanel();
}

/* Deep links, so the deck can point at a screen: `#area=Releases` selects a global area,
* `#tab=Config` selects a workspace tab over the root scope. */
function applyHash() {
if (typeof location === 'undefined' || !location.hash) return;
const h = new Map(
location.hash.replace(/^#/, '').split('&').filter(Boolean).map((kv) => {
const i = kv.indexOf('=');
return i < 0 ? [kv, ''] : [kv.slice(0, i), decodeURIComponent(kv.slice(i + 1))];
}),
);
const area = h.get('area');
if (area && GLOBAL_AREAS.some((g) => g.id === area)) {
state.sel = { kind: 'global', id: area };
return;
}
const tab = h.get('tab');
if (tab) {
// Pick a selection that actually offers the requested tab (Components/Topology need nodes).
const node = state.data.nodes[0];
if (node && (tab === 'Components' || tab === 'Topology')) {
state.sel = { kind: 'node', id: node.key };
}
if (tabsFor(state.sel).includes(tab)) state.tab = tab;
}
}

function load(key) {
state.key = key;
state.data = window.MOCK_DATA[key];
state.sel = { kind: 'scope', id: state.data.scopes.find((s) => !s.parent).id };
state.tab = 'Overview';
applyHash();
render();
}

Expand Down
2 changes: 1 addition & 1 deletion design/mock-app/data/dallas.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ window.MOCK_DATA.dallas =
"repo": "bottling-company-test/sites/dallas-site",
"description": "Bottles-R-Us Dallas plant — the north-star site.",
"hierarchy": { "levels": ["enterprise", "site", "line", "device"] },
"draft": { "branch": "deploy/add-file-replicator", "author": "@m.breissinger", "changed": 3 },
"draft": { "title": "Add file-replicator to the filling line", "branch": "draft/add-file-replicator-7f3a", "author": "@m.breissinger", "changed": 3 },
"evidence": { "mode": "snapshot", "age": "02:15", "source": "dallas", "degraded": false },
"scopes": [
{ "id": "enterprise/bottles-r-us", "parent": null, "layer": "layers/scopes/enterprise-bottles-r-us.json", "keys": 6 },
Expand Down
Loading