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
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
---
id: AILOG-2026-09-17-004
title: Baton batch inheritance — nearest declaration (#428)
status: accepted
created: 2026-09-17
agent: claude-opus-5-1m
confidence: high
review_required: false
risk_level: medium
eu_ai_act_risk: not_applicable
nist_genai_risks: [human_ai_config, information_integrity]
iso_42001_clause: []
files_modified:
- experiment-baton/src/units.rs
- experiment-baton/src/main.rs
- experiment-baton/tests/batch_inheritance.rs
- experiment-baton/tests/task_inheritance.rs
- experiment-baton/06-work-verb-schema-ratification.md
- experiment-baton/07-track-c-adopter-kit.md
observability_scope: none
tags: [baton, adopter-feedback, inheritance, track-c]
related:
- 07-ai-audit/decisions/AIDEC-2026-09-17-001-baton-batch-parent-nearest-declaration.md
---

# AILOG: Baton batch inheritance (#428)

## Summary

Baton now reads batch declarations. A batch takes the nearest declaration: its own ledger line,
else its AILOG's frontmatter, else the Charters whose ledger that AILOG is, when they agree. Before,
every batch was `undeclared`, and the ratification and the Track C kit disagreed on where a batch
declares.

## Context

Estoa's #428. The ratification placed batch verbs on a ledger line, while the framework's AILOG
template and the Track C kit used the AILOG frontmatter, and `read_batches` read neither. This
follows the task inheritance of #430 and the inventory hygiene of #435, and reuses both: the
Charter parent index and the comment-aware scanning.

## Actions Performed

1. The parent-declaration code moves into its own section of `units.rs`, shared by tasks and
batches:
- `CharterOrigin` gains `ledger_ailog`, resolved as `charter batch-complete` does
(`originating_ailogs[0]`, else `execution_ailogs[0]`); a link field that is not a list of ids
counts as unreadable;
- `ailog_key` compares ids on the CLI's five-segment key;
- `agreed` is the all-parents-agree rule, now used by both granularities.
2. `read_batches` scans live ledger text only (HTML comments, fences and other sections are
ignored) and collects each batch's own `Work verb` / `Design provenance` lines. An override
replaces the parent's declaration as a whole. `batch_parent` resolves the AILOG frontmatter,
else the agreeing ledger Charters; an unreadable AILOG frontmatter blocks inheritance.
3. `task_inheritance_blockers` → `inheritance_blockers`. The CLI note now covers `--granularity
batch` and says "inheritance from Charters disabled"; the #430 test assertion follows.
4. `tests/batch_inheritance.rs` has 9 cases: the #428 reproduction, both ledger links, non-ledger
and `…-001b` links, agreement, AILOG-over-Charter precedence, whole-declaration overrides,
examples and other sections, an unreadable AILOG and an unreadable Charter. 8 of them fail
before the change; the ninth is a negative case.
5. Ratification §3 note and kit §2 table describe the one contract.

## Decisions Made

`AIDEC-2026-09-17-001`: the maintainer chose "nearest declaration" over ledger-only (which would
leave the AILOG slot inert) and over any Charter mentioning the AILOG (which would make motivating
Charters into parents).

## Impact

Measured read-only (git status unchanged in each corpus). Batches classified, previously all
`undeclared`:

| Corpus | Classified batches | Source of the declaration |
|---|---|---|
| Sentinel | 22 | Charters 54 (`operate`) and 55 (`implement/new`), plus two others, through their ledger AILOGs |
| LNXDrive | 7 | Charter 02 through its ledger AILOG |
| Estoa | 8 | AILOG frontmatter |

Spot-checked against the source AILOGs and Charters. **Performance / Security / Privacy /
Environmental**: N/A; still read-only.

## Verification

- [x] `cargo test -p straymark-baton --locked`: 112 passed
- [x] `cargo clippy -p straymark-baton --all-targets --locked -- -D warnings`
- [x] Read-only measurement on three adopter corpora
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
---
id: AIDEC-2026-09-17-001
title: Baton batch parent — nearest declaration (ledger line → AILOG → Charter)
status: accepted
created: 2026-09-17
agent: claude-opus-5-1m
confidence: high
review_required: false
risk_level: medium
eu_ai_act_risk: not_applicable
nist_genai_risks: [human_ai_config]
iso_42001_clause: []
tags: [baton, inheritance, adopter-feedback]
related:
- 07-ai-audit/decisions/AIDEC-2026-09-13-001-baton-task-parent.md
---

# AIDEC: Baton batch parent — nearest declaration

## Context

The work-verb ratification (#332 §3) places a batch's declaration on a `- **Work verb**:` line in
its AILOG ledger entry and says batches inherit from their parent by default. The framework's AILOG
template, however, ships `work_verb` / `design_provenance` in the AILOG **frontmatter**, and the
Track C kit told adopters to use that. Baton read neither, so every batch was `undeclared` (#428,
Estoa).

## Problem

What is a batch's parent, and how do the two existing slots relate, without inventing a new slot
and without guessing ownership?

## Alternatives Considered

1. **Ledger line only, no inheritance.** Minimal, but it leaves the AILOG frontmatter slot inert
and forces a line on every batch of a homogeneous ledger. That is the artificial
fragmentation the ratification rules out.
2. **Nearest declaration: ledger line → AILOG frontmatter → Charter.** Each existing slot has a
role. The AILOG frontmatter is the homogeneous default for its batches, and a ledger line is the
override for a heterogeneous one. When the AILOG declares nothing, the batches inherit from the
Charters whose ledger the AILOG is.
3. **Inherit from any Charter that mentions the AILOG** (`originating_ailogs` anywhere, or
`execution_ailogs`). More coverage, but a Charter that was merely *motivated* by an AILOG
would become its batches' parent.

## Decision

Option 2, chosen by the maintainer on 2026-09-17.

- **Charter link.** It is the one `straymark charter batch-complete` writes through:
`originating_ailogs[0]`, else `execution_ailogs[0]`. AILOG ids are compared on their first five
`-` segments, as the CLI resolves them, so `…-028` and `…-028b` never match.
- **Several such Charters must agree.** This is the rule of AIDEC-2026-09-13-001 for tasks.
- **An override replaces the whole declaration.** A verb line without provenance does not inherit
the parent's provenance, and a provenance line alone declares nothing. The same unit semantics
apply to `followups declare`.
- **Unreadable sources.** If an AILOG's frontmatter cannot be read, its batches do not inherit from
anywhere, because it may declare something unseen. Their own lines still apply. An unreadable
Charter disables Charter-level inheritance project-wide, as for tasks, but not AILOG-level
declarations.
- **Scope of the scan.** Only live ledger text counts. HTML comments, fenced blocks and any line
after another heading are ignored.

## Consequences

- The Track C kit and the ratification now describe one contract instead of two.
- Adopters who already declared on the AILOG frontmatter see their batches classified without
touching anything. Measured read-only: Sentinel 0 → 22 batches, LNXDrive 0 → 7, Estoa 0 → 8.
- A Charter chain sharing one ledger AILOG with disagreeing declarations leaves its batches
`undeclared`. That is correct, since ownership is then ambiguous. Such batches can be declared
on their own lines.
- Not decided here: follow-up parent links and declarations on specs.

## Implementation

`experiment-baton/src/units.rs` (`read_batches`, `batch_parent`, `ledger_ailog`, `agreed`),
covered by `experiment-baton/tests/batch_inheritance.rs`.

## References

- `experiment-baton/06-work-verb-schema-ratification.md` §3
- `experiment-baton/07-track-c-adopter-kit.md` §2
- Issues #332, #428
23 changes: 19 additions & 4 deletions experiment-baton/06-work-verb-schema-ratification.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,10 +114,25 @@ incumple el esquema tipado sigue contando como padre. No se deduce pertenencia d
títulos ni desde el orden de archivos. Un verbo inválido conserva el comportamiento
conservador del clasificador. El vínculo `context_spec` no establece parentesco.

Batch, overrides por ledger, vínculos de follow-ups y declaraciones directas en
spec continúan pendientes. El kit Track C y el placement de batches tienen una
discrepancia documentada en #428; esta corrección de tareas no redefine ese contrato.
La herencia es una regla ratificada; estos límites describen su implementación
La herencia de Batch también está implementada (#428). Un batch toma la **declaración
más cercana**:

1. su propia línea `- **Work verb**:` en la entrada del ledger (el override por
heterogeneidad);
2. si no la tiene, el frontmatter del AILOG que contiene el ledger (el slot que ya trae
la plantilla de AILOG, y que actúa como el "padre" homogéneo de sus batches);
3. si tampoco, los Charters cuyo AILOG de ledger es ese AILOG, con la misma resolución
que usa `straymark charter batch-complete` (`originating_ailogs[0]`, si no
`execution_ailogs[0]`), siempre que todos declaren lo mismo.

Un override reemplaza la declaración completa: una línea de verbo sin procedencia no
hereda la procedencia del padre, y una línea de procedencia sola no declara nada. Si el
frontmatter del AILOG es ilegible, sus batches no heredan: podría declarar algo que no
se ve. Las líneas dentro de comentarios, bloques de código u otras secciones no
declaran nada.

Siguen pendientes los vínculos de follow-ups con un padre y las declaraciones directas
en spec. La herencia es una regla ratificada; estos límites describen su implementación
parcial en [`src/units.rs`](src/units.rs), no una validación humana de las declaraciones.

## 4. Undeclared = estado honesto
Expand Down
2 changes: 1 addition & 1 deletion experiment-baton/07-track-c-adopter-kit.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ descomentar y llenar:
| Unidad | Slot |
|---|---|
| Charter | Frontmatter: `work_verb:` / `design_provenance:` |
| Batch (AILOG ledger) | Línea `- **Work verb**:` en la entrada del batch; placement ratificado, aún pendiente en el inventario (#428) |
| Batch (AILOG ledger) | Línea `- **Work verb**:` (y `- **Design provenance**:`) en la entrada del batch **solo si difiere** de su padre. Si no, hereda del frontmatter del AILOG y, en su defecto, de los Charters cuyo ledger es ese AILOG, si coinciden (#428) |
| Follow-up | Líneas `- **Work verb**:` / `- **Design provenance**:`, escritas con `straymark followups declare FU-NNN --work-verb …` o con `followups new --work-verb …` (cli-3.49.0+; nunca a mano ni en `Notes`) |
| Task (SpecKit) | Sin slot propio — hereda de los Charters cuyo `originating_spec` resuelve al `spec.md` hermano, si todos declaran lo mismo; vínculo ausente o discrepante → `undeclared` |

Expand Down
13 changes: 7 additions & 6 deletions experiment-baton/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use straymark_baton::signals::signals_for;
use straymark_baton::speckit;
use straymark_baton::telemetry::{build_report, EconomicTelemetry, UnitRouting};
use straymark_baton::tiers::Policy;
use straymark_baton::units::{inventory, task_inheritance_blockers, Granularity};
use straymark_baton::units::{inheritance_blockers, inventory, Granularity};

#[derive(Parser)]
#[command(
Expand Down Expand Up @@ -174,16 +174,17 @@ fn parse_granularity(s: &str) -> anyhow::Result<Option<Granularity>> {
})
}

/// Say why tasks stay undeclared when an unreadable Charter disables task
/// inheritance (#427) — otherwise the only hint is "declare the verb".
/// Say why tasks and batches stay undeclared when an unreadable Charter
/// disables inheritance (#427, #428) — otherwise the only hint is "declare the
/// verb".
fn note_inheritance_blockers(root: &Path, only: Option<Granularity>) {
if only.is_some_and(|g| g != Granularity::Task) {
if only.is_some_and(|g| !matches!(g, Granularity::Task | Granularity::Batch)) {
return;
}
let blockers = task_inheritance_blockers(root);
let blockers = inheritance_blockers(root);
if !blockers.is_empty() {
eprintln!(
"{} task inheritance disabled — unreadable Charter frontmatter could hide a competing parent: {}",
"{} inheritance from Charters disabled — unreadable Charter frontmatter could hide a competing parent: {}",
"note:".yellow(),
blockers.join(", ")
);
Expand Down
Loading