Skip to content

Complete workforce access and evidence-aware operations - #10

Merged
mrlucas679 merged 19 commits into
mainfrom
developers
Jul 29, 2026
Merged

Complete workforce access and evidence-aware operations#10
mrlucas679 merged 19 commits into
mainfrom
developers

Conversation

@mrlucas679

Copy link
Copy Markdown
Owner

Summary

  • productize first-owner bootstrap, workforce invitations, activation, recovery, role management, deactivation, and versioned sessions
  • add deterministic evidence-partition planning and retrieval receipts for grounded chat routing
  • project normalized POS sales and stock updates into the tenant inventory ledger with idempotent receipts
  • complete the browser account lifecycle, guided operator documentation, schema/RLS contracts, and capability manifest

Verification

  • 907 passed, 21 skipped locally (928 collected)
  • Ruff clean across src, tests, and scripts
  • TypeScript typecheck passed
  • Vite production build passed (288 modules)
  • capability contract passed: 241 capabilities, sha256:3697ed095051760ab9146f1247320b68c175bfd4cc0b2abde5ca4a25aedc3f47
  • Playwright golden path passed: 10/10 on isolated ports 5187/8017

External acceptance boundaries

Live SMTP delivery, retailer sandbox traffic, and MI300X/Fireworks inference require operator credentials and are intentionally not inferred from local or CI results.

Preview/commit API over the existing connector pipeline: header-alias
mapping inference, per-row validation with provenance-preserving
quarantine, idempotent re-commit, catalog population with identifier
conflict detection, and an expiry-entry seam so dated stock exports
feed expiry math. Ships the client-intake runbook, backup and
restore-drill scripts, and the product readiness plan.
Store owners could already save ERP/POS credentials in the frontend, but had
no way to know whether the values actually worked before relying on them, and
adding a new poll-based system required hand-written frontend code (fields
were hard-coded twice: once in the frontend panel, once in the backend poll
service). This adds a single source of truth for credential fields
(shelfwise_connectors.connector_test.CREDENTIAL_FIELDS), exposes it via
GET /connectors/systems, and adds POST /connectors/{system}/credentials/test
which reuses each connector's real fetch_page(None) call to probe the source
system live - no cursor persisted, no record ingested - so a store owner gets
an honest pass/fail before saving, not just a "saved" confirmation.
ShelfWise deploys one backend instance per tenant - the tenant id and first
owner login are environment configuration (SHELFWISE_TENANT_ID,
SHELFWISE_LOGIN_EMAIL, SHELFWISE_LOGIN_PASSWORD_HASH), not something the
running API can create for itself. That was previously a manual step:
hand-deriving a scrypt hash and a Fernet-compatible secret. This adds
scripts/provision_new_shop.py, which generates a ready-to-paste .env fragment
for a new shop from just a company name and owner email in one command -
the one remaining step before a shop is fully self-serve (connectors,
cameras, CSV import, staff accounts already are, per the prior commit).

Extracted the scrypt hashing app.py's /auth/login already used into
shelfwise_backend/auth_credentials.py so the script and the login route use
the exact same verified implementation instead of a second, driftable one.
CI's first run against the onboarding-wizard work (662612e) surfaced packaging debt
local dev environments had masked: pyproject.toml declared cryptography>=42 (used by
the connector credential encryption module) but requirements.txt - what CI and the
backend Dockerfile actually install - did not, so a clean-environment wheel install
failed to import that module. Adds the missing pin and a package-contract test that
keeps every pyproject runtime dependency present in requirements.txt going forward,
plus a bounded-deadline fix for a worker-journal reclaim test that could flake under
full-suite thread contention.
Guided onboarding now requires confirming the current product-policy templates per
category, stored tenant-scoped with central-schema RLS. A superseded template version
stops satisfying readiness rather than silently reusing an old acknowledgement, so
readiness is four required steps: company, store, data source, policies.

Decision queues are personalized by the verified workforce role through one server-owned
assignment matrix. queue_view=assigned derives strictly from TenantContext.role and never
from a query parameter; queue_view=all still returns the complete tenant ledger so owner
and auditor visibility is unchanged.

Monthly value reporting separates verified recovered money from estimated exposure. Only
explicitly recorded actual amounts contribute to verified_recovered; modeled approval
outcomes remain separately labelled and are never summed into it.

Operations controls add no paid dependency: a stdlib health monitor requiring both
liveness and seed readiness, exiting non-zero and writing bounded incident receipts
without copying response bodies or accepting credential-bearing webhook URLs, plus
release/rollback and POPIA operator documentation.

Gates: 921 passed / 21 skipped, Ruff clean, frontend typecheck and production build clean,
capability contract 244, Playwright 10/10 on isolated ports 5187/8017.
Removes the last connector path that still needed a developer. Shopify, Square,
Lightspeed, and Yoco authenticate the sender with an HMAC signature rather than a
credential ShelfWise stores, so they were excluded from the credential panel and had to
reach /connectors/{system}/intake, which is gated by the operator's shared ingest API
key. A store owner could self-serve connect an ERP but not their own till.

An owner now provisions a per-tenant, per-system endpoint in the console and receives a
delivery URL and signing secret exactly once, then pastes both into that retailer's
webhook settings. POST /connectors/webhook/{endpoint_id} authenticates on that tenant's
signature alone and runs deliveries through the same _process_inbound_record pipeline as
the keyed intake route, so dedup, validation, and projection behaviour are identical.

Secrets are encrypted at rest with the existing credential-encryption mechanism and are
never returned again by any route. Like the edge-device registry, the table is
deliberately not RLS-scoped and says why: resolving a tenant from an opaque endpoint id
happens before any tenant is known, so an RLS predicate would match zero rows and break
every delivery. List and revoke are tenant-scoped with explicit application predicates.

Tests cover the properties that matter: a tampered body fails its signature, a revoked
endpoint stops accepting previously valid deliveries, tenants cannot list or revoke each
other's endpoints, a payload cannot name the tenant it lands in, an unknown endpoint is
indistinguishable from a bad signature, and poll-based systems are refused an endpoint
they could never deliver to.

Gates: 932 passed / 21 skipped, Ruff clean, frontend typecheck and production build clean,
capability contract 248, Playwright 11/11 on isolated ports 5187/8017.
provision_new_shop.py generated a shop's secrets but stopped there: an operator still
had to paste the fragment into a .env, know which Compose file to run, and know how to
tell when the stack was actually ready. That was the remaining developer step between
downloading the application and signing in as the owner.

scripts/start_shelfwise.py provisions the shop only if it has not been provisioned
before, brings the Compose stack up, polls the real /health endpoint, and prints the
console URL with the first-login credentials.

An existing .env is never regenerated. Re-minting SHELFWISE_CREDENTIAL_ENCRYPTION_KEY
would make every stored connector credential, webhook secret, and device secret
permanently undecryptable, and a new password hash would lock the owner out of their own
instance, so a re-run only restarts the stack. An empty .env still counts as
unprovisioned so a half-created file is not mistaken for a configured shop. The health
wait is bounded and fails closed, treating ok:false as not-ready rather than ready. The
plaintext owner password never reaches the env file, only its scrypt hash, and a
password the owner supplied is not echoed back as if generated. Missing Docker produces
an installable instruction rather than a traceback.

Verification boundary: the provisioning, health-gating, and Compose detection logic is
unit-tested, but a live docker compose up was not executed here - the machine had zero
free disk and an unresponsive Docker CLI. That end-to-end run remains outstanding and is
recorded in HANDOFF.md rather than implied.

Scope: this deploys ShelfWise on the machine that runs it, which is what a single shop
evaluating the product needs. It is not a hosted multi-tenant sign-up service.

Gates: 943 passed / 21 skipped, Ruff clean, frontend typecheck clean, capability
contract 248.
The startup script was executed against real Docker on this machine. That run proved the
.env-preservation guard outside of tests (it reported keeping the existing setup and left
the file untouched) and proved the Docker-failure path prints its intended actionable
message and exits non-zero instead of hanging or raising.

The successful-start path is still unproven and is now recorded as such with the exact
reason: this machine's C: drive fell to 297 MB free during the attempt and the Docker
Desktop engine stopped responding on its named pipe. Replaced the earlier, vaguer note
that implied the script had not been run at all.
@mrlucas679
mrlucas679 marked this pull request as ready for review July 29, 2026 21:06
@mrlucas679
mrlucas679 merged commit 5eb0f89 into main Jul 29, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant