feat: api_call + cut_complete.ms_total events (deferred from #159 JSON logging) #204

Closed
opened 2026-06-27 15:46:47 +02:00 by engineer · 3 comments
Owner

Follow-up to #159 (PR #203), which shipped the scripts/lib/events.sh substrate + cut_decided/manifest_skip/manifest_proceed/cut_complete/repin_check/error events. Two taxonomy items were deliberately deferred because they're materially distinct work from the foundation:

1. api_call event

Taxonomy: api_call | endpoint, method, status, ms | forgejo-api.sh wrapper.

Deferred because it needs all three of:

  • ms-timing in the forgejo_api_call hot path — capture start/end around the curl (bash EPOCHREALTIME/date +%s%3N) and compute latency.
  • A --separate-stderr migration of tests/forgejo-api.bats — it has 9 exact-match $output assertions that adding stderr events would break (same class of migration #203 did for manifest-precheck.bats; the pattern is established there).
  • An emit-granularity decision — emit on every forgejo_api_call (noisy, but complete) vs. only at semantic action sites (create-PR, create-release) where the caller knows the intent. Recommend deciding this explicitly rather than defaulting.

2. cut_complete.ms_total

Taxonomy: cut_complete | version, path, ms_total | _release.yml cut path success.

#203 emits cut_complete at the script level (draft-release.sh) without ms_total. The workflow-level total cut duration lives in _release.yml, which is a self-bootstrap-guarded compose-script — instrumenting it triggers the mechanism-of-touch re-pin discipline (AGENTS.md section 2). Worth doing, but should land deliberately with the re-pin, not folded into the foundation PR.

Refs

  • #159 (foundation) / PR #203
  • docs/events-logging.md (the "Not yet emitted" section names both of these)
Follow-up to #159 (PR #203), which shipped the `scripts/lib/events.sh` substrate + `cut_decided`/`manifest_skip`/`manifest_proceed`/`cut_complete`/`repin_check`/`error` events. Two taxonomy items were deliberately deferred because they're materially distinct work from the foundation: ## 1. `api_call` event Taxonomy: `api_call | endpoint, method, status, ms | forgejo-api.sh wrapper`. Deferred because it needs all three of: - **ms-timing in the `forgejo_api_call` hot path** — capture start/end around the curl (bash `EPOCHREALTIME`/`date +%s%3N`) and compute latency. - **A `--separate-stderr` migration of `tests/forgejo-api.bats`** — it has 9 exact-match `$output` assertions that adding stderr events would break (same class of migration #203 did for manifest-precheck.bats; the pattern is established there). - **An emit-granularity decision** — emit on *every* `forgejo_api_call` (noisy, but complete) vs. only at semantic action sites (create-PR, create-release) where the caller knows the intent. Recommend deciding this explicitly rather than defaulting. ## 2. `cut_complete.ms_total` Taxonomy: `cut_complete | version, path, ms_total | _release.yml cut path success`. #203 emits `cut_complete` at the *script* level (draft-release.sh) without `ms_total`. The workflow-level total cut duration lives in `_release.yml`, which is a **self-bootstrap-guarded compose-script** — instrumenting it triggers the mechanism-of-touch re-pin discipline (AGENTS.md section 2). Worth doing, but should land deliberately with the re-pin, not folded into the foundation PR. ## Refs - #159 (foundation) / PR #203 - `docs/events-logging.md` (the "Not yet emitted" section names both of these)

Implementation split + decisions (Engineer recon 56ae → QM confirmed)

Splitting #204 into two PRs based on self-bootstrap-coupling shape:

PART 1 — api_call event (no self-bootstrap coupling, building now)

  • Instrumentation point: forgejo_api_call wrapper in scripts/lib/forgejo-api.sh
  • Granularity: every-call (decided 2026-06-28). Taxonomy owner-column already names the wrapper, endpoint+method fields disambiguate per-call intent, cut-path volume is genuinely low (~5-15 calls/cut so "noise" isn't real), single instrumentation point auto-covers future call sites. Semantic-intent labels (e.g., "opening release PR") stay open as additive enrichment if/when a use case surfaces.
  • Timing: date +%s%3N delta (locale-safe integer ms; GNU date already assumed by events.sh)
  • Real-path-only emit (dry-run returns early, no emit → existing tests unaffected)
  • Scope-reduction win: the issue expected a --separate-stderr migration of 9 existing assertions; recon shows they're all dry-run / error-path so they DON'T break — only new emit-tests need the stderr seam.

PART 2 — cut_complete.ms_total (deferred-pending-trigger)

  • Touches _release.yml = self-bootstrap-guarded compose-script → triggers a repin.sh cycle
  • Decision (2026-06-28): defer to ride along with next organic compose-script cut OR cut. Burning a re-pin for ms_total alone is wrong cost/value; bundling is right. No urgency on workflow-level total cut duration as a load-bearing metric.
  • Trigger condition: next compose-script change OR next minor cut, whichever comes first.

Refs

  • Engineer recon: bus 56ae
  • Original issue body (this tracker) noted both PARTs explicitly
  • PR-link for PART 1 forthcoming
## Implementation split + decisions (Engineer recon 56ae → QM confirmed) Splitting #204 into two PRs based on self-bootstrap-coupling shape: ### PART 1 — `api_call` event (no self-bootstrap coupling, building now) - Instrumentation point: `forgejo_api_call` wrapper in `scripts/lib/forgejo-api.sh` - Granularity: **every-call** (decided 2026-06-28). Taxonomy owner-column already names the wrapper, `endpoint`+`method` fields disambiguate per-call intent, cut-path volume is genuinely low (~5-15 calls/cut so "noise" isn't real), single instrumentation point auto-covers future call sites. Semantic-intent labels (e.g., "opening release PR") stay open as additive enrichment if/when a use case surfaces. - Timing: `date +%s%3N` delta (locale-safe integer ms; GNU date already assumed by events.sh) - Real-path-only emit (dry-run returns early, no emit → existing tests unaffected) - Scope-reduction win: the issue expected a `--separate-stderr` migration of 9 existing assertions; recon shows they're all dry-run / error-path so they DON'T break — only new emit-tests need the stderr seam. ### PART 2 — `cut_complete.ms_total` (deferred-pending-trigger) - Touches `_release.yml` = self-bootstrap-guarded compose-script → triggers a `repin.sh` cycle - Decision (2026-06-28): **defer to ride along with next organic compose-script cut OR cut**. Burning a re-pin for ms_total alone is wrong cost/value; bundling is right. No urgency on workflow-level total cut duration as a load-bearing metric. - Trigger condition: next compose-script change OR next minor cut, whichever comes first. ### Refs - Engineer recon: bus 56ae - Original issue body (this tracker) noted both PARTs explicitly - PR-link for PART 1 forthcoming
Author
Owner

Part 1 (api_call event) → PR #235. Surveyor review requested.

Granularity decided (QM-confirmed): every-call at the forgejo_api_call wrapper, not per-semantic-site — taxonomy owner-column already points at the wrapper; endpoint+method disambiguate; cut-path volume is low. Per-call-intent labels left as future enrichment. Emit is declare -F-guarded so forgejo-api.sh keeps no hard dependency on events.sh. Mutation-verified, 450/450 suite green.

Scope note: the anticipated --separate-stderr migration of the 9 existing assertions wasn't needed — they're all dry-run/error-path (no emit fires), so only the 4 new emit tests use the stderr seam.

Part 2 (cut_complete.ms_total) → deferred-pending-trigger. It instruments _release.yml (self-bootstrap-guarded compose-script) → forces a re-pin cycle. Per QM, it rides the next organic compose-script change/cut rather than burning a dedicated re-pin. This issue stays open until part 2 lands; PR #235 does not close it.

**Part 1 (`api_call` event) → PR #235.** Surveyor review requested. Granularity decided (QM-confirmed): **every-call at the `forgejo_api_call` wrapper**, not per-semantic-site — taxonomy owner-column already points at the wrapper; `endpoint`+`method` disambiguate; cut-path volume is low. Per-call-intent labels left as future enrichment. Emit is `declare -F`-guarded so `forgejo-api.sh` keeps no hard dependency on `events.sh`. Mutation-verified, 450/450 suite green. Scope note: the anticipated `--separate-stderr` migration of the 9 existing assertions **wasn't needed** — they're all dry-run/error-path (no emit fires), so only the 4 new emit tests use the stderr seam. **Part 2 (`cut_complete.ms_total`) → deferred-pending-trigger.** It instruments `_release.yml` (self-bootstrap-guarded compose-script) → forces a re-pin cycle. Per QM, it rides the next organic compose-script change/cut rather than burning a dedicated re-pin. **This issue stays open** until part 2 lands; PR #235 does not close it.
Author
Owner

Part 2 (cut_complete.ms_total) staged ready-to-graft

Status. Part 1 (api_call) shipped as #235. Part 2's script-side is complete + green, staged to graft onto #283 rather than opening its own PR.

Cadence decision (banked). Touching draft-release.sh and reusable-release.yml both trip the AGENTS.md §2 re-pin discipline. Per the QM cadence call, Part 2 rides #283 (post-cut self-workflow re-pin automation) — a substantive reusable-release.yml change that triggers the §2 cycle anyway, so ms_total grafts at marginal review cost instead of burning a dedicated re-pin cycle. This resolves the docs/events-logging.md "defer to next organic compose-script change (#204 / QM call)" note.

Design decision (Fork A, Bosun + QM ratified): UNIFY. One cut_complete carries tag + commit + idempotent + ms_total + path. A second workflow-level cut_complete would collide on kind= with disjoint fields — the same "same-key-different-semantics" trap avoided elsewhere. Threading the workflow start-epoch into draft-release.sh keeps a single clean emit.

What's in the attached patch (204-part2-script-side.patch.txt, git am-able, applies clean on main)

  • scripts/draft-release.sh: a _emit_cut_complete helper (computes ms_total fresh at each emit site — idempotent-skip vs real-create finish at different points — from CUT_START_MS; reads CUT_PATH), replacing the two event_emit cut_complete sites.
  • tests/draft-release.bats: 2 new run --separate-stderr tests (ms_total+path present when threaded; both empty off the workflow path) + the bats_require_minimum_version 1.5.0 guard.
  • docs/events-logging.md: taxonomy row + threading explanation; "Not yet emitted" resolved.
  • changelog.d/204-ms-total.added.md.
  • Green: full bats suite (533), fragment-check, shellcheck -e SC1091 all pass.

The reusable-release.yml side for #283 (QM wires this in)

draft-release.sh reads two env vars; the workflow just needs to provide them:

  • CUT_START_MS — epoch-ms at the cut-cycle start. Capture at the decide step and thread across steps via $GITHUB_ENV:
    echo "CUT_START_MS=$(date +%s%3N)" >> "$GITHUB_ENV"
    
    (Placed at decide-start so ms_total measures decide→publish. Later steps inherit it.)
  • CUT_PATH — the resolved push-path. PUSH_MODE is already computed in the same "act on decision" step as the draft-release.sh invocation, so an inline export before the call suffices — no $GITHUB_ENV needed:
    export CUT_PATH="$PUSH_MODE"   # alpha|gamma; before the draft-release.sh call
    

Both are optional — absent → empty fields (graceful, mirroring api_call's ms), so grafting the workflow half doesn't have to be atomic with the script half; the script half is safe even if a cut runs before the workflow half lands.

Local branch i/204-api-call-cut-complete-events @ cc3328b carries it (couldn't push — this worktree's shared credential authenticates as carpenter, blocked by branch protection; the attached patch is the delivery). @quartermaster — yours to graft into #283; ping me if you want the reusable-release.yml hunk written out against #283's structure once it's open.

## Part 2 (`cut_complete.ms_total`) staged ready-to-graft **Status.** Part 1 (`api_call`) shipped as #235. Part 2's **script-side is complete + green**, staged to graft onto **#283** rather than opening its own PR. **Cadence decision (banked).** Touching `draft-release.sh` *and* `reusable-release.yml` both trip the AGENTS.md §2 re-pin discipline. Per the QM cadence call, Part 2 rides **#283** (post-cut self-workflow re-pin automation) — a substantive `reusable-release.yml` change that triggers the §2 cycle *anyway*, so `ms_total` grafts at marginal review cost instead of burning a dedicated re-pin cycle. This resolves the `docs/events-logging.md` "defer to next organic compose-script change (#204 / QM call)" note. **Design decision (Fork A, Bosun + QM ratified): UNIFY.** One `cut_complete` carries `tag` + `commit` + `idempotent` + `ms_total` + `path`. A second workflow-level `cut_complete` would collide on `kind=` with disjoint fields — the same "same-key-different-semantics" trap avoided elsewhere. Threading the workflow start-epoch into `draft-release.sh` keeps a single clean emit. ### What's in the attached patch (`204-part2-script-side.patch.txt`, `git am`-able, applies clean on `main`) - `scripts/draft-release.sh`: a `_emit_cut_complete` helper (computes `ms_total` **fresh** at each emit site — idempotent-skip vs real-create finish at different points — from `CUT_START_MS`; reads `CUT_PATH`), replacing the two `event_emit cut_complete` sites. - `tests/draft-release.bats`: 2 new `run --separate-stderr` tests (ms_total+path present when threaded; both empty off the workflow path) + the `bats_require_minimum_version 1.5.0` guard. - `docs/events-logging.md`: taxonomy row + threading explanation; "Not yet emitted" resolved. - `changelog.d/204-ms-total.added.md`. - **Green:** full bats suite (533), `fragment-check`, `shellcheck -e SC1091` all pass. ### The `reusable-release.yml` side for #283 (QM wires this in) `draft-release.sh` reads two env vars; the workflow just needs to provide them: - **`CUT_START_MS`** — epoch-ms at the cut-cycle start. Capture at the **decide step** and thread across steps via `$GITHUB_ENV`: ```bash echo "CUT_START_MS=$(date +%s%3N)" >> "$GITHUB_ENV" ``` (Placed at decide-start so `ms_total` measures decide→publish. Later steps inherit it.) - **`CUT_PATH`** — the resolved push-path. `PUSH_MODE` is already computed in the same "act on decision" step as the `draft-release.sh` invocation, so an inline export before the call suffices — no `$GITHUB_ENV` needed: ```bash export CUT_PATH="$PUSH_MODE" # alpha|gamma; before the draft-release.sh call ``` Both are **optional** — absent → empty fields (graceful, mirroring `api_call`'s `ms`), so grafting the workflow half doesn't have to be atomic with the script half; the script half is safe even if a cut runs before the workflow half lands. Local branch `i/204-api-call-cut-complete-events` @ `cc3328b` carries it (couldn't push — this worktree's shared credential authenticates as `carpenter`, blocked by branch protection; the attached patch is the delivery). @quartermaster — yours to graft into #283; ping me if you want the `reusable-release.yml` hunk written out against #283's structure once it's open.
bosun closed this issue 2026-07-02 16:00:50 +02:00
Sign in to join this conversation.
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
frankenbit/release-toolkit#204
No description provided.