commit 148b695cab846884c7340c153f645c3a6291866a
parent a66723352d1c160257286be2be3040eebf0d1eb2
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Fri, 17 Jul 2026 18:26:21 -0700
build: remove redundant config ids from traces
Diffstat:
19 files changed, 196 insertions(+), 210 deletions(-)
diff --git a/doc/BUILD_COORDINATOR.md b/doc/BUILD_COORDINATOR.md
@@ -345,8 +345,8 @@ WORKSPACE.kit [config default]
⊕ CLI --config / --env entries
```
-The resulting map is still serialized as the ordinary `config-id`; traces do not
-record which profile names produced it. Changing a workspace default therefore
+The resulting map is hashed as the ordinary `config-id`; traces do not record
+that full map or which profile names produced it. Changing a workspace default therefore
affects only targets whose recipes actually consume the changed keys, by the
same config-observation rules as CLI config. `def-name` selects the package
build-file basename for this workspace unless overridden by `--def-name`; it is
@@ -455,7 +455,7 @@ kit build export-collect VAR TARGET...
`export-get` is equivalent to `need TARGET` followed by parsing the target's
exports and printing the selected variable. `export-collect` repeats that for
several targets in argument order. The dependency recorded in the trace is still
-only the ordinary target dep edge `(dep, dep-config-id, dep-argv-id,
+only the ordinary target dep edge `(dep, dep-overlay-id, dep-argv-id,
output-tree-id)`: if the provider's exports change, its output tree changes, so
the dependent's shallow dep-output comparison fails and the dependent recipe
runs again. There is no separate exports trace section, no ambient package-config
@@ -477,8 +477,11 @@ Configuration has two scopes, distinguished by visibility:
config dep); and a `need` may **overlay** it for the sub-build it triggers. A
target's *effective* propagated config = its inherited config with overlays
applied along the `need` path from the root. It is canonicalized to byte-stable
- text and content-addressed: `config-id = BLAKE2b(canonical map)`, stored as a
- CAS blob so any recorded `config-id` resolves back to the actual map.
+ text and content-addressed: `config-id = BLAKE2b(canonical map)`. That id is
+ part of the in-memory resolution identity, but the complete effective map is
+ not recorded in a trace: only consumed config observations are. Explicit
+ `need` overlay maps are stored as CAS blobs because shallow replay must recover
+ their values.
Driver spelling is `--profile NAME` and `--config K=V`. For recipe environment pass-through,
`--config env.NAME` copies the current process's `$NAME` value into config key
`env.NAME`; `--env NAME` and `--env NAME=VALUE` are shorthand for
@@ -495,17 +498,17 @@ Configuration has two scopes, distinguished by visibility:
to its own canonical CAS blob, `argv-id`, and — unlike propagated config —
`argv-id` *is* a first-class component of the resolution identity (below).
-**Serialized for replay, not just hashed.** Both the effective config map and the
-argv vector are persisted *by value* as canonical, content-addressed CAS blobs
-(`config-id`, `argv-id`) — never reduced to an opaque one-way hash. A trace
-references them by id, and they are part of its reachable closure (GC-rooted, and
-bundled with [shared traces](#shared-traces-trusted-as-signed-packages)). This is
-what lets the shallow path **replay every `need` request exactly** — reconstruct
-the dep's target name, its full effective config with overlays applied, and its
-argv — even when driving from an imported shallow trace with no access to the
-original package catalog or workspace. Source files and globs are the opposite
-case: kept by hash only, because they are *verified* (did this still hash the
-same?), never replayed, and their bytes already live in the CAS.
+**Serialized for replay, not just hashed.** A `need`'s explicit overlay map and
+local argv vector are persisted *by value* as canonical, content-addressed CAS
+blobs (`overlay-id`, `argv-id`). A shallow trace references those ids, and they
+are part of its reachable closure (GC-rooted and bundled with shared traces).
+Replay reconstructs the dep request as the target name plus the current parent's
+effective config overlaid by the recovered map, and the recovered argv. The
+parent's complete effective config is deliberately not persisted: its consumed
+values are verified through `[config]` observations, while the current request
+supplies inherited values for replay. Source files and globs are kept by hash
+because they are verified, never replayed, and their bytes already live in the
+CAS.
The **resolution identity** of a build is therefore the triple
`(target-name, config-id, argv-id)`, where `config-id` is the effective
@@ -554,7 +557,7 @@ immutable and self-verifying; exactly one class of object is mutable.
```
<store>/
cas/ # the shared content store, per DISTRIBUTE.md
- blob/<pp>/<blob-id> # raw bytes: sources, config maps, argv vectors, deepset nodes
+ blob/<pp>/<blob-id> # raw bytes: sources, need overlays, argv vectors, deepset nodes
tree/<pp>/<tree-id> # canonical directory manifests (output trees)
... # (chunk/, index/ as in DISTRIBUTE.md)
build/
@@ -601,18 +604,17 @@ kit-build-shallow 1
target //app:server
recipe <recipe-id>
output <output-tree-id>
-config <config-id> ; this target's effective propagated config (full map blob)
argv <argv-id> ; this target's local config (serialized vector blob)
-[config] ; CONSUMED propagated key NAMES, sorted (values live in the map)
-opt
+[config] ; consumed propagated observations, sorted
+opt present <value-hash> -
[source] ; sorted by path ("<path> -" marks an absent read)
src/main.c <blob-id>
[glob] ; sorted by pattern
src/*.c <glob-result-hash>
[blob] ; sorted by blob id
<blob-id>
-[dep] ; sorted by (dep-name, dep-config-id, dep-argv-id) — each row replays one `need`
-//lib:core <dep-config-id> <dep-argv-id> <dep-output-tree-id>
+[dep] ; sorted by (dep-name, dep-overlay-id, dep-argv-id) — each row replays one `need`
+//lib:core <dep-overlay-id> <dep-argv-id> <dep-output-tree-id>
```
The `recipe <recipe-id>` scalar is the only catalog-derived input (recomputed
@@ -642,7 +644,6 @@ kit-build-test-shallow 1
target //pkg:unit
recipe <recipe-id>
result <result-tree-id>
-config <config-id>
argv <argv-id>
[config]
...
@@ -731,9 +732,12 @@ src/*.c <glob-result-hash>
recorded because they are transport choices. A matching trace requires the
blob to be present in the local CAS, and trace bundles include these blobs so
imported traces retain the real dependency.
-- `[dep]` rows carry the **dep's config-id and argv-id** (and output `tree-id`);
- together with the dep's own serialized config/argv blobs this re-resolves each
- dep under the exact propagated config *and* argv it used, overlays included.
+- `[dep]` rows carry the **need's overlay-id and the dep's argv-id** (and output
+ `tree-id`). Replay recomputes the dep's effective propagated config as *this*
+ target's current config overlaid by the overlay, then re-resolves the dep under
+ that config *and* argv. The effective config-id is deliberately *not* stored: it
+ is exactly `id(overlay(config, overlay-id))` — a pure function of two fields
+ already in the row/trace, so it would be redundant.
**Why the deep trace needs argv + the deepset closure.** At Phase 1 query time
the coordinator has only the requested target's effective propagated config and
@@ -775,7 +779,8 @@ different argvs).
The store must never hand back a wrong output after a crash. The rules:
-1. **Content objects** (`cas/blob`, `cas/tree`, `build/trace`, config maps, and a
+1. **Content objects** (`cas/blob`, `cas/tree`, `build/trace`, need-overlay maps,
+ argv vectors, and a
materialized `build/cache/<tree-id>/`) are written into `build/tmp/`, fsync'd,
then **atomically renamed** to their final content-keyed path. A half-written
object only ever exists under `tmp/` and is orphaned, never under its content
@@ -879,8 +884,7 @@ resolve(T, cfg, argv, chain):
for S in shallow_traces(T), newest-first:
if S.recipe != recipe_id(T): continue # recipe edit/repoint (live defn)
if S.argv != argv.id: continue # local config
- M = config_by_id(S.config) # the built map (serialized blob)
- if any consumed key in S.config-keys differs between cfg and M: continue
+ if any config observation in S differs from cfg: continue
if any direct source/glob/blob leaf of S changed: continue
ok = true ; child_nodes = []
for (dep, overlay_id, dep_argv_id, recorded_tree) in S.deps: # may run in parallel
@@ -926,11 +930,12 @@ This is the brief's flow, sharpened by config:
projection is the same.
- *Phase 2* is the shallow path, taken when Phase 1 finds nothing (config or argv
changed, or a source moved). It guards on the target's own argv-id, consumed
- config, and direct leaves, then re-resolves each recorded dep **under the dep's
- recorded config-id and argv-id** and compares outputs. Re-using the recorded
- `(dep_cfg_id, dep_argv_id)` is correct: if the parent's consumed config or argv
- were different, the guards above would have failed first and the parent would
- re-run, recomputing its `need` overlays.
+ config observations, and direct leaves, then re-resolves each recorded dep
+ under **the current parent config plus the recorded overlay**, with the
+ recorded argv, and compares outputs. This preserves newly inherited values
+ while replaying the need's explicit overrides exactly. If a parent-consumed
+ value that produced an overlay changes, the parent's config guard fails and
+ the recipe re-runs to discover a fresh overlay.
- A Phase-2 hit also writes a *fresh* deep trace from the now-known closure, so the
next request gets the Phase-1 fast path back.
- *Phase 3* runs the recipe only when no trace holds, or when a hit's bytes were
@@ -1055,11 +1060,11 @@ run_recipe(T, cfg, argv, chain):
on nonzero exit: propagate failure, write NO trace
on success:
output = kit_cas_add_tree_from_dir(out)
- put serialized cfg map (cfg.id) and argv vector (argv.id) into the CAS
+ put the serialized argv vector (argv.id) and each need-overlay map into the CAS
node = union(direct{recipe_id, config/source/glob/blob/absent leaves}, child deepset nodes)
# builds + stores the root deepset as a CAS blob, yields its deep-set-id
- write_shallow_trace(T, recipe_id, argv.id, cfg.id, consumed key names,
- direct leaves, dep edges with their config-ids and argv-ids, output)
+ write_shallow_trace(T, recipe_id, argv.id, config observations,
+ direct leaves, dep edges with overlay-ids and argv-ids, output)
write_deep_trace (T, argv.id, node.id, output) # references the deepset
prepend both to T's target record (dedup, truncate to cap)
install out in build/cache/<output>/
@@ -1133,9 +1138,9 @@ command.
| `source <path>` | `blob-id` (or *absent*) + a path to read | source dep: `(path, blob-id)`, or `(path, absent)` |
| `glob <pattern>` | sorted list of matching paths (streamed) | glob dep: `(pattern, glob-result-hash)` |
| `fetch <blob-id> <url>...` | the verified blob-id + a local CAS path | blob dep: `blob-id` |
-| `need <target> [k=v…] [env…] [argv…]` | the dep's output `tree-id` + a path (blocks) | target dep edge: `(dep, dep-config-id, dep-argv-id, output-tree-id)` |
+| `need <target> [k=v…] [env…] [argv…]` | the dep's output `tree-id` + a path (blocks) | target dep edge: `(dep, dep-overlay-id, dep-argv-id, output-tree-id)` |
| `need-submit <target> [k=v…] [env…] [argv…]` | a **token** (does not block) | *(nothing yet — logged on await)* |
-| `need-await <token>` | the submitted dep's output `tree-id` + a path | target dep edge: `(dep, dep-config-id, dep-argv-id, output-tree-id)` |
+| `need-await <token>` | the submitted dep's output `tree-id` + a path | target dep edge: `(dep, dep-overlay-id, dep-argv-id, output-tree-id)` |
- **`config-get`** reads the target's *effective propagated* config and records
whether the key was present, plus the value/default hashes needed to replay that
@@ -1179,8 +1184,9 @@ command.
argv vector sets the dep's **local argv** (omitted ⇒ the empty argv; local argv
never propagates). The coordinator resolves `(dep, cfg ⊕ overrides, argv)`
recursively, returns its output `tree-id` and a readable path, and records the
- edge with the dep's `config-id` and `argv-id` so the shallow path can re-resolve
- identically. `need` is also where cycles are caught.
+ edge with the need's overlay-id and the dep's argv-id so the shallow path can
+ reconstruct the request from the current parent config. `need` is also where
+ cycles are caught.
- **`need-submit` / `need-await`** are the *future-based* form of `need`, for
building a recipe's deps **concurrently**. `need-submit` resolves nothing
inline — it dispatches `(dep, cfg ⊕ overrides, argv)` onto the coordinator's
@@ -1304,9 +1310,9 @@ machinery wholesale:
- A bundle is a signed manifest (`kit-build-traces 1`, signed exactly like a
`kit-package` manifest) listing `(target-name, kind, trace-id, output-tree-id)`
claims, carried in a `.kpkg` (portable tar.gz or native kpkg) alongside the
- trace bodies and the CAS blobs they reference — the **serialized config-map and
- argv blobs** (required, else an imported shallow trace cannot replay its
- `need`s) and the **deepset closure blobs** a deep trace points at (required,
+ trace bodies and the CAS blobs they reference — the serialized **need-overlay
+ maps and argv blobs** required to replay shallow deps, and the **deepset
+ closure blobs** a deep trace points at (required,
else an imported deep trace cannot refresh), including any **blob dependency**
leaves in shallow traces or deepsets — plus, optionally, the referenced output
trees/blobs.
@@ -1410,5 +1416,5 @@ deterministically.
the current package catalog format has no target-level no-cache flag.
- **GC.** Build-store garbage collection is not implemented. The intended sweep
roots at live target records, follows trace bodies to referenced output
- trees/blobs, config maps, argv vectors, and deepset closure blobs, then removes
+ trees/blobs, need-overlay maps, argv vectors, and deepset closure blobs, then removes
unreachable `build/trace/`, `cas/`, and `build/cache/` entries.
diff --git a/doc/plan/BUILD_INTERNALS.md b/doc/plan/BUILD_INTERNALS.md
@@ -23,8 +23,8 @@ The build layer sits entirely on the public kit surface — the content store
(`<kit/core.h>`), and signed packages (`<kit/package.h>`) — and adds exactly one
thing the CAS lacks: a *mutable per-target trace index* plus the resolution
algorithm that drives it. Every id it computes is a plain BLAKE2b-256 of canonical
-bytes, which **is** a CAS blob id (`kit_blob_info().id`), so config maps, argv
-vectors, and **deepset closure nodes** content-address themselves through the
+bytes, which **is** a CAS blob id (`kit_blob_info().id`), so need-overlay maps,
+argv vectors, and **deepset closure nodes** content-address themselves through the
ordinary CAS as ordinary blobs (self-verifying content). Trace bodies are
content-addressed too, but live in `build/trace/` rather than `cas/`, because a
trace is a *claim* (not self-verifying) — the criterion that sorts the two stores
@@ -73,11 +73,12 @@ sets in the spike). Arrows point "depends on":
**One id invariant the whole layer leans on:** for any canonical byte string `b`,
`kit_blob_info(&info, b, len); info.id` equals the BLAKE2b-256 that names `b` as a
-CAS blob. So "compute the config-id/argv-id/trace-id" and "store the blob and read
-its id back" agree by construction — a trace can reference a config map by id and
-later recover the exact bytes with `kit_cas_get_blob`. Pure index keys that are
-*never* stored as content (`target-key`, `glob-result-hash`) use the same hash over
-a domain-separated buffer.
+CAS blob. So "compute the overlay-id/argv-id/trace-id" and "store the blob and
+read its id back" agree by construction — a trace can reference an overlay map
+by id and later recover the exact bytes with `kit_cas_get_blob`. Effective config
+maps are hashed for resolution identity but are not trace content. Pure index
+keys that are never stored as content (`target-key`, `glob-result-hash`) use the
+same hash over a domain-separated buffer.
---
@@ -113,12 +114,13 @@ void build_diagf(ctx, fmt, ...): // mirror cas_diagf
## L1 — `cfg` (propagated config + local argv)
-**Role.** The two configuration objects a build records, each serialized *by value*
-as a canonical content-addressed CAS blob (never an opaque one-way hash) so an
-imported trace can replay every `need` exactly. `BuildConfig` is the effective
-**propagated** map (`config-id`); `BuildArgv` is the **local** argv vector
-(`argv-id`). Pure value logic: init / set / get / overlay / canonical emit / parse /
-id. Entries hang off caller storage (the `DistTree` growable-buffer pattern, no VLAs).
+**Role.** Canonical propagated-config maps and local argv vectors. Effective
+propagated maps are hashed for the in-memory resolution identity but traces keep
+only consumed observations. Explicit `need` overlay maps and local argv vectors
+are serialized by value as CAS blobs so an imported shallow trace can replay a
+need exactly. Pure value logic: init / set / get / overlay / canonical emit /
+parse / id. Entries hang off caller storage (the `DistTree` growable-buffer
+pattern, no VLAs).
**Depends on.** `build` (L0), `<kit/core.h>` (`KitWriter`, `KitHeap`),
`<kit/cas.h>` (`kit_blob_info`), `<kit/build_coord.h>` (`KitBuildKV` overlay pairs).
@@ -213,11 +215,11 @@ as a match (fail safe). Sections hang off caller-provided growable buffers.
int build_shallow_emit(t, w, err, errcap):
write "kit-build-shallow 1\n"
write "target ", t.target, '\n'; "recipe ", hex(t.recipe), '\n'
- "output ", hex(t.output), '\n'; "config ", hex(t.config), '\n'; "argv ", hex(t.argv), '\n'
- write "[config]\n"; for k in sort(config_keys): write k.name, '\n'
+ "output ", hex(t.output), '\n'; "argv ", hex(t.argv), '\n'
+ write "[config]\n"; for k in sort(configs): write observation(k), '\n'
write "[source]\n"; for s in sort_by_path(sources): require path-valid; write s.path, ' ', s.absent?"-":hex(s.blob), '\n'
write "[glob]\n"; for g in sort_by_pattern(globs): write g.pattern, ' ', hex(g.result_hash), '\n'
- write "[dep]\n"; for d in sort_by(name,cfg,argv): write d.name,' ',hex(d.config_id),' ',hex(d.argv_id),' ',hex(d.output_tree),'\n'
+ write "[dep]\n"; for d in sort_by(name,overlay,argv): write d.name,' ',hex(d.overlay_id),' ',hex(d.argv_id),' ',hex(d.output_tree),'\n'
return kit_writer_status(w)
int build_deep_emit(t, w, err, errcap): // no [dep], no [config], no inline closure
@@ -593,7 +595,8 @@ header promises.
## L4 — `resolve` (the three-phase algorithm)
**Role.** `build_resolve(T, cfg, argv, chain) → {output-tree, path, leafset}`, doing
-the least work necessary: Phase 1 deep fast path (same config-id+argv-id, did sources
+the least work necessary: Phase 1 deep fast path (same argv-id and matching
+scope-projected config observations, did sources
move?), Phase 2 shallow path (config/argv/sources moved — guard consumed keys + direct
leaves, re-resolve recorded deps by their recorded ids, compare *output* tree-ids),
Phase 3 run the recipe (delegated to `runner`). Owns the per-path build **chain**
@@ -642,12 +645,11 @@ resolve_phases(c, T, cfg, argv, chain, out):
S = load+parse shallow trace (absent/garbage → skip)
if !id_eq(S.recipe, recipe_id(T)): continue // recipe edit/repoint (live defn)
if !id_eq(S.argv, argv.id): continue // local config differs
- M = config_by_id(S.config)
- if any name in S.config_keys differs between cfg and M (absent==unset): continue
+ if any observation in S.configs differs from cfg: continue
if any direct source/glob leaf of S changed (refresh): continue
ok = 1; children = []
- for (dep, dcfg_id, dargv_id, recorded_tree) in S.deps: // may fan out on threads
- dcfg = config_by_id(dcfg_id); dargv = argv_by_id(dargv_id)
+ for (dep, overlay_id, dargv_id, recorded_tree) in S.deps: // may fan out on threads
+ dcfg = overlay(cfg, config_by_id(overlay_id)); dargv = argv_by_id(dargv_id) // recompute effective config
r = build_resolve(c, dep, dcfg, dargv, chain, &child) // replay the need exactly
if r != OK or !id_eq(child.output_tree, recorded_tree): ok = 0; break
children.push(child.leafset) // child deepset nodes
@@ -753,19 +755,20 @@ int build_runner_service(c, conn, T, cfg, chain, log):
append (req.arg, result_hash) to log.globs
reply matches in OK batch frames, then a GLOB_END frame // streamed, multi-frame
NEED: dcfg = build_config_overlay(cfg, req.overrides, …); dargv = build_argv_set(req.argv, …)
+ overlay_id = store(overlay(empty, req.overrides)) // just the delta; effective cfg is derivable
build_coord_jobs_release(c) // ← yield slot while blocked (findings #6)
build_resolve(c, req.arg, &dcfg, &dargv, chain, &r) // inline (no worker)
build_coord_jobs_acquire(c) // ← reclaim before resuming work
- record_dep(log, req.arg, dcfg.id, dargv.id, &r) // append edge + push child node
+ record_dep(log, req.arg, overlay_id, dargv.id, &r) // append edge + push child node
reply {OK, id=r.output_tree, text=r.path}
- NEED_SUBMIT: dcfg = overlay; dargv = set
+ NEED_SUBMIT: dcfg = overlay; dargv = set; overlay_id = store(overlay(empty, req.overrides))
if build_dispatch(c, req.arg, &dcfg, &dargv, chain, &f, err) != OK: reply {ERROR} // cycle/unknown
- tok = log.next_token++; log.pending.push({tok, req.arg, dcfg.id, dargv.id, f})
+ tok = log.next_token++; log.pending.push({tok, req.arg, overlay_id, dargv.id, f})
reply {OK, token=tok} // does NOT block
NEED_AWAIT: p = find_pending(log, req.token); if none: reply {ERROR}
build_coord_jobs_release(c); build_coord_target_await(c, p.future, &r); build_coord_jobs_acquire(c)
if r failed: reply {ERROR}
- record_dep(log, p.dep, p.config_id, p.argv_id, &r) // edge logged HERE, on await
+ record_dep(log, p.dep, p.overlay_id, p.argv_id, &r) // edge logged HERE, on await
remove p from log.pending; reply {OK, id=r.output_tree, text=r.path}
// on EOF: for each still-pending need, exec->kill its in-flight recipes (best-effort); record NO edge
@@ -773,7 +776,7 @@ int build_runner_record_traces(c, T, cfg, argv, log, output[32], &out_node):
recipe_id = build_coord_recipe_id(c, T)
build_leafset_union(c, direct_node{T, recipe_id, log.sources, log.globs}, log.child_leafsets, &node)
// builds the root deepset node, emits it, stores it as a CAS blob, yields node.id (= deep-set-id)
- S = shallow{T, recipe_id, output, cfg.id, argv.id, log.config_keys, log.sources, log.globs, log.deps}
+ S = shallow{T, recipe_id, output, argv.id, log.configs, log.sources, log.globs, log.deps}
emit S → bytes; build_store_put_trace(store, bytes, &sid); build_store_record_update(store, key(T), T, SHALLOW, sid)
D = deep{T, recipe_id, output, cfg.id, argv.id, deepset=node.id} // one pointer, not an inline closure
emit D → bytes; build_store_put_trace(store, bytes, &did); build_store_record_update(store, key(T), T, DEEP, did)
@@ -825,9 +828,9 @@ int build_bundle_export(c, opts):
for T in opts.targets:
rec = store_record_load(key(T), T)
for row in rec (newest candidates): copy build/trace/<row.trace_id> → stage/trace/<id>
- parse the body → collect referenced config-id, argv-id (and output-tree for the claim row)
+ parse the body → collect referenced overlay-id, argv-id (and output-tree for the claim row)
claims.push({T, row.kind, row.trace_id, output_tree})
- for each referenced config-id/argv-id: kit_cas_get_blob → stage/cas/blob/<id> // required for replay
+ for each referenced overlay-id/argv-id: kit_cas_get_blob → stage/cas/blob/<id> // required for replay
if opts.include_outputs: also copy referenced output trees + their blobs into stage/cas/
build_bundle_manifest_emit(claims, &w) → stage/manifest "kit-build-traces 1"
kit_pkg_create({ root_dir: stage, format: opts.format, sk: opts.sk, keyid: opts.keyid,
@@ -839,7 +842,7 @@ int build_bundle_import(c, opts, result):
kit_pkg_verify({ pkg_data, pkg_len, format, pubkey_bytes, trusted_keys, tofu, unpack_dir: unpack }, &vr)
if !verified: return BUILD_ERR // signature/trust failure
read unpack/manifest → build_bundle_manifest_parse → claims
- for blob in unpack/cas/blob/*: kit_cas_add_blob(cas, bytes) // config/argv/(outputs), hash-checked
+ for blob in unpack/cas/blob/*: kit_cas_add_blob(cas, bytes) // overlays/argv/(outputs), hash-checked
for c in claims:
install unpack/trace/<c.trace_id> via build_store_put_trace // id re-checked on store
build_store_record_update(store, key(c.target), c.target, c.kind, c.trace_id) // prepend
@@ -1074,7 +1077,7 @@ end-to-end coordinator to validate L0-L2 work.
| `coord` | `src/build/coord.c`, `src/build/coord.h` | `cfg`, `defn`, `store`, `trace`, public CAS | context open/close; source/glob/config/argv/recipe memos; deepset intern/load; futures; stats; jobs semaphore | fake workspace memo counts; config/argv by-id load; recursive deepset load; sequential degrade; future dedup; stats bumps under lock | no resolution phase policy, no recipe spawn loop |
| `resolve` | `src/build/resolve.c`, `src/build/resolve.h` | `coord`, `cfg`, `trace`; calls `build_run_recipe` by declared seam | chain/cycle detection; Phase 1/2/3 selection; materialization ladder; leafset refresh/union | deep hit; shallow hit after changed dep with same output; recipe-id bust; materialize miss; cycle path; exact stats deltas | no process I/O, no protocol service loop |
| `runner` | `src/build/runner.c`, `src/build/runner.h` | `coord`, `resolve`, `protocol`, `trace`, `cfg` | spawn environment; protocol service; dep log; job-slot release across needs; trace recording | scripted transport/exec: config/source/glob/need logs; nonzero exit writes no trace; need-submit/await determinism; jobs=1 deep chain | no Phase 1/2 matching policy, no signed bundle logic |
-| `bundle` | `src/build/bundle.c`, `src/build/bundle.h` | `coord`, `store`, public package/CAS | signed trace export/import; trace remote pull | package round-trip; untrusted signer rejected; tofu pin result; imported trace has config/argv/deepset blobs; bad trace id rejected | no object remote fetch, no recipe execution |
+| `bundle` | `src/build/bundle.c`, `src/build/bundle.h` | `coord`, `store`, public package/CAS | signed trace export/import; trace remote pull | package round-trip; untrusted signer rejected; tofu pin result; imported trace has overlay/argv/deepset blobs; bad trace id rejected | no object remote fetch, no recipe execution |
| `api` | `src/api/build_coord.c` or chosen composition TU, Makefile gating | all internal modules | public coordinator, stats, trace import/export, stubs under disabled gate | public API smoke using fake host; unsupported gate; status/diag mapping | no policy hidden in driver |
| `driver` | `driver/cmd/build_coord.c` or final command name, driver tables | public API only | CLI parses flags, supplies hosted vtables, exposes shell helper subcommands for recipes | `run.sh` subprocess/socket smoke; helper commands call client API; clean env behavior | no internal `src/build/*` includes |
diff --git a/doc/plan/BUILD_TESTING.md b/doc/plan/BUILD_TESTING.md
@@ -76,7 +76,7 @@ harness (decision below).
| L3 | `coord` | INTERNAL | all | memo (file hashed once); **futures dedup**; **sequential degrade** (`sched==NULL`); `deepset_load` interns+recurses, absent→ERR; `pull_once` idempotent; **stat counters** |
| L4 | `resolve` | INTERNAL/PUBLIC | ipc + FakeExec | the **scenario spine** + soundness scenarios |
| L5 | `runner` | INTERNAL | ipc + FakeExec | service loop logs every dep; nonzero exit → **no trace**; both traces + deepset written; **jobs slot released across `need`** (chain deeper than `jobs` completes) |
-| L5 | `bundle` | PUBLIC | real pkg | export→import round-trip; **untrusted signer rejected**; tofu pin; deepset + config/argv blobs present so an imported trace refreshes/replays |
+| L5 | `bundle` | PUBLIC | real pkg | export→import round-trip; **untrusted signer rejected**; tofu pin; deepset + overlay/argv blobs present so an imported trace refreshes/replays |
| L6 | `api` + driver | PUBLIC + `run.sh` | ipc / real | `kit build` end-to-end; `kit_build_stats` deltas |
## Integration
diff --git a/include/kit/build_coord.h b/include/kit/build_coord.h
@@ -20,8 +20,8 @@
* and a complete cached-build workflow in `kit build --help`.
*
* Layering. The coordinator is built entirely on the public kit surface: the
- * content store (<kit/cas.h>) holds source bytes, output trees, config maps,
- * argv vectors, and deepset nodes; build/trace holds content-addressed trace
+ * content store (<kit/cas.h>) holds source bytes, output trees, need-overlay
+ * maps, argv vectors, and deepset nodes; build/trace holds content-addressed trace
* claims; signed packages (<kit/package.h>) carry shared trace bundles;
* digests (<kit/hash.h>) key the mutable index. The only mutable state the
* coordinator adds over the CAS is a per-target trace index.
@@ -352,10 +352,10 @@ typedef struct KitBuildExportOptions {
KitSlice out_path;
} KitBuildExportOptions;
-/* Bundle the named targets' trace bodies (plus the serialized config-map and
- * argv blobs they reference, required for replay, and optionally the referenced
- * output trees/blobs) into a signed `.kpkg`, using the <kit/package.h> trust
- * model. */
+/* Bundle the named targets' trace bodies (plus the serialized need-overlay maps
+ * and argv blobs they reference, required for replay, and optionally the
+ * referenced output trees/blobs) into a signed `.kpkg`, using the
+ * <kit/package.h> trust model. */
KIT_API KitStatus kit_build_traces_export(KitBuildCoordinator*,
const KitBuildExportOptions*);
@@ -379,7 +379,7 @@ typedef struct KitBuildImportResult {
/* Verify a trace bundle's signature against the trust inputs, then install its
* trace bodies into the store and prepend them to the relevant target records;
- * referenced config/argv blobs (and any packed outputs) land in the CAS,
+ * referenced overlay/argv blobs (and any packed outputs) land in the CAS,
* hash-verified on use. */
KIT_API KitStatus kit_build_traces_import(KitBuildCoordinator*,
const KitBuildImportOptions*,
diff --git a/include/kit/cas.h b/include/kit/cas.h
@@ -91,7 +91,7 @@ KIT_API KitStatus kit_cas_blob_path(KitCas* cas,
/* Read a stored blob's bytes by id. On success *out borrows the bytes; release
* them with kit_cas_release. Returns KIT_NOT_FOUND on a miss. This is the
- * by-id read path that lets a recorded config-id / argv-id resolve back to the
+ * by-id read path that lets a recorded overlay-id / argv-id resolve back to the
* actual serialized map or vector (the build coordinator's `need` replay). */
KIT_API KitStatus kit_cas_get_blob(KitCas* cas,
const uint8_t id[KIT_CAS_HASH_LEN],
diff --git a/src/build/build.h b/src/build/build.h
@@ -15,8 +15,8 @@
* digest surface (`<kit/hash.h>`), and the signed-package machinery
* (`<kit/package.h>`). It deliberately does not reach into src/dist internals:
* every id it computes is a plain BLAKE2b-256 of canonical bytes, which is
- * exactly a CAS blob id (`kit_blob_info`), so config maps, argv vectors, and
- * trace bodies content-address themselves through the ordinary CAS.
+ * exactly a CAS blob id (`kit_blob_info`), so need-overlay maps, argv vectors,
+ * and trace bodies content-address themselves through the ordinary CAS.
*
* Naming/result conventions mirror src/dist: internal modules return `int`
* (BUILD_OK / BUILD_ERR), parse/emit modules also take a `char* err, size_t
diff --git a/src/build/bundle.c b/src/build/bundle.c
@@ -772,14 +772,11 @@ static int BUILD_MAYBE_UNUSED export_trace_refs(KitBuildCoordinator* c,
t.cap_deps = rows;
if (build_shallow_parse(data, len, &t, err, sizeof err) != BUILD_OK)
goto shallow_out;
- if (payload_write_blob_from_cas(c, t.config, root, seen) != BUILD_OK ||
- payload_write_blob_from_cas(c, t.argv, root, seen) != BUILD_OK)
+ if (payload_write_blob_from_cas(c, t.argv, root, seen) != BUILD_OK)
goto shallow_out;
for (i = 0u; i < t.n_deps; ++i) {
if (payload_write_blob_from_cas(c, t.deps[i].overlay_id, root, seen) !=
BUILD_OK ||
- payload_write_blob_from_cas(c, t.deps[i].config_id, root, seen) !=
- BUILD_OK ||
payload_write_blob_from_cas(c, t.deps[i].argv_id, root, seen) !=
BUILD_OK)
goto shallow_out;
@@ -824,14 +821,11 @@ shallow_out:
t.cap_deps = rows;
if (build_test_shallow_parse(data, len, &t, err, sizeof err) != BUILD_OK)
goto test_out;
- if (payload_write_blob_from_cas(c, t.config, root, seen) != BUILD_OK ||
- payload_write_blob_from_cas(c, t.argv, root, seen) != BUILD_OK)
+ if (payload_write_blob_from_cas(c, t.argv, root, seen) != BUILD_OK)
goto test_out;
for (i = 0u; i < t.n_deps; ++i) {
if (payload_write_blob_from_cas(c, t.deps[i].overlay_id, root, seen) !=
BUILD_OK ||
- payload_write_blob_from_cas(c, t.deps[i].config_id, root, seen) !=
- BUILD_OK ||
payload_write_blob_from_cas(c, t.deps[i].argv_id, root, seen) !=
BUILD_OK)
goto test_out;
@@ -910,10 +904,6 @@ static int BUILD_MAYBE_UNUSED import_trace_refs(KitBuildCoordinator* c,
t.cap_deps = rows;
if (build_shallow_parse(data, len, &t, err, sizeof err) != BUILD_OK)
goto shallow_out;
- if (payload_install_blob(c, t.config, root, seen, NULL, NULL, &fd) !=
- BUILD_OK)
- goto shallow_out;
- release_file(c, &fd);
if (payload_install_blob(c, t.argv, root, seen, NULL, NULL, &fd) !=
BUILD_OK)
goto shallow_out;
@@ -923,10 +913,6 @@ static int BUILD_MAYBE_UNUSED import_trace_refs(KitBuildCoordinator* c,
&fd) != BUILD_OK)
goto shallow_out;
release_file(c, &fd);
- if (payload_install_blob(c, t.deps[i].config_id, root, seen, NULL, NULL,
- &fd) != BUILD_OK)
- goto shallow_out;
- release_file(c, &fd);
if (payload_install_blob(c, t.deps[i].argv_id, root, seen, NULL, NULL,
&fd) != BUILD_OK)
goto shallow_out;
@@ -977,10 +963,6 @@ shallow_out:
t.cap_deps = rows;
if (build_test_shallow_parse(data, len, &t, err, sizeof err) != BUILD_OK)
goto test_out;
- if (payload_install_blob(c, t.config, root, seen, NULL, NULL, &fd) !=
- BUILD_OK)
- goto test_out;
- release_file(c, &fd);
if (payload_install_blob(c, t.argv, root, seen, NULL, NULL, &fd) !=
BUILD_OK)
goto test_out;
@@ -990,10 +972,6 @@ shallow_out:
&fd) != BUILD_OK)
goto test_out;
release_file(c, &fd);
- if (payload_install_blob(c, t.deps[i].config_id, root, seen, NULL, NULL,
- &fd) != BUILD_OK)
- goto test_out;
- release_file(c, &fd);
if (payload_install_blob(c, t.deps[i].argv_id, root, seen, NULL, NULL,
&fd) != BUILD_OK)
goto test_out;
diff --git a/src/build/bundle.h b/src/build/bundle.h
@@ -18,10 +18,10 @@
* <kit/package.h> manifest + minisign + trust machinery wholesale: a signed
* `kit-build-traces 1` manifest lists (target, kind, trace-id, output-tree-id)
* claims, carried in a `.kpkg` alongside the trace bodies and the CAS blobs they
- * reference — the serialized config-map / argv blobs and need-overlay blobs
- * (required, else an imported shallow trace cannot replay its `need`s) AND the
- * deepset closure blobs a deep trace points at (required, else an imported deep
- * trace cannot refresh) — plus optionally the referenced output trees/blobs.
+ * reference — the serialized need-overlay maps and argv blobs (required, else
+ * an imported shallow trace cannot replay its `need`s) AND the deepset closure
+ * blobs a deep trace points at (required, else an imported deep trace cannot
+ * refresh) — plus optionally the referenced output trees/blobs.
*
* The security split is precise: a local build can deep/shallow-HIT on a remote
* builder's trace (obtaining the output without running the recipe) while
@@ -38,7 +38,7 @@ typedef struct BuildTraceClaim {
} BuildTraceClaim;
/* Gather the named targets' recorded traces (newest candidates), collect the
- * trace bodies + referenced config/argv blobs + the deepset-closure blobs each
+ * trace bodies + referenced overlay/argv blobs + the deepset-closure blobs each
* deep trace transitively points at (+ optional output trees/blobs), emit and
* sign a kit-build-traces manifest, and write the `.kpkg` to out_path.
* Signing/format/key come from `opts`. */
@@ -48,7 +48,7 @@ int build_bundle_export(KitBuildCoordinator*,
/* Verify a bundle's signature against the trust inputs (the unchanged
* DISTRIBUTE.md model: -p key, the trusted-keys anchor, or --tofu; the signed
* trusted comment binds the signature to the manifest hash), then install the
- * trace bodies into build/trace/, install referenced config/argv (and any
+ * trace bodies into build/trace/, install referenced overlay/argv (and any
* packed outputs) into the CAS, and prepend the imported traces to the relevant
* target records. Output bytes are hash-verified on use. */
int build_bundle_import(KitBuildCoordinator*, const KitBuildImportOptions* opts,
diff --git a/src/build/cfg.h b/src/build/cfg.h
@@ -9,21 +9,19 @@
#include "build.h"
/*
- * The two configuration objects a build records, both serialized BY VALUE as
- * canonical, content-addressed CAS blobs (never reduced to an opaque hash) so
- * that an imported trace can replay every `need` exactly:
+ * The two configuration value forms used by the build coordinator:
*
- * BuildConfig - the effective PROPAGATED config: a key->value map that flows
- * down the subtree, canonicalized to byte-stable text. Its
- * blob id IS the `config-id`. Overlays applied along a `need` path produce a
- * new map (and new id). BuildArgv - the LOCAL config: the target's argv
- * vector, supplied by the build request, visible only to that one recipe and
- * never propagated. Its blob id is the `argv-id`.
+ * BuildConfig - a PROPAGATED config map. Effective maps flow down the subtree
+ * and are hashed for resolution identity; traces record only
+ * consumed observations. Explicit `need` overlay maps are
+ * serialized by value as CAS blobs for shallow replay.
+ * BuildArgv - the LOCAL config: the target's argv vector, supplied by the
+ * build request, visible only to that recipe and never
+ * propagated. It is serialized by value as an `argv-id` blob.
*
* Pure value logic: canonicalize, hash, parse, look up, overlay. No I/O — the
- * store persists/loads the resulting blobs through the CAS. Entries hang off a
- * caller-provided, growable buffer (the DistTree pattern) so there are no VLAs
- * and the api layer owns sizing.
+ * store persists/loads overlay and argv blobs through the CAS. Entries hang off
+ * caller-provided storage so there are no VLAs and the api layer owns sizing.
*/
/* The propagated-config value types are part of the public build model
diff --git a/src/build/coord.h b/src/build/coord.h
@@ -179,9 +179,9 @@ int build_coord_glob_target(KitBuildCoordinator*, KitSlice target,
uint8_t out_result_hash[BUILD_HASH_LEN],
BuildCoordGlobFn cb, void* cb_user);
-/* Recover a propagated config map / an argv vector by its id (loads + parses
- * the serialized CAS blob, memoized) — the replay path for a recorded `need`,
- * which re-resolves the dep under its exact (config-id, argv-id). */
+/* Recover a need-overlay map / an argv vector by its id (loads + parses the
+ * serialized CAS blob, memoized) — the replay path that applies the overlay to
+ * the current parent config and re-resolves the dep with its recorded argv. */
int build_coord_config_by_id(KitBuildCoordinator*,
const uint8_t config_id[BUILD_HASH_LEN],
BuildConfig* out);
diff --git a/src/build/remote.h b/src/build/remote.h
@@ -29,9 +29,9 @@
* the whole tree is verified before any cache hit is served from it.
*
* There is no separate deepset kind: a deepset closure node is a self-verifying
- * CAS blob (deep-set-id == blob-id), so it — like config-map and argv blobs — is
- * fetched as an ordinary BLOB ({kind}=blob) on demand when build_coord_deepset_-
- * load needs one the local CAS lacks.
+ * CAS blob (deep-set-id == blob-id), so it — like need-overlay and argv blobs —
+ * is fetched as an ordinary BLOB ({kind}=blob) on demand when
+ * build_coord_deepset_load needs one the local CAS lacks.
*
* (Trust, where it exists, is for signed trace bundles — bundle.h. Content
* fetched here is trustless.)
diff --git a/src/build/resolve.c b/src/build/resolve.c
@@ -399,7 +399,7 @@ static int try_shallow_trace(KitBuildCoordinator* c, KitSlice target,
log.n_deps = st->n_deps;
log.child_leafsets = child_leafsets;
log.n_children = st->n_deps;
- return build_runner_record_traces(c, target, cfg, argv, &log,
+ return build_runner_record_traces(c, target, argv, &log,
out->output_tree, &out->leafset);
}
@@ -462,7 +462,7 @@ static int try_test_shallow_trace(KitBuildCoordinator* c, KitSlice target,
log.n_deps = st->n_deps;
log.child_leafsets = child_leafsets;
log.n_children = st->n_deps;
- return build_runner_record_test_traces(c, target, cfg, argv, &log,
+ return build_runner_record_test_traces(c, target, argv, &log,
out->result_tree, &out->leafset);
}
diff --git a/src/build/resolve.h b/src/build/resolve.h
@@ -21,10 +21,11 @@
* recomputes the same) => reuse, no graph walk.
* Phase 2 shallow path - config, argv, and/or sources moved: guard on the
* recipe-id, argv-id, consumed keys + direct leaves,
- * then re-resolve each recorded dep under its
- * recorded (config-id, argv-id) and compare OUTPUT
- * tree-ids; a match still skips the recipe (and
- * writes a fresh deep trace + deepset).
+ * then re-resolve each recorded dep under the
+ * current parent config plus its recorded overlay,
+ * with its recorded argv, and compare OUTPUT tree-ids;
+ * a match still skips the recipe (and writes a fresh
+ * deep trace + deepset).
* Phase 3 run the recipe - no trace holds (runner.h).
*
* On a local-record miss, resolution first attempts one lazy trace-remote pull
diff --git a/src/build/runner.c b/src/build/runner.c
@@ -191,20 +191,18 @@ static int append_blob(BuildDepLog* log,
static int append_dep(BuildDepLog* log, KitSlice target,
const uint8_t overlay_id[BUILD_HASH_LEN],
- const uint8_t config_id[BUILD_HASH_LEN],
const uint8_t argv_id[BUILD_HASH_LEN],
const BuildResolved* r) {
BuildDepEdge* row;
size_t i;
if (!log || !target.s || target.len == 0u || target.len >= BUILD_TARGET_MAX ||
- !overlay_id || !config_id || !argv_id || !r || !r->leafset)
+ !overlay_id || !argv_id || !r || !r->leafset)
return BUILD_ERR;
for (i = 0; i < log->n_deps; ++i) {
row = &log->deps[i];
if (strlen(row->name) == target.len &&
memcmp(row->name, target.s, target.len) == 0 &&
build_id_eq(row->overlay_id, overlay_id) &&
- build_id_eq(row->config_id, config_id) &&
build_id_eq(row->argv_id, argv_id) &&
build_id_eq(row->output_tree, r->output_tree))
return BUILD_OK;
@@ -216,7 +214,6 @@ static int append_dep(BuildDepLog* log, KitSlice target,
memcpy(row->name, target.s, target.len);
row->name[target.len] = '\0';
memcpy(row->overlay_id, overlay_id, BUILD_HASH_LEN);
- memcpy(row->config_id, config_id, BUILD_HASH_LEN);
memcpy(row->argv_id, argv_id, BUILD_HASH_LEN);
memcpy(row->output_tree, r->output_tree, BUILD_HASH_LEN);
log->child_leafsets[log->n_children++] = r->leafset;
@@ -504,7 +501,7 @@ static int build_run_recipe_impl(KitBuildCoordinator* c, KitSlice target,
sizeof out->path) != BUILD_OK)
goto out_cleanup;
if (record_traces) {
- if (build_runner_record_traces(c, target, cfg, argv, &log,
+ if (build_runner_record_traces(c, target, argv, &log,
out->output_tree, &out->leafset) != BUILD_OK)
goto out_cleanup;
} else {
@@ -643,7 +640,7 @@ static int build_run_test_recipe_impl(KitBuildCoordinator* c, KitSlice target,
out->exit_code = exit_code;
out->status = exit_code == 0 ? KIT_TEST_PASS : KIT_TEST_FAIL;
if (out->status == KIT_TEST_PASS && record_traces) {
- if (build_runner_record_test_traces(c, target, cfg, argv, &log,
+ if (build_runner_record_test_traces(c, target, argv, &log,
out->result_tree, &out->leafset) !=
BUILD_OK)
goto out_cleanup;
@@ -792,8 +789,7 @@ int build_runner_service(KitBuildCoordinator* c, KitBuildConn* conn,
BuildConfig dep_cfg, overlay_cfg, empty_cfg;
BuildArgv dep_argv;
BuildResolved r;
- uint8_t overlay_id[BUILD_HASH_LEN], cfg_id[BUILD_HASH_LEN],
- argv_id[BUILD_HASH_LEN];
+ uint8_t overlay_id[BUILD_HASH_LEN], argv_id[BUILD_HASH_LEN];
char dep_target[BUILD_TARGET_MAX];
build_config_init(&dep_cfg, cfg_entries,
sizeof cfg_entries / sizeof cfg_entries[0]);
@@ -811,13 +807,12 @@ int build_runner_service(KitBuildCoordinator* c, KitBuildConn* conn,
&overlay_cfg) != BUILD_OK ||
store_config_blob(c, &overlay_cfg, overlay_id) != BUILD_OK ||
build_argv_set(&dep_argv, req.argv, req.argc) != BUILD_OK ||
- build_config_id(c->ctx->heap, &dep_cfg, cfg_id) != BUILD_OK ||
build_argv_id(c->ctx->heap, &dep_argv, argv_id) != BUILD_OK ||
build_resolve(c, kit_slice_cstr(dep_target), &dep_cfg, &dep_argv,
chain, &r) !=
BUILD_OK ||
- append_dep(log, kit_slice_cstr(dep_target), overlay_id, cfg_id,
- argv_id, &r) !=
+ append_dep(log, kit_slice_cstr(dep_target), overlay_id, argv_id,
+ &r) !=
BUILD_OK) {
resp_error(&resp, KIT_ERR, "need failed");
} else {
@@ -832,8 +827,7 @@ int build_runner_service(KitBuildCoordinator* c, KitBuildConn* conn,
BuildArgv dep_argv;
BuildTargetFuture* f = NULL;
BuildPendingNeed* p;
- uint8_t overlay_id[BUILD_HASH_LEN], cfg_id[BUILD_HASH_LEN],
- argv_id[BUILD_HASH_LEN];
+ uint8_t overlay_id[BUILD_HASH_LEN], argv_id[BUILD_HASH_LEN];
char dep_target[BUILD_TARGET_MAX];
char err[128];
build_config_init(&dep_cfg, cfg_entries,
@@ -853,7 +847,6 @@ int build_runner_service(KitBuildCoordinator* c, KitBuildConn* conn,
&overlay_cfg) != BUILD_OK ||
store_config_blob(c, &overlay_cfg, overlay_id) != BUILD_OK ||
build_argv_set(&dep_argv, req.argv, req.argc) != BUILD_OK ||
- build_config_id(c->ctx->heap, &dep_cfg, cfg_id) != BUILD_OK ||
build_argv_id(c->ctx->heap, &dep_argv, argv_id) != BUILD_OK ||
build_dispatch(c, kit_slice_cstr(dep_target), &dep_cfg, &dep_argv,
chain, &f, err, sizeof err) != BUILD_OK) {
@@ -864,7 +857,6 @@ int build_runner_service(KitBuildCoordinator* c, KitBuildConn* conn,
p->token = log->next_token++;
target_copy(kit_slice_cstr(dep_target), p->dep);
memcpy(p->overlay_id, overlay_id, BUILD_HASH_LEN);
- memcpy(p->config_id, cfg_id, BUILD_HASH_LEN);
memcpy(p->argv_id, argv_id, BUILD_HASH_LEN);
p->future = f;
resp.token = p->token;
@@ -880,7 +872,7 @@ int build_runner_service(KitBuildCoordinator* c, KitBuildConn* conn,
resp.text = kit_slice_cstr(p->result.path);
} else if (build_coord_target_await(c, p->future, &r) != BUILD_OK ||
append_dep(log, kit_slice_cstr(p->dep), p->overlay_id,
- p->config_id, p->argv_id, &r) != BUILD_OK) {
+ p->argv_id, &r) != BUILD_OK) {
resp_error(&resp, KIT_ERR, "need-await failed");
} else {
p->awaited = 1;
@@ -896,8 +888,7 @@ int build_runner_service(KitBuildCoordinator* c, KitBuildConn* conn,
}
int build_runner_record_traces(KitBuildCoordinator* c, KitSlice target,
- const BuildConfig* cfg, const BuildArgv* argv,
- const BuildDepLog* log,
+ const BuildArgv* argv, const BuildDepLog* log,
const uint8_t output[BUILD_HASH_LEN],
const BuildLeafSet** out_leafset) {
BuildLeafSet direct;
@@ -911,7 +902,7 @@ int build_runner_record_traces(KitBuildCoordinator* c, KitSlice target,
char err[128];
int ok = BUILD_ERR;
- if (!c || !cfg || !argv || !log || !output || !out_leafset)
+ if (!c || !argv || !log || !output || !out_leafset)
return BUILD_ERR;
memset(&direct, 0, sizeof direct);
if (target_copy(target, direct.target) != BUILD_OK) return BUILD_ERR;
@@ -935,8 +926,7 @@ int build_runner_record_traces(KitBuildCoordinator* c, KitSlice target,
if (target_copy(target, shallow.target) != BUILD_OK) return BUILD_ERR;
memcpy(shallow.recipe, direct.recipe, BUILD_HASH_LEN);
memcpy(shallow.output, output, BUILD_HASH_LEN);
- if (store_config_blob(c, cfg, shallow.config) != BUILD_OK ||
- store_argv_blob(c, argv, shallow.argv) != BUILD_OK)
+ if (store_argv_blob(c, argv, shallow.argv) != BUILD_OK)
return BUILD_ERR;
shallow.configs = log->configs;
shallow.n_configs = log->n_configs;
@@ -986,7 +976,6 @@ out:
}
int build_runner_record_test_traces(KitBuildCoordinator* c, KitSlice target,
- const BuildConfig* cfg,
const BuildArgv* argv,
const BuildDepLog* log,
const uint8_t result[BUILD_HASH_LEN],
@@ -1002,7 +991,7 @@ int build_runner_record_test_traces(KitBuildCoordinator* c, KitSlice target,
char err[128];
int ok = BUILD_ERR;
- if (!c || !cfg || !argv || !log || !result || !out_leafset)
+ if (!c || !argv || !log || !result || !out_leafset)
return BUILD_ERR;
memset(&direct, 0, sizeof direct);
if (target_copy(target, direct.target) != BUILD_OK) return BUILD_ERR;
@@ -1026,8 +1015,7 @@ int build_runner_record_test_traces(KitBuildCoordinator* c, KitSlice target,
if (target_copy(target, shallow.target) != BUILD_OK) return BUILD_ERR;
memcpy(shallow.recipe, direct.recipe, BUILD_HASH_LEN);
memcpy(shallow.output, result, BUILD_HASH_LEN);
- if (store_config_blob(c, cfg, shallow.config) != BUILD_OK ||
- store_argv_blob(c, argv, shallow.argv) != BUILD_OK)
+ if (store_argv_blob(c, argv, shallow.argv) != BUILD_OK)
return BUILD_ERR;
shallow.configs = log->configs;
shallow.n_configs = log->n_configs;
diff --git a/src/build/runner.h b/src/build/runner.h
@@ -25,14 +25,13 @@
*/
/* One submitted-but-not-yet-awaited need: the coordinator-assigned token, the
- * resolved (config-id, argv-id) it was dispatched under, and the in-flight
- * future. Becomes a recorded dep edge only when AWAITED; un-awaited entries are
+ * (overlay-id, argv-id) it was dispatched under, and the in-flight future.
+ * Becomes a recorded dep edge only when AWAITED; un-awaited entries are
* cancelled when the recipe exits. */
typedef struct BuildPendingNeed {
uint64_t token;
char dep[BUILD_TARGET_MAX];
uint8_t overlay_id[BUILD_HASH_LEN];
- uint8_t config_id[BUILD_HASH_LEN];
uint8_t argv_id[BUILD_HASH_LEN];
BuildTargetFuture* future; /* from build_coord_dispatch */
int awaited;
@@ -108,16 +107,15 @@ int build_runner_service(KitBuildCoordinator*, KitBuildConn*, KitSlice target,
* - emit + store the SHALLOW trace (direct inputs + dep edges, by hash) and the
* DEEP trace (scalars + the root deep-set-id), and prepend both to the target
* record.
- * Records the target's recipe-id, config-id and argv-id, and each dep edge's
- * (config-id, argv-id). Returns the root deepset node via *out_leafset so the
- * caller can hand it back in BuildResolved. */
+ * Records the target's recipe-id and argv-id, its propagated-config
+ * observations, and each dep edge's (overlay-id, argv-id). Returns the root
+ * deepset node via *out_leafset so the caller can hand it back in
+ * BuildResolved. */
int build_runner_record_traces(KitBuildCoordinator*, KitSlice target,
- const BuildConfig* cfg, const BuildArgv* argv,
- const BuildDepLog*,
+ const BuildArgv* argv, const BuildDepLog*,
const uint8_t output[BUILD_HASH_LEN],
const BuildLeafSet** out_leafset);
int build_runner_record_test_traces(KitBuildCoordinator*, KitSlice target,
- const BuildConfig* cfg,
const BuildArgv* argv,
const BuildDepLog*,
const uint8_t result[BUILD_HASH_LEN],
diff --git a/src/build/trace.c b/src/build/trace.c
@@ -127,8 +127,6 @@ static int dep_cmp(const BuildDepEdge* a, const BuildDepEdge* b) {
if (c != 0) return c;
c = id_cmp(a->overlay_id, b->overlay_id);
if (c != 0) return c;
- c = id_cmp(a->config_id, b->config_id);
- if (c != 0) return c;
return id_cmp(a->argv_id, b->argv_id);
}
@@ -290,18 +288,14 @@ static int emit_sorted_blobs(const BuildBlobLeaf* rows, size_t n,
}
static int emit_dep_row(KitWriter* out, const BuildDepEdge* row) {
- char ovr[BUILD_HEX_LEN], cfg[BUILD_HEX_LEN], argv[BUILD_HEX_LEN],
- tree[BUILD_HEX_LEN];
+ char ovr[BUILD_HEX_LEN], argv[BUILD_HEX_LEN], tree[BUILD_HEX_LEN];
hex_encode(ovr, row->overlay_id);
- hex_encode(cfg, row->config_id);
hex_encode(argv, row->argv_id);
hex_encode(tree, row->output_tree);
if (emit_bytes(out, row->name) != BUILD_OK) return BUILD_ERR;
if (emit_bytes(out, " ") != BUILD_OK) return BUILD_ERR;
if (emit_bytes(out, ovr) != BUILD_OK) return BUILD_ERR;
if (emit_bytes(out, " ") != BUILD_OK) return BUILD_ERR;
- if (emit_bytes(out, cfg) != BUILD_OK) return BUILD_ERR;
- if (emit_bytes(out, " ") != BUILD_OK) return BUILD_ERR;
if (emit_bytes(out, argv) != BUILD_OK) return BUILD_ERR;
if (emit_bytes(out, " ") != BUILD_OK) return BUILD_ERR;
if (emit_bytes(out, tree) != BUILD_OK) return BUILD_ERR;
@@ -421,7 +415,6 @@ static int shallow_emit_like(const BuildShallowTrace* t, KitWriter* out,
if (emit_text_kv(out, "target", t->target) != BUILD_OK) return BUILD_ERR;
if (emit_kv(out, "recipe", t->recipe) != BUILD_OK) return BUILD_ERR;
if (emit_kv(out, result_key, t->output) != BUILD_OK) return BUILD_ERR;
- if (emit_kv(out, "config", t->config) != BUILD_OK) return BUILD_ERR;
if (emit_kv(out, "argv", t->argv) != BUILD_OK) return BUILD_ERR;
if (emit_sorted_configs(t->configs, t->n_configs, out, err, errcap) !=
BUILD_OK)
@@ -794,16 +787,15 @@ static int append_deepset_blob(BuildDeepSet* out, char** fields, size_t n,
static int append_dep(BuildShallowTrace* out, char** fields, size_t n, char* err,
size_t errcap) {
BuildDepEdge* row;
- if (expect_field_count(n, 5u, err, errcap) != BUILD_OK) return BUILD_ERR;
+ if (expect_field_count(n, 4u, err, errcap) != BUILD_OK) return BUILD_ERR;
if (out->n_deps >= out->cap_deps)
return set_err(err, errcap, "too many dep rows");
if (!valid_target(fields[0])) return set_err(err, errcap, "bad dep target");
row = &out->deps[out->n_deps];
snprintf(row->name, sizeof row->name, "%s", fields[0]);
if (hex_decode_strict(fields[1], row->overlay_id) != BUILD_OK ||
- hex_decode_strict(fields[2], row->config_id) != BUILD_OK ||
- hex_decode_strict(fields[3], row->argv_id) != BUILD_OK ||
- hex_decode_strict(fields[4], row->output_tree) != BUILD_OK)
+ hex_decode_strict(fields[2], row->argv_id) != BUILD_OK ||
+ hex_decode_strict(fields[3], row->output_tree) != BUILD_OK)
return set_err(err, errcap, "bad dep hash");
if (out->n_deps && dep_cmp(&out->deps[out->n_deps - 1u], row) >= 0)
return set_err(err, errcap, "non-canonical dep rows");
@@ -855,9 +847,8 @@ static int append_record_row(BuildTargetRecord* out, char** fields, size_t n,
#define F_TARGET 0x01u
#define F_RECIPE 0x02u
#define F_OUTPUT 0x04u
-#define F_CONFIG 0x08u
-#define F_ARGV 0x10u
-#define F_DEEPSET 0x20u
+#define F_ARGV 0x08u
+#define F_DEEPSET 0x10u
static int shallow_parse_like(const uint8_t* data, size_t len,
BuildShallowTrace* out, char* err,
@@ -884,7 +875,7 @@ static int shallow_parse_like(const uint8_t* data, size_t len,
p.first = 1;
for (;;) {
char* line;
- char* fields[5];
+ char* fields[4];
size_t n = 0;
int r = parser_next(&p, &line, err, errcap);
if (r < 0) return BUILD_ERR;
@@ -897,7 +888,7 @@ static int shallow_parse_like(const uint8_t* data, size_t len,
}
if (line[0] == '[') {
if (strcmp(line, "[config]") == 0 && sec == TRACE_SEC_TOP) {
- if (seen != (F_TARGET | F_RECIPE | F_OUTPUT | F_CONFIG | F_ARGV))
+ if (seen != (F_TARGET | F_RECIPE | F_OUTPUT | F_ARGV))
return set_err(err, errcap, "missing shallow field");
sec = TRACE_SEC_CONFIG;
} else if (strcmp(line, "[source]") == 0 && sec == TRACE_SEC_CONFIG) {
@@ -930,10 +921,6 @@ static int shallow_parse_like(const uint8_t* data, size_t len,
if (parse_top_hash(fields, n, result_key, out->output, &seen,
F_OUTPUT, err, errcap) != BUILD_OK)
return BUILD_ERR;
- } else if (!(seen & F_CONFIG)) {
- if (parse_top_hash(fields, n, "config", out->config, &seen, F_CONFIG,
- err, errcap) != BUILD_OK)
- return BUILD_ERR;
} else if (!(seen & F_ARGV)) {
if (parse_top_hash(fields, n, "argv", out->argv, &seen, F_ARGV, err,
errcap) != BUILD_OK)
diff --git a/src/build/trace.h b/src/build/trace.h
@@ -50,15 +50,15 @@ typedef struct BuildBlobLeaf {
} BuildBlobLeaf;
/* One target-dep edge (shallow only): replays one `need`. Carries the explicit
- * need overlay-id, the dep's effective config-id and argv-id, and the output
- * tree-id it produced. Shallow replay recomputes the dep config as the current
- * parent config overlaid by overlay-id; config-id is retained for sharing,
- * exact trace description, and bundle closure. Sorted by
- * (name, overlay-id, config-id, argv-id). */
+ * need overlay-id, the dep's effective argv-id, and the output tree-id it
+ * produced. Shallow replay recomputes the dep config as the current parent
+ * config overlaid by the map recovered through overlay-id, so the effective
+ * config-id is not stored: it is derived from the current request and carries
+ * no independent replay information. Sorted by
+ * (name, overlay-id, argv-id). */
typedef struct BuildDepEdge {
char name[BUILD_TARGET_MAX];
uint8_t overlay_id[BUILD_HASH_LEN];
- uint8_t config_id[BUILD_HASH_LEN];
uint8_t argv_id[BUILD_HASH_LEN];
uint8_t output_tree[BUILD_HASH_LEN];
} BuildDepEdge;
@@ -79,13 +79,12 @@ typedef struct BuildConfigLeaf {
} BuildConfigLeaf;
/* The shallow trace: direct inputs + direct deps, for fine-grained rechecking.
- * `config` is this target's effective propagated config-id (full map blob);
- * `argv` is its serialized local-config blob id. */
+ * Propagated config is represented only by the consumed observations below;
+ * `argv` is the target's serialized local-config blob id. */
typedef struct BuildShallowTrace {
char target[BUILD_TARGET_MAX];
uint8_t recipe[BUILD_HASH_LEN];
uint8_t output[BUILD_HASH_LEN];
- uint8_t config[BUILD_HASH_LEN];
uint8_t argv[BUILD_HASH_LEN];
BuildConfigLeaf* configs; /* [config] section */
size_t n_configs;
diff --git a/test/build/build_pure_test.c b/test/build/build_pure_test.c
@@ -256,8 +256,6 @@ static void test_trace(void) {
"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f\n"
"output "
"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20\n"
- "config "
- "02030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f2021\n"
"argv "
"030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122\n"
"[config]\n"
@@ -273,8 +271,6 @@ static void test_trace(void) {
"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f\n"
"output "
"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20\n"
- "config "
- "02030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f2021\n"
"argv "
"030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122\n"
"[config]\n"
@@ -302,7 +298,6 @@ static void test_trace(void) {
strcpy(st.target, "//app:bin");
fill_id(st.recipe, 0);
fill_id(st.output, 1);
- fill_id(st.config, 2);
fill_id(st.argv, 3);
st.configs = keys;
st.n_configs = 2;
@@ -335,7 +330,6 @@ static void test_trace(void) {
st.cap_deps = 1;
strcpy(deps[0].name, "//lib:core");
fill_id(deps[0].overlay_id, 6);
- fill_id(deps[0].config_id, 7);
fill_id(deps[0].argv_id, 8);
fill_id(deps[0].output_tree, 9);
@@ -344,6 +338,15 @@ static void test_trace(void) {
"emit shallow");
bytes.data = kit_writer_mem_bytes(w, &bytes.len);
build_trace_id(bytes.data, bytes.len, trace_id);
+ EXPECT(!bytes_contains(bytes.data, bytes.len, "\nconfig "),
+ "shallow omits redundant effective config id");
+ EXPECT(bytes_contains(
+ bytes.data, bytes.len,
+ "\n[dep]\n//lib:core "
+ "060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425 "
+ "08090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f2021222324252627 "
+ "090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728\n"),
+ "shallow canonical dep row");
memset(&parsed, 0, sizeof parsed);
parsed.configs = parsed_keys;
@@ -361,6 +364,10 @@ static void test_trace(void) {
strcmp(parsed.sources[0].path, "src/a.c") == 0 &&
parsed.sources[0].absent &&
memcmp(parsed.deps[0].overlay_id, deps[0].overlay_id,
+ BUILD_HASH_LEN) == 0 &&
+ memcmp(parsed.deps[0].argv_id, deps[0].argv_id,
+ BUILD_HASH_LEN) == 0 &&
+ memcmp(parsed.deps[0].output_tree, deps[0].output_tree,
BUILD_HASH_LEN) == 0,
"shallow canonical sections");
EXPECT(build_shallow_parse((const uint8_t*)dup_source, sizeof dup_source - 1u,
diff --git a/test/buildcoord/run.sh b/test/buildcoord/run.sh
@@ -131,6 +131,9 @@ EOF
cat > "$ws/src/noop.txt" <<'EOF'
first
EOF
+cat > "$ws/src/scope-noop.txt" <<'EOF'
+first
+EOF
cat > "$ws/src/depfile/a.h" <<'EOF'
dep-a:v1
EOF
@@ -488,6 +491,7 @@ cat > "$ws/recipes/scope_leaf.sh" <<'EOF'
#!/bin/sh
set -eu
mkdir -p "$KIT_BUILD_OUT"
+"$KIT" build source src/scope-noop.txt >/dev/null
mode=$("$KIT" build config-get mode || true)
printf 'mode:%s\n' "$mode" > "$KIT_BUILD_OUT/mode.txt"
EOF
@@ -1320,6 +1324,23 @@ else
"$work/config-override-tree.diag"
fi
+cat > "$ws/src/scope-noop.txt" <<'EOF'
+second
+EOF
+build_assert_ok buildcoord-config-override-shallow --trace --stats \
+ --config mode=profile //scope:parent-override
+contains "buildcoord-config-override-shallow-parent-hit" \
+ "$work/buildcoord-config-override-shallow.err" \
+ "trace shallow-hit target=//scope:parent-override"
+contains "buildcoord-config-override-shallow-one-run" \
+ "$work/buildcoord-config-override-shallow.err" "recipes_run=1"
+contains "buildcoord-config-override-shallow-count" \
+ "$work/buildcoord-config-override-shallow.err" "shallow_hits=1"
+override_shallow_path=$(
+ tree_path_from "$work/buildcoord-config-override-shallow.out")
+contains "buildcoord-config-override-shallow-output" \
+ "$override_shallow_path/mode.txt" "mode:forced"
+
build_assert_ok buildcoord-config-shadow-parent-cold --stats --config mode=parent1 \
//shadow:parent
contains "buildcoord-config-shadow-parent-cold-run" \