Skip to content

external-workspaces — An external workspace is read the way this repository reads anything else, and its content never becomes public by accident

A workspace is a declared object of the layer — vendor, identity, what the operator authorised, which browser profile reaches it, which capabilities it is expected to have — and rides the existing kind pipeline onto every surface with no code that special-cases it. Its content is synced by the Node tooling layer that already drives the system browser, lands under `.ai/local/workspaces/`, carries provenance and a support level on every record, and is read back through exactly one capability shaped like `continuity`: served, never published. A sync is incremental from a durable checkpoint, idempotent on replay, fails closed when an observed contract breaks, and never writes a credential anywhere.

ACTIVE 1 of 10 issues done

ready
1
blocked
8
active
0
verify
0
done
1
cancelled
0

Problem

The operator holds work in vendor-hosted workspaces — a ChatGPT Project first, others after — and has no machine access to it. Whatever is decided there is invisible to every instrument this repository has: it cannot be searched, cited, carried into a handover, or linked to the decision it produced. The obvious repair is the wrong one twice over. Scraping a page produces a corpus with no identity, no provenance and no way to tell a change from a re-render; and building the corpus into the layer the way tracked content is built would publish, on a public website, a body of private conversation the vendor holds under an authenticated session. ADR 0025 settles the shape: the catalogue of workspaces is the repository's own statement and is tracked; the content is this checkout's state and is not.

Outcome

A workspace is a declared object of the layer — vendor, identity, what the operator authorised, which browser profile reaches it, which capabilities it is expected to have — and rides the existing kind pipeline onto every surface with no code that special-cases it. Its content is synced by the Node tooling layer that already drives the system browser, lands under `.ai/local/workspaces/`, carries provenance and a support level on every record, and is read back through exactly one capability shaped like `continuity`: served, never published. A sync is incremental from a durable checkpoint, idempotent on replay, fails closed when an observed contract breaks, and never writes a credential anywhere.

Current state

Nothing exists. There is no workspace kind, no adapter, no store, no capability and no gate. The repository does drive a browser — Playwright over the system Chrome, in `scripts/lib/ui-audit.mjs` and `scripts/lib/cockpit-probe.mjs`, wired into CI and skipping cleanly where Chrome is absent — and it has no convention at all for holding a credential, only a negative one that fails the site build when a secret-shaped string reaches rendered output.

Desired state

`majordomus workspaces` lists what is declared and what is synced, with the age and the support level of each. A sync is one command in the tooling layer, resumable, bounded, and provable from sanitised fixtures in CI without touching a live account. Adding a second vendor means writing an adapter and a declaration, not editing every consumer. Nothing under `.ai/local/workspaces/` reaches the website, the generated documentation, or any published projection, and a gate proves it rather than a sentence promising it.

Scope

  • .ai/repo/adrs
  • .ai/repo/project
  • .ai/repo/rules/project
  • .ai/repo/policy.yaml
  • .ai/repo/ci/gates.yaml
  • apps/majordomus-cli/src/capability/builtin
  • share/kinds.yaml
  • share/allow
  • share/schemas
  • scripts
  • docs
  • test/cases

Out of scope

  • Any write to the vendor — no message is sent, no file is uploaded, no conversation is renamed
  • Any dependency added to majordomus-cli; the boundary of ADR 0025 is the point
  • Any credential, cookie, token or authorisation header stored anywhere in the tree or the state directory
  • Any bypass of authentication, multi-factor prompts, rate limits or anti-abuse controls
  • Publishing synced content to the website or any public projection
  • A second vendor adapter before the first one is proven

Acceptance criteria

  • A workspace declaration is a tracked object of a declared kind, closed by an allow file, described by a schema, and reaches the object surface with no code that special-cases it
  • Every synced record carries its upstream identity, a local identity that does not depend on a title, a content hash, the transport that produced it and a support level, all as typed fields rather than prose
  • The store under .ai/local/workspaces/ has a declared layout, a checkpoint, a retention cap in policy and a doctor stanza that measures it
  • A sync resumes from its checkpoint, produces the same store when replayed, and marks a record removed upstream rather than deleting it silently
  • An observed contract that breaks fails closed and falls back to the browser transport; no partial parse is ever persisted
  • CI proves the mapper and the sync from sanitised fixtures, with no live account and no network
  • No credential reaches the repository, the state directory, a fixture, a log or a snapshot, and a gate greps for it
  • A gate fails if majordomus-cli gains a dependency, and a gate fails if any published projection reaches .ai/local/workspaces/
  • The subsystem is documented in docs/ and every rule it states is enforced by a check CI executes

Validation

  • bash test/run.sh
  • bin/majordomus doctor
  • cargo test --workspace --all-features
  • majordomus generate --check

Evidence required

  • workspace_kind_declared
  • identity_and_provenance_typed
  • store_bounded
  • sync_idempotent
  • fails_closed
  • fixtures_prove_it
  • credentials_isolated
  • boundary_gated
  • rules_enforced

Risks

  • The vendor's private surface is not a contract and will change without notice. Anything that treats an observed shape as canonical will break loudly and, worse, silently. The observed shape is a transport detail with a fingerprint and a compatibility state; the canonical model is this repository's.
  • A synced corpus is the largest body of text this repository has ever held and the only one it did not write. Every mechanism that walks files must be told about it explicitly, because none of them will notice it on their own.
  • The distance between reading one's own authorised workspace and automating a vendor's site is a policy, not a technicality. The declaration names what was authorised; the adapter never exceeds it.
  • The tooling layer is a devDependency of this checkout, not part of the released tool. A feature that only works where node_modules is installed must say so rather than appear broken.

Issues

Each one is an execution contract with its own acceptance criteria, validation command and required evidence. The wave column is the layer of the dependency graph it sits in.

issuestatuswavedepends ontitle
I1301 DONE 0 A workspace is a declared object of the layer; only its content is local
I1302 READY 1 I1301 Identity, provenance and support level are typed fields, not prose
I1303 BLOCKED 2 I1302 The store is bounded, measured and invisible to every published projection
I1304 BLOCKED 2 I1302 The browser transport is the one this repository already drives
I1305 BLOCKED 3 I1304 An observed contract has a fingerprint, a compatibility state and a fixture
I1306 BLOCKED 4 I1303, I1305 A sync resumes, repeats itself without harm, and says how fresh it is
I1307 BLOCKED 4 I1302, I1305 The ChatGPT shape is mapped from evidence and never becomes the canonical model
I1308 BLOCKED 3 I1303 The executable's share is one capability, shaped like continuity
I1309 BLOCKED 4 I1303, I1308 The boundaries this milestone asserts are gates, not sentences
I1310 BLOCKED 5 I1308, I1309 The subsystem is documented where a person looks, and the plan is closed against the repository

The graph

external-workspaces dependency graphfrom .ai/repo/project
flowchart LR
    I1301["I1301<br/>A workspace is a declared object of the layer; only its content is local"]:::done
    I1302["I1302<br/>Identity, provenance and support level are typed fields, not prose"]:::ready
    I1303["I1303<br/>The store is bounded, measured and invisible to every published projection"]:::blocked
    I1304["I1304<br/>The browser transport is the one this repository already drives"]:::blocked
    I1305["I1305<br/>An observed contract has a fingerprint, a compatibility state and a fixture"]:::blocked
    I1306["I1306<br/>A sync resumes, repeats itself without harm, and says how fresh it is"]:::blocked
    I1307["I1307<br/>The ChatGPT shape is mapped from evidence and never becomes the canonical model"]:::blocked
    I1308["I1308<br/>The executable's share is one capability, shaped like continuity"]:::blocked
    I1309["I1309<br/>The boundaries this milestone asserts are gates, not sentences"]:::blocked
    I1310["I1310<br/>The subsystem is documented where a person looks, and the plan is closed against the repository"]:::blocked
    I1301 --> I1302
    I1302 --> I1303
    I1302 --> I1304
    I1302 --> I1307
    I1303 --> I1306
    I1303 --> I1308
    I1303 --> I1309
    I1304 --> I1305
    I1305 --> I1306
    I1305 --> I1307
    I1308 --> I1309
    I1308 --> I1310
    I1309 --> I1310
    classDef done stroke:#16a34a,fill:#052e16,stroke-width:2px
    classDef active stroke:#2563eb,fill:#eff6ff,stroke-width:2px
    classDef verify stroke:#7c3aed,fill:#f5f3ff,stroke-width:2px
    classDef ready stroke:#0891b2,fill:#ecfeff,stroke-width:2px
    classDef blocked stroke:#b45309,fill:#fffbeb,stroke-width:2px
    classDef cancelled stroke:#6b7280,fill:#f9fafb,stroke-width:2px

Canonical record: .ai/repo/project/milestones/external-workspaces.yaml. Read it back with majordomus plan show external-workspaces.