@groundnuty/macf 0.2.27 → 0.2.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/.build-info.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@groundnuty/macf",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.32",
|
|
4
4
|
"description": "Multi-Agent Coordination Framework CLI — coordinate Claude Code agents via GitHub. Installs as `macf` binary; use `macf init` to set up an agent workspace, `macf update` to refresh rules + version pins.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"test:watch": "vitest"
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@groundnuty/macf-core": "0.2.
|
|
38
|
+
"@groundnuty/macf-core": "0.2.32",
|
|
39
39
|
"commander": "^14.0.3",
|
|
40
40
|
"reflect-metadata": "^0.2.2",
|
|
41
41
|
"zod": "^4.0.0"
|
|
@@ -4,7 +4,9 @@
|
|
|
4
4
|
|
|
5
5
|
> **Workspaces without full `macf init`** (e.g. `groundnuty/macf` itself, or any Claude Code workspace operated by a bot that isn't a MACF-registered agent) can still get this canonical rule via `macf rules refresh --dir <workspace>`. Same copy, no App credentials or registry required.
|
|
6
6
|
|
|
7
|
-
This rule names the CLASS so agents recognize the shape on first encounter rather than re-discovering each instance from scratch.
|
|
7
|
+
This rule names the CLASS so agents recognize the shape on first encounter rather than re-discovering each instance from scratch. Nine specific instances are documented below as worked examples spanning different architectural layers (identity, parsing, TUI binding, observability routing, config substitution, multi-agent coordination protocol, metric-instrumentation lifecycle, observability-endpoint routing, release-pipeline-partial-publish). Seven of nine have structural defenses applied or in flight — the pattern of defense generalizes alongside the pattern of hazard.
|
|
8
|
+
|
|
9
|
+
Instance 9 is annotated as **sister-shape** (failure correctly surfaced + partial side-effect breaks retry idempotency) — listed here for cross-reference convenience but warrants a sibling canonical rule (`partial-side-effect-hazards.md`) if more instances surface. The two classes share "multi-step pipeline where consumer assumes atomicity" but the failure surface differs: silent-fallback hides at the API boundary; partial-side-effect surfaces loudly but persists semi-state.
|
|
8
10
|
|
|
9
11
|
---
|
|
10
12
|
|
|
@@ -21,7 +23,7 @@ The trap is that defensive programming targets exit codes, but exit-code success
|
|
|
21
23
|
|
|
22
24
|
---
|
|
23
25
|
|
|
24
|
-
##
|
|
26
|
+
## Nine known instances
|
|
25
27
|
|
|
26
28
|
### Instance 1 — gh-token attribution traps
|
|
27
29
|
|
|
@@ -114,6 +116,53 @@ curl -G "$TEMPO/api/search" --data-urlencode 'q={resource."gen_ai.agent.name"=~"
|
|
|
114
116
|
|
|
115
117
|
This is a **secondary Pattern A failure mode** — the assertion script CAN return zero-traces and look like a Tier-1/2/3/4 firing when actually it's a query-syntax issue. Defense: when investigating "Pattern A reports zero traces," cross-check with the alternative query `{resource.service.name=~"macf-agent.*"}` (uses the OTel-canonical service-name attribute which TraceQL handles natively, no dotted-key quoting needed). If that returns non-zero, the issue is query-syntax not silent-fallback.
|
|
116
118
|
|
|
119
|
+
### Instance 9 — Sigstore TLOG orphans on failed npm publish (partial-side-effect-on-failed-publish)
|
|
120
|
+
|
|
121
|
+
**Surface:** `npm publish` with `--provenance` (sigstore attestation) — the multi-step pipeline `sigstore TLOG entry submit → attest binding → npm registry PUT` where each step's success isn't atomic with the others.
|
|
122
|
+
|
|
123
|
+
**Failure shape:** Two observed sub-shapes, both producing orphan TLOG entries in the public sigstore transparency log:
|
|
124
|
+
|
|
125
|
+
- **Sub-shape A — sigstore-409-on-retry** (`v0.2.25` 2026-05-18T21:48Z, groundnuty/macf#373): first publish-run aborted on pre-existing test flakes after TLOG entry submitted. Retry via tag-recreate hit `TLOG_CREATE_ENTRY_ERROR (409)` — sigstore correctly rejects the duplicate entry, but the retry's attestation is rejected. Two consumer packages already published (`@groundnuty/macf{,-core}@0.2.25`) became orphans pointing at a never-published `@groundnuty/macf-channel-server@0.2.25`.
|
|
126
|
+
- **Sub-shape B — npm-404-after-sigstore-success** (`v0.2.29` 2026-05-19T21:30:32Z + `v0.2.30` 2026-05-19T22:17:36Z, groundnuty/macf#391+#397+#395 release-cuts): publish-workflow auth-step succeeded (`NPM_TOKEN` present + valid); sigstore TLOG entry submitted successfully (logIndex 1575263520 for v0.2.29; logIndex 1575475073 for v0.2.30); npm registry `PUT /@groundnuty%2fmacf-core` returned HTTP 404 `"not in this registry"`. Sigstore TLOG entries persisted; npm registry never received the package. Operator-side root-cause investigation in flight at codification time (candidate causes: npm-token-scope mismatch / OIDC trust conflict / 2FA-bypass missing / transient registry issue).
|
|
127
|
+
|
|
128
|
+
**Recurrence:** 3 instances across 2 distinct trigger mechanisms (sigstore-409-on-retry; npm-404-after-sigstore-success); same root-shape (partial side-effect persists after downstream publish failure).
|
|
129
|
+
|
|
130
|
+
**Why this is sister-shape, not strict silent-fallback:**
|
|
131
|
+
|
|
132
|
+
| Aspect | Silent-fallback (Instances 1-8) | sigstore-TLOG-orphan (Instance 9) |
|
|
133
|
+
|---|---|---|
|
|
134
|
+
| API exit code | success (0 / HTTP 200) | failure (non-0 / HTTP 4xx-5xx) |
|
|
135
|
+
| Failure signal | absent at API boundary | LOUD at API boundary (npm 404; sigstore 409) |
|
|
136
|
+
| Hazard surface | detection (failure invisible until downstream breaks) | recovery (failure surfaces correctly but partial side-effect breaks retry idempotency) |
|
|
137
|
+
| Class generalization | "operation success masks semantic failure" | "operation failure persists partial state that breaks retry" |
|
|
138
|
+
|
|
139
|
+
Both classes share "multi-step pipeline where consumer assumes atomicity" but the failure mode + structural defense differ. Listed here as Instance 9 for cross-reference convenience; **future structural-defense work warrants a sibling canonical rule `partial-side-effect-hazards.md`** if more instances surface (e.g., partial-side-effect database migrations, partial-side-effect distributed-transaction failures). Codification at sibling-rule level happens at the 5-instance threshold per the rule-promotion convention.
|
|
140
|
+
|
|
141
|
+
**Defense status:** Pattern recovery codified via DR-022 Amendment L (groundnuty/macf#380); pre-publish validation via Pattern D analog (Pattern D adapted from workflow-secrets-prechecks to publish-pipeline-prechecks). Two specific defense layers shipped + one in operator's queue:
|
|
142
|
+
|
|
143
|
+
- **Defense 1 — bump-version recovery (NOT tag-retry)** — DR-022 Amendment L canonical recovery procedure. When publish fails leaving sigstore TLOG orphans, recover via `npm version <next>` + fresh push, NOT `git tag -d + tag-recreate + force-push`. Fresh version produces a fresh TLOG entry + a fresh attestation; idempotent + clean. Applied successfully for v0.2.25 → v0.2.26 (instance 1 recovery). v0.2.29 + v0.2.30 also followed bump-version (29 → 30 → 31-pending), but the underlying npm-404 trigger is operator-side configuration, not retry-idempotency:
|
|
144
|
+
```bash
|
|
145
|
+
# WRONG (idempotency-breaking when retry hits TLOG-409 or repeats the original 404)
|
|
146
|
+
git tag -d v0.2.25
|
|
147
|
+
git tag v0.2.25 <new-sha>
|
|
148
|
+
git push --force origin v0.2.25 # triggers re-publish with same version → TLOG 409 collision
|
|
149
|
+
|
|
150
|
+
# RIGHT (orthogonal recovery)
|
|
151
|
+
# Bump version: 0.2.25 → 0.2.26 with whatever fixes
|
|
152
|
+
npm version 0.2.26 && git push origin v0.2.26 # fresh version, fresh TLOG entry, clean publish
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
- **Defense 2 — pre-flight registry-collision check** (Pattern D analog; groundnuty/macf#380): publish-workflow precheck step queries `npm view @scope/package@<version>` BEFORE invoking sigstore; aborts if the version already exists on the registry. Catches the same-version-retry-on-TLOG-409 failure shape at the workflow boundary (Pattern D's aggregate-fail-loud discipline applied to publish pipeline). Won't catch first-attempt npm-404 (instance 2/3 shape) where the version genuinely doesn't exist yet — that fail-mode requires Defense 3.
|
|
156
|
+
|
|
157
|
+
- **Defense 3 — TLOG-state observability** (in flight; devops-toolkit#74 + #77 dashboard live as of 2026-05-20T00:06:56Z): release-hygiene Grafana dashboard surfaces orphan-sigstore-attestations via `grafana-dashboards-release-hygiene` (UID `macf-release-hygiene`). Auto-detects "package N.M.K depends on N.M.K of sibling but sibling never published" + alerts when a tag's matching npm version is missing 24+ hours post-tag. Closes the operator-visibility gap that left v0.2.29 + v0.2.30 orphans accumulating without an automated catch.
|
|
158
|
+
|
|
159
|
+
**Cleanup pending after instance N+1 (codification followup):**
|
|
160
|
+
|
|
161
|
+
- v0.2.25 orphans (instance 1): `@groundnuty/macf{,-core}@0.2.25` published on npm → deprecate via `npm-deprecate.yml` workflow_dispatch (operator-side App-permission gate per DR-019 Amendment A — granted 2026-05-19, dispatch-allowlist for `npm-deprecate.yml` codified)
|
|
162
|
+
- v0.2.29 + v0.2.30 orphans (instances 2/3): npm registry never received the PUT → nothing to deprecate on npm side; sigstore TLOG entries persist by design (transparency log is append-only). Future audit-trail check should reconcile sigstore-TLOG entries against npm-registry-state to catch shape-mismatched orphans proactively.
|
|
163
|
+
|
|
164
|
+
**Codification rationale:** 3 instances across 2 trigger mechanisms + defense pattern stable + operator-witnessed across 2 calendar days (2026-05-18 + 2026-05-19) + cross-agent (instance 1 via science-agent's authoring; instances 2/3 via code-agent's release-cut workflow) — meets all four "When to add a new instance" criteria. The sister-class question (separate `partial-side-effect-hazards.md` canonical rule) is acknowledged inline + deferred to the 5-instance threshold per rule-promotion convention.
|
|
165
|
+
|
|
117
166
|
---
|
|
118
167
|
|
|
119
168
|
## How to recognize the class on first encounter
|
|
@@ -257,7 +306,7 @@ Silent-fallback hazards are **architectural**, not implementation bugs. They eme
|
|
|
257
306
|
|
|
258
307
|
For coordination-system safety analysis: this is a class of hazards multi-agent systems must explicitly defend against. Each new instance teaches the same lesson; the class-name is what makes the lesson transferable across agents.
|
|
259
308
|
|
|
260
|
-
### Defense-pattern emergence (
|
|
309
|
+
### Defense-pattern emergence (7-of-9 known instances have structural defense applied or shipped)
|
|
261
310
|
|
|
262
311
|
| Instance | Surface | Structural defense | Pattern |
|
|
263
312
|
|---|---|---|---|
|
|
@@ -266,13 +315,14 @@ For coordination-system safety analysis: this is a class of hazards multi-agent
|
|
|
266
315
|
| 3 — Remote Control IPC blocking tmux send-keys | Claude Code TUI input | Two-tier: consumer fleet structurally retired via channel-server primitive (DR-020 mTLS HTTPS POST); substrate fleet permanent operational reality — defense = rule-discipline + Pattern C fragility detector | Pattern C deployable as fragility detector |
|
|
267
316
|
| 4 — Loki/CH-logs pipeline divergence | OTLP logs routing | manifest warnings + shape-aware diagnostic | Pattern A |
|
|
268
317
|
| 5 — Workflow secrets-misnamed | GitHub Actions workflow inputs | Workflow precheck step | Pattern D |
|
|
269
|
-
| 6 — Cross-agent notification loop | Multi-agent coordination protocol | macf v0.2.4:
|
|
318
|
+
| 6 — Cross-agent notification loop | Multi-agent coordination protocol | macf v0.2.4 + v0.2.21: event-discriminator in receiver's `decideWake()` — autonomous events skip wake (observational-only); `event: 'custom'` (operator-driven) wakes; other `NotifyType`s preserve wake-on-receipt | Pattern E |
|
|
270
319
|
| 7 — OTel-counter cumulative-state vs short-lived-process lifecycle | Metric-instrumentation lifecycle | Two-phase: doc workaround `sum(increase(...))` + OTel SDK delta temporality | Pattern A |
|
|
271
320
|
| 8 — OTLP endpoint silent-drop | Observability-endpoint routing | Five-surface defense: CLI release-discipline + substrate testers env-override + canonical template `:14318` default + cluster-side compat port-map + agent-process `doctor-otel.sh` Pattern A | Pattern A (composite — first multi-architectural-layer case in this rule; instances 1-7 have single-pattern defenses) |
|
|
321
|
+
| 9 — Sigstore TLOG orphans on failed npm publish (sister-class) | npm publish + sigstore attestation pipeline | Three-defense composite: bump-version recovery (DR-022 Amendment L) + pre-flight registry-collision check (Pattern D analog, macf#380) + TLOG-state observability (devops-toolkit#74+#77 Grafana dashboard live) | Pattern D analog (pre-flight precheck) + recovery-procedure-codification |
|
|
272
322
|
|
|
273
|
-
|
|
323
|
+
Seven of nine instances have structural defense applied or shipped. Defense patterns (A, B, C, D, E) generalize across instances — they're reusable defense templates, not case-specific fixes. **Pattern A (result-invariant assertion at the boundary) bears the most weight** — it's the structural defense for instances 4, 7, AND 8 (3 of 9), each at a different architectural boundary (logs pipeline, metric counter, observability endpoint). Instance 8's five-surface defense topology (consumer canonical + cluster-side compat port-map + concrete Pattern A impl) demonstrates that structural defense at the observability-pipeline-class can compose across architectural layers — the canonical-distribution layer + the cluster-infrastructure layer + the assertion-script layer all reinforce each other rather than substituting for each other. Instance 9 demonstrates that the Pattern D template generalizes from workflow-secrets-prechecks to release-pipeline-prechecks AND that recovery-procedure-codification (DR-022 Amendment L's bump-version-not-tag-retry) is its own defense category — distinct from detection-pre-merge defenses (Patterns A/B/D) and discrimination-at-receiver defenses (Pattern E).
|
|
274
324
|
|
|
275
|
-
The breadth of layers spanned by 5 different defense patterns (identity, parsing, TUI binding, observability routing, config substitution, multi-agent coordination protocol, metric-instrumentation lifecycle, observability-endpoint routing) is independent evidence that the hazard CLASS is real. If silent-fallback was a single-instance accident, no defense pattern would emerge. **Pattern A's recurrence across 3 different observability boundaries (logs / metrics / endpoint) is the strongest signal that result-invariant assertion is the load-bearing structural-defense template for the entire observability-pipeline-class** of silent fallback.
|
|
325
|
+
The breadth of layers spanned by 5 different defense patterns (identity, parsing, TUI binding, observability routing, config substitution, multi-agent coordination protocol, metric-instrumentation lifecycle, observability-endpoint routing, release-pipeline-partial-publish) is independent evidence that the hazard CLASS is real. If silent-fallback was a single-instance accident, no defense pattern would emerge. **Pattern A's recurrence across 3 different observability boundaries (logs / metrics / endpoint) is the strongest signal that result-invariant assertion is the load-bearing structural-defense template for the entire observability-pipeline-class** of silent fallback.
|
|
276
326
|
|
|
277
327
|
---
|
|
278
328
|
|
|
@@ -286,7 +336,7 @@ Add when ALL of the following hold:
|
|
|
286
336
|
|
|
287
337
|
The class-name is what makes the lesson transferable, not multi-agent witness. A single-agent-confirmed instance with a concrete trace + identified defense pattern is sufficient for canonicalization (instances 4, 5, 7, 8 are all single-agent-confirmed). Cross-agent triangulation strengthens the framing but isn't a precondition.
|
|
288
338
|
|
|
289
|
-
Add as a new numbered section under "
|
|
339
|
+
Add as a new numbered section under "Nine known instances" (will become "Ten known instances" etc.) with the same fields: Surface / Failure shape / Recurrence / Defense status. Increment the intro paragraph's instance count + the Defense-pattern emergence header's `N-of-M known instances` count too.
|
|
290
340
|
|
|
291
341
|
---
|
|
292
342
|
|
|
@@ -237,6 +237,40 @@ _macf_audit_parse_workflow() {
|
|
|
237
237
|
echo ""
|
|
238
238
|
}
|
|
239
239
|
|
|
240
|
+
_macf_audit_build_resource_attrs_json() {
|
|
241
|
+
# Build OTLP-shape resource.attributes JSON array from the canonical
|
|
242
|
+
# claude.sh-exported OTel env vars (`OTEL_SERVICE_NAME` +
|
|
243
|
+
# `OTEL_RESOURCE_ATTRIBUTES`). Per macf#388 / observability-wiring.md:
|
|
244
|
+
# claude.sh exports these at session bootstrap as the single source
|
|
245
|
+
# of truth for the agent's identity attrs. The hook silent-skips
|
|
246
|
+
# service.name + gen_ai.* attrs when emitting outside a claude.sh-
|
|
247
|
+
# wrapped session (graceful degradation; returns the empty array).
|
|
248
|
+
#
|
|
249
|
+
# OTEL_RESOURCE_ATTRIBUTES format per OTel spec: comma-separated
|
|
250
|
+
# `key=value` pairs, no quoting. Canonical export:
|
|
251
|
+
# gen_ai.agent.name=<name>,gen_ai.agent.role=<role>,service.namespace=macf
|
|
252
|
+
local service_name="${OTEL_SERVICE_NAME:-}"
|
|
253
|
+
local resource_attrs="${OTEL_RESOURCE_ATTRIBUTES:-}"
|
|
254
|
+
jq -n \
|
|
255
|
+
--arg service_name "$service_name" \
|
|
256
|
+
--arg resource_attrs "$resource_attrs" \
|
|
257
|
+
'
|
|
258
|
+
(
|
|
259
|
+
if $service_name != ""
|
|
260
|
+
then [{key: "service.name", value: {stringValue: $service_name}}]
|
|
261
|
+
else []
|
|
262
|
+
end
|
|
263
|
+
)
|
|
264
|
+
+
|
|
265
|
+
(
|
|
266
|
+
$resource_attrs
|
|
267
|
+
| split(",")
|
|
268
|
+
| map(select(length > 0))
|
|
269
|
+
| map(split("=") | select(length >= 2 and .[0] != "") | {key: .[0], value: {stringValue: (.[1] // "")}})
|
|
270
|
+
)
|
|
271
|
+
' 2>/dev/null || echo "[]"
|
|
272
|
+
}
|
|
273
|
+
|
|
240
274
|
_macf_audit_emit() {
|
|
241
275
|
# Emit span + counter for an actions:write-scoped invocation.
|
|
242
276
|
# Observational only — every emission path is best-effort. Failures
|
|
@@ -370,6 +404,14 @@ _macf_audit_emit_curl_span() {
|
|
|
370
404
|
+ ( if $url_full != "" then [{key: "url.full", value: {stringValue: $url_full}}] else [] end )' 2>/dev/null
|
|
371
405
|
)" || return 1
|
|
372
406
|
|
|
407
|
+
# macf#388: populate resource.attributes from claude.sh's exported
|
|
408
|
+
# OTel env vars so hook-emitted spans match the rest of the MACF
|
|
409
|
+
# stack's service.name / gen_ai.* attrs (instead of falling under
|
|
410
|
+
# `rootServiceName=<root span not yet received>` in Tempo).
|
|
411
|
+
# Graceful degradation: empty array when env unset.
|
|
412
|
+
local resource_attrs_json
|
|
413
|
+
resource_attrs_json="$(_macf_audit_build_resource_attrs_json)"
|
|
414
|
+
|
|
373
415
|
local body
|
|
374
416
|
body="$(
|
|
375
417
|
jq -n \
|
|
@@ -378,9 +420,10 @@ _macf_audit_emit_curl_span() {
|
|
|
378
420
|
--arg name "macf.app.gh_api_call" \
|
|
379
421
|
--arg ts_ns "$ts_ns" \
|
|
380
422
|
--argjson attrs "$attrs_json" \
|
|
423
|
+
--argjson resource_attrs "$resource_attrs_json" \
|
|
381
424
|
'{
|
|
382
425
|
resourceSpans: [{
|
|
383
|
-
resource: { attributes:
|
|
426
|
+
resource: { attributes: $resource_attrs },
|
|
384
427
|
scopeSpans: [{
|
|
385
428
|
scope: { name: "macf" },
|
|
386
429
|
spans: [{
|
|
@@ -425,15 +468,22 @@ _macf_audit_emit_curl_metric() {
|
|
|
425
468
|
+ ( if $workflow != "" then [{key: "workflow", value: {stringValue: $workflow}}] else [] end )' 2>/dev/null
|
|
426
469
|
)" || return 1
|
|
427
470
|
|
|
471
|
+
# macf#388: same resource-attrs population as the span side, for
|
|
472
|
+
# cross-signal consistency on Tempo / Prometheus aggregation by
|
|
473
|
+
# service.name / gen_ai.* dimensions.
|
|
474
|
+
local resource_attrs_json
|
|
475
|
+
resource_attrs_json="$(_macf_audit_build_resource_attrs_json)"
|
|
476
|
+
|
|
428
477
|
local body
|
|
429
478
|
body="$(
|
|
430
479
|
jq -n \
|
|
431
480
|
--arg name "macf.app.gh_actions_write_total" \
|
|
432
481
|
--arg ts_ns "$ts_ns" \
|
|
433
482
|
--argjson attrs "$attrs_json" \
|
|
483
|
+
--argjson resource_attrs "$resource_attrs_json" \
|
|
434
484
|
'{
|
|
435
485
|
resourceMetrics: [{
|
|
436
|
-
resource: { attributes:
|
|
486
|
+
resource: { attributes: $resource_attrs },
|
|
437
487
|
scopeMetrics: [{
|
|
438
488
|
scope: { name: "macf" },
|
|
439
489
|
metrics: [{
|