Repository navigation
Modules: a module provides its own declared data mount (repository_db_mount) at preparation and install - #476
Conversation
…ranch Content install left every repository database out as "the Organization's own bootstrap", so a module's data mount (materialization repository_db_mount, often on branch v3) was never put on an Environment. Install now materializes it when its module is materialized or present in the same install, and reports it excluded while the module is absent. The steps behind a child repository move into one function, provideRepository (src/content/provide.ts), which a module's own preparation reuses next: a present checkout is only looked at (origin and, for a data mount, its declared branch), an absent one is asked of GitHub and cloned through materializeRepository. The clone branch is now a parameter, validated before git runs, with main kept for every existing caller. Nothing creates, initializes or pushes a repository. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Modules whose repository-db data mount was missing started without data and failed at runtime. Before every start, prepare and Launchpad Open, the module now provides each repository_db_mount declared directly below it: an absent one is cloned on its declared branch with this Environment's GitHub sign-in under the Folder's content lock, a present one is only verified and never fetched, pulled or reset. A failure refuses the start through the existing required-slot mapping with the slot: data-mount-denied (ask the Admin for access), data-mount-occupied (something else at the place, left untouched), data-mount-unavailable (retried at the next start) and data-mount-clone-failed (an empty remote or a missing branch is never bootstrapped). An explicit prepare keeps the reason too. The CLI explains each reason and the Launchpad card says it in English and Czech. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
launchpad-parity B7, content-sync, workspace-composition and the superseded alternative in decisions.md stated that repository-db mounts are the Organization's own bootstrap. They now state the Owner's direction of 2026-10-17: a module provides its declared data mount in its own preparation and fresh install, never as a background sync and never as an update of an existing mount. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…is filled Review of #476: the Owner asked only that a module provide a missing data repository. Refusing a checkout that is already there (another origin or another branch than declared) would stop modules that start today. A checkout at the data place now starts as before, whatever its origin or branch, and is never fetched or changed; install reports a present checkout as for any child (present, or blocked for another repository), without the branch check. An empty directory at the data place counts as absent: it is removed with rmdir, which refuses anything not empty, just before the clone, so a mount left as an empty directory is filled instead of blocking forever. A directory with files, a file or a link stays data-mount-occupied and untouched. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
0dbb891 to
02df847
Compare
agentrozjedemeai
left a comment
There was a problem hiding this comment.
Blocking race on the exact head 02df847: the contract says a present data checkout never refuses start, but provideRepository observes absence before GitHub lookup and does not re-inspect after acquiring the content lock or when materializeRepository reports occupied (src/content/provide.ts:93-107, 121-166). I reproduced this in an isolated checkout by making the stub GitHub repository() publish a valid checkout of example-org/orders-data at workspace/orders/db after the first absence observations and before returning access. provideDataMounts returned RequiredSlotRefused(data-mount-occupied, workspace/orders/db), although the destination was now a valid checkout. The same window exists if another content operation fills the mount while this start waits for the lock; the start can instead report data-mount-unavailable. Recheck the destination under the lock and on occupied/unavailable paths, and treat any now-present checkout as success regardless of origin or branch (without fetching or changing it). Add a deterministic race regression test for this transition. The committed focused suite passed locally (50 tests across module-data-mounts, content-install, application-environment and module-view); the additional race probe failed exactly as described. Existing materializeRepository callers still default to main, the steady-state refusal mapping reaches CLI/JSON/card, and no client Organization names occur in the PR diff.
Review of #476: provideRepository saw the destination absent before the GitHub lookup and the content lock and never looked again, so a checkout another start or install published in that window refused this start as data-mount-occupied, or data-mount-unavailable while the lock was held. The destination is now inspected again after the lock is taken (or right before the clone without one), when the lock is busy and when the publish finds the place occupied. A checkout found there is present: for a module's data mount any checkout, as at the first look; for install only one of the declared repository, as before. A regression test publishes the checkout from inside GitHub's answer, with the lock free and held. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@agentrozjedemeai Thanks, the race you reproduced was real. Fixed in 68318c6 (
If a checkout is there by then, the result is Regression test: Verified on 68318c6: |
agentrozjedemeai
left a comment
There was a problem hiding this comment.
@immakermatty Exact-head review of 68318c6: the reported successful-GitHub-response race is fixed (including a different-origin checkout and a busy content lock), but the same contract still fails when the repository lookup returns denied or unavailable after another operation publishes the data checkout. In an isolated checkout I extended the committed regression fixture: its repository() first calls checkoutWithOrigin(db, "example-org/other-data"), then returns {kind: "missing" (or the stub's unavailable answer). provideDataMounts refuses with data-mount-denied (respectively data-mount-unavailable), although workspace/orders/db is now a valid checkout and the contract says any present checkout starts as-is without asking its origin or branch. The early returns in src/content/provide.ts:134-140 bypass the new destination reinspection at 143-153. Re-inspect on these negative GitHub answers before refusing, with a deterministic test for both outcomes; never fetch or alter the checkout. On the unmodified head the focused four-file run had 50 passes and one unrelated application-environment fixture assertion failure, which passed when rerun alone; GitHub checks are green. The two added race probes above fail deterministically with the stated refusal reasons. The temporary probe was removed and the review checkout restored clean before posting. This is a blocker for the no-new-refusal-for-present-data contract; please push a new head for another exact-head review.
Re-review of #476: a checkout published while GitHub answered could still be refused when GitHub then said denied or did not answer, because only the lock, the busy lock and an occupied publish looked again. provideRepository now looks at the place once, then hands an absent one to provideAbsent, and checks every refusal it returns (denied, unavailable, occupied, clone failed, invalid) against the place once more: a checkout found there is present by the same rule as at the first look (any checkout for a module's data mount, the declared repository for install), otherwise the refusal stands. The three separate rechecks are gone. The regression test now covers denied and unavailable answers with the checkout published inside GitHub's answer. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@agentrozjedemeai Thanks. Fixed structurally in cb9feff (
The three separate rechecks from 68318c6 (after the lock, busy lock, occupied publish) are removed. The helper covers them: the publish refuses an occupied destination before cloning, so nothing is cloned in vain. Regression test (
Verified on cb9feff: |
agentrozjedemeai
left a comment
There was a problem hiding this comment.
@immakermatty APPROVED on exact head cb9feff. The earlier race is closed for the provideRepository path: after the initial inspection, every non-success outcome from provideAbsent is re-inspected at the destination, including denied/unavailable GitHub answers, a held lock, occupied publication, clone failure and invalid destination. A now-present checkout follows the existing rule (any origin for a module data mount; matching repository for installation); an absent or incompatible destination keeps the original refusal. The deterministic GitHub-response race fixture covers readable/denied/unavailable and declared/other origins, plus the lock-held case. Independent isolated checkout: 51 focused tests passed across four files, check:static passed, git diff --check clean. Live head unchanged, successful CI, no unresolved threads. This is a review verdict, not a merge.
…l help states the data mount, the candidate smoke runs again (#487) * Release: the candidate smoke accepts the preset a new Folder states `scripts/smoke-candidate.ts`, the documented check of a locally built candidate (docs/release-cycle.md, Build), expected `folder-init` to answer exactly `{kind, revision}`. Since the workspace presets (#13) the answer also names the preset, so the smoke failed on every candidate before it reached the Launchpad. It now checks the kind, the revision and that a preset is named; the rest of the smoke is unchanged and passes on a candidate built from main. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Content: install help and the parity table say a module's data mount follows it Since #476 (B7 amendment of 2026-10-17) `lazurio organization install` clones a module's declared data mount (repository_db_mount) on its declared branch when the module is there after the install. Its help still said repository databases are left out, and the parity table's row 15 said the same. Both now state the shipped behavior: the data mount follows its module, other database slots and infra stay out. Text only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Launchpad: a tile whose Open was refused shows the reason on the overview Opening a workstation module from its tile starts it first. When the start was refused (data-mount-denied, required-slot-missing, a missing secret, …) the page said "<name> could not be opened. Its overview says why." and showed the overview, but the overview only read the status (Stopped) and never said why: the reason appeared only after pressing Start there. With #476 a missing data mount is now the most common such refusal. The refused start's sentence is now handed to the overview it navigates to and shown there exactly as after Start, and a refused start ends at once instead of polling the status first. Nothing changes for a start that succeeds, is pending or for a hosted Environment's tiles. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Launchpad: a refused Open from the overview itself shows its reason there too Pablo's review of #487: the refusal reached the overview only when another module's overview was shown before; an Open from the same overview (its Open button or its favourite) left "Stopped" while the notice promised the overview says why. The reason is now put into the matching lifecycle whether it is fresh or already shown (lifecycleShown), taken once; another module's refusal stays for that module. Covered by tests/catalog-panel-lifecycle.test.ts (home tile, same overview, unrelated refusal). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Tests: a recorded duration for the new catalog-panel lifecycle test Without an entry the time-balanced shard split moved files between the two CI shards and exposed the known background-GitHub-peek flakiness (#425) three runs in a row on shard 1. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Launchpad: the overview's lifecycle state in a module of its own The test of lifecycleShown imported catalog-panel.ts, the whole Apps panel, into a parallel test worker; since then CI shard 1 failed on unrelated Launchpad tests each run (port in use, settings peeks). The pure state function now lives in src/launchpad/module-lifecycle-state.ts with type-only imports, and the test imports only that. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Launchpad: the overview's lifecycle state in a module of its own (rest of the move) Completes 507ef1a, which committed only the test's rename: lifecycleShown and its types leave catalog-panel.ts for src/launchpad/module-lifecycle-state.ts (type-only imports), the test imports that module, and tests/timings.json names the renamed test file. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
… brokered gh (#488) (#491) * Content: a Team Environment provides an absent data mount through its brokered gh Since #476 a module provides its declared data mount (repository_db_mount) before every start. On a Team Environment the gh found there is the Organization's brokered gh (Lazurio/github-app adapter/brokered-gh.mjs), and both steps of providing an absent private mount failed against it: the readability check named no repository (the adapter refuses outside a checkout, read as data-mount-unavailable on every start), and the clone reset git's credential helpers and asked `gh auth git-credential`, which the adapter denies (#488). The Platform now speaks the adapter's contract, with one rule for every Environment: gh runs outside any checkout (the filesystem root); the readability check names the repository in GH_REPO; an answer to a GitHub App identity (whose REST permissions are all false even with write) is the read; the adapter's refusals are read by their words (the broker refusing the repository, or no repository named: data-mount-denied; the broker not reachable: data-mount-unavailable); and an HTTPS clone keeps git's configured credential helper wherever gh's own sign-in probe reports a GitHub App identity, so the broker's helper with useHttpPath answers for exactly that repository. A person's gh keeps gh as the helper. On a Team Environment the Launchpad's data-mount-denied names the Team's access instead of "your GitHub account" (Czech and English); the CLI text covers both. Tests pin it against a stub of the brokered gh and of the broker's credential helper, with real git over a local server that answers 401 without the repository's credential. B7 and content-sync record it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Content: an identity probe that does not answer is GitHub not answering, never a refusal Pablo's review of #491: a repository read that succeeded with an App token's all-false permissions, followed by a timed-out `gh auth status`, was taken as not readable, so an absent data mount was refused as data-mount-denied and the Admin was asked to grant access the Team already has. The identity probe is now app | other | unknown; unknown makes the read unavailable (the start says GitHub did not answer and the next start tries again), and a definite answer is kept for the instance, so the clone after the read asks gh once. Test: the read answers, the probe times out → unavailable; once the probe answers, its answer stands. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Why
A module whose data live in a repository database (a
repository_db_mountslot such asworkspace/<module>/dbon branchv3) depends on that checkout being there. Nothing in the Platform ever puts it there: content install explicitly leaves repository databases out ("the Organization's own bootstrap", launchpad-parity B7), synchronization never touches them, and the module start only checksrequired_module_slots. In practice Environments ended up with many missing data mounts that nobody restored, and the modules started without data and failed at runtime instead of saying why.The Owner chose direction A on 2026-10-17: a module watches for its own missing data repository and provides it itself, as part of its own preparation, with a clear refusal when it cannot (B).
Target contract
materialization: "repository_db_mount"whosepathlies directly below a module (workspace/<module>/db,mission-control/db) is implicitly required for that module.lazurio module prepare) or opened from the Launchpad (all go through the one preflight inlocalApplicationAdapters), the module provides it:git.branchinto a verified temporary sibling, published with a no-replace rename — under the Folder's content lock. An empty directory is removed withrmdir(which refuses anything not empty) just before the clone is published;RequiredSlotRefusedmapping, so the CLI, the Launchpad card and the JSON result show it:data-mount-denied— GitHub refuses this identity the repository; the person asks the Admin for access;data-mount-occupied— something that is no checkout is at the place (a directory with files, a file, a link); it is never touched;data-mount-unavailable— GitHub did not answer, or another content operation holds the Folder's content lock; tried again at the next start;data-mount-clone-failed— an empty remote, a missing branch or an unreachable remote; never bootstrapped.excludedwhile the module is absent. A present checkout is reported as for any child:present, orblockedfor another repository (report-only, as for module slots).What changes
src/content/provide.ts(new):provideRepository, the one implementation of "make a declared child repository present" (present check, empty-directory fill for data mounts, readability, lock,materializeRepository), anddataMountOf, which reads arepository_db_mountdeclaration. Install'smaterializeChildnow delegates to it.src/content/git.ts,src/content/materialize.ts: the clone branch is a parameter (defaultmainfor every existing caller), validated byisBranchNamebefore git runs.src/content/install.ts: a declared data mount of a module is no longer excluded; it follows its module in the same install.src/modules/data-mounts.ts(new):provideDataMountsfor one module: a checkout already there is skipped as it is; an absent mount is provided and its failures map to the four refusal reasons.src/modules/local-application-adapters.ts,module-operations.ts: the preflight runs it first for Organization modules;ModuleHost.dataMountscarries this Environment's gh, git and the install base's content lock (the same ones content install uses). A public Environment clones anonymously.src/modules/lifecycle.ts: an explicitpreparekeeps the refusal reason and slot instead of a barepreparation-preflight-failed.src/modules/sibling-origins.ts,module-cli.ts,launchpad/module-view.ts,launchpad/messages.ts: the four reasons, CLI explanations and human-facing English and Czech card copy.What intentionally does not change
initDataRepois not used. Creating one stays withlazurio module create --data.repository_db_mount, for Personalspace modules, and for synchronization (docs/content-sync.mdexclusions stay).Verification
bun run check(lint, typecheck, public guard, proof build and smoke, fullbun test): GitHub CI on02df847is green (scope, static checks, tests 1/2 and 2/2, check ubuntu-24.04; macOS job skipped by the workflow). Locally on the same head: lint (biome, 752 files) clean,tsc --noEmitclean, public guard PASS, proof build and smoke PASS,bun test3913 pass / 9 skip / 3 fail of 3925. The local failures were timeouts in files this PR does not touch (install-script,tools-login,cli; other full runs hitlaunchpad-new-tab,content-routes,integrations-composio-organization). The set changed with every run, the machine load average was about 120 during the run, and each of those files passes when run alone.tests/module-data-mounts.test.ts(real git over local bare repositories, stub GitHub): absent → cloned onv3; present and correct → untouched (draft kept, newer remote commit not fetched, GitHub not asked again); a module without a data slot asks nothing; another repository's checkout and the right one on another branch → start goes on, untouched, GitHub not asked; an empty directory → cloned into its place onv3; a directory with files, a file and a link →data-mount-occupied, untouched, GitHub not asked; denied; unavailable; content lock held → unavailable, then provided at the next call; missing branch and empty remote →data-mount-clone-failed, nothing created, no leftover sibling, nothing pushed. A checkout published by another start or install while this one asks GitHub, whether GitHub then answers readable, denied or not at all, and with the lock held → start goes on, untouched (reviews of Modules: a module provides its own declared data mount (repository_db_mount) at preparation and install #476: every refusalprovideRepositoryreturns after its first look is checked against the place once more).tests/content-install.test.ts: a data mount below a materialized module is materialized onv3andpresenton a second install; one below a denied module isexcludedand GitHub is not asked about it; a db slot withoutrepository_db_mountstays excluded.tests/application-environment.test.ts: throughlazurio module start|prepare --jsonwith the systemd fake, a denied data mount refuses both withdata-mount-deniedandfile: workspace/orders/dbbefore any unit, the text output names it, and with access the start clones it onv3and starts the app.tests/module-view.test.ts: the four reasons render with their slot in English and Czech.Risks and rollout
data-mount-deniedinstead of starting without data.data-mount-occupiedand has to be moved aside once by the person or an agent.data-mount-unavailable(the install holds the content lock); the install's repository list still names the real reason, and the next start retries.Follow-up
AGENTS.mdand the root decision text that call repository-db mounts "the Organization's own bootstrap" will be amended separately to this contract.🤖 Generated with Claude Code