Skip to content

feat(hardware): backfill legacy hardware projects into the hardware flow#560

Draft
dhamariT wants to merge 1 commit into
mainfrom
feat/backfill-legacy-hardware-stage
Draft

feat(hardware): backfill legacy hardware projects into the hardware flow#560
dhamariT wants to merge 1 commit into
mainfrom
feat/backfill-legacy-hardware-stage

Conversation

@dhamariT

Copy link
Copy Markdown
Collaborator

what's this do?

Adds a one-time backfill that brings legacy hardware projects into the new hardware flow.

the problem

There are two unrelated "type" concepts on Project:

  • project_type — an AI taxonomy label ("Web App", "CLI", "Hardware"…) set by Project::TypeCheckJobSwAi::ProjectTypeService.
  • hardware_stage (design/build/nil) — the canonical hardware discriminator. Project#hardware? is literally hardware_stage.present?, and it drives funding → build → payout → the Lookout recorder.

hardware_stage was added 2026-06-02. Every project shipped before then has hardware_stage = nil, so hardware? is false for them even when they're genuine hardware builds. There's no deterministic signal to recover them — funding requests, Lookout sessions, and phase-stamped devlogs all post-date the column (funding even validates design_stage? on create), and project_categories is a dead column with no write path. The only available signal is project_type == "Hardware" from the AI classifier.

what it does

OneTime::BackfillHardwareStageJob stamps hardware_stage on projects where project_type == "Hardware" and hardware_stage IS NULL.

  • Dry-run by default — logs/returns the candidate ids and writes nothing. Pass dry_run: false to persist.
  • Defaults to "design" (the flow's entry stage). Safe for already-shipped projects: it implies no funding grant, design-phase time isn't credited toward build payout (so no payout change), and projects in scope can't already have a funding request, so the funding-stage lock never trips. Stage is overridable via stage:.
  • Writes via save(validate: false) so legacy rows that would fail today's unrelated validations (URL format, banner type…) still get classified — while PaperTrail audit and the Gorse / semantic-search re-index callbacks still fire. Wrapped in PaperTrail.request so the version is attributed to the job.

how to run

# 1. dry run — see what would change (writes nothing)
OneTime::BackfillHardwareStageJob.new.perform(dry_run: true)

# 2. commit
OneTime::BackfillHardwareStageJob.new.perform(dry_run: false)

⚠️ operator notes

  • Only catches what the AI labeled "Hardware" — under-labeled hardware still needs manual admin tagging. Eyeball the dry-run list first.
  • Setting hardware_stage flips hardware? → enables the Lookout recorder and hardware shipping gates going forward (intended).
  • Do NOT re-run OneTime::BackfillDevlogPhaseJob after this — it back-dates nil-phase devlogs into the project's current stage. Harmless with "design" (uncounted), but a "build" run would over-credit payout.

show it works

Tested in test/jobs/one_time/backfill_hardware_stage_job_test.rb: dry-run writes nothing, commit stamps design on AI-typed hardware projects only (software + already-staged untouched), explicit stage honored, invalid stage raises, legacy rows that fail current validations still classify, and the change is PaperTrail-audited to the job.

Reviewer: please run the dry run in a console and sanity-check the candidate list before this is merged/run in prod.

follow-up (not in this PR)

OneTime::BackfillProjectTypeJob#scope filters where.not(shipped_at: nil), but "shipped" really means having ship events (shipped_at is set inconsistently), so it enqueues nothing for old projects. Correct scope: Project.with_ship_events.where(project_type: nil, deleted_at: nil). Happy to fix in a separate PR.

ai?

Claude 🥀

Projects shipped before hardware_stage existed (column added 2026-06-02)
sit at hardware_stage = nil, so Project#hardware? is false for them even
when they are genuine hardware builds. The only available signal is the AI
type classifier's project_type == "Hardware".

Add OneTime::BackfillHardwareStageJob to promote those projects into the
flow by stamping hardware_stage:

- dry-run by default; pass dry_run: false to persist
- defaults to "design" (no implied grant; design-phase time is uncounted
  for payout; can't trip the funding-stage lock)
- writes via save(validate: false) so legacy rows that fail today's
  unrelated validations still classify, while PaperTrail audit and the
  Gorse / semantic-search re-index callbacks still fire
- wrapped in PaperTrail.request so the version is attributed to the job
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