Move to openspec/changes/archive/2026-07-09-service-credential-tester/ (R100 renames preserved). 9 artifacts. Canonical openspec/specs/ service-credential-testing/ remains. Resolves qBit 'login failed' #3 pain at the UI layer (auth failure surfaced in result pill, no log-digging). Carry-overs in archive-report incl N-2 strengthened, N-6 presentational panel, edit-surface-is-Settings.tsx source-finding.
17 KiB
Archive Report — service-credential-tester
Phase: archive · Change:
service-credential-tester· Repo:/home/user/manageMode: file-backed (artifactStore: openspec). No source-code edits; only OpenSpec artifacts were touched. Not committed — the parent/orchestrator owns the archive commit. No push, nogh.
Status: ARCHIVED. All eight lifecycle phases are complete (proposal → spec → design → tasks →
apply → verify → sync → archive). Every archive precondition is verified PASS (see §2). The
canonical openspec/specs/service-credential-testing/spec.md (a new domain created by
sdd-sync — pure ## ADDED Requirements, 21 requirements CT-101 … CT-121) remains in place as the
durable end-state spec and is not moved (archive never moves canonical specs). The change folder
was moved to openspec/changes/archive/2026-07-09-service-credential-tester/ via git mv to
preserve history.
0. Archive disposition
- Disposition:
archived. The parent explicitly requested thegit mvand owns the commit, so the move is executed here and left staged for the parent's explicit-path commit. - Archive convention: OpenSpec SDD archive contract for
openspecmode — completed file-backed sync → write the in-folder archive report → move the change folder toopenspec/changes/archive/YYYY-MM-DD-{change}/. No standalone manifest/index exists underopenspec/(onlyconfig.yaml,changes/,specs/), so the folder move is the archive mechanism. Norules.archiveoverride exists inopenspec/config.yaml(verified: noarchivekey underrules). - Target archived path:
openspec/changes/archive/2026-07-09-service-credential-tester/ - Archive date:
2026-07-09(ISO). - Canonical spec left in place (not moved):
openspec/specs/service-credential-testing/spec.md— 21 requirements (CT-101 … CT-121). Verified present and untouched after the move (sha256edcdfecc54fafb3729a9c4f5bcbe48a2f6b6b430336e20823bb363b9a2c2e330, unchanged pre/post move). - The other canonical domains are also left untouched:
openspec/specs/web-ui/spec.md,openspec/specs/prometheus-charting/spec.md,openspec/specs/service-storage/spec.md. - Audit-trail integrity: the change folder was moved as a whole, including the legacy flat
spec.md, the per-domain deltaspecs/service-credential-testing/spec.md,apply-progress.md,verify-report.md, andsync-report.md, which travel with the record. Nothing was silently deleted or rewritten.
1. Native sdd-status read & discrepancy statement
The native gentle-pi.sdd-status engine supplied by the parent reports non-actionable state for
this archive because it was resolved without a change context: changeName: null,
artifacts: all missing, applyState: blocked, dependencies.archive: blocked,
blockedReasons: ["Change selection is ambiguous: per-instance-hook-scoping, service-credential-tester."], isNonAuthoritative: false. This is a parent-resolution artifact:
the engine auto-detected two active changes and could not pick one. The ambiguity does not
reflect the state of service-credential-tester, which this archive task was explicitly
assigned.
Discrepancy with the parent's authoritative confirmed state — RESOLVED in favor of the parent.
The parent physically verified (and this executor re-confirmed directly against the filesystem in
§2) that all 29 tasks are ticked, apply-progress.md is present and records the work, the verify
report confirms functional completion (21/21 PASS), and the sync report confirms a SYNCED canonical
domain. Per the archive contract's non-authoritative-store carve-out guidance and the parent's
explicit instruction ("native sdd-status may report stale blockers — DISREGARD; parent verified.
PROCEED"), the stale archive: blocked / "ambiguous" labels are disregarded and the archive
proceeds.
Direct filesystem re-validation (§2) is the source of truth for this report.
2. Archive preconditions (validated directly against the filesystem)
| Precondition | Evidence | Result |
|---|---|---|
| Verify report present | verify-report.md |
✓ verdict PASS (functionally) — 21/21 requirements PASS |
Verify clearly passing — no unresolved FAIL/BLOCKED/CRITICAL |
the two verify CRITICALs were reconcilable archive-only items (missing apply-progress.md + 29 unchecked boxes), now resolved; no unresolved critical verification issue remains |
✓ |
| Sync report present & successful | sync-report.md → Status: SYNCED |
✓ |
| Canonical spec exists (sync target, created) | openspec/specs/service-credential-testing/spec.md (21 requirements, CT-101 … CT-121) |
✓ |
| Change-side domain delta exists | specs/service-credential-testing/spec.md (## ADDED Requirements) |
✓ |
| Delta op-class = pure additive (ADDED; new domain; no MODIFIED/REMOVED) | ADDED=21, MODIFIED=0, REMOVED=0, RENAMED=0 | ✓ |
| Requirement-ID parity (flat ↔ delta ↔ canonical) | 21 == 21 == 21; identical IDs CT-101 … CT-121 | ✓ |
| proposal / design / tasks artifacts present | all populated | ✓ |
Final Task Completion Gate — zero unchecked - [ ] |
grep -cE '^\s*- \[ \]' tasks.md → 0; grep -cE '^\s*- \[x\]' → 29 |
✓ |
apply-progress.md present & records the work |
present; status "complete — all 29 tasks done, all gates green"; 2 slices documented | ✓ |
No active same-domain (service-credential-testing) collision |
new domain; the only other active change (per-instance-hook-scoping) carries no service-credential-testing delta; sameDomainActiveChanges: [], collisions: [] (per sync-report §5) |
✓ |
Stale-checkbox reconciliation note. At verify time, all 29 implementation/verification checkboxes
were unchecked and apply-progress.md did not exist (verify findings B-1/B-2). That condition was
reconciled before archive: apply-progress.md was authored documenting the two landed slices
(3391fbc slice-1 backend, f6c67bd slice-2 frontend) and the gate results, and all 29 boxes are
now ticked. apply-progress.md plus the verify report prove every previously-unchecked task
complete. No archive-time mechanical checkbox repair was needed — the gate now passes on the
persisted tasks.md as-is (0 unchecked). No partial-archive approval applies.
3. Artifacts read (archive preflight)
openspec/changes/service-credential-tester/proposal.mdopenspec/changes/service-credential-tester/spec.md(flat, authoritative planning artifact — 21 requirements CT-101 … CT-121)openspec/changes/service-credential-tester/specs/service-credential-testing/spec.md(change-side domain delta —## ADDED Requirements)openspec/changes/service-credential-tester/design.mdopenspec/changes/service-credential-tester/tasks.mdopenspec/changes/service-credential-tester/apply-progress.mdopenspec/changes/service-credential-tester/verify-report.mdopenspec/changes/service-credential-tester/sync-report.mdopenspec/specs/service-credential-testing/spec.md(canonical, sync target — verified present and untouched)openspec/config.yaml(rules: proposal/tasks; norules.archiveoverride)- House-style reference:
openspec/changes/archive/2026-07-09-grafana-metric-gateway/archive-report.md
The legacy flat
spec.mdis not the only spec artifact: a per-domain delta (specs/service-credential-testing/spec.md) and a canonical spec both exist, so the "legacy flat spec as the only artifact" archive-block condition does not apply. The flat spec travels with the archived folder as part of the audit trail.
4. Domains synced & requirement delta
| Domain | Change-side delta | Canonical | Action |
|---|---|---|---|
service-credential-testing |
specs/service-credential-testing/spec.md |
openspec/specs/service-credential-testing/spec.md |
NEW domain — ADDED — 21 requirements copied into the canonical store as a brand-new spec |
- ADDED (21) — all to the new
service-credential-testingdomain (canonical did not exist pre-change). IDs and text preserved verbatim from the verified flatspec.md. Grouped logically:- Endpoint shape, validation, dispatch — CT-101, CT-102, CT-103
- Per-type test routines — CT-104, CT-105, CT-106, CT-107, CT-108, CT-109, CT-110, CT-111
- No-persistence + security — CT-112, CT-113
- Frontend test UI — CT-114, CT-115, CT-116, CT-117, CT-118
- Tests + gate greenness — CT-119, CT-120, CT-121
- MODIFIED (0) — none (new domain; no pre-existing canonical requirements to replace).
- REMOVED (0) · RENAMED (0) — nothing destructive.
5. Final lifecycle status (all 8 phases done)
| Phase | Status | Evidence |
|---|---|---|
| Proposal | ✅ done | proposal.md |
| Spec | ✅ done | flat spec.md (21) + domain delta specs/service-credential-testing/spec.md (21 ADDED) |
| Design | ✅ done | design.md |
| Tasks | ✅ done | tasks.md — 29/29 checked, zero - [ ] |
| Apply | ✅ done | 2 slices delivered (commits 3391fbc slice-1 backend, f6c67bd slice-2 frontend) |
| Verify | ✅ PASS | verify-report.md — 21/21 PASS; gates green (verify's two CRITICALs were archive-only hygiene, now resolved) |
| Sync | ✅ done | sync-report.md — SYNCED; canonical service-credential-testing domain ADDED (now 21 requirements) |
| Archive | ✅ done | this report + folder move performed |
6. Gate results (per verify-report / apply-progress)
| Gate | Command | Result |
|---|---|---|
| Backend tests | cd backend && PYTHONPATH=src python3 -m pytest -q |
PASS — 362 passed (2 pre-existing warnings) |
| Backend lint | cd backend && PYTHONPATH=src python3 -m ruff check src tests |
PASS — All checks passed |
| Frontend build | cd frontend && npm run build |
PASS — exit 0 (pre-existing chunk-size advisory) |
| Frontend lint | cd frontend && npm run lint |
PASS — 0 errors (1 pre-existing unrelated warning) |
| Frontend tests | cd frontend && npx vitest run |
PASS — 158 passed / 45 files |
7. Carry-over follow-ups & non-blocking notes (recorded for the record)
- [RESOLVED — verify N-2] No-secret-logs test was strengthened. Verify flagged the original
test_secrets_not_loggedas weak/misleading because it sent an emptysecrets: {}payload (the assertion was then trivially satisfied). The test was rewritten (backend/tests/test_api.py) to drive the full endpoint path (validate → dispatch to the realtest_callable→ success log) with real-looking secret payloads —grafana_api_key: "glc_somethingverysecret"andpassword: "SUPER-SECRET-PW-12345"— mocking at the network boundary so both requests succeed, then asserting that neither the full values nor meaningful fragments ("verysecret","SUPER") leak intocaplog.text. This genuinely proves CT-113 (secrets never logged). The verify finding is closed. - [DESIGN — intentional improvement, verify N-6]
ServiceTestPanelis presentational. The design proposed a stateful panel (useEffect([input])); the shipped component is deliberately presentational (props{ input, onResult, disabled }), with the parent owningtestResultsaveAnywaystate and the store-previous pattern that clears the result on input change. This avoids the ReactsetState-in-effect footgun and works identically in both surfaces. All of CT-114 … CT-118 are still satisfied via parent wiring. Documented in the component docstring andapply-progress.md. Informational only.
- [DESIGN — source-finding] Edit surface is
Settings.tsx::ServiceConfigEditor, notServicePage.tsx. The spec's literal CT-115 namedServicePage.tsx, but that file is read-only (it renders tab content for a configured service). The actual service add/edit dialog surfaced by the app isServiceConfigEditorinsideSettings.tsx(create surface:CreateServiceDialoginServicesPage.tsx). The design's §0 source-finding corrected this, and tasks targeted the right file.ServiceTestPanelis wired into bothCreateServiceDialogandServiceConfigEditor.ServicePage.tsxwas correctly left untouched. - [USER VALUE — pain resolved] qBittorrent "login failed" surfaced at the UI layer (CT-104).
Previously a wrong qBittorrent username/password produced only a generic
RuntimeError/ "login failed" buried in backend logs, leaving the operator to dig through logs to diagnose a credential problem. The credential tester now logs into qBittorrent + probesmaindata()during the live Test, and specifically translates the"Fails."/"login failed"response into "Authentication failed — qBittorrent rejected the credentials.", shown directly in the result pill (✗ red detail). The credential-error pain is resolved at the UI layer — the operator gets an actionable message without touching logs. - [INFO — non-blocking, verify N-3] Defensive
logger.exception.services.pyemits a full traceback vialogger.exceptiononly on the rare path where atest_callableitself raises (all routines catch internally). Low risk; a future change may switch to a truncatedlogger.warning. Not blocking. - [INFO — non-blocking, verify N-4] Edit flow requires re-typing secrets to test. In
ServiceConfigEditor, stored secrets are encrypted at rest and are not sent as plaintext on the test request, so testing an existing authed service without re-entering its secret yields a "required" validation error. This is inherent to the no-persistence design (the test operates on request-body plaintext) and is hinted by the "Leave blank to keep the current value" copy. Non-blocking UX note; a future change may document it more prominently in the UI/user docs. - [INFO — non-blocking, verify N-5] Page-level gating proven by source, not a page test.
ServiceTestPanel.test.tsxcovers the presentational panel (button states, success/failure pills, checkbox toggle). Because the panel is stateless, there is no explicit automated test that the Create/Save confirm button enables only after a passing test, or that editing a field clearstestPassed. The logic is correct by source inspection (store-previous pattern in both parents); a future change may add a page-level integration test. - [INFO] The working tree carries unrelated dirty/untracked items not owned by this archive:
an uncommitted cosmetic (indentation/prettier reflow, no functional diff) modification to
frontend/src/pages/ServicesPage.tsxand untracked.pi-tmp/*scratch files (other-change strays). Per discipline these were left untouched.
8. Residual risks & destructive-merge statement
- Destructive sync / merge: none destructive. This was a brand-new canonical domain
(
service-credential-testing) — a pure## ADDED Requirementsdelta. Zero MODIFIED and zero REMOVED requirements, so no destructive-removal guard was triggered and no destructive-sync parent approval was required. - Backend / data-contract impact: none. The
POST /api/services/testendpoint is side-effect-free (no persistence, no cache invalidation) and sanitized (logs onlytype+ok, never secrets). It gates on the existing validation path (_validate_input) and dispatches through the closedtest_callableregistry that mirrorsSERVICE_DEFINITIONS. Archive touched only OpenSpec docs + the folder move. - No critical verification issues remain (CRITICAL issues are non-overridable; the two verify CRITICALs were the reconcilable checkbox/apply-progress gap, now resolved; N-2's weak-test gap was strengthened and closed).
- No browser/visual smoke was performed (out of scope); the panel is covered by Vitest component tests only.
- Memory observation IDs: none —
artifactStore: openspec; traceability lives in the filesystem archive + canonical spec.
9. Move performed
git mv openspec/changes/service-credential-tester openspec/changes/archive/2026-07-09-service-credential-tester
- All 9 artifacts confirmed present at the archived path:
proposal.md,spec.md,specs/service-credential-testing/spec.md(delta),design.md,tasks.md,apply-progress.md,verify-report.md,sync-report.md,archive-report.md(this file). - Canonical
openspec/specs/service-credential-testing/spec.mdremains in place (verified untouched after the move — sha256 unchanged).openspec/specs/web-ui/spec.md,openspec/specs/prometheus-charting/spec.md, andopenspec/specs/service-storage/spec.mdalso untouched. - Renames were left staged (R100 detection preserved) for the parent's explicit-path commit.
git restore --stagedwas not run after thegit mv.
Appendix — Files written/moved by this archive (OpenSpec only; no source code)
- Written:
openspec/changes/service-credential-tester/archive-report.md(this file) — at the active path before the move; travels with the move into the archive. - Moved (via
git mv): the entireopenspec/changes/service-credential-tester/directory →openspec/changes/archive/2026-07-09-service-credential-tester/. - Left in place (durable canonical):
openspec/specs/service-credential-testing/spec.md. - Not committed / not pushed — the parent owns the commit with explicit paths.