Skip to content

feat: stop TUI browsers from silently skipping the agent-facing surface #360

Description

@YoungJinJung

Summary

The agent-facing surface (JSON CLI + MCP) covers 6 of 27 catalog services, and nothing prevents that gap from widening. Every browser merged since the MCP work landed has shipped TUI-only, and there is no test that notices.

Evidence

On a563207:

surface count
Catalog services / features (TUI) 27 / 38
Agent-facing resource commands 6
Parity or coverage test none

The six exposed resources are backup-vaults, alarms, cloudtrail-events, ecs-rollout, elb-target-health, and rds-instances (internal/cli/resources.go, internal/cli/resources_operations.go). The remaining MCP tools — get_capabilities, get_command_schema, get_mcp_capabilities, plan_context_sync — are meta, not resource reads.

Browsers with no agent-facing counterpart include CloudFormation, Step Functions, EventBridge, DynamoDB, Auto Scaling, ElastiCache, KMS, ACM, SQS, S3, Secrets Manager, Route53, IAM, VPC, EKS, ECR, and FIS.

Why this matters now

Adding a browser currently means wiring ~8 TUI touchpoints (catalog.go, model.go, app.go, filter.go, keymap.go, messages.go, feature_submodel.go, screen_views.go). The agent surface is a ninth touchpoint that is easy to miss because nothing fails when you skip it.

Concrete live example: #324 (SNS browser) adds a full TUI browser and no unic resources sns-topics. That was not a deliberate scoping decision — I simply did not know the agent surface existed, because nothing in the build, tests, or docs pointed at it.

Each JSON resource is hand-written — a jsonEnvelope[T] wrapper, a per-resource DTO, and a loadXxx function variable — so the cost is real and the gap will not close by itself.

Proposed change

Two parts, guard first.

1. A parity test with an explicit allowlist. Walk domain.Catalog(), assert each feature either has an agent-facing command or appears in a documented agentSurfaceExempt map with a one-line reason. New browsers then fail the build until the author makes a deliberate call — expose it, or record why not.

This is the high-leverage half: it is small, it prevents recurrence, and it converts an invisible gap into a visible, reviewable list.

2. Fill incrementally, highest value first. Not one PR. Suggested order, by how much a raw AWS API bridge would struggle to reproduce the view:

  • inspect — RunSecurityScan / RunChecklist already return serializable reports (SecurityScanReport{Findings, ScannerCount, Warnings, ScannedAt}); 10 rule packs behind one call
  • ElastiCache, SNS — multi-call joins with partial-failure semantics
  • CloudFormation, Step Functions — failure-first triage ordering
  • The rest as demand appears

Non-goals

  • Exposing all 89 read-only repository methods. The shipped design is ~11 curated tools and that is the right shape; this issue does not propose changing it.
  • Widening mutation coverage. The confirmation gate in internal/cli/mutation.go correctly ports the TUI's type-the-name guard, and new mutations are out of scope here.

Checklist

Current implementation status

The prioritized fills are merged: SQS #364, ElastiCache #365, SNS #366, CloudFormation #367, and Step Functions #368. The catalog guard currently records 12 mapped features and 26 explicit exemptions. Inspector is a separate non-catalog workflow. This issue remains open for incremental fills as demand appears; no additional service contract is selected here.

Activity

  1. YoungJinJung commented on Sep 13, 2026

    @YoungJinJung
    ContributorAuthor

    @Unic-bot: assign me

  2. github-actions commented on Sep 13, 2026

    @github-actions

    @YoungJinJung has been assigned to this issue.

  3. YoungJinJung commented on Sep 14, 2026

    @YoungJinJung
    ContributorAuthor

    Progress on the checklist.

    Two things worth recording while they are fresh.

    Inspector sits outside the guard. grep -c Inspector internal/domain/{model,catalog}.go → 0. It is a workflow, not a catalog feature, so #361's parity test cannot see it — a green guard does not mean full coverage. #363 pins the boundary with TestSecurityInspectorToolIsReadOnlyAndNotAResourceContract and says so in both architecture docs, but any future non-catalog surface will have the same blind spot.

    Four exemptions are justified more weakly than they read. FeatureAutoScalingBrowser, FeatureEventBridgeRules, FeatureLambdaBrowser, and FeatureSQSBrowser all cite mutations as the reason. But FeatureECSExec — which opens an interactive shell, the most mutation-capable feature in the catalog — is mapped to a read-only ecs-rollout command. So the read path is separable; the real reason is the trailing "yet". SQS looks like the strongest next candidate: deepest-backlog-first ordering with DLQ relationships is precisely the curated view a raw API bridge cannot reproduce.

    Suggested order for the rest, by that same criterion — how much a generic AWS API bridge would struggle:

    1. SQS queue backlogs (join + ordering + DLQ edges)
    2. ElastiCache (replication-group/cluster join with partial-failure semantics)
    3. SNS (topic + subscription join; the repository methods already return ([]T, []error, error), which maps straight onto data/warnings)
    4. CloudFormation and Step Functions (failure-first triage ordering)
  4. YoungJinJung commented on Sep 15, 2026

    @YoungJinJung
    ContributorAuthor

    Status reconciliation as of 2026-09-15:

  5. YoungJinJung commented on Sep 16, 2026

    @YoungJinJung
    ContributorAuthor

    Review-state update as of 2026-09-16: #365, #366, and #368 now have approvals from youngjinjung-linq on their unchanged heads. The collaborator-permission API reports that account has only read access, and GitHub still reports REVIEW_REQUIRED / BLOCKED for all six open follow-ups (#362, #364–#368). Main requires one approving review; an eligible reviewer with write access other than the PR author is still needed.

    All current hosted checks pass. No new actionable feedback from YoungJinJung is outstanding, and all six heads already have completed code reviews. No duplicate review, additional implementation, or merge was attempted. Issue #360 remains open while the existing follow-ups await eligible approval.

  6. youngjinjung-linq commented on Sep 20, 2026

    @youngjinjung-linq
    Contributor

    Status reconciliation as of 2026-09-21:

    Issue #360 remains open while the existing follow-ups await eligible approval.

  7. youngjinjung-linq commented on Sep 29, 2026

    @youngjinjung-linq
    Contributor

    State change as of 2026-09-29: PRs #367 and #368 are now reported by GitHub as CONFLICTING / DIRTY against main. Their head SHAs remain unchanged (2ed37ff and f79d9cb), hosted checks at those heads still pass, and both heads were already fully reviewed, so no duplicate review was performed. Each branch now needs its conflicts with current main resolved and validation rerun before an eligible approval or merge can proceed.

  8. YoungJinJung commented on Sep 29, 2026

    @YoungJinJung
    ContributorAuthor

    All five fills from the suggested order are merged: SQS #364, ElastiCache #365, SNS #366, CloudFormation #367, Step Functions #368. The invocation guard #362 landed too.

    Coverage on c38984b, counted by parsing agent_surface_test.go with go/ast rather than grepping — a line count was off by one on each map and I did not want to report a number I had not verified:

    before this pass now
    mapped 7 12
    exempt 31 26
    catalog features 38 38

    No feature appears in both maps; the two sets partition the catalog exactly.

    unic resources now exposes: alarms, backup-vaults, cloudformation-stacks, cloudtrail-events, ec2-instances, ecs-rollout, elasticache-resources, elb-target-health, rds-instances, sns-topics, sqs-queues, step-function-executions.

    Two things worth recording from the merge itself

    Every one of these conflicted once the previous had landed, all in the same four places — resources.go, resources_operations.go, server.go, agent_surface_test.go. That is inherent to five parallel branches editing the same registries, not a problem with any of them, but it is why merging them serially took longer than reviewing them.

    While resolving #368 I found it had introduced a generic resourceTimeJSON while #367 had already landed cloudFormationTimeJSON doing exactly the same thing. I unified on the generic name rather than keeping two identical helpers. Worth knowing that the branches were written in parallel against a main that did not yet have each other's work, so this kind of near-duplicate is likely in anything still outstanding.

    One README line was also changed permanently: the "the server provides ... including list_X" sentence named one example tool, which made it conflict on every single fill. The example is gone — the command list directly above already enumerates them.

    Remaining

    26 exemptions, each with a stated reason. The ones I would still question are the value-judgement ones rather than the genuinely-blocked ones (secrets and parameter values needing operator-controlled reveal, SSM session being an interactive shell, reachability analysis creating real resources — those look correctly excluded). Most of the rest say "no curated query is defined yet", which is honest and is exactly what the guard is for: they stay visible instead of being forgotten.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

enhancementNew feature or requestpriority/nextQueued after current focustech-debtRefactoring / maintainability

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions