Commit Graph
5 Commits
Author SHA1 Message Date
pastilhasandClaude Opus 5 62ccce86a6 the skill's name is an identifier, not a label
lowercase-hyphenated and equal to its directory, which is what both harnesses
require to LOAD it. carrying a human title there meant the skill was listed to an
agent and then failed to open. the prose moves to `title`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-27 13:11:36 +00:00
pastilhasandClaude Opus 5 867226d897 a certificate for a name Let's Encrypt cannot visit
the whole reason the credential store exists. a tailnet-only hostname cannot be
reached on port 80, so HTTP-01 is impossible and a DNS record is the only proof
Let's Encrypt accepts — which needs the provider's API key.

POST /domains/certificate is the one route here that is deliberately not a
pass-through, and it has to be: the request needs dns_provider_credentials, and
officer never returns a stored key to any caller. so the platform resolves
which provider owns the name from the Domains list, decrypts, builds the
request and returns NPM's own answer unchanged. same arrangement as the NPM
token one level up — the thing that wants the work done is not the thing
holding the password.

NOT under /_config. reading and writing a DNS key stays owner-only; USING it on
the owner's behalf is the feature, and gating this to the owner would defeat
it. a read-level account still cannot reach it, because it is a POST.

three refusals, all before anything is placed at the registrar: a name whose
apex is not in the domain list, a provider with no key stored, and a
certificate spanning two providers — NPM takes one credential per certificate,
and discovering that halfway through leaves a challenge record behind. tested,
because each one otherwise fails deep inside certbot with an error about
something else.

ten minute timeout, 120s default propagation. both err long on purpose: NPM
waits for the record to spread before asking Let's Encrypt to look, and
abandoning the request does not stop it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Officer-Session: 656f5c85-e49a-4f1f-b9f3-042a04b4d043
2026-08-25 20:07:09 +00:00
pastilhasandClaude Opus 5 39692ea89b how to actually issue a certificate, read off NPM's own source
verified against 2.15.1 rather than remembered, because the remembered version
is wrong: every tutorial sends letsencrypt_email and letsencrypt_agree, and
this NPM rejects both — `meta` is additionalProperties:false. the email comes
from the NPM ACCOUNT, looked up in internal/certificate.js, which refuses with
"a valid email address must be set on your user account" if it has none. that
account here has one; checked.

so for a name that already resolves publicly the whole body is provider plus
domain_names. HTTP-01, nothing else to arrange. the DNS-01 shape is written
down beside it for the private case, along with the fact that the agent cannot
fill dns_provider_credentials in — officer never returns a stored DNS key, and
reaching for one is the refusal-shaped mistake this skill already warns about.

folded the old trailing "not built" section into it rather than leaving two
places saying the same thing differently.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Officer-Session: 656f5c85-e49a-4f1f-b9f3-042a04b4d043
2026-08-25 20:05:12 +00:00
pastilhasandClaude Opus 5 9be2ec7e6c hand over NPM's API instead of a curated subset of it
this plugin refused to create, edit or delete anything, and argued the refusal
was care: NPM's own UI does those well and each is a form with real
consequences. the owner's call on 2026-08-25 is that the argument ends up
somewhere else —

  "even though we are assuming users that don't know how to operate nginx, the
   last thing I want to do is treat users as stupid"

and a curated surface cannot keep up anyway. every NPM feature we did not wrap
is one the owner cannot reach through Officer at all, so the plugin becomes a
worse NPM rather than a way into the real one.

so /api/proxy-manager/npm/* forwards method, path, query and body, attaches a
token, and returns NPM's own status and body unchanged — errors included. a
proxy that improves the answers is not a proxy. authentication is the only
thing it does not pass through, which is the entire job.

the permission gate needed nothing: isRequestAllowedAtLevel already returns
true for safe methods and false otherwise, so read level IS a read-only view of
NPM through here, with no code in this file deciding it twice.

the curated routes stay, for the panels. a screen wants sorted hosts and a
computed daysUntilExpiry; an agent wants NPM.

what replaces the refusal is confirmation, and it lives in the skill because
that is what it is — an instruction, not a boundary. saying so is better than a
boundary that only looks like one. the skill now carries NPM's API, names what
must be confirmed before it happens (deleting, editing a live upstream,
touching a certificate in use), says creating needs no ceremony, and forbids
reaching NPM any way but this route.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Officer-Session: 656f5c85-e49a-4f1f-b9f3-042a04b4d043
2026-08-25 18:04:38 +00:00
pastilhasandClaude Opus 5 b8fd43d7ab a chat under the lists, and the skill that makes it worth having
the layout gains a third node across the bottom, a third of the height. a
conversation reads badly in a narrow column and the two lists do not, so the
chat gets width rather than a column of its own.

it is officerdev/chat, the platform's own panel, not a simplified one of ours.
seedAppRegistry merges the platform's panels and every plugin's into one
registry, so naming it is all it takes — and a second chat maintained here
would be the one that falls behind.

the skill is deliberately NOT scoped to these routes. that was considered and
dropped once it was clear what a skill costs: compileSkills emits a name, a
description and a path, and the body is only read if the agent decides it is
relevant. two lines in every conversation. scoping would have meant a platform
change to buy back nothing.

(affordances are the opposite — loaded in full, every conversation — so this
plugin ships none. by docs/affordances.md's own test the 404 quirk does not
qualify: it CAN be fixed instead, and the platform route already fixes it.)

what the skill carries: the routes, that meta.nginx_online is the field for
"is it down", that renewal is at 30 days so under 14 is a real failure worth
raising unprompted and nothing else will raise it, that the 404 has already
been interpreted before it reaches you, and that disabling a host takes a
domain off the internet so it is confirmed and never inferred. plus what is
not built, so it does not get offered.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Officer-Session: 656f5c85-e49a-4f1f-b9f3-042a04b4d043
2026-08-25 17:17:32 +00:00