Skip to content

Hosted context is never taken for a workstation when it cannot be read (#83) - #89

Merged
immakermatty merged 4 commits into
mainfrom
claude/DEV-6628-issue-83-hosted-context
Sep 29, 2026
Merged

immakermatty merged 4 commits into
mainfrom
claude/DEV-6628-issue-83-hosted-context

Conversation

@immakermatty

@immakermatty immakermatty commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

Why

On a hosted Machine, lazurio tools login gh and --ssh-key decide the Team rule of #66 from the preset of the declared operator's Folder. Until now, every failure on that path — an unreadable or malformed handover, a missing declared Folder, missing or malformed Folder preferences, a resolver error — collapsed into "no hosted context", so the CLI behaved as on a workstation and allowed a personal gh sign-in and SSH-key linking on a Team Environment (#83). A context that is there but cannot be read is not evidence of a workstation.

What changes

  • The real discovery is three-way. discoverHostedOperator (src/machine/operator.ts) returns absent (not Linux, no handover at all, a valid handover whose operator is another account or root), hosted (the declared operator's Folder) or unreadable (unsafe custody, a permission error, malformed JSON, a schema mismatch, operator fields that disagree with each other, an operator record that cannot be resolved). hostedOperatorFolder keeps the hostedFolder seam shape: it resolves the Folder, resolves undefined only when there positively is none, and rejects with the typed MachineContextError when the context is unreadable.
  • The preset read no longer swallows errors. hostedEnvironmentPreset (src/tools/github-gate.ts) rejects with HostedEnvironmentUnreadable when the seam rejects or the declared Folder, its state or its preferences cannot be read (a lock that stays busy counts too). One reader per command (hostedEnvironmentReader) is sticky: once a hosted Folder was found, a later read that finds none is unreadable (lost), not a workstation.
  • gh is refused before it runs. tools login gh, tools login gh --ssh-key and tools logout gh on an unreadable Environment return the existing { kind: "failed", tool: "gh", reason: "environment-unreadable" } with the existing JSON envelope and exit 1, before gh starts. A running session already ended as environment-unreadable when its re-check threw; the re-check now throws on an unreadable or lost context, so key creation, registration and known_hosts do not follow. A pending key-scope refresh keeps the existing key and the signed-in account. Nothing is signed out or removed; an already approved device flow is not undone.
  • The handover reader takes its location as a parameter. readMachineContext(source) takes the platform, filesystem root and custodian. Production stays Linux, / and root. No CLI flag or environment variable reaches it. This lets the consumer proof read real files.
  • Doctor names it. Without a Folder because the hosted context is unreadable, machine-binding is warn / handover-unreadable and no longer skipped / no-folder.
  • Docs: docs/environment-tools.md and docs/decisions.md (F19 "How the kind of Environment is known", with an addendum for Curated gh login fails open when the hosted Environment context cannot be read #83). The known limit that the CLI treats an unreadable Folder as a workstation is removed. No base-instructions template text described this behaviour, so the template is unchanged.

Every caller of the hosted discovery and its decision

Caller Needs Decision
lazurio tools login gh / --ssh-key (src/cli.ts → runToolsCommand → runLogin) gates a personal sign-in and key effects Fail closed. environment-unreadable before gh runs, and again before every later effect of a running session (sticky reader).
lazurio tools logout gh gates which account may be signed out Fail closed. environment-unreadable, nothing signed out. This matches the Launchpad, which already fails when its preset cannot be read.
lazurio tools login composio / wacli not under the Team rule Unchanged. Runs as before. No shared-sign-in notice, because the kind is unknown.
lazurio tools install text only (shared-Environment wording) Degrade: the workstation text, as before.
lazurio tools prompt gh text only Degrade: the ordinary prompt, which already carries the Team note "where the kind is not known". The sign-in it leads to refuses.
lazurio update, update status (standardFolder) Folder to report a needed refresh against, read-only Keep: no Folder, so no refresh report. The seam's rejection is caught explicitly and documented. Before, it would have surfaced as a usage error.
lazurio organization …, lazurio module … (operatorFolder) which Folder to act on Keep: no Folder, so blocked / folder-unknown. They never act on a guessed Folder, which is already fail-closed for effects. Proven on real files in the new suite.
lazurio chat link (operatorFolder) the recorded entry, read-only Keep: answers without an entry, as on a workstation without a unit.
lazurio doctor diagnosis Visible notice. machine-binding warn handover-unreadable.
lazurio recover / Recovery source diagnosis, acts on no unnamed Folder Keep (explicit .catch, now commented). Its own handover readout already shows null.
Launchpad hosted mode its own served Folder (folderPreset), not this discovery Not a caller. Already fail-closed (operation-failed, environment-unreadable for sessions).
lazurio machine inspect / folder-init / folder-refresh readMachineContext directly, typed errors Unchanged. Default source is production.

What does not change

Verification (macOS, pinned Bun 1.4.2)

  • The twelve test: replace misleading checks and define fail-closed hosted GitHub behavior #87 regression tests pass, as ordinary assertions: tests/tools-team-github.test.ts 37 pass, 0 fail.
  • New consumer proof tests/hosted-context-discovery.test.ts, 15 pass, 0 skip. It uses a real lazurio.machine.json built from the repository's P3 fixtures and a real Folder in a private temporary root, the real discoverHostedOperator → hostedOperatorFolder → hostedEnvironmentPreset, and the real lazurio tools CLI with fake gh and a temporary HOME. Cases:
    • no handover: absent, and login reaches gh;
    • valid single-operator: hosted, and login reaches gh;
    • valid Team: team-environment;
    • unreadable (mode 000; skipped only as root), unsafe custody (0666), malformed JSON, off-schema, declared Folder missing, Folder without state, without preferences, with malformed preferences: each is environment-unreadable for login, --ssh-key and logout, gh is never called, gh.state is kept and no .ssh is created;
    • operator record unresolvable: unreadable and refused;
    • inconsistent operator fields: invalid;
    • platform other than Linux, another account: absent;
    • organization list on an unreadable handover: folder-unknown.
  • Doctor: one new test, with 13 pass in the suite.
  • Full suite: 999 pass, 4 skip (existing platform skips), 0 fail, 37 snapshots, 1003 tests across 115 files.
  • bun run lint, bun run typecheck, bun run check:public, bun run build:proof and bun run smoke:proof pass.
  • Not run here: Linux. The Ubuntu CI job is the Linux run; the discovery test is not platform-specific because the source names the platform.

Open questions

  1. Another account on a hosted Machine (a valid handover whose operator is not this account, or root) stays absent, which means gh login is allowed for that account, as before. Should a non-operator account on a Team Machine also be refused?
  2. update, organization, module and chat link keep "no Folder" on an unreadable context. They do not refuse with a distinct environment-unreadable reason. Should they say so explicitly, beyond doctor?
  3. Seam in readMachineContext. The handover reader gained a parameter-only source (root, custodian, platform) for the consumer proof, because production had no path override (by design, none via CLI or environment). Confirm that this seam is acceptable.

Refs #83 (implements it; closing the issue is left to the Principal)
Contains the test contract of #87 (commits efc74da, e6fa282) unchanged.

🤖 Generated with Claude Code

Note

Fail GitHub tool operations closed when hosted context is unreadable

  • Adds three-way hosted discovery in operator.ts: positively absent, readable with a Folder, or unreadable. Unreadable handovers, unsafe custody, unresolved operator records, and missing/malformed Folder data reject with a typed MachineContextError instead of collapsing into a workstation result.
  • gh login, install, and logout now carry explicit none/hosted/unreadable environment state via github-gate.ts and curated-cli.ts. Unreadable context fails with exit 1 before any gh call, and credentials and SSH keys are never removed. A previously found hosted Folder that disappears mid-command is reported as lost, not absent.
  • The doctor machine-binding check in doctor.ts reports an unreadable-handover warning instead of the skipped/no-folder workstation result.
  • standardFolder in update/cli.ts treats a rejected hosted lookup as no Folder, so Folder-independent update paths keep running.
  • Extensive new test coverage in hosted-context-discovery.test.ts and tools-team-github.test.ts covers the unreadable fault matrix and mid-login evidence loss.
  • Behavioral Change: callers of hostedOperatorFolder now get a rejection for unreadable context where they previously received undefined; GitHub login/logout on unreadable hosted context now exit 1 without invoking gh, replacing the old workstation fallback.

Macroscope summarized b8fd319.

immakermatty and others added 4 commits September 28, 2026 22:11
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
#83)

The hosted operator discovery now tells three kinds apart: positively
absent (not Linux, no handover, another account), hosted (the declared
operator's Folder) and unreadable (unsafe custody, a permission error,
malformed or off-schema handover, inconsistent operator fields, an
operator record that cannot be resolved). `hostedEnvironmentPreset`
rejects with `HostedEnvironmentUnreadable` when that context, or the
declared Folder, its state or preferences, cannot be read.

gh's curated login, key linking and sign-out stop before gh runs as
failed / environment-unreadable; a running session re-reads the context
through one sticky reader per command before each later effect, so a
lost or no-longer-found hosted Folder stops key creation, registration
and known_hosts. Nothing is signed out or removed.

Commands that only look up a Folder keep taking none from an unreadable
context; doctor names it as machine-binding warn handover-unreadable.
The handover reader takes its root and custodian as a parameter only, so
a consumer proof reads real handover and Folder files in a private
temporary root.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

@agentrozjedemeai agentrozjedemeai left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@immakermatty APPROVED on exact head b8fd319; do not treat this as a merge instruction.

#87 contract is intact: git diff e6fa282 -- tests/tools-team-github.test.ts tests/qualify-evidence.test.ts is empty; all 12 ordinary regression assertions pass. Real-file discovery covers absent, single-operator, Team, unreadable/unsafe/malformed handover, missing Folder/state/preferences and malformed preferences; unreadable never becomes workstation for gh. The typed errors, sticky lost-context reader, initial and running-session refusals and credential-preserving logout refusal check out. Independently reproduced the issue #83 empty Folder and throwing resolver: both refuse login, --ssh-key and logout before gh is invoked. Test-only source is not exposed by production CLI or environment, whose Linux default remains / with UID 0 custody. I traced the hosted discovery callers: curated gh fails closed; install/prompt and composio/wacli are unaffected; update, organization/module, chat link and recover act on no guessed Folder; doctor warns. No concrete customer/Organization/host or private operational detail introduced in this public diff (fixture names are placeholders).

Local pinned Bun 1.4.2: 999 pass, 4 platform skips, 0 fail; lint, typecheck, public guard (385 files), build:proof, smoke:proof and diff --check pass. Exact-head CI: ubuntu-24.04 and macos-14 both pass; zero unresolved threads at review.

Open questions: (1) Keep another-account behavior unchanged for #83; decide separately whether the Team rule should prohibit non-operator accounts, with an explicit account-scope contract and tests, rather than infer operator authority. (2) Prefer distinct environment-unreadable diagnostics in organization/module and read-only surfaces as a follow-up for operator clarity; current no-guessed-Folder refusal is safe. (3) Accept the parameter-only source for real-file tests while preserving unoverrideable production defaults and custody checks.

@immakermatty
immakermatty merged commit 446330e into main Sep 29, 2026
3 checks passed
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