Repository navigation
feat(cli): add mem doctor and first-run guidance that names the documented deploy/compose path #112
Description
schemaVersion: requirement-record.v1
revision: R3
status: ready
priority: P2
productOwner: "@PeterGuy326"
technicalOwner: "@waterbro-8"
userOutcome: "A first-time user who is missing a prerequisite is told which one, by the CLI, and is pointed at the documented container path instead of at a bare-metal install they cannot complete."
requirements:
- REQ-001
- REQ-002
- REQ-003
acceptanceCriteria:
- AC-001
- AC-002
- AC-003
parent: "https://github.com/bytefolk/mem/issues/109"
dependencies:
- "https://github.com/bytefolk/mem/issues/109 at R2"
supersedes:
- "#109 R1 REQ-003 / AC-003"
lastDecisionAt: "2026-09-04T06:30:41Z"Context and why this is a separate issue
Split out of #109 by technical-owner adjudication on 2026-08-30: #109 is a documentation repositioning (README/onboarding lead with deploy/compose), while its REQ-003 asked for a CLI surface that does not exist in this repository at all. Verified at main @ 419a420: the strings doctor and first-run appear nowhere in the tree — no command, no messaging, no flag. Shipping it is net-new Go code with its own output contract and tests, which cannot share a reviewable PR with a README rewrite, and which kept #109 mislabelled type:feature for a docs problem.
#109's own open decision asked whether doctor-level container-path detection belongs there or in a shared doctor surface. Answer: the shared surface, which is this issue.
Requirements
- REQ-001: A read-only
mem doctorcommand reporting, per check and with--format jsonper SPEC.md:579: server reachability for the configured URL, credential presence, selected workspace, and CLI/server version skew. Exit codes follow SPEC.md:582 (0ok ·2not_found ·3auth ·4plan/quota ·5provider/timeout). - REQ-002: First-run guidance on the existing fail-closed paths. Every command that currently returns
newCliError(3, "not logged in", "runmem auth loginfirst")(e.g.server/cmd/mem/cmds_file.go:45) must, when no credential exists at all, additionally name the documented deployment path from docs(onboarding): make deploy/compose the primary onboarding path #109 — so the hint matches the path the docs now recommend. - REQ-003: Diagnosis only. Zero writes, zero auto-remediation, zero dependency installation. An auto-installer would re-create the exact bare-metal burden (
brew install postgresql@17 pgvector,server/.envauthoring, MinIO, Ollama) that made the container path the recommended entry in the first place (docs/RUN_LOCAL.md:24,scripts/dev_up.sh:31,117,208).
Acceptance criteria
- AC-001: Fixture per finding class — unreachable server, no credential, no workspace selected, version skew — each producing its named check result, its code, and a hint that names the documented path; asserted at the HTTP-contract level following the
cmds_ingest_test.gostub pattern. - AC-002:
mem doctorperforms no write request of any kind — a stub transport fails the test on any non-read call — and never prints a secret, token value, or DSN. - AC-003:
mem doctor --format jsonoutput validates against a checked-in schema or golden file, so ops tooling can consume it without parsing prose.
Non-goals and forbidden shortcuts
- Not an environment-certification matrix: it does not validate Go/Python/protoc versions, install anything, or manage Docker/compose (no start, stop, or
compose upfrom the CLI). - No detection of host container runtimes in v1 — "is the stack container-hosted?" is unverifiable from inside a container and must not be guessed into a reported fact.
- No TTY wizard; text output is a fixed ordered list of findings.
- No removal or weakening of docs(onboarding): make deploy/compose the primary onboarding path #109's docs deliverables; this issue consumes them.
Lifecycle, status, priority, blockers, and open decisions
- status=ready (R3); priority P2 — deliberately below docs(onboarding): make deploy/compose the primary onboarding path #109 (P1): the docs fix ships without waiting for this, and nothing here gates it.
- technicalOwner
@waterbro-8; independent reviewer: aCODEOWNERSowner (not the author). - Dependency: docs(onboarding): make deploy/compose the primary onboarding path #109 at R2 must land first, so this issue names a path that exists in the docs rather than inventing one.
- Open decisions: (1) command name and shape (
mem doctorvsmem doctor envvs amem auth status --doctormode) — tech adjudication at kickoff; (2) whethermemdlogs an equivalent readiness line at startup so a compose operator sees the same facts without an exec; (3) whether the check set is a registry thatmem-mcpand the web setup screen can reuse.
Evidence plan
- E2 available now: source-level — the absence of any doctor/first-run surface at
main @ 419a420, and thenot logged inhint sites that REQ-002 must extend. - E3/E4 at delivery: deterministic fixtures per finding class plus one real run against a stopped and a healthy stack.
Decisions
- R2 owner decision (requirement-decision:v1, decidedAt 2026-09-01T02:54:08Z, approver @PeterGuy326): see #112#issuecomment-5488211237. Approved scope: fixed four checks, fixed ordered text + versioned JSON v1, read-only GET probes only, fail-closed secret handling. This record is append-only; the comment is canonical and this body mirrors it.
- Resolved by R3 (requirement-decision:v1, decidedAt 2026-09-04T06:30:41Z, approver @PeterGuy326): see #112#issuecomment-5536659086. The doctor's exit-code contract is the SPEC.md §7.1 mapping (
0ok ·2not_found ·3auth ·4plan/quota ·5provider/timeout); R2's "returns exit code 0 while reporting findings" sentence is replaced. All other R2 scope and security criteria stand.
Revision history
- R1 (2026-08-30): issue created by splitting docs(onboarding): make deploy/compose the primary onboarding path #109 R1 REQ-003 + AC-003 into an owned CLI surface.
- R2 (2026-09-01): owner decision approved the secure read-only doctor scope (see Decisions). Body synchronized to the decision comment on 2026-09-04; REQ-001 text left verbatim, with its exit-code conflict against R2's approved scope recorded as an open owner adjudication.
- R3 (2026-09-04): owner adjudication of the exit-code conflict — SPEC.md §7.1 mapping stands, R2's exit-0 sentence replaced (see Decisions). Not a merge authorization; feat(cli): add mem doctor and first-run guidance that names the documented deploy/compose path #131/fix(cli): redact malformed URLs and request build errors #164 blockers otherwise unchanged.
需要 maintainer 打标(本账号无 label 写权限):建议 type:feature、area:cli、priority:p2、evidence:e2-source、status:needs-triage。
Activity
PeterGuy326 commentedon Sep 1, 2026
R2 owner decision — secure, read-only
mem doctorThis is the effective product-owner decision for R2. The contract is ready, but implementation is prohibited until #109 R2 documentation is accepted with its verification evidence. It is not a PR approval or merge authorization.
Approved scope
mem doctor.Security acceptance criteria
Tests must cover valid and malformed credential URLs plus text, JSON, and transport-error paths. Every such path must fail closed without printing raw URLs, raw error values, token or credential values, DSNs, or equivalent secret-bearing data. A transport stub must prove that any non-read request fails the test.
Non-goals
No dependency installation, environment certification, Compose or Docker-host detection, container start/stop, TTY wizard, memd readiness log, shared check registry, automatic remediation, or credential mutation is authorized.
After #109 R2 is accepted, implementation may be planned against R2 with normal independent review, required CI, and merge evidence still required.
feat(cli): address all bytefolk#112 acceptance criteria
PeterGuy326 commentedon Sep 4, 2026
R3 owner decision — doctor exit codes follow SPEC.md §7.1
This adjudicates the single contradiction recorded against R2: its approved scope said "v1 returns exit code 0 while reporting findings", while REQ-001 and SPEC.md §7.1 (line 582) map exit codes
0ok ·2not_found ·3auth ·4plan/quota ·5provider/timeout, and the implementation (#131/#164) plus its tests already conform to the SPEC mapping and pin it (cmds_doctor_test.goasserts a non-zero/5 exit for an unreachable server).Decision. The SPEC.md §7.1 mapping is the doctor's contract. R2's "returns exit code 0 while reporting findings" sentence is replaced by: v1 reports findings with exit codes
0ok ·2not_found ·3auth ·4plan/quota ·5provider/timeout, per SPEC.md §7.1. Everything else in R2 stands unchanged: the fixed four checks, fixed ordered text and versioned JSON v1, read-only GET probes only, no writes or remediation, and the fail-closed secret-handling acceptance criteria.Why this direction. SPEC.md §7.1 is the repository-wide CLI contract, so doctor deviating from it would be the exception that needs justifying, not the rule. The conforming implementation and tests already exist and are green; enforcing R2's sentence would have required reworking code and tests that pin the opposite behaviour, while still leaving doctor inconsistent with every other command's exit semantics. Diagnostic-and-advisory is preserved in what matters: probes are still read-only and the command never remediates; the exit code is a machine-readable classification of the finding, not an error in the CLI itself.
Scope of this adjudication. It resolves the exit-code contradiction only. It is not an approval or merge authorization for #131 or #164. Their remaining blockers stand: the no-scheme/Opaque credential shape still leaks (fix direction: gate on "parses as a recognized transport scheme with userinfo redacted, otherwise withhold the whole value", one shared gate at each of the three egresses — doctor, apiclient, memd log line), #164's CHANGELOG conflict, the absence of any CI run on its head, and the independent acceptance signature #112 R2 requires from a reviewer who is neither the technical owner nor the PR author.
feat(cli): add mem doctor and first-run guidance
sun-970 commentedon Sep 8, 2026
Questions and proposed solutions
1. Credential leak on no-scheme / Opaque URLs
R3 decision mentions the no-scheme/Opaque credential shape still leaks across three egresses (doctor, apiclient, memd log line). The suggested fix direction is "parses as a recognized transport scheme with userinfo redacted, otherwise withhold the whole value."
Question: Should the redaction gate live in a single shared helper (e.g.
internal/credutil.Redact) that all three call sites import, or should each site implement its own guard? A shared helper is easier to audit and keeps the fail-closed invariant in one place.Proposed solution: Introduce one
redactCredential(raw string) (string, error)in an internal package. It returns the redacted string for recognized schemes and a sentinel error (not the raw value) for anything unparseable. All three egresses call this single function. One test table covers valid URL, no-scheme, Opaque, empty, and malformed inputs — and asserts the raw value never appears in any output path.2. Command name is still an open decision
The issue lists three candidates:
mem doctor,mem doctor env,mem auth status --doctor. R2 already approved top-levelmem doctor, but it was listed again as an open decision.Question: Is the command name truly still open, or was it settled by the R2 decision? Re-opening it would delay implementation without adding user value.
Proposed solution: Treat the R2 decision as final on naming —
mem doctorit is. If there's new evidence that another shape fits better, raise it before implementation starts, not during review.3.
memdstartup readiness lineOpen decision (2) asks whether
memdshould log an equivalent readiness line at startup so compose operators see the same facts without exec-ing into the container.Question: Is this in scope for v1, or deferred? If deferred, operators lose the main benefit of the doctor (visibility without SSH/exec).
Proposed solution: Defer to v2. v1 should ship the CLI command first, get operator feedback on the check set and output format, then mirror it in
memdonce the contract is stable. Adding it now doubles the review surface and risks the two outputs drifting.4. Shared check registry for
mem-mcpand web setupOpen decision (3) asks whether the check set should be a registry reusable by
mem-mcpand the web setup screen.Question: Is there a concrete consumer today, or is this speculative? Building a registry for one caller is premature abstraction (YAGNI).
Proposed solution: Keep checks as plain functions in the doctor package for v1. If
mem-mcpor web setup actually needs the same checks, extract a registry at that point with real call-site requirements. One caller now, refactor later when the second caller arrives.5. Path to unblocking #131 / #164
R3 lists four blockers: credential leak fix, CHANGELOG conflict in #164, no CI run on its head, and missing independent acceptance signature.
Proposed solution:
redactCredentialhelper first (smallest, highest-risk fix).CODEOWNERSmember who is neither@waterbro-8nor the PR author, per the R2 requirement.These are sequential — each depends on the previous — so a single stacked-PR approach or one PR with ordered commits would keep the review tractable.
Happy to discuss any of these further. The credential leak fix feels like the most urgent item — everything else can follow once that's closed.
fix(cli-memd): withhold URLs whose credentials cannot be attributed (#…
PeterGuy326 commentedon Sep 10, 2026
Closing as completed. The deliverable is on
main, and I re-verified each acceptance criterion againstmain @ 55d09b203ba9dc011f140d13d6bbb32f186fc16ctoday rather than relying on the delivery comments.Carrier: merge commit
d9d0969deae252f3619429e8eb07aa2533e26e0c(#165), whose message carriesRefs #112. #165 deliberately usedRefsand notCloses, stating that "merging it would not by itself settle #112's acceptance — that needs a reviewer's decision, not just a diff landing." This comment is that decision.Acceptance criteria
mainserver/cmd/mem/cmds_doctor_test.go(748 lines):TestDoctorUnreachableServer:180,TestDoctorMissingCredential:213,TestDoctorMissingCredentialOnConfiguredHost:244,TestDoctorNoWorkspaceSelected:263,TestDoctorVersionSkew:328; exit-code classesTestDoctorRejectedCredential:285 (3),TestDoctorQuotaIsItsOwnCode:309 (4); healthy pathTestDoctorHealthyReportsAllChecksAndExitsZero:143cmds_doctor_test.go:47-50) and on any unexpected method/path (:63); exact request set asserted asGET /healthz, GET /v1/capabilities, GET /v1/version(:172,:237). Secret handling:TestDoctorNeverPrintsSecretValues:371,TestRedactURLStripsUserinfo:405,TestDoctorMalformedServerURLDoesNotLeakCredentials:428,TestDoctorSchemelessServerURLDoesNotLeakCredentials:468--format jsonvalidates against a checked-in schema or golden filedocs/schemas/mem-doctor.v1.schema.json(3024 B, blob3f8dd30a) andserver/cmd/mem/testdata/doctor_healthy.golden.json(842 B, blob7fcef975).TestDoctorJSONMatchesCheckedInSchema:580 reads the schema from disk (:562), validates the document against it (:592-593), diffs the golden (:609-614), and enforcesadditionalProperties: false(:631)Requirements
server/cmd/mem/cmds_doctor.go(377 lines) documents the check set at:25-28(server_reachability,credential,workspace,version_skew) and implements them at:207,:231,:257,:296. Exit constantsexitOK=0,exitNotFound=2,exitAuth=3,exitPlanQuota=4,exitProvider=5at:56-60, with the §7.1 citation at:53— i.e. the R3 adjudication, not R2's superseded exit-0 sentence.deployPathHint()atcmds_doctor.go:349returnsstart the documented container path: deploy/compose, see docs/DEPLOYMENT.md. Both targets exist onmaintoday:deploy/compose/(compose.yaml,.env.example,generate-env.sh,backup.sh,restore.sh,fixtures/compose.env) anddocs/DEPLOYMENT.md. Guidance path covered byTestNotLoggedInGuidance:705.cmds_doctor.go:31("no dependency is installed, no Docker or compose command is issued").On the stated dependency
#112 recorded "Dependency: #109 at R2 must land first, so this issue names a path that exists in the docs rather than inventing one." #109 is still open, so I want to be explicit about why that does not block this close: the dependency's purpose was that the hint name a real path.
deploy/composeanddocs/DEPLOYMENT.mdboth exist onmainindependently of #109's README/onboarding repositioning, so the hint is not inventing anything. #109 remains open on its own docs merits and is unaffected by this close.Evidence level, stated honestly
I am not raising this above what the record supports. #165's own ledger claims E3 (independently reproduced) and explicitly declines E4 because its author wrote the change. I am a co-author of #165 and the author of #164, whose
fffcd4cdoctor commit is an ancestor of the delivered head — so my verification here is not a non-author E4 either. What I can attest to is source- and test-level: the files, line numbers and assertions above are read offmainat55d09b20today, and AC-002/AC-003 are enforced by checked-in tests that fail on regression rather than by prose. The doctor half also has 15/15 CI checks green on the delivered head. If anyone wants E4, the gap is a non-author running the seven malformed-credential shapes end to end — #165's table is the procedure.Two residual facts worth keeping visible rather than burying in a closed issue:
redis://host:6379/?password=xstill echoes), pinned by the named characterization testTestTextKnownGapQueryParameterCredentialsAreEchoed. Closing feat(cli): add mem doctor and first-run guidance that names the documented deploy/compose path #112 does not close that gap; it was adjudicated out of R3's scope.cmds_doctor_test.go:456to accept redact-or-withhold. Its author asked that anyone who considers the withhold form unacceptable say so on fix(cli): redact malformed URLs and request build errors #164's thread. Nobody has, and the assertion count went up (15 → 16 doctor tests, none dropped), so I am treating that as settled.