Skip to content

feat(plugin): lua drains, logs --since/--follow, 18 sources [PRO-1896] - #14

Merged
lua-stefan-kruger merged 2 commits into
mainfrom
feat/log-drains-plugin
Sep 22, 2026
Merged

lua-stefan-kruger merged 2 commits into
mainfrom
feat/log-drains-plugin

Conversation

@lua-stefan-kruger

@lua-stefan-kruger lua-stefan-kruger commented Sep 22, 2026 •

Copy link
Copy Markdown
Contributor

Closes the plugin half of the Log drains programme: PRO-1896 (I4).

The plugin now knows the lua-cli 3.38.0 log-drain surface and the lua logs read window. Every claim was read from lua-core-services main — packages/lua-cli/src/cli/command-definitions.ts (the drains and logs declarations), src/commands/drains.ts, src/commands/drains.mutations.ts, src/commands/logs.ts, src/api/drains.api.service.ts, src/utils/aliases.ts (drains.action, logs.type), @lua/shared-types log-drain.types.ts / vm-execution-log.types.ts, @lua/shared-observability drain-scrubber.ts — never from the public docs, per the repo's "verified against the shipped lua-cli source" rule.

What's in it

/lua-drains (new slash) — all eleven verbs (list, status, deliveries, create, update, delete, test, verify, pause, resume, rotate-secret) with the plugin's single-permission pattern: one AskUserQuestion, then the Bash prompt as the single confirmation for a mutation.

  • Secrets. The HMAC signing secret is never an input — the platform mints it and the CLI prints it exactly once (create, and rotate-secret without --finalize), so the slash never copies it into a file, an env var, a commit or its own reply. A header value never reaches a command line: --header <NAME> prompts hidden (and so cannot work under --ci), --header-from-env <NAME>=<ENV_VAR> is the CI form, and for a vendor preset (DD-API-KEY, Authorization: Bearer …) the slash stops at the secret and hands the user the line to run in their own terminal.
  • JSON contract. --json on stdout (lua drains create --json | jq -r '.secret' has to work); the verification instructions and the content acknowledgement on stderr (emitAside); a typed envelope rather than the ✖ line for a refusal that escapes a verb. Per-verb payloads are tabulated.
  • --include-content is run without --yes first: the CLI prints the warning and the acknowledgement and exits non-zero having created nothing, the slash quotes both verbatim, and the user's approval of the re-run is the acknowledgement.
  • ⚠ lua drains status exits 2 both for a usage error and for "some drain is failing" — the slash decides from drains[].state, never from the exit code. test (30 s) / verify (60 s) are 202-then-poll: pending: true with exit 1 means not yet, not failed.

lib/knowledge/log-drains.md (new, the sixth knowledge file) — what a drain is (an organization resource), the eleven verbs and their aliases, the six states, the selectors (19 selectable sources = the 18 AGENT_LOG_SOURCES plus execution), the verification handshake (only http echoes X-Lua-Verify, with the /.well-known/lua-drain-verify fallback; token_not_echoed is the common failure; 5 attempts/drain/hour), the http / otlp / datadog / betterstack presets, delivery guarantees and the Retry-After contract (honoured exactly, clamped at an hour; the drain routes answer 429 DRAIN_RATE_LIMITED with a bare Retry-After and no X-RateLimit-* headers), the quota ladder (80 % notify → 100 % drop debug → 125 % drop info → 150 % pause with reason quota), the scrubber and its [redacted:builtin] / [redacted:lua-token] / [redacted:vendor-key] / [redacted:org:<rule-id>] markers, and logs:read vs the sensitive logs:manage.

--since / --follow everywhere a window was being reconstructed — /lua-logs gains the full 18-source --type list and the read window; lua-debug gains step 2b and the "watch a promote" recipe; lua-qa records T0 before the first turn and scans with --since <T0> --environment <target> instead of pulling 100 rows and filtering by timestamp.

hooks/post-deploy-smoke.mjs — the other half of the ticket's Context sentence ("the plugin's smoke checks and the lua-debug agent poll logs by page and reconstruct time windows"). It pulled a 20-row page and filtered it against this machine's clock; it now asks the route for the window with --since 1m and trusts it. A lua-cli older than 3.38.0 exits 1 on the unknown option, so one fallback to the pre-3.38.0 shape keeps the check working — the pin only warns, so those sessions are still in the field. --environment production is deliberately not passed: the step-1 ping goes through lua chat, whose rows the A1 call sites may stamp sandbox, and a smoke check that hid its own ping's errors would be worse than a slightly wider scan. Four new tests cover both paths; the trigger set (SMOKE_LABELS), the warning and the non-blocking behaviour are unchanged.

Two corrections worth a reviewer's eye

  • A log entry now HAS an environment field. The plugin asserted "there is no environment field" in three places; AgentLogMetadata gained orgId, environment, agentVersion, executionId, executionSeq, traceparent and the truncation pair. It is optional and additive, and a row without it reads as production — on the drain matcher and under --environment alike — so its absence never means sandbox.
  • lua logs has no severity flag. --min-severity is a log-drain selector. The ticket's suggested recipe (lua logs --since … --follow --min-severity warn) would exit 1 on the shipped CLI, so lua-debug filters severity client-side on subType and lint-cli-flags now denies the spelling.

Permission tiering — a deliberate deviation from the ticket's wording

The ticket asks for the production-gate hook to treat create/update/delete/rotate-secret as production verbs. This PR puts the seven configuration verbs in the ask tier instead, which is how the repo already treats every other org-level mutation (lua env *, lua integrations connect, lua triggers rotate-token, the workflow run-control verbs), and allows list|status|deliveries|test.

Reason: lib/tokenizer.mjs is documented as the single source of truth for "what changes production", and nothing a drain verb does changes what runs in production — a drain ships a copy of logs. Adding them would force /lua-drains to emit LUA_DEPLOY_CONFIRMED=1 lua drains create … ("the user confirmed a deploy") and would make confirm-deploy send the user to /lua-deploy for a log-shipping change. The ask prompt is a real gate and is /lua-drains's single confirmation. Happy to move them into the tokenizer if reviewers prefer the ticket's literal reading — say so and it's a small follow-up.

test is in allow on the ticket's own authority ("list/status/deliveries/test as safe"): it changes no Lua-side configuration — it sends one synthetic lua.drain.test record down the delivery path the drain is already using — whereas verify is in ask because it mutates the drain's state. The per-verb spelling is deliberate: a blanket Bash(lua drains *) would either prompt for a read or admit rotate-secret, and because the rules are per verb the slash always spells the verb rather than relying on bare lua drains. test/lib/permissions-mirror.test.mjs now pins all eleven.

Checks

Check Result
eslint . pass
16 of the 17 lint scripts pass (lint-knowledge-commands run with LUA_CLI_SRC pointed at a lua-cli checkout: 877 lua … references validated)
scripts/lint-pinned-version.mjs RED by design — PINNED_MIN_LUA_CLI moves to 3.38.0 and npm's latest is still 3.37.0. Identical to the window 1.4.0 sat in before 3.37.0 published; the lint was not weakened, and it goes green the moment lua-cli 3.38.0 ships
npm test -- --coverage + scripts/check-coverage.mjs 430 tests, 18 suites, hooks 100 % / lib ≥ 90 %
MCP server (mcp/lua-platform) lint clean, 147 tests, rebuilt dist/server.js, 0.10 MB (budget 5 MB)

There is no claude plugin eval suite in this repo — CI runs the lint chain plus jest, and the §3.7 rule is scripts/lint-single-permission.mjs (21 slash commands pass).

Not in scope

Docs pages (I2, a separate ticket) and the throwaway-org E2E run, which needs a published lua-cli 3.38.0 to exercise lua drains at all. The tail_logs MCP tool is unchanged and stays documented as the pre-3.38.0 fallback.

🤖 Generated with Claude Code

lua-stefan-kruger and others added 2 commits September 22, 2026 10:36
Teaches the lua-agent-builder plugin the lua-cli 3.38.0 log-drain surface
and the `lua logs` read window, all verified against lua-core-services
`main` (src/cli/command-definitions.ts, src/commands/drains.ts,
drains.mutations.ts, logs.ts, src/api/drains.api.service.ts,
src/utils/aliases.ts, @lua/shared-types log-drain.types.ts and
vm-execution-log.types.ts, @lua/shared-observability drain-scrubber.ts).

- /lua-drains: a new slash for all eleven verbs (list, status, deliveries,
  create, update, delete, test, verify, pause, resume, rotate-secret) with
  the single-permission pattern, the secret rules (the signing secret is
  minted server-side and printed once, never stored by the plugin;
  --header-from-env NAME=ENV_VAR for a vendor key; no secret in the
  transcript) and the JSON contract (--json on stdout, chrome on stderr).
- lib/knowledge/log-drains.md: what a drain is, the six states, the
  verification handshake (only http echoes X-Lua-Verify), the http / otlp /
  datadog / betterstack presets, delivery guarantees and the Retry-After
  contract, the quota ladder, the scrubber and its [redacted:<rule>]
  markers, logs:read vs the sensitive logs:manage.
- /lua-logs, lua-debug and lua-qa now read a window instead of paging:
  --since / --until / --environment / --follow, the full 18-source --type
  list, a "watch a promote" recipe, and T0-bounded QA log scans. Corrects
  the stale "a log entry has no environment field" claim.
- Permissions: allow lua drains list|status|deliveries|test, ask
  create|update|delete|verify|pause|resume|rotate-secret. Deliberately not
  added to lib/tokenizer.mjs -- a drain ships logs, it does not change what
  runs in production, so LUA_DEPLOY_CONFIRMED=1 would be the wrong sentence.
- lint-cli-flags drops `lua logs --follow` (it exists now) and gains
  `lua logs --min-severity` and `lua drains update --type`;
  lint-knowledge-commands learns the drains.action verbs.
- 1.4.0 -> 1.5.0, PINNED_MIN_LUA_CLI 3.37.0 -> 3.38.0, MCP bundle rebuilt.

Checks: eslint + 16 of 17 lints pass, 428 plugin tests and 147 MCP tests
pass with coverage thresholds met. lint-pinned-version is RED by design
until lua-cli 3.38.0 is published, the same window 1.4.0 sat in before
3.37.0 shipped.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Review follow-up on the same ticket.

- hooks/post-deploy-smoke.mjs was the other half of the ticket's Context
  sentence ("the plugin's smoke checks and the lua-debug agent poll logs by
  page and reconstruct time windows"). It pulled a 20-row page and filtered
  it against THIS machine's clock; it now asks the route for the window with
  `--since 1m`, which the server resolves, and trusts it. A lua-cli older
  than 3.38.0 exits 1 on the unknown option, so one fallback to the
  pre-3.38.0 shape keeps the check working -- the pin only warns, so those
  sessions are still in the field. `--environment production` is deliberately
  not passed: the step-1 ping goes through `lua chat`, whose rows the A1 call
  sites may stamp `sandbox`, and a smoke check that hid its own ping's errors
  would be worse than a slightly wider scan. Four new tests cover both paths.
- /lua-drains no longer suggests the bare `lua drains` spelling: the allow
  rules are per verb, so a bare invocation matches none of them and would
  prompt for a read, breaking the single-permission contract.
- /lua-drains says out loud that the --include-content flow is the one place
  two ask prompts can occur, and that the second is a new user-initiated turn.

430 plugin tests pass; hooks stay at 100% coverage.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@lua-stefan-kruger
lua-stefan-kruger merged commit 7a0441c into main Sep 22, 2026
10 of 14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant