diff --git a/.project-docs/30-worklog/tasks/20260816-enable-guided-hotspot-4e91c2.md b/.project-docs/30-worklog/tasks/20260816-enable-guided-hotspot-4e91c2.md new file mode 100644 index 0000000..6dd9417 --- /dev/null +++ b/.project-docs/30-worklog/tasks/20260816-enable-guided-hotspot-4e91c2.md @@ -0,0 +1,112 @@ +# Task: Enable Guided Hotspot Binding by default + +## Identity + +- Task ID: 20260816-enable-guided-hotspot-4e91c2 +- Mode: Feature +- Branch: codex/20260816-enable-guided-hotspot-4e91c2-enable-guided-hotspot +- Worktree: D:\Datas\OthersProjects\makelore-enable-guided-hotspot-4e91c2 +- Base commit: 971865c256c2ddfe1f8ab4f8f4731a1f608599c0 +- Owner: codex-root +- Status: Ready for Integration + +## Scope + +- Make the existing Robot Guided Hotspot Binding capability enabled when the Electron Main environment variable is absent. +- Preserve an explicit local rollback by treating `NIANCODE_AI_HARDWARE_GUIDED_HOTSPOT_BINDING=0` as disabled. +- Update focused Main route tests for the default-enabled and explicit-disabled behaviors. +- Do not change the Renderer journey, Host API shapes, cloud Binding contract, portal address, or firmware. + +## Intent And Constraints + +- The user explicitly confirmed that the guided provisioning journey must be enabled by default after observing the current direct six-digit-code fallback. +- Preserve `Renderer -> Host API -> Electron Main`; only Main owns the capability and the fixed `http://192.168.4.1/` system-browser action. +- Preserve the safe capability-read fallback in Renderer: a failed local capability request still falls back to the direct six-digit flow. +- Makelore must not scan nearby devices, accept arbitrary portal URLs, or collect/log/persist Wi-Fi credentials. Binding success remains separate from online readiness. +- Keep firmware changes at zero. +- This feature task may update only its own task record; the accepted default-on policy must be promoted into ADR-002 and canonical project memory during Integration Gate. + +## Project Context Loaded + +Task context: +- Task ID: `20260816-enable-guided-hotspot-4e91c2` +- Mode: Feature +- Branch: `codex/20260816-enable-guided-hotspot-4e91c2-enable-guided-hotspot` +- Worktree: `D:\Datas\OthersProjects\makelore-enable-guided-hotspot-4e91c2` +- Base commit: `971865c256c2ddfe1f8ab4f8f4731a1f608599c0` +- Other active local tasks: task registry reviewed; the Robot installer provenance task shares product context but owns no Robot logic changes, while older Robot design/implementation tasks are ready for integration or superseded by integrated ADR-002. +- Overlap or semantic-conflict assessment: ADR-002 and canonical memory currently require default-off. This is an intentional policy change explicitly confirmed by the user; feature work remains isolated and canonical reconciliation is deferred to Integration Gate. The placeholder `20260816-assess-design-delta-a4d91c` record has unknown scope but no declared files or intent and cannot override the current human decision. + +Read: +- `.project-docs/05-agent-entry/read-before-planning.md` +- `.project-docs/05-agent-entry/memory-index.md` +- `.project-docs/05-agent-entry/concurrent-task-gate.md` +- `.project-docs/05-agent-entry/planning-gate.md` +- `.project-docs/05-agent-entry/read-before-coding.md` +- `.project-docs/00-brief/project-positioning.md` +- `.project-docs/00-brief/success-criteria.md` +- `.project-docs/30-worklog/current-state.md` +- `.project-docs/10-decisions/decision-index.md` +- `.project-docs/10-decisions/adr-002-robot-guided-hotspot-binding-v1.md` +- `.project-docs/20-architecture/system-overview.md` +- `.project-docs/20-architecture/module-map.md` +- `.project-docs/20-architecture/data-flow.md` +- `.project-docs/40-domain/business-rules.md` +- `.project-docs/80-commitments/commitments.md` +- `.project-docs/90-maintenance/stale-items.md` +- Every other registered owner's task record: `Scope`, `Intent And Constraints`, and `Promotion Candidates` sections only. + +Relevant understanding: +- Project goal: keep Robot onboarding inside the existing Makelore Robot experience with Main-owned security boundaries and no firmware change. +- Current integrated focus: Guided Hotspot Binding is implemented but Main currently reports the capability false unless an environment variable is exactly `1`. +- Active task scope: reverse only that default and its regression expectations. +- Active constraints: no Renderer/firmware/cloud-contract changes; fixed portal only; no Wi-Fi credential handling; preserve explicit rollback and direct-code fallback on capability-read failure. +- Decisions affecting this task: ADR-002's default-off release policy is deliberately superseded by the user's default-on decision; all other ADR-002 security/recovery rules remain in force. +- Evidence, reflections, or commitments affecting this task: physical-device and exact shipped-firmware validation remain unverified release risks even though the product default is being changed. +- Files or modules likely involved: `electron/api/routes/ai-hardware.ts`, `tests/unit/ai-hardware-routes.test.ts`. +- Unknowns, stale docs, or conflicts: exact physical-device smoke and deployed activation issuer semantics remain unverified; canonical docs are stale for the new decision until integration. + +Gate result: +- Passed. Ownership is isolated, the explicit human decision resolves the only semantic conflict, and the plan preserves all non-default security boundaries. + +## Implementation Plan + +1. Change the Main-owned environment default to enabled, with exact `0` as the opt-out. +2. Change the capability regression to expect enabled by default and make the disabled portal test inject `guidedHotspotBinding: false` explicitly. +3. Run focused Robot tests, typecheck, scoped lint, Vite build, and any broader verification required by the changed Main/Renderer seam. +4. Obtain independent final review, complete this task record, and hand the reviewed commit to Integration Gate for canonical documentation reconciliation. + +## Outcome + +- Changed the Main-owned default to `process.env.NIANCODE_AI_HARDWARE_GUIDED_HOTSPOT_BINDING !== '0'`. +- Ordinary launches now report `guided_hotspot_binding: true`, so the existing Binding dialog opens at the guided/direct path chooser instead of jumping directly to six-digit entry. +- Exact environment value `0` remains the binary-free rollback; explicit dependency injection `guidedHotspotBinding: false` is covered and continues to disable both capability reporting and portal opening. +- No Renderer, Host API wire shape, Works Square contract, portal URL, firmware file, or Wi-Fi credential boundary changed. +- The implementation diff is limited to one Main constant and focused route expectations. + +## Verification + +- `pnpm vitest run tests/unit/ai-hardware-routes.test.ts`: PASS, 37/37 tests, including a fresh module load with exact environment value `0`. +- `pnpm vitest run tests/unit/ai-hardware-routes.test.ts tests/unit/ai-hardware-api.test.ts tests/unit/ai-hardware-page.test.tsx`: PASS, 94/94 tests. +- `pnpm run typecheck`: PASS. +- `pnpm exec eslint electron/api/routes/ai-hardware.ts tests/unit/ai-hardware-routes.test.ts`: PASS. +- `pnpm test`: PASS before the final environment-regression test, 156 files and 1757/1757 tests. Two later default-parallel reruns each passed 1757/1758 but timed out the same unrelated OpenCode port-release test at its 10-second limit; that full test file then passed 40/40 in isolation. +- `pnpm vitest run --maxWorkers=4`: PASS after the final test addition, 156 files and 1758/1758 tests. The bounded concurrency removes the confirmed suite-level resource contention without changing test behavior. +- `pnpm run build:vite`: PASS for Renderer, Electron Main, and Preload; only pre-existing chunk-size/dynamic-import warnings were emitted. +- `git diff --check`: PASS. +- Initial sandboxed parallel pnpm typecheck/lint launch failed before running checks with `EPERM lstat C:\Users\7brot`; both commands were rerun outside that filesystem restriction and passed. +- Independent final Sol review: PASS, no P0-P3 findings. The reviewer independently reran 37/37 route tests and confirmed default-on, exact environment opt-out, dependency-injection opt-out, fixed portal ownership, local-before-Works-token behavior, error redaction, unchanged Binding contract, and zero firmware changes. + +## Follow-ups + +- Canonical ADR/project memory must be changed from default-off to default-on in Integration Gate; historical source task records remain unchanged. +- A real installed Electron path-choice/native-opener smoke and a physical-device end-to-end smoke remain unverified. The user's explicit default-on decision accepts changing the product default but does not turn these missing checks into completed evidence. + +## Promotion Candidates + +- Target: ADR-002, decision index, success criteria, Robot business rules/module map/data flow/current state, README where applicable, and release guidance. + - Proposal: Guided Hotspot Binding is enabled by default; `NIANCODE_AI_HARDWARE_GUIDED_HOTSPOT_BINDING=0` is the explicit rollback. Preserve direct six-digit fallback when the local capability request fails, the fixed Main-owned portal, zero firmware change, and all existing security/recovery limitations. + - Evidence: user confirmation in this task, Main route implementation, and focused regression verification. + - Future impact: ordinary installed builds enter the path chooser before six-digit Binding; release/support can disable the journey without changing binaries. + - Semantic conflicts: supersedes ADR-002's default-off/public-disabled policy, but not its firmware, credential, identity, online-readiness, or physical-validation caveats. + - Human confirmation required: no further confirmation; the user explicitly requested default-on. diff --git a/electron/api/routes/ai-hardware.ts b/electron/api/routes/ai-hardware.ts index c08f94c..ae86963 100644 --- a/electron/api/routes/ai-hardware.ts +++ b/electron/api/routes/ai-hardware.ts @@ -18,7 +18,7 @@ const CATALOG_ID = /^[A-Za-z0-9][A-Za-z0-9_.:-]{0,254}$/; const OPERATION_ID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i; const MAX_RETRY_AFTER_SECONDS = 2; const PROVISIONING_PORTAL_URL = 'http://192.168.4.1/'; -const GUIDED_HOTSPOT_BINDING_ENABLED = process.env.NIANCODE_AI_HARDWARE_GUIDED_HOTSPOT_BINDING === '1'; +const GUIDED_HOTSPOT_BINDING_ENABLED = process.env.NIANCODE_AI_HARDWARE_GUIDED_HOTSPOT_BINDING !== '0'; type TokenGetter = typeof getValidWorksSquareAccessToken; diff --git a/tests/unit/ai-hardware-routes.test.ts b/tests/unit/ai-hardware-routes.test.ts index 33d0973..14a1b6e 100644 --- a/tests/unit/ai-hardware-routes.test.ts +++ b/tests/unit/ai-hardware-routes.test.ts @@ -65,7 +65,7 @@ async function invoke(handler: ReturnType, } describe('AI hardware Host API route', () => { - it('returns the default-off provisioning capability before credentials or upstream access', async () => { + it('returns the default-on provisioning capability before credentials or upstream access', async () => { const { handler, fetchImpl, getAccessToken } = setup(); const result = await invoke( handler, @@ -73,6 +73,22 @@ describe('AI hardware Host API route', () => { '/api/works/ai-hardware/provisioning-capabilities', ); + expect(result.payload).toEqual({ + success: true, + data: { guided_hotspot_binding: true }, + }); + expect(getAccessToken).not.toHaveBeenCalled(); + expect(fetchImpl).not.toHaveBeenCalled(); + }); + + it('honors an explicitly injected disabled provisioning capability', async () => { + const { handler, fetchImpl, getAccessToken } = setup(undefined, { guidedHotspotBinding: false }); + const result = await invoke( + handler, + 'GET', + '/api/works/ai-hardware/provisioning-capabilities', + ); + expect(result.payload).toEqual({ success: true, data: { guided_hotspot_binding: false }, @@ -81,6 +97,36 @@ describe('AI hardware Host API route', () => { expect(fetchImpl).not.toHaveBeenCalled(); }); + it('honors the exact environment opt-out when Main loads the route', async () => { + vi.stubEnv('NIANCODE_AI_HARDWARE_GUIDED_HOTSPOT_BINDING', '0'); + vi.resetModules(); + try { + const { createAiHardwareRouteHandler: createHandlerWithEnv } = await import('@electron/api/routes/ai-hardware'); + const fetchImpl = vi.fn().mockResolvedValue(jsonResponse(overview)); + const getAccessToken = vi.fn().mockResolvedValue('secret-token'); + const handler = createHandlerWithEnv({ + fetchImpl, + getAccessToken, + apiBaseUrl: 'https://square.example', + }); + const result = await invoke( + handler, + 'GET', + '/api/works/ai-hardware/provisioning-capabilities', + ); + + expect(result.payload).toEqual({ + success: true, + data: { guided_hotspot_binding: false }, + }); + expect(getAccessToken).not.toHaveBeenCalled(); + expect(fetchImpl).not.toHaveBeenCalled(); + } finally { + vi.unstubAllEnvs(); + vi.resetModules(); + } + }); + it('rejects queried, body-bearing, and wrong-method capability requests locally', async () => { const { handler, fetchImpl, getAccessToken } = setup(); const queried = await invoke( @@ -129,7 +175,7 @@ describe('AI hardware Host API route', () => { it('rejects disabled, expanded, or queried portal opens before invoking native or cloud dependencies', async () => { const openExternal = vi.fn().mockResolvedValue(undefined); - const disabled = setup(undefined, { openExternal }); + const disabled = setup(undefined, { guidedHotspotBinding: false, openExternal }); const disabledResult = await invoke( disabled.handler, 'POST',