feat(robot): enable guided hotspot binding by default
This commit is contained in:
@@ -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.
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -65,7 +65,7 @@ async function invoke(handler: ReturnType<typeof createAiHardwareRouteHandler>,
|
||||
}
|
||||
|
||||
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<typeof fetch>().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',
|
||||
|
||||
Reference in New Issue
Block a user