hosystem Engagements

ho-05 — The Workshop: SwiftUI shell

created 2026-07-10
status complete
type ho-document
project sharibako
ho 05
kamae 5
shape ha
agent-tasks
  • Ho-05-AT-01.md
  • Ho-05-AT-02.md
  • Ho-05-AT-03.md

Stand up Sharibako.app as a real three-pane SwiftUI window that opens the existing dogfooded vault, lists it, views and edits secrets, materializes a scope, and syncs. Every operation routes through SharibakoCore — the same engine the CLI drives. This is the first surface built on top of the library that isn't the CLI; the discipline for the whole is the GUI is a surface, not a second implementation.

Phase 3 (the CLI) is closed and hardened through ho-04.15. ho-05 does not reopen it. The one architectural pull toward the CLI — the Keychain reveal path lives in SharibakoCLI — is resolved by giving the GUI its own small adapter against the public SharibakoCore seam, touching zero CLI files (Decision 1).

Out of scope (all ho-06 or later):

Resolves deferred decisions (from the ho-overview's ho-05 entry):


Phase 1 —

Eight decisions before the spec lands. The heaviest is Decision 1 (it decides the target graph and keeps the closed CLI closed); the rest follow from it.

Decision 1 — Keychain reveal: the GUI owns a thin adapter; the CLI is untouched

The GUI's reveal/decrypt path needs the age key. The Keychain retrieval (KeychainAgeKeyProvider, AgeKeyProvider, KeychainProbe, TempKeySignalGuard) and vault-path resolution (VaultLocator) all live in SharibakoCLI — an executable target the GUI cannot and should not depend on. SharibakoCore is deliberately portable (Linux-buildable, no LocalAuthentication / Security).

Three options were weighed:

The duplication is a small, stable, security-reviewed adapter — accepted because the alternative is editing a closed target. If the Keychain query ever needs to change in both places at once, that is the moment to reconsider extraction; not before.

Shared Keychain item. The GUI reads the same item the CLI writes: service sharibako, account sharibako.age-key, access group 3N8F759K8D.net.sageframe.sharibako. The GUI's entitlement declares that access group. Consequence: a vault set up via the CLI opens in the Workshop with no re-keying, and vice versa.

Decision 2 — App state: one @Observable, @MainActor root model

A single WorkshopModel (Observation framework's @Observable, macOS 14+), @MainActor, injected once via .environment. It owns the resolved configuration and constructs VaultCore / Materializer / Conduit per operation from the resolved vault URL. Views read published state and call the model's intent methods; no view touches SharibakoCore types directly beyond displaying them. This is where all testable logic concentrates (Decision 8).

Decision 3 — Vault and scan-root resolution: no wizard, no guessing

The GUI has no command-line flags and no first-run yet (first-run is ho-06), but must find a vault and a scan root.

Decision 4 — Secret reveal idiom: Touch ID, stays until selection changes

Values render masked by default. Revealing a value triggers Touch ID (through the Decision 1 adapter) and the plaintext stays visible while that secret is selected; changing selection (a different secret, a different scope) re-masks it, and re-revealing re-authenticates. No auto-hide timer, no clipboard-clear — those are ho-06 refinements. This matches the overview's v1 default.

Decision 5 — Mutation boundary: CRUD and both output verbs; linking is ho-07

ho-05 carries these writes, each a direct call into SharibakoCore:

The link target picker (browse shared/, search, "what links here?") and the shared-secret browser stay ho-07 — that is the -defining rotation-propagation feature and Phase 5 exists to build it right across both surfaces. ho-05 shows that a key is a link (from inspect's .link(sharedID:)) and can create a shared entry; it does not let you bind a scope key to a shared entry from the UI.

Decision 6 — Two small Core additions: Conduit.log and VaultCore.updateNotes

The detail pane's rotation history and its notes editing each need a capability the library doesn't expose yet. Both are small, belong in Core (not the view layer), and get their own unit tests.

Decision 7 — Xcode project, signing, and the file-key dev loop

Decision 8 — Coverage: logic in the model is tested; View structs are excluded

All branching logic lives in WorkshopModel and small pure helpers, unit-tested to the project's ≥90% floor. SwiftUI View structs (declarative body, hard to drive headlessly) are coverage-excluded by extending ci.yml's named-EXCLUDED regex, the same convention KeychainAgeKeyProvider|ExecReplace|TerminalDetector| SecureValuePrompt already uses. The GUI's own Keychain adapter joins that excluded set (dogfood-only, like the CLI's). Every exclusion is a named file with a comment saying why.

Discovery (deferred to execution) — how the .xcodeproj is generated

Hand-authoring a pbxproj is error-prone; the realistic options are a committed project created once through Xcode's own new-app template, or a generator (XcodeGen / Tuist) checked in as the source of truth. AT-01 picks the lightest path that yields a reproducible, committed project and does not add a heavyweight build dependency without surfacing it first — see AT-01's Stop Condition. The constraint is fixed regardless of tool: the project references the local package, and Package.swift stays canonical for CLI/CI.


Phase 2 — Execute

Branch ho-05 off main. Three agent tasks, executed and verified in order — each has a dependency on the one before (the model can't be built without the target graph; the actions can't be tested without the read surface).

Ho-05-AT-01 — Foundation: Xcode project, GUI Keychain adapter, WorkshopModel, sidebar

Xcode project + entitlements; the GUI's own Keychain reveal adapter (Decision 1); WorkshopModel with vault/scan-root resolution (Decisions 2–3); the scope sidebar grouped by ScopeType. Model: claude-opus-4-8. → /agent-tasks/Ho-05-AT-01.md

Verifiable: the app opens against the real vault (file-key dev path), lists its scopes grouped by type, or shows the "no vault" empty state when the path is empty.

Ho-05-AT-02 — Read + reveal: secret list, detail pane, Touch-ID reveal, history

Center secret list (from inspect); detail slide-in with masked value, Touch-ID reveal (Decision 4), notes, link-target display, and rotation history via the new Conduit.log (Decision 6). Model: claude-sonnet-4-6. → ho-process/agent-tasks/Ho-05-AT-02.md

Verifiable: select a scope, view its secrets, reveal a real value with the file-key dev path, read the secret's git history.

Ho-05-AT-03 — Write + actions: add/rotate/notes, materialize, sync, rescan

Add Scope / Add Secret / Add Shared Entry; edit value (rotate) and notes (updateNotes, new — Decision 6); Materialize, Sync, Rescan (Decisions 3, 5). Model: claude-sonnet-4-6. → ho-process/agent-tasks/Ho-05-AT-03.md

Verifiable: full round-trip against the vault, then the signed-install dogfood gate (the only proof the real Keychain reveal works).

Testing and iteration approach

Per task: the verification rhythm — swift build -Xswiftc -warnings-as-errorsswift-format lint --strictswiftlint --strictswift test → coverage ≥90%. WorkshopModel and the Core additions (Conduit.log, updateNotes) carry the coverage; View structs and the Keychain adapter are excluded by name (Decision 8). Run swift package clean before Swift-touching commits — the known SwiftPM incremental-link bug after SharibakoCore changes bites this ho every time the shared model changes.

The whole session iterates against a file-based age key and the real (read-only-until-you-mutate) dogfooded vault, unsigned. The signed build is built once, at AT-03's gate.

Done means

Verification and the dogfood gate

  1. The rhythm above, green, on the ho-05 branch.
  2. Dogfood gate (signed install + Touch ID — the only thing that proves the real reveal path): build and sign the .app (identity from palana), install it, open the existing vault, and reveal a real secret through Touch ID — not the file-key bypass. Confirm the shared-Keychain-item claim: a value the CLI can get reveals in the Workshop with the same age key, no re-keying. Materialize a scope from the GUI and confirm the .env matches sharibako materialize. Not done until this passes.

Phase 3 —

Executed 2026-07-10: three agent tasks (AT-01 Opus 4.8; AT-02/03 Sonnet 4.6) driven sequentially by a Fable orchestrator, with the practitioner at the two human gates. The dogfood gate ran three build-fix-rebuild rounds before passing.


Appendix — fresh-session bootstrap

To execute this ho in a new Claude Code session, load and run:

Execute ho-05 (Think is ratified — do NOT relitigate the decisions, especially
Decision 1: the GUI owns its Keychain adapter and the CLI is NOT touched). Read:
  @ho-process/hos/ho-05-workshop-shell.md            (this doc — the plan)
  @ho-process/agent-tasks/Ho-05-AT-01.md             (foundation)
  @ho-process/agent-tasks/Ho-05-AT-02.md             (read + reveal)
  @ho-process/agent-tasks/Ho-05-AT-03.md             (write + actions)
  @Sources/Sharibako/App.swift                        (the placeholder to replace)
  @Sources/SharibakoCore/VaultCore.swift  @Sources/SharibakoCore/VaultCore+Encryption.swift
  @Sources/SharibakoCore/Materializer.swift  @Sources/SharibakoCore/Conduit.swift
  @Sources/SharibakoCLI/Support/KeychainAgeKeyProvider.swift  (the adapter to MIRROR, not import)
  @CLAUDE.md  @~/.claude/modules/languages-swift.md   (conventions)

Execute AT-01 → AT-02 → AT-03 in order (verification dependency). Signing
details come from ~/Vaults/sageframe-no-kaji-dev/palana at AT-01. Run
`swift package clean` before Swift-touching commits. Verify each task with the
lint/build/test/coverage rhythm; close the ho with the signed-install + Touch-ID
dogfood gate. Branch ho-05 off main; PR-based. Do not sign commits or PRs.

_Authored 2026-07-10 (Think phase ratified same day). Executed and reflected 2026-07-10: three agent tasks on branch ho-05, closed with the signed-install

Rendered from the corpus, verbatim · source on GitHub →

ingested: sharibako @ 75a79d08a2b3 · ho-system @ 0f93b7fa32f7 · the glossary · the colophon