main · last commit
13 days ago ·
7g0stsfu
ah-jzv config: export a Configured() predicate per optional block; stop re-deriving presence in the wiring
Past Stand
bd reopen ah-jzv
| Created by | Eugene Blikh |
| Owner | bigbes@gmail.com |
| Created | 2026-08-04T23:44:00Z |
| Started | 2026-08-04T23:54:12Z |
| Updated | 2026-08-05T00:00:42Z |
| Closed | 2026-08-05T00:00:42Z |
Found while fixing ah-1cx.3. config's optional blocks each have an unexported present() predicate, so every consumer outside the package hand-rolls its own presence test against raw fields: cmd/agenthubd/main.go:180 (yonote client + boot identity probe), :192 (mem0 client + health probe), :292/:298 (notifier selection), plus internal/reconcile/{publish,qa,artifacts}.go reading cfg.Yonote.BaseURL directly. They agree with present() TODAY only because resolve trims those base URLs — which is exactly the coupling that produced ah-1cx.3, where a whitespace-only ntfy.url was absent to validation and present to wiring. The next field added without a matching trim reintroduces the same class of bug. Fix: export one predicate per optional block (e.g. func (y Yonote) Configured() bool wrapping the unexported present()) and have every consumer call it instead of restating the test. present() being unexported is precisely why the wiring hand-rolls it.
No consumer outside internal/config decides whether an optional block is configured by inspecting its raw fields; each calls the exported predicate. A grep for cfg.<Block>.<Field> != "" outside the package comes back empty.
No outgoing dependencies.
Nothing depends on this issue.
| id | ah-jzv |
| content_hash | 0d78f613570ca2c75adf528c012e34dcb3619fcf996b32327a645896ef97baa4 |
| title | config: export a Configured() predicate per optional block; stop re-deriving presence in the wiring |
| description | Found while fixing ah-1cx.3. config's optional blocks each have an unexported present() predicate, so every consumer outside the package hand-rolls its own presence test against raw fields: cmd/agenthubd/main.go:180 (yonote client + boot identity probe), :192 (mem0 client + health probe), :292/:298 (notifier selection), plus internal/reconcile/{publish,qa,artifacts}.go reading cfg.Yonote.BaseURL directly. They agree with present() TODAY only because resolve trims those base URLs — which is exactly the coupling that produced ah-1cx.3, where a whitespace-only ntfy.url was absent to validation and present to wiring. The next field added without a matching trim reintroduces the same class of bug. Fix: export one predicate per optional block (e.g. func (y Yonote) Configured() bool wrapping the unexported present()) and have every consumer call it instead of restating the test. present() being unexported is precisely why the wiring hand-rolls it. |
| design | |
| acceptance_criteria | No consumer outside internal/config decides whether an optional block is configured by inspecting its raw fields; each calls the exported predicate. A grep for cfg.<Block>.<Field> != "" outside the package comes back empty. |
| notes | |
| status | closed |
| priority | 3 |
| issue_type | chore |
| assignee | NULL |
| estimated_minutes | NULL |
| created_at | 2026-08-04T23:44:00Z |
| created_by | Eugene Blikh |
| owner | bigbes@gmail.com |
| updated_at | 2026-08-05T00:00:42Z |
| closed_at | 2026-08-05T00:00:42Z |
| closed_by_session | |
| external_ref | NULL |
| spec_id | |
| compaction_level | 0 |
| compacted_at | NULL |
| compacted_at_commit | NULL |
| original_size | NULL |
| sender | |
| ephemeral | 0 |
| wisp_type | |
| pinned | 0 |
| is_template | 0 |
| mol_type | |
| work_type | |
| source_system | |
| metadata | �{} |
| source_repo | |
| close_reason | Done in baa8075. Each optional block's presence rule lived only in an unexported present(), so every consumer outside the package restated it against raw fields — cmd/agenthubd/main.go at the yonote client + identity probe, the mem0 client + health probe, and both arms of the notifier-selection switch. Those raw != "" tests agreed with present() ONLY because resolve happens to trim the fields they read; that coupling is what produced ah-1cx.3, where a whitespace-only ntfy.url was absent to validation and present to the wiring and killed a boot. Added an exported Configured() on AgentsView, Telegram, Ntfy, Yonote and Mem0 — each a one-line wrapper over present(), so there is exactly one definition of presence and validate() and the wiring can never diverge. AgentsView.Configured carries the full doc comment (Configured() is the only sanctioned presence test outside the package, plus the ah-1cx.3 boot failure that justifies the rule); the other four state the rule and point at it. All four cmd/agenthubd call sites migrated, comments rewritten to record why. Purely additive: present() stays, no signature changed, go build ./... passes for every package. Cover: table-driven TestConfiguredPredicates asserting Configured()==present() for absent, single-field-set and whitespace-only inputs per block (the whitespace rows are exactly where a raw != "" test would disagree), plus an end-to-end check through Load. FOLLOW-UP NOT DONE (internal/reconcile was owned by another agent at the time), split into its own bead: reconcile.go:2010 agentsViewLink is the one real drift risk left — its raw test silently ignores a set-but-blank machine and renders a link ending in '~pi:' rather than no link; reconcile.go:1996, publish.go:83/93/114, qa.go:83/203 and artifacts.go:77/89 are correct today because they are gated upstream by r.yonote != nil, which main.go now sets from Configured(). The Configured() doc comment also asserts a rule no test or lint enforces — the acceptance grep is verified by hand. |
| event_kind | |
| actor | |
| target | |
| payload | |
| await_type | |
| await_id | |
| timeout_ns | 0 |
| waiters | |
| hook_bead | |
| role_bead | |
| agent_state | |
| last_activity | NULL |
| role_type | |
| rig | |
| due_at | NULL |
| defer_until | NULL |
| no_history | 0 |
| started_at | 2026-08-04T23:54:12Z |
| is_blocked | 0 |
| issue_id | ah-jzv |
| label | milestone:v0.1.0 |
| id | 019fcf29-a21f-7f26-b3cc-c9e46cec2c7f |
| issue_id | ah-jzv |
| event_type | created |
| actor | Eugene Blikh |
| old_value | |
| new_value | |
| comment | NULL |
| created_at | 2026-08-05T02:44:00Z |
| id | 019fcf32-f980-7b04-b949-d078af8d8d6f |
| issue_id | ah-jzv |
| event_type | status_changed |
| actor | Eugene Blikh |
| old_value | {"id":"ah-jzv","title":"config: export a Configured() predicate per optional block; stop re-deriving presence in the wiring","description":"Found while fixing ah-1cx.3.\n\nconfig's optional blocks each have an unexported present() predicate, so every consumer outside the package hand-rolls its own presence test against raw fields: cmd/agenthubd/main.go:180 (yonote client + boot identity probe), :192 (mem0 client + health probe), :292/:298 (notifier selection), plus internal/reconcile/{publish,qa,artifacts}.go reading cfg.Yonote.BaseURL directly.\n\nThey agree with present() TODAY only because resolve trims those base URLs — which is exactly the coupling that produced ah-1cx.3, where a whitespace-only ntfy.url was absent to validation and present to wiring. The next field added without a matching trim reintroduces the same class of bug.\n\nFix: export one predicate per optional block (e.g. func (y Yonote) Configured() bool wrapping the unexported present()) and have every consumer call it instead of restating the test. present() being unexported is precisely why the wiring hand-rolls it.","acceptance_criteria":"No consumer outside internal/config decides whether an optional block is configured by inspecting its raw fields; each calls the exported predicate. A grep for cfg.\u003cBlock\u003e.\u003cField\u003e != \"\" outside the package comes back empty.","status":"open","priority":3,"issue_type":"chore","owner":"bigbes@gmail.com","created_at":"2026-08-04T23:44:00Z","created_by":"Eugene Blikh","updated_at":"2026-08-04T23:44:00Z"} |
| new_value | {"status":"in_progress"} |
| comment | NULL |
| created_at | 2026-08-05T02:54:12Z |
| id | 019fcf38-ea48-7357-80a2-118316b8c63f |
| issue_id | ah-jzv |
| event_type | closed |
| actor | Eugene Blikh |
| old_value | |
| new_value | Done in baa8075. Each optional block's presence rule lived only in an unexported present(), so every consumer outside the package restated it against raw fields — cmd/agenthubd/main.go at the yonote client + identity probe, the mem0 client + health probe, and both arms of the notifier-selection switch. Those raw != "" tests agreed with present() ONLY because resolve happens to trim the fields they read; that coupling is what produced ah-1cx.3, where a whitespace-only ntfy.url was absent to validation and present to the wiring and killed a boot. Added an exported Configured() on AgentsView, Telegram, Ntfy, Yonote and Mem0 — each a one-line wrapper over present(), so there is exactly one definition of presence and validate() and the wiring can never diverge. AgentsView.Configured carries the full doc comment (Configured() is the only sanctioned presence test outside the package, plus the ah-1cx.3 boot failure that justifies the rule); the other four state the rule and point at it. All four cmd/agenthubd call sites migrated, comments rewritten to record why. Purely additive: present() stays, no signature changed, go build ./... passes for every package. Cover: table-driven TestConfiguredPredicates asserting Configured()==present() for absent, single-field-set and whitespace-only inputs per block (the whitespace rows are exactly where a raw != "" test would disagree), plus an end-to-end check through Load. FOLLOW-UP NOT DONE (internal/reconcile was owned by another agent at the time), split into its own bead: reconcile.go:2010 agentsViewLink is the one real drift risk left — its raw test silently ignores a set-but-blank machine and renders a link ending in '~pi:' rather than no link; reconcile.go:1996, publish.go:83/93/114, qa.go:83/203 and artifacts.go:77/89 are correct today because they are gated upstream by r.yonote != nil, which main.go now sets from Configured(). The Configured() doc comment also asserts a rule no test or lint enforces — the acceptance grep is verified by hand. |
| comment | NULL |
| created_at | 2026-08-05T03:00:41Z |
| id | 019fcf42-0485-7b8d-a6fc-fab4bff9e63b |
| issue_id | ah-jzv |
| event_type | label_added |
| actor | Eugene Blikh |
| old_value | NULL |
| new_value | NULL |
| comment | Added label: milestone:v0.1.0 |
| created_at | 2026-08-05T03:10:38Z |
No comments.
Close reason