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
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ All notable changes to this project will be documented in this file.

### Fixed

- **Megarepo tasks**: Remove `after` and `before` edges to tasks omitted through
`disabledTasks`, including the optional bootstrap-to-Genie edge, so partial
task cuts always leave a closed devenv graph.
- **Buck2 rules distribution**: Give the standalone rules artifact its own
minimal root package for package runtimes and the TypeScript action runner,
so consumer roots can resolve the configured TypeScript toolchain without
Expand Down Expand Up @@ -233,6 +236,10 @@ All notable changes to this project will be documented in this file.

### Removed

- **Buck2 checks**: remove megarepo setup, apply, and validation gates from the
repository `check:quick` and `check:all` dependency graphs. Repository
composition remains available through explicit mr operations.

- **nix**: the repository-local pnpm source builders for the ci-tools, Genie,
Genie bootstrap-closure-check, megarepo, notion-cli, notion-md, npm-release,
and tui-stories CLIs are gone, together with their fixed-output dependency
Expand Down
35 changes: 35 additions & 0 deletions context/buck2/.experiments/2026-09-20-standalone-check-surface.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Standalone check surface

Date: 2026-09-20

## Question

Can effect-utils run its repository check graph from the standalone repository root without invoking megarepo setup, apply, or validation tasks?

## Method

Run every Buck, Nix, and devenv command through the shared heavy-command gate. Capture three warm `check:quick` samples before and after the task-graph change. Run `check:all` once. Inspect the evaluated task graph and require both check aggregates and every Buck-backed repository task to avoid `mr:setup`, `mr:apply`, `mr:check`, `mr:lock-sync-check`, and `mr:source-policy-check`.

The inherited branch could not evaluate its devenv task graph because the committed Genie product descriptor and its import contract disagree about the `opentui-core-native` capability. A temporary local alignment let the task graph evaluate for structural inspection. The alignment was then reverted and is not part of this change.

## Result

| Probe | Samples | Result |
| --- | --- | --- |
| Before, warm `check:quick` | 68.280 s, 10.235 s, 9.012 s | REJECTED: all three failed during devenv evaluation with `javascript-product-import: external capability mismatch` |
| After, warm `check:quick` | 2.183 s, 2.081 s, 1.950 s | REJECTED: all three reused the diagnostic evaluation and failed in inherited Genie generation; the committed product reports `nixCacheSetupStep is not defined` |
| After, `check:all` | 8.701 s | REJECTED: inherited Genie, Nix flake, and Rust workspace checks failed |
| Task graph assertions | 204 assertions | PASS: `mr:setup` and the three composition checks are absent, both check aggregates avoid `mr:apply`, and standalone Buck tasks retain the `genie:check` freshness edge |
| Standalone format task | 14.654 s | INCONCLUSIVE: the task reached the repository-root Buck target without composition, but an unavailable remote-cache credential caused retries and the probe was interrupted |

The before and after timings are failure timings. They do not support a performance comparison. They are retained to make the blocked control explicit rather than presenting rejected samples as successful evidence.

The task graph retains `mr:bootstrap`, `mr:fetch-apply`, `mr:lock`, and `mr:apply` as explicit repository-composition operations. None is reachable from `check:quick` or `check:all`.

## Conclusion

The structural cut is complete: repository checks and Buck-backed check tasks no longer depend on composition. The repository-level runtime proof on the original base was blocked by an inherited stale Genie product, so the before/after samples do not support a speedup claim. The accepted evidence after rebasing onto the delivery stack is the 204-assertion task-graph proof.

## VRS Impact

The composition spec now defines the repository root as the check root and limits mr to explicit composition operations. The three residual ledger rows for `composition-mr-check`, `composition-mr-lock-sync-check`, and `composition-mr-source-policy-check` can close when this change lands. The stale Genie product must be repaired by its owning publication slice before successful aggregate timings can replace the rejected samples above.
25 changes: 15 additions & 10 deletions context/buck2/05-composition/spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,13 +148,12 @@ manifest, and kept out of action digests by the root's `[project] ignore`
This is what gives editors and typecheck actions cross-member types through
the unchanged `exports` types→dist mechanism.

The workspace root is not a git repository; the owned member is, and it is
the default working directory (git, devenv, genie, and pnpm all operate from
the member; nothing operates only from the root). `buck2 build` works from
the root, the member, and package dirs alike (COMP-R06); note `buck2 root`
defaults to `--kind cell` (the member) — scripts wanting the workspace pass
`--kind project`. Teardown is an mr operation (protected mounts need a
dirs-only unprotect before removal), never a bare `rm -rf`.
The composed workspace root is not a git repository; the owned member is, and
it is the default working directory for explicit composition operations. Buck
can run from the root, owned member, or a package directory (COMP-R06);
scripts that need the composed project root pass `buck2 root --kind project`.
Teardown is an mr operation (protected mounts need a dirs-only unprotect before
removal), never a bare `rm -rf`.

## Composed-Exception Agent Workflow Contract — Revision 3

Expand All @@ -176,9 +175,9 @@ exceptional shape, agents follow these rules:
create an independent composed checkout outside the store.
2. Use `repos/<owned>` as the default cwd and the only source tree mutated by
the session.
3. Run git, devenv, Genie, pnpm, and package-local commands from the owned
member. A command that needs the composition root resolves it through mr or
`buck2 root --kind project`; it does not infer `../..` in application code.
3. Run git, Genie, pnpm, and package-local commands from the owned member.
Resolve a composition root through mr or `buck2 root --kind project` only
for an explicit composition operation; do not infer `../..` in application code.
4. Treat every non-owned `repos/<member>` as immutable input. Never edit,
chmod, replace, branch, or run a producer that writes there.
5. Treat ignored members as reference-only. They are excluded from Buck cells,
Expand Down Expand Up @@ -212,6 +211,12 @@ discovery of an outer project. Effect-utils CI and devenv Buck tasks use this
shape directly; a second standalone checkout at the same revision is the
BUCK-R06 cache-reuse comparison context.

`devenv tasks run check:quick` and `check:all` execute their Buck aggregates,
lint actions, test actions, and editor-view publication from this root. The
check graph does not run `mr:setup`, `mr:apply`, or any `mr:*` validation gate.
Repository composition remains an explicit mr operation outside the check
surface.

The paused composed shape remains distinct until L3 cut 2: the member is
mounted at `repos/<name>` under the same canonical cell name. Action-digest
parity between these two shapes must be measured rather than assumed; the
Expand Down
24 changes: 13 additions & 11 deletions devenv.nix
Original file line number Diff line number Diff line change
Expand Up @@ -680,9 +680,6 @@ in
"check:devenv-eval-inputs"
"lint:check"
"lint:nix"
"mr:check"
"mr:lock-sync-check"
"mr:source-policy-check"
"nix:flake:check"
"buck2:editor:publish"
"test:run"
Expand All @@ -698,12 +695,21 @@ in
inputs.playwright.devenvModules.default
# Shared task modules
taskModules.genie
(taskModules.megarepo { mrPkg = mrCli; })
(taskModules.megarepo {
mrPkg = mrCli;
disabledTasks = [
"mr:setup"
"mr:check"
"mr:lock-sync-check"
"mr:source-policy-check"
];
})
(taskModules.lint-nix { })
# No repository JavaScript package is source-built by Nix anymore. Import
# the empty module contract to retain repository-wide flake validation.
(taskModules.nix-cli { cliPackages = [ ]; })
(taskModules.check {
hasMegarepoCheck = false;
extraChecks = [
"devenv:trace-audit"
"workspace:check"
Expand Down Expand Up @@ -980,13 +986,9 @@ in
# reads RESTATE_SERVER_BIN to locate the native server, else falls back to $PATH).
env.RESTATE_SERVER_BIN = "${restate}/bin/restate-server";

# Composed development workspaces remain available until L3 cut 2, but no CI
# or repository Buck task depends on their mutators.
tasks."mr:setup".after = [ "mr:bootstrap" ];
tasks."mr:apply".after = [
"genie:check"
"mr:setup"
];
# Repository composition remains an explicit mr operation. Generated-source freshness
# is its only repository-local prerequisite; the check aggregates do not invoke it.
tasks."mr:apply".after = [ "genie:check" ];

# buck2-tools executes inside pinned Bun actions and exercises Bun.YAML/Bun.which.
# Keep its package gate on that runtime rather than Vitest's Node process.
Expand Down
19 changes: 17 additions & 2 deletions nix/devenv-modules/tasks/shared/megarepo.nix
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
# - bootstrapMembers: Minimal members that must exist before tooling like genie
# can evaluate. Uses lock-based `mr apply --only ...` and never fetches remote
# refs. Default: [ ] (task becomes a no-op)
# - disabledTasks: Task names omitted from this module instance. Default: [ ].
# NOTE: No pnpm:install:megarepo dependency here — this shared module is used by
# repos where megarepo may be a Nix package (no pnpm install needed). Repos that
# use source-mode megarepo via pnpm should add dependencies in their devenv.nix:
Expand All @@ -27,6 +28,7 @@
{
syncAll ? true,
bootstrapMembers ? [ ],
disabledTasks ? [ ],
# Real derivation/path backing the `mr` guard. When set, the guard owns
# `bin/mr` and exec's this by absolute path under passthrough (see
# cli-guard.nix). Required for source-mode `mr` (no node_modules/.bin
Expand Down Expand Up @@ -155,7 +157,7 @@ let
>/dev/null 2>&1
'';

tasks = {
allTasks = {
"mr:bootstrap" = {
guard = "mr";
description = "Materialize bootstrap members from megarepo.lock";
Expand Down Expand Up @@ -391,6 +393,19 @@ let
'';
};
};
taskIsEnabled = taskName: builtins.elem taskName disabledTasks == false;
removeDisabledDependencies =
task:
task
// lib.optionalAttrs (task ? after) {
after = builtins.filter taskIsEnabled task.after;
}
// lib.optionalAttrs (task ? before) {
before = builtins.filter taskIsEnabled task.before;
};
tasks = lib.mapAttrs (
_: task: removeDisabledDependencies task
) (builtins.removeAttrs allTasks disabledTasks);
in
{
# mr shells out to git for clone/fetch/worktree operations
Expand All @@ -405,7 +420,7 @@ in

tasks =
cliGuard.stripGuards tasks
// lib.optionalAttrs (bootstrapMembers != [ ]) {
// lib.optionalAttrs (bootstrapMembers != [ ] && taskIsEnabled "mr:bootstrap") {
# Repos that source-import genie helpers from bootstrap members should ensure
# those members exist before any genie-backed task runs.
"genie:prepare".after = lib.mkAfter [ "mr:bootstrap" ];
Expand Down
32 changes: 31 additions & 1 deletion nix/devenv-modules/tasks/shared/tests/devenv-task-graph.test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,34 @@ TESTS_DIR="$(cd "$(dirname "$0")" && pwd)"
ROOT="${DEVENV_ROOT:-$(cd "$TESTS_DIR/../../../../.." && pwd)}"
NODE_BIN="${NODE_BIN:-node}"

exec "$NODE_BIN" "$ROOT/scripts/devenv-task-graph-check.mjs" "$ROOT"
"$NODE_BIN" "$ROOT/scripts/devenv-task-graph-check.mjs" "$ROOT"

fixture="$(mktemp)"
trap 'rm -f "$fixture"' EXIT
cat > "$fixture" <<'EOF'
{
"tasks": [
{ "name": "kept", "after": ["missing-upstream"] },
{ "name": "also-kept", "before": ["missing-downstream"] }
]
}
EOF

if DEVENV_TASKS_JSON="$fixture" DEVENV_TASK_GRAPH_DEPENDENCIES_ONLY=1 \
"$NODE_BIN" "$ROOT/scripts/devenv-task-graph-check.mjs" "$ROOT"
then
echo "FAIL: task graph checker accepted dependencies on undefined tasks" >&2
exit 1
fi

cat > "$fixture" <<'EOF'
{
"tasks": [
{ "name": "upstream", "before": ["downstream"] },
{ "name": "downstream", "after": ["upstream"] }
]
}
EOF

DEVENV_TASKS_JSON="$fixture" DEVENV_TASK_GRAPH_DEPENDENCIES_ONLY=1 \
"$NODE_BIN" "$ROOT/scripts/devenv-task-graph-check.mjs" "$ROOT"
33 changes: 26 additions & 7 deletions scripts/devenv-task-graph-check.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ ok({
name: 'every task dependency resolves to an evaluated task',
detail: missingDependencies.join(', '),
})
if (process.env.DEVENV_TASK_GRAPH_DEPENDENCIES_ONLY === '1') process.exit(0)
const requireTask = (name) => {
const task = tasks.get(name)
ok({ condition: task !== undefined, name: `evaluated graph contains ${name}` })
Expand Down Expand Up @@ -150,6 +151,12 @@ for (const name of [
name: `${name} is absent after its Buck authority cutover`,
})
}
for (const name of ['mr:setup', 'mr:check', 'mr:lock-sync-check', 'mr:source-policy-check']) {
ok({
condition: tasks.has(name) === false,
name: `${name} is absent after the standalone check-surface cut`,
})
}
for (const name of ['nix:build', 'nix:check']) {
ok({
condition: tasks.has(name) === false,
Expand Down Expand Up @@ -196,6 +203,12 @@ ok({
condition: reaches({ start: 'check:all', target: 'nix:flake:check' }),
name: 'check:all retains repository-wide Nix flake validation',
})
for (const checkTask of ['check:quick', 'check:all']) {
ok({
condition: reaches({ start: checkTask, target: 'mr:apply' }) === false,
name: `${checkTask} does not reach mr:apply`,
})
}
// `test:run` must schedule the one Buck aggregate and the source-side batches which own
// packages absent from the authority plus admitted lanes' exact unbounded complements. Either
// edge going missing would silently omit a disjoint side of the test partition.
Expand All @@ -208,10 +221,8 @@ ok({
condition: testRunDependencies.some((name) => name.startsWith('test:run:batch:') === true),
name: 'test:run executes the source-owned complement partition',
})
// `genie:check` is the source-side stage-zero guard against a graph proving its own stale
// projection. `mr apply` runs only after that proof, then reconciles the workspace and
// installs the `.buck2/capabilities` projection Buck analysis reads. Together they are the
// ordering barriers for every public task that invokes Buck.
// `genie:check` prevents a stale graph from proving itself. It is the freshness barrier for
// repository-root Buck tasks. `mr:apply` remains an explicit composition operation.
const buck2TestAuthority = JSON.parse(readFileSync(`${root}/buck2-test-authority.json`, 'utf8'))
if (buck2TestAuthority.schemaVersion !== 2 || Array.isArray(buck2TestAuthority.lanes) === false) {
throw new Error('buck2-test-authority.json does not match schemaVersion 2')
Expand Down Expand Up @@ -239,7 +250,7 @@ for (const name of [...buck2UnboundedTaskNames, ...buck2ExternalOwnerTaskNames])
name: `${name} exists as a source-side test owner`,
})
}
for (const name of [
const standaloneBuckTaskNames = [
'buck2:editor:authority',
'buck2:editor:publish',
'buck2:editor:check',
Expand All @@ -254,7 +265,9 @@ for (const name of [
'workspace:check',
'test:buck2:unit',
...buck2TestLaneTaskNames,
]) {
...buck2ExternalOwnerTaskNames,
]
for (const name of standaloneBuckTaskNames) {
ok({
condition: reaches({ start: name, target: 'mr:apply' }) === false,
name: `${name} remains standalone`,
Expand All @@ -264,7 +277,13 @@ for (const name of [
name: `${name} waits for source-side generation freshness`,
})
}
for (const name of ['buck2:check', 'buck2:quick', 'buck2:all', 'buck2:nix-bridge:check']) {
for (const name of [
'buck2:check',
'buck2:quick',
'buck2:all',
'buck2:nix-bridge:check',
'buck2:editor:bootstrap',
]) {
ok({
condition: reaches({ start: name, target: 'mr:apply' }) === false,
name: `${name} remains standalone`,
Expand Down
Loading