From 4292776a40d872840de5ee17eef0f47a0b5fa0b0 Mon Sep 17 00:00:00 2001 From: John Paul Wile Date: Mon, 10 Aug 2026 16:18:35 -0600 Subject: [PATCH 1/2] feat: add permission-first security cases to demos --- PLAN.md | 1 + README.md | 3 +- docs/PROJECT_STATUS.md | 3 +- src/App.test.tsx | 3 + src/App.tsx | 9 +- src/features/ideas/idea-detail-page.test.tsx | 45 +++++ src/features/ideas/idea-detail-page.tsx | 86 +++++++++- .../ideas/idea-discovery-page.test.tsx | 25 +++ src/features/ideas/idea-discovery-page.tsx | 12 ++ .../ideas/idea-discovery-service.test.ts | 12 ++ src/features/ideas/idea-discovery-service.ts | 6 + src/theme-css.test.ts | 7 + ...20260810160000_add_demo_security_cases.sql | 158 ++++++++++++++++++ .../database/idea-security-cases.test.sql | 142 ++++++++++++++++ supabase/tests/database/ideas.test.sql | 5 +- 15 files changed, 510 insertions(+), 7 deletions(-) create mode 100644 supabase/migrations/20260810160000_add_demo_security_cases.sql create mode 100644 supabase/tests/database/idea-security-cases.test.sql diff --git a/PLAN.md b/PLAN.md index 96b01c3..2b50130 100644 --- a/PLAN.md +++ b/PLAN.md @@ -33,6 +33,7 @@ Ideascape is in **Exploration mode**. Published entries are concept previews, no - [x] Expand the catalog with bold, measurable infrastructure concepts and explicit operating boundaries - [x] Refine broad launch previews into bounded tests with decision-grade evidence - [x] Replace generic previews with issue-driven Colorado Springs civic campaigns +- [x] Give every demo a threat scenario, control boundary, and proof requirement consistent with the security claim policy - [x] Add persistent light and dark themes ## Phase 3 — Validation evidence and campaign design diff --git a/README.md b/README.md index 86ed6e4..825780e 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ Ideascape is an exploration-mode concept-validation platform for learning which - Public, unauthenticated idea discovery at `/ideas` and detail pages at `/ideas/:slug` - Twenty-one deterministic demo concepts, seeded through versioned Supabase migrations +- A concept-specific security case on every demo: threat scenario, control boundary, and proof required before expansion - Three issue-driven Colorado Springs campaigns covering plate-reader surveillance, dangerous crossings, and inaccessible transit stops - URL-persistent text search and category filtering, including a direct permission-first technology view at `/ideas?category=technology` - Same-category recommendations on detail pages with accessible full-card links back into the catalog @@ -64,7 +65,7 @@ Fresh databases contain these published concept previews: 20. [Model Commons Lab](https://ideascape-gamma.vercel.app/ideas/model-commons-lab) 21. [Flock Off Colorado Springs](https://ideascape-gamma.vercel.app/ideas/glass-box-sensor-network) -Each concept uses a stable UUID and slug, polished multi-paragraph copy, and a locally hosted SVG illustration so local resets and production verification remain reproducible. The earliest five broad previews now define bounded first tests, required permissions, privacy limits, measurable continuation evidence, and explicit stop conditions instead of assuming that backing or an operating program already exists. Those five concepts and Project Time Capsule also ask one focused, private-answer validation question so practical demand, permissions, and pilot capability can be evaluated as aggregate evidence rather than public member activity. Three previously generic previews are now sharp Colorado Springs campaigns: challenge the plate-reader dragnet through public records and lawful organizing, force a dangerous Academy Boulevard crossing into the engineering record, and turn Mountain Metro's own accessibility findings into five concrete stop fixes. Technology-forward previews remain permission-first: device and home-lab work stays inside written scope, compute jobs follow transparent acceptable-use controls, mesh nodes use legal spectrum and opt-in relays, repair documentation has lawful provenance, software archives preserve license provenance, heat recovery fails back to the original cooling path, and local-model comparisons use approved task packs. +Each concept uses a stable UUID and slug, polished multi-paragraph copy, and a locally hosted SVG illustration so local resets and production verification remain reproducible. Every demo now uses the landing page's security-claim discipline: it names a concrete threat scenario, an authorization, privacy, safety, provenance, or fail-safe control boundary, and measurable proof required before the concept earns a larger test. The earliest five broad previews define bounded first tests, required permissions, privacy limits, measurable continuation evidence, and explicit stop conditions instead of assuming that backing or an operating program already exists. Those five concepts and Project Time Capsule also ask one focused, private-answer validation question so practical demand, permissions, and pilot capability can be evaluated as aggregate evidence rather than public member activity. Three previously generic previews are now sharp Colorado Springs campaigns: challenge the plate-reader dragnet through public records and lawful organizing, force a dangerous Academy Boulevard crossing into the engineering record, and turn Mountain Metro's own accessibility findings into five concrete stop fixes. ## Stack diff --git a/docs/PROJECT_STATUS.md b/docs/PROJECT_STATUS.md index d56e8fd..ba7d8b7 100644 --- a/docs/PROJECT_STATUS.md +++ b/docs/PROJECT_STATUS.md @@ -19,7 +19,7 @@ Ideascape is in **Exploration mode**. The twenty-one published entries are conce ## Current focus -1. Learn which concepts produce practical participation intent, not only passive curiosity. +1. Learn which concepts produce practical participation intent, not only passive curiosity, while testing each one inside a published security case. 2. Use focused private-answer questions across six bounded concepts to test practical demand, permissions, and pilot capability through aggregate evidence. 3. Use the Project Time Capsule readiness dashboard to compare aggregate evidence with the published continue, revise, and archive thresholds. 4. Use the private operations dashboard to watch aggregate membership, concepts, validation, pilot intake, and published-concept activity without opening respondent-level records. @@ -35,6 +35,7 @@ Ideascape is in **Exploration mode**. The twenty-one published entries are conce - Idea, category, media, lifecycle, and private interest-signal schema - Private draft creation and editing - Public idea discovery and detail routes with twenty-one deterministic demo concepts, including thirteen permission-first or technology-forward previews +- Catalog badges and detail-page security cases connecting all twenty-one demos to a concrete threat scenario, control boundary, and proof requirement - Three localized Colorado Springs campaigns with explicit demands, public-record evidence, permitted field tests, and anti-tampering boundaries - Five early previews refined into bounded, permissioned tests with measurable continue, revise, and stop evidence - Shareable, URL-persistent discovery search and category filters with useful zero-result recovery diff --git a/src/App.test.tsx b/src/App.test.tsx index a191414..27fe2d0 100644 --- a/src/App.test.tsx +++ b/src/App.test.tsx @@ -200,6 +200,9 @@ describe("App", () => { name: /security before custody/i, }); expect(securityModel).toHaveTextContent(/no custody is live/i); + expect(securityModel).toHaveTextContent( + /every seeded concept.*threat scenario.*control boundary.*proof required/i, + ); expect( within(securityModel).getByRole("heading", { name: /a milestone is claimed too early/i, diff --git a/src/App.tsx b/src/App.tsx index fbea882..56397ad 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -459,9 +459,12 @@ function HomePage() { Security before custody

- No custody is live. These examples show the failure paths a - real funding design must handle before a contract, wallet - connection, or fiat rail can reach production. + No custody is live. Every seeded concept applies the same + claim policy by naming a threat scenario, control boundary, + and proof required before expansion. The examples below show + the failure paths a real funding design must handle before a + contract, wallet connection, or fiat rail can reach + production.

diff --git a/src/features/ideas/idea-detail-page.test.tsx b/src/features/ideas/idea-detail-page.test.tsx index 035a112..59b8940 100644 --- a/src/features/ideas/idea-detail-page.test.tsx +++ b/src/features/ideas/idea-detail-page.test.tsx @@ -42,6 +42,12 @@ const idea = { summary: "Affordable clean water powered directly by sunlight.", description: "A modular desalination system designed for coastal communities.", + threat_scenario: + "Source water or maintenance failures could expose residents to unsafe output.", + control_boundary: + "The pilot stays isolated from drinking-water service and requires independent water-quality review.", + proof_required: + "Advance only after repeated lab results meet the published water-quality and shutdown thresholds.", status: "published" as const, published_at: "2026-08-09T00:00:00.000Z", created_at: "2026-08-08T00:00:00.000Z", @@ -112,6 +118,27 @@ describe("IdeaDetailPage", () => { ).toBeInTheDocument(); expect(screen.getByText(idea.summary)).toBeInTheDocument(); expect(screen.getByText(idea.description)).toBeInTheDocument(); + const securityCase = screen.getByRole("region", { + name: /security case/i, + }); + expect( + within(securityCase).getByText("Threat scenario"), + ).toBeInTheDocument(); + expect( + within(securityCase).getByText(idea.threat_scenario), + ).toBeInTheDocument(); + expect( + within(securityCase).getByText("Control boundary"), + ).toBeInTheDocument(); + expect( + within(securityCase).getByText(idea.control_boundary), + ).toBeInTheDocument(); + expect( + within(securityCase).getByText("Proof required"), + ).toBeInTheDocument(); + expect( + within(securityCase).getByText(idea.proof_required), + ).toBeInTheDocument(); expect(screen.getByText("Technology")).toBeInTheDocument(); expect(screen.getByText("Concept preview")).toBeInTheDocument(); expect(screen.getByRole("link", { name: /idea creator/i })).toHaveAttribute( @@ -135,6 +162,24 @@ describe("IdeaDetailPage", () => { expect(screen.getByText(/3 people are interested/i)).toBeInTheDocument(); }); + it("omits the security case when a creator has not defined one", async () => { + mockedGetPublishedIdea.mockResolvedValue({ + ...idea, + threat_scenario: null, + control_boundary: null, + proof_required: null, + }); + + renderDetail(); + + expect( + await screen.findByRole("heading", { name: idea.title }), + ).toBeInTheDocument(); + expect( + screen.queryByRole("region", { name: /security case/i }), + ).not.toBeInTheDocument(); + }); + it("recommends other concepts in the same category without repeating the current idea", async () => { const relatedIdea = { ...idea, diff --git a/src/features/ideas/idea-detail-page.tsx b/src/features/ideas/idea-detail-page.tsx index f672aad..ffa72e2 100644 --- a/src/features/ideas/idea-detail-page.tsx +++ b/src/features/ideas/idea-detail-page.tsx @@ -1,5 +1,12 @@ import { useQuery } from "@tanstack/react-query"; -import { ArrowUpRight, LoaderCircle, Sparkles } from "lucide-react"; +import { + ArrowUpRight, + BadgeCheck, + LoaderCircle, + ShieldCheck, + Sparkles, + TriangleAlert, +} from "lucide-react"; import { Link, useParams } from "react-router-dom"; import { InterestModeNotice } from "@/components/interest-mode-notice"; import { SiteHeader } from "@/components/site-header"; @@ -68,6 +75,74 @@ function IdeaMediaItem({ media }: { media: IdeaMedia }) { ); } +function IdeaSecurityCase({ + threatScenario, + controlBoundary, + proofRequired, +}: { + threatScenario: string; + controlBoundary: string; + proofRequired: string; +}) { + const claims = [ + { + label: "Threat scenario", + value: threatScenario, + icon: TriangleAlert, + }, + { + label: "Control boundary", + value: controlBoundary, + icon: ShieldCheck, + }, + { + label: "Proof required", + value: proofRequired, + icon: BadgeCheck, + }, + ]; + + return ( +
+
+
+

+

+

+ Security case: what must be true before this expands +

+
+

+ A proposed control is not a guarantee. This preview names the failure + path, the operating boundary, and the evidence needed to earn a larger + test. +

+
+
+ {claims.map(({ label, value, icon: Icon }) => ( +
+
+
+

{value}

+
+ ))} +
+
+ ); +} + export function IdeaDetailPage() { const { slug = "" } = useParams<{ slug: string }>(); const ideaQuery = useQuery({ @@ -197,6 +272,15 @@ export function IdeaDetailPage() { {ideaQuery.data.description}

+ {ideaQuery.data.threat_scenario && + ideaQuery.data.control_boundary && + ideaQuery.data.proof_required ? ( + + ) : null} { expect(screen.getByText(idea.summary)).toBeInTheDocument(); expect(screen.getAllByText("Technology").length).toBeGreaterThan(0); expect(screen.getByText("Concept preview")).toBeInTheDocument(); + expect(screen.getByText("Security case defined")).toBeInTheDocument(); expect(screen.getByText("1 demo concept")).toBeInTheDocument(); expect(screen.getByText("4 people interested")).toBeInTheDocument(); expect( @@ -113,6 +120,24 @@ describe("IdeaDiscoveryPage", () => { ).toHaveTextContent(/concept previews, not active fundraisers/i); }); + it("does not claim a security case for concepts without all three fields", async () => { + mockedListPublishedIdeas.mockResolvedValue([ + { + ...idea, + threat_scenario: null, + control_boundary: null, + proof_required: null, + }, + ]); + + renderDiscovery(); + + expect( + await screen.findByRole("link", { name: `View ${idea.title}` }), + ).toBeInTheDocument(); + expect(screen.queryByText("Security case defined")).not.toBeInTheDocument(); + }); + it("restores a category filter from the URL and only shows matching concepts", async () => { mockedListPublishedIdeas.mockResolvedValue([idea, healthIdea]); diff --git a/src/features/ideas/idea-discovery-page.tsx b/src/features/ideas/idea-discovery-page.tsx index 9bdc5c0..a94518b 100644 --- a/src/features/ideas/idea-discovery-page.tsx +++ b/src/features/ideas/idea-discovery-page.tsx @@ -5,6 +5,7 @@ import { ArrowUpRight, LoaderCircle, Search, + ShieldCheck, Sparkles, UsersRound, } from "lucide-react"; @@ -322,6 +323,17 @@ export function IdeaDiscoveryPage() { {statusLabels[idea.status]}
+ {idea.threat_scenario && + idea.control_boundary && + idea.proof_required ? ( +

+

+ ) : null}

{idea.title}

diff --git a/src/features/ideas/idea-discovery-service.test.ts b/src/features/ideas/idea-discovery-service.test.ts index 378e73f..352665a 100644 --- a/src/features/ideas/idea-discovery-service.test.ts +++ b/src/features/ideas/idea-discovery-service.test.ts @@ -55,6 +55,12 @@ const detail = { ...summary, description: "A modular desalination system designed for coastal communities.", + threat_scenario: + "Source water or maintenance failures could expose residents to unsafe output.", + control_boundary: + "The pilot stays isolated from drinking-water service and requires independent water-quality review.", + proof_required: + "Advance only after repeated lab results meet the published water-quality and shutdown thresholds.", media: [ { id: "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb", @@ -129,6 +135,12 @@ describe("idea discovery service", () => { expect(detailEq).toHaveBeenCalledWith("slug", summary.slug); expect(detailNeq).toHaveBeenCalledWith("status", "draft"); expect(detail.media.map((item) => item.sort_order)).toEqual([0]); + const detailSelect = select.mock.calls.find(([columns]) => + columns.includes("description"), + )?.[0]; + expect(detailSelect).toContain("threat_scenario"); + expect(detailSelect).toContain("control_boundary"); + expect(detailSelect).toContain("proof_required"); }); it("sorts returned media defensively", async () => { diff --git a/src/features/ideas/idea-discovery-service.ts b/src/features/ideas/idea-discovery-service.ts index b69909a..a98d60c 100644 --- a/src/features/ideas/idea-discovery-service.ts +++ b/src/features/ideas/idea-discovery-service.ts @@ -26,6 +26,9 @@ type PublishedIdeaBase = { slug: string; title: string; summary: string; + threat_scenario: string | null; + control_boundary: string | null; + proof_required: string | null; status: PublishedIdeaStatus; published_at: string | null; created_at: string; @@ -56,6 +59,9 @@ const summaryColumns = ` slug, title, summary, + threat_scenario, + control_boundary, + proof_required, status, published_at, created_at, diff --git a/src/theme-css.test.ts b/src/theme-css.test.ts index 7233b6f..1873ae4 100644 --- a/src/theme-css.test.ts +++ b/src/theme-css.test.ts @@ -11,6 +11,10 @@ const interestPanelSource = readFileSync( resolve(process.cwd(), "src/features/ideas/idea-interest-panel.tsx"), "utf8", ); +const ideaDetailSource = readFileSync( + resolve(process.cwd(), "src/features/ideas/idea-detail-page.tsx"), + "utf8", +); const pilotPageSource = readFileSync( resolve(process.cwd(), "src/features/pilots/pilot-page.tsx"), "utf8", @@ -69,6 +73,9 @@ describe("civic field-notebook design system", () => { ); expect(interestPanelSource).toContain("border-t-[oklch(0.82_0.15_60)]"); expect(interestPanelSource).toContain("dark:border-t-[oklch(0.5_0.18_48)]"); + expect(ideaDetailSource).toContain( + "text-[oklch(0.82_0.15_60)] dark:text-[oklch(0.5_0.18_48)]", + ); }); it("sets the saved or system theme before the application loads", () => { diff --git a/supabase/migrations/20260810160000_add_demo_security_cases.sql b/supabase/migrations/20260810160000_add_demo_security_cases.sql new file mode 100644 index 0000000..02a3b6f --- /dev/null +++ b/supabase/migrations/20260810160000_add_demo_security_cases.sql @@ -0,0 +1,158 @@ +alter table public.ideas +add column threat_scenario text, +add column control_boundary text, +add column proof_required text, +add constraint ideas_threat_scenario_length check ( + threat_scenario is null or char_length(threat_scenario) between 40 and 500 +), +add constraint ideas_control_boundary_length check ( + control_boundary is null or char_length(control_boundary) between 40 and 500 +), +add constraint ideas_proof_required_length check ( + proof_required is null or char_length(proof_required) between 40 and 500 +); + +comment on column public.ideas.threat_scenario is + 'Concrete failure path the concept must address before expansion.'; +comment on column public.ideas.control_boundary is + 'Permission, privacy, safety, or fail-safe boundary for the concept pilot.'; +comment on column public.ideas.proof_required is + 'Measurable evidence required before the concept earns a larger test.'; + +update public.ideas as ideas +set + threat_scenario = security_cases.threat_scenario, + control_boundary = security_cases.control_boundary, + proof_required = security_cases.proof_required, + updated_at = '2026-08-10 16:00:00+00' +from ( + values + ( + 'clean-air-library', + 'A poorly matched or maintained purifier could create false confidence while smoke exposure remains unsafe for residents with respiratory risk.', + 'The trial uses room-matched, non-ozone-generating units, documented filter custody, voluntary indoor readings, and no collection of names or medical histories.', + 'Expand only if checkout works within the drill window, indoor PM2.5 falls by the published target at both sites, and every filter is accounted for.' + ), + ( + 'repair-commons', + 'Unsafe repairs, undocumented parts, or work beyond a mentor''s competence could damage property or expose neighbors to electrical and mechanical hazards.', + 'Owners authorize each repair; mentors stop at mains voltage, batteries, pressure vessels, or structural work unless a qualified professional and approved procedure are present.', + 'Advance only after repair outcomes, repeat failures, tool incidents, and safe-stop referrals are logged across the pilot with no unresolved safety event.' + ), + ( + 'neighbor-ride-credits', + 'An informal dispatch program could expose trip details, strand riders, or create an unlicensed transport service without reliable accessibility.', + 'The pilot shares only minimum scheduling data with a licensed accessible transport partner, collects no fares, and promises no ride until the partner confirms it.', + 'Continue only if the clinic referral flow closes the target share of essential trips, missed pickups stay below the published limit, and riders confirm accessibility.' + ), + ( + 'after-dark-storefronts', + 'Lighting and art intended to increase welcome could create glare, surveillance concerns, electrical hazards, or inaccessible sidewalk conditions.', + 'Installations require owner permission, listed equipment, timed shutoff, no cameras or microphones, an unobstructed accessible path, and an immediate takedown contact.', + 'Expand only if baseline-versus-pilot observations show the stated attention and welcome gains without complaints, accessibility failures, or unresolved electrical issues.' + ), + ( + 'shade-stop-network', + 'Unapproved shade hardware or incomplete measurements could obstruct boarding, sightlines, drainage, or circulation while misrepresenting transit conditions.', + 'Teams collect infrastructure measurements without faces or trip histories; nothing enters the right-of-way without written agency, property, accessibility, and wind-safety approval.', + 'Proceed only after at least 45 complete stop audits, five independently verified priority packets, five acknowledged case numbers, and one permitted corrective design.' + ), + ( + 'skill-swap-saturdays', + 'A short workshop could spread unsafe or inaccurate repair instructions that participants later apply beyond the demonstrated scope.', + 'Each session uses low-risk tasks, a named safety check, mentor stop authority, accessible instruction, and a one-page guide tested only on the covered device or material.', + 'Repeat only after at least twelve participants perform the skill unassisted, the guide reproduces the result, and every error or near miss is incorporated.' + ), + ( + 'civic-accessibility-lab', + 'Field observation or an unauthorized demonstration could put participants in traffic, capture identifying data, or create a new inaccessible route.', + 'Observers stay on lawful public paths, record no faces or plates, never direct traffic, and install nothing without city authorization, traffic control, and accessible detours.', + 'Advance only after 100 usable observations, 40 accessibility audits, affected-user agreement on the top failure, and a city-accepted packet with a response or permitted test.' + ), + ( + 'block-ready-kits', + 'Shared outage equipment may be missing, inaccessible, unsafe to operate, or controlled by people unavailable when power fails.', + 'Named stewards inventory sealed equipment, follow manufacturer limits, avoid backfeeding or improvised wiring, and provide accessible alerts without resident tracking.', + 'Continue only if every item is located, inventoried, and safely deployed within 20 minutes during the drill, with gaps assigned and corrected before a repeat.' + ), + ( + 'device-liberation-lab', + 'Firmware modification can become unauthorized access, disable safety features, expose data, or permanently brick equipment.', + 'Work requires owner authorization and model-specific recovery steps; the clinic refuses stolen, managed, leased, or third-party devices and never offers credential bypass.', + 'Expand only after test devices can be restored, data is backed up, safety functions remain intact, and outcomes document failures as well as successful installs.' + ), + ( + 'file-rescue-cooperative', + 'Recovery work can expose intimate files, overwrite the only copy, or retain customer data after the job ends.', + 'The owner gives written consent and scope; technicians use write blockers where appropriate, minimize previews, encrypt working copies, isolate media, and verify deletion at handoff.', + 'Continue only if recovered files pass agreed checksums or spot checks, every device and working copy is reconciled, and no access or retention exception remains unresolved.' + ), + ( + 'cloud-exit-toolkit', + 'Exports can silently omit files, corrupt metadata, leak credentials, or leave people believing they have a complete backup when they do not.', + 'Tools use least-privilege user tokens, never store account passwords, write to user-controlled storage, redact logs, and make no deletion request against the source service.', + 'Advance only when representative exports reconcile file counts, checksums, folder structure, and metadata against the source, with failures visible and repeatable.' + ), + ( + 'private-ai-workbench', + 'A local model can still leak sensitive documents through telemetry, network calls, prompt logs, insecure indexes, or an overbroad filesystem scope.', + 'Approved folders stay on the device; model, index, and interface run offline by default with encrypted storage, explicit retention controls, and no hidden analytics.', + 'Expand only after network-denial tests, deletion tests, access-boundary checks, and a representative task benchmark all pass on the documented workstation image.' + ), + ( + 'home-lab-defense-clinic', + 'Internet-exposed services, stale firmware, flat networks, weak recovery plans, and undocumented admin access can turn a home lab into a pivot point.', + 'The clinic touches participant-owned systems only under written scope, makes backups before changes, uses no persistence, and leaves owners with an access and rollback record.', + 'Continue only when scoped exposures are rechecked, critical updates or mitigations are verified, segmentation works, and a restore drill succeeds without technician-only secrets.' + ), + ( + 'community-compute-cooperative', + 'Shared accelerators can leak member data, hide abusive workloads, exhaust power, or let one operator control access and billing without accountability.', + 'Jobs run in isolated member workspaces under a published acceptable-use policy, resource quotas, transparent scheduling, minimal logs, and separated operator privileges.', + 'Expand only after isolation, quota, deletion, incident-response, power, and scheduling tests pass under concurrent pilot workloads with auditable aggregate reporting.' + ), + ( + 'offline-mesh-field-kit', + 'An emergency mesh could expose participant locations, relay abuse, interfere with licensed services, or fail exactly when cellular service is unavailable.', + 'Nodes use legal spectrum, opt-in relays, rotating identifiers, minimal message retention, authenticated administration, and no promise of private or life-safety-critical delivery.', + 'Advance only after an offline field drill verifies range, delivery, battery, relay removal, lost-node revocation, and a documented fallback when messages fail.' + ), + ( + 'open-repair-atlas', + 'Incorrect diagnostics, counterfeit parts, leaked manuals, or advice for equipment outside a contributor''s authority can create safety and legal harm.', + 'Entries require lawful provenance, device ownership or service authority, hazard labels, source citations, revision history, and exclusion of leaked or access-bypass material.', + 'Expand only when independent maintainers reproduce a sample of repairs, part matches and safety notes survive review, and disputed guidance can be corrected or withdrawn.' + ), + ( + 'accessible-interface-retrofit-lab', + 'An assistive retrofit can override user intent, create unsafe automation, expose disability information, or leave a person dependent on a fragile prototype.', + 'The participant controls goals and consent; prototypes minimize personal data, preserve manual operation, fail safely, and undergo caregiver or clinician review when the risk requires it.', + 'Continue only after the participant completes agreed tasks across repeated sessions, can disable or reverse the retrofit, and reports no unresolved safety or autonomy failure.' + ), + ( + 'project-time-capsule', + 'An archive may preserve secrets, unauthorized code, unverifiable dependencies, or a build that works only inside the original maintainer''s environment.', + 'Only owner-authorized material enters; secrets and private production data are excluded, licenses and checksums are recorded, and builds run in an isolated clean environment.', + 'Advance only when an independent person rebuilds the project from the capsule, verifies outputs and provenance, and documents every unresolved dependency or permission gap.' + ), + ( + 'waste-heat-works', + 'A heat-recovery retrofit could overheat compute equipment, contaminate occupied space, create pressure or condensation hazards, or hide poor energy economics.', + 'The skid remains isolated from production cooling, uses licensed engineering review, monitored temperatures and leaks, automatic bypass, and a fail-back to the original cooling path.', + 'Expand only after measured useful heat, compute temperatures, pump energy, air or water quality, and fail-back behavior meet published limits through the full pilot.' + ), + ( + 'model-commons-lab', + 'A model comparison can expose sensitive task data, reward benchmark gaming, or imply trustworthiness beyond the specific tests performed.', + 'The rack stays offline, accepts participant-approved task packs, records model and harness versions, separates evaluators from model selection, and publishes known test limits.', + 'Advance only when runs are reproducible across operators, task data is deleted as promised, scoring discrepancies are explained, and no claim exceeds the measured benchmark.' + ), + ( + 'glass-box-sensor-network', + 'Networked plate readers can create a searchable movement history with weak retention, broad sharing, opaque searches, and no evidence that the intrusion improves safety.', + 'The campaign uses public records and lawful observation only, publishes no private plates or operational blind spots, and permits no tampering, trespass, harassment, or evasion guidance.', + 'Proceed only after at least 90% of the known inventory has source-linked contracts, retention and sharing rules, with a hearing request and independent review sponsor documented.' + ) +) as security_cases(slug, threat_scenario, control_boundary, proof_required) +where ideas.slug = security_cases.slug + and ideas.creator_id = '00000000-0000-4000-8000-000000000101'; diff --git a/supabase/tests/database/idea-security-cases.test.sql b/supabase/tests/database/idea-security-cases.test.sql new file mode 100644 index 0000000..334b01a --- /dev/null +++ b/supabase/tests/database/idea-security-cases.test.sql @@ -0,0 +1,142 @@ +begin; + +select plan(14); + +select has_column( + 'public', + 'ideas', + 'threat_scenario', + 'ideas expose an explicit threat scenario' +); +select has_column( + 'public', + 'ideas', + 'control_boundary', + 'ideas expose an explicit control boundary' +); +select has_column( + 'public', + 'ideas', + 'proof_required', + 'ideas expose the proof required before expansion' +); + +select col_type_is( + 'public', + 'ideas', + 'threat_scenario', + 'text', + 'threat scenarios are stored as text' +); +select col_type_is( + 'public', + 'ideas', + 'control_boundary', + 'text', + 'control boundaries are stored as text' +); +select col_type_is( + 'public', + 'ideas', + 'proof_required', + 'text', + 'proof requirements are stored as text' +); + +select ok( + exists ( + select 1 + from pg_catalog.pg_constraint + where conname = 'ideas_threat_scenario_length' + and conrelid = 'public.ideas'::regclass + ), + 'threat scenarios have a database-enforced length boundary' +); +select ok( + exists ( + select 1 + from pg_catalog.pg_constraint + where conname = 'ideas_control_boundary_length' + and conrelid = 'public.ideas'::regclass + ), + 'control boundaries have a database-enforced length boundary' +); +select ok( + exists ( + select 1 + from pg_catalog.pg_constraint + where conname = 'ideas_proof_required_length' + and conrelid = 'public.ideas'::regclass + ), + 'proof requirements have a database-enforced length boundary' +); + +select is( + ( + select count(*) + from public.ideas + where creator_id = '00000000-0000-4000-8000-000000000101' + and status = 'published' + and threat_scenario is not null + and control_boundary is not null + and proof_required is not null + and char_length(threat_scenario) between 40 and 500 + and char_length(control_boundary) between 40 and 500 + and char_length(proof_required) between 40 and 500 + and threat_scenario not like '%' || chr(10) || '%' + and control_boundary not like '%' || chr(10) || '%' + and proof_required not like '%' || chr(10) || '%' + ), + 21::bigint, + 'all 21 demo concepts carry concise, complete security cases' +); + +select is( + ( + select count(distinct threat_scenario) + from public.ideas + where creator_id = '00000000-0000-4000-8000-000000000101' + and status = 'published' + ), + 21::bigint, + 'every demo has a concept-specific threat scenario' +); +select is( + ( + select count(distinct control_boundary) + from public.ideas + where creator_id = '00000000-0000-4000-8000-000000000101' + and status = 'published' + ), + 21::bigint, + 'every demo has a concept-specific control boundary' +); +select is( + ( + select count(distinct proof_required) + from public.ideas + where creator_id = '00000000-0000-4000-8000-000000000101' + and status = 'published' + ), + 21::bigint, + 'every demo has concept-specific proof requirements' +); + +set local role anon; +select is( + ( + select count(*) + from public.ideas + where creator_id = '00000000-0000-4000-8000-000000000101' + and status = 'published' + and threat_scenario is not null + and control_boundary is not null + and proof_required is not null + ), + 21::bigint, + 'anonymous visitors can inspect every demo security case' +); +reset role; + +select * from finish(); +rollback; diff --git a/supabase/tests/database/ideas.test.sql b/supabase/tests/database/ideas.test.sql index 5f6d000..42554be 100644 --- a/supabase/tests/database/ideas.test.sql +++ b/supabase/tests/database/ideas.test.sql @@ -39,7 +39,10 @@ select columns_are( 'status', 'published_at', 'created_at', - 'updated_at' + 'updated_at', + 'threat_scenario', + 'control_boundary', + 'proof_required' ], 'ideas exposes the expected columns' ); From 456fe144260f4eef08419c6044553e51deef8f78 Mon Sep 17 00:00:00 2001 From: John Paul Wile Date: Mon, 10 Aug 2026 17:25:26 -0600 Subject: [PATCH 2/2] [verified] feat: enforce monochrome orange palette --- PLAN.md | 1 + README.md | 3 +- docs/PROJECT_STATUS.md | 3 +- index.html | 4 +- src/App.tsx | 30 +- src/components/ui/button.tsx | 2 +- src/features/auth/auth-callback-page.tsx | 2 +- src/features/auth/auth-page.tsx | 4 +- src/features/ideas/idea-detail-page.tsx | 22 +- src/features/ideas/idea-discovery-page.tsx | 8 +- src/features/ideas/idea-editor-page.tsx | 13 +- src/features/ideas/idea-interest-panel.tsx | 33 +- src/features/ideas/idea-validation-panel.tsx | 2 +- src/features/pilots/pilot-page.tsx | 7 +- src/features/theme/theme-provider.test.tsx | 2 +- src/features/theme/theme-provider.tsx | 2 +- src/index.css | 144 +++---- src/theme-css.test.ts | 399 +++++++++++++++++-- 18 files changed, 520 insertions(+), 161 deletions(-) diff --git a/PLAN.md b/PLAN.md index 2b50130..488cf6f 100644 --- a/PLAN.md +++ b/PLAN.md @@ -35,6 +35,7 @@ Ideascape is in **Exploration mode**. Published entries are concept previews, no - [x] Replace generic previews with issue-driven Colorado Springs civic campaigns - [x] Give every demo a threat scenario, control boundary, and proof requirement consistent with the security claim policy - [x] Add persistent light and dark themes +- [x] Restrict the rendered palette to black, white, neutral grays, and bright orange ## Phase 3 — Validation evidence and campaign design diff --git a/README.md b/README.md index 825780e..4076131 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,8 @@ Ideascape is an exploration-mode concept-validation platform for learning which - Email/password, Google, and GitHub OAuth-capable authentication with safe post-auth return paths - Public member profiles plus owner-only profile updates - Private creator drafts and editing at `/ideas/new` and `/ideas/:id/edit` -- Persistent, system-aware light and dark themes using the warm orange visual system +- Persistent, system-aware light and dark themes restricted to black, white, + neutral grays, and `#ff5a1f` bright orange - Route-level code splitting for non-home pages - Supabase row-level security and pgTAP coverage for profiles, ideas, media, categories, and interest signals diff --git a/docs/PROJECT_STATUS.md b/docs/PROJECT_STATUS.md index ba7d8b7..94ee749 100644 --- a/docs/PROJECT_STATUS.md +++ b/docs/PROJECT_STATUS.md @@ -28,7 +28,8 @@ Ideascape is in **Exploration mode**. The twenty-one published entries are conce ## Delivered - React/TypeScript/Vite application foundation, CI, and route-level code splitting -- Responsive orange visual system with persistent system-aware light and dark themes +- Responsive light/dark visual system restricted to black, white, neutral grays, + and `#ff5a1f` bright orange - Supabase authentication with email, Google, and GitHub-capable OAuth flows - Safe internal return paths across email authentication, OAuth, and callback failures - Public profiles with owner-only updates diff --git a/index.html b/index.html index 4ba3bb4..4161a8c 100644 --- a/index.html +++ b/index.html @@ -7,7 +7,7 @@ name="description" content="Explore IdeaScape concept previews and signal which ideas you would like to see happen. No payment or commitment." /> - + diff --git a/src/App.tsx b/src/App.tsx index 56397ad..14d0f52 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -265,11 +265,11 @@ function HomePage() {
Field sample / 021 - Permission checked + Permission checked
Owner-controlled devices, an open padlock, and a terminal share an isolated repair bench
@@ -282,7 +282,7 @@ function HomePage() {
A read-only recovery station transfers files from damaged storage into an encrypted folder
@@ -352,10 +352,10 @@ function HomePage() {
-
+
-
+
@@ -365,17 +365,17 @@ function HomePage() { > Planned smart-contract funding -

+

If a concept earns a campaign phase, the goal is to move from social proof to explicit, inspectable funding terms. No funds are accepted today.