Release Readiness intelligence platform — foundation (Phases 0–2)#51
Release Readiness intelligence platform — foundation (Phases 0–2)#51ChrisAdamsdevelopment wants to merge 7 commits into
Conversation
Foundation for the pivot from metadata utility to a Release Readiness Intelligence Platform. All new behavior is gated behind feature flags (off by default), so production is unchanged until flags are enabled. Phase 0 — feature-flag + branch scaffolding: - server/featureFlags.js, src/utils/featureFlags.ts (flags off by default, unknown names ignored), GET /api/features, FEATURES/VITE_FEATURES env. Phase 1 — intelligence framework (server/readiness/): - findings schema, scoring engine, verdict engine (with honesty cap), safe declarative compliance rule evaluator (no eval), report orchestrator, flag-gated provider registry. - Storage (releases + release_reports) and flag-gated API: POST /api/releases, GET /api/releases, GET /api/releases/:id, POST /api/releases/:id/check. - Gated "Release readiness" dashboard view in the app (verdict-led, top issues, per-finding what/why/impact/fix/est-time/score-gain, expandable education, visible "not assessed"). Specs/docs: release-readiness-spec.md, release-risk-and-packaging.md (risk/readiness/scoring definitions; packaging deferred, Stripe frozen), feature-flags-and-migration.md. Tests: 35 passing; store test runs on CI (Node 20) where the native better-sqlite3 binding is available. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
First real CheckProvider feeding the Release Readiness framework. Findings are authored as risk (businessImpact, why, how-to-fix, estimated fix time, score impact) rather than technical observations. - server/readiness/providers/metadata.js: validateMetadata() + metadataProvider (category metadata, flag metadata_validation) — required fields, placeholder/ featured-in-title/formatting/whitespace/control-char checks, copyright + rights, discoverability, and AI-marker residue reused from existing file analysis. - server/readiness/registerProviders.js: registers built-in providers; required once in server.js so getEnabledProviders() picks them up at runtime. With metadata_validation + release_readiness enabled, the dashboard now produces a real scored verdict instead of an all-"not assessed" report. Tests: 42 passing (store test CI/Node 20 only). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Reviewer's GuideAdds a flag-gated Release Readiness intelligence framework (findings/scoring/verdict/report engines, provider registry, storage, and API), a metadata validation provider, and a new SPA dashboard view, all behind feature flags that default off so existing cleanse/auth/billing behavior is unchanged. Sequence diagram for Release Readiness check flowsequenceDiagram
actor User
participant ReleaseReadiness as ReleaseReadiness_component
participant ReadinessClient as readinessClient
participant API as API_Server
participant Store as readinessStore
participant Providers as providers
participant Metadata as metadataProvider
participant Report as generateReport
User->>ReleaseReadiness: click Run readiness check
ReleaseReadiness->>ReadinessClient: runReadinessCheck(apiBaseUrl, authToken, input)
ReadinessClient->>ReadinessClient: createRelease(apiBaseUrl, token, input)
ReadinessClient->>API: POST /api/releases
API->>Store: createRelease(userId, releaseInput)
Store-->>API: release
API-->>ReadinessClient: { release }
ReadinessClient->>ReadinessClient: checkRelease(apiBaseUrl, token, releaseId)
ReadinessClient->>API: POST /api/releases/:id/check
API->>Store: getRelease(userId, releaseId)
Store-->>API: release
API->>Providers: getEnabledProviders()
API->>Report: generateReport({ releaseId, context, providers, ruleRegistryVersion })
loop for each enabled provider
Report->>Metadata: evaluate(context)
Metadata-->>Report: findings
end
Report-->>API: report
API->>Store: saveReport(releaseId, report)
API-->>ReadinessClient: { report }
ReadinessClient-->>ReleaseReadiness: ReadinessReport
ReleaseReadiness-->>User: display verdict and prioritized fixes
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Hey - I've found 4 issues
Prompt for AI Agents
Please address the comments from this code review:
## Individual Comments
### Comment 1
<location path="server/readiness/ruleRegistry.js" line_range="36-45" />
<code_context>
+ if (Array.isArray(condition.all)) return condition.all.every((c) => evaluateCondition(c, context));
+ if (Array.isArray(condition.any)) return condition.any.some((c) => evaluateCondition(c, context));
+ if (condition.not) return !evaluateCondition(condition.not, context);
+ if (typeof condition.field === 'string' && typeof condition.op === 'string') {
+ const op = OPS[condition.op];
+ if (!op) return false;
+ return Boolean(op(getField(context, condition.field), condition.value));
+ }
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Consider failing fast or logging when a rule uses an unknown operator instead of silently returning false.
Right now, an unknown `condition.op` just returns `false`, effectively disabling the rule (e.g. `
```suggestion
function evaluateCondition(condition, context) {
if (!condition || typeof condition !== 'object') return false;
if (Array.isArray(condition.all)) return condition.all.every((c) => evaluateCondition(c, context));
if (Array.isArray(condition.any)) return condition.any.some((c) => evaluateCondition(c, context));
if (condition.not) return !evaluateCondition(condition.not, context);
if (typeof condition.field === 'string' && typeof condition.op === 'string') {
const op = OPS[condition.op];
if (!op) {
// Fail fast-ish: log unknown operator usage so misconfigured rules are discoverable.
// We keep returning false to preserve existing behaviour while adding observability.
// eslint-disable-next-line no-console
console.warn(
`[readiness] Unknown operator "${condition.op}" in condition for field "${condition.field}". ` +
'This rule will evaluate to false.'
);
return false;
}
return Boolean(op(getField(context, condition.field), condition.value));
}
```
</issue_to_address>
### Comment 2
<location path="tests/feature-flags.test.ts" line_range="4-9" />
<code_context>
+import { describe, expect, it } from 'vitest';
+const { parseFeatures, getEnabledFeatures, isFeatureEnabled, KNOWN_FEATURES } = require('../server/featureFlags');
+
+describe('feature flag parsing', () => {
+ it('returns no features for empty / undefined input (production default is off)', () => {
+ expect(parseFeatures('')).toEqual([]);
+ expect(parseFeatures(undefined)).toEqual([]);
+ expect(parseFeatures(null)).toEqual([]);
+ });
+
+ it('parses known features and is case/whitespace insensitive', () => {
</code_context>
<issue_to_address>
**suggestion (testing):** Server-side feature-flag tests are thorough; consider mirroring coverage on the frontend helper
The server tests validate parsing, normalization, unknown-name handling, de-duplication, and env injection, but `src/utils/featureFlags.ts` adds client-side logic (`parseFeatures`, `getBuildTimeFeatures`, `isFeatureEnabled`, `fetchEnabledFeatures`) that currently lacks coverage. Please add a small Vitest suite to verify:
- `parseFeatures` matches server behavior
- `fetchEnabledFeatures` returns `[]` on non-200 responses and network errors
- `getBuildTimeFeatures` respects `import.meta.env.VITE_FEATURES`
This will help keep server and client feature-flag behavior aligned and avoid regressions.
Suggested implementation:
```typescript
import { describe, expect, it, vi } from 'vitest';
// Server-side feature flags (existing tests)
const {
parseFeatures,
getEnabledFeatures,
isFeatureEnabled,
KNOWN_FEATURES,
} = require('../server/featureFlags');
// Client-side feature flags (new tests)
import * as clientFeatureFlags from '../src/utils/featureFlags';
```
To fully implement the requested coverage, please append a new `describe` block near the end of `tests/feature-flags.test.ts` (outside the server-side `describe`) with tests similar to:
```ts
describe('client feature flags', () => {
describe('parseFeatures (client)', () => {
it('matches server parseFeatures behavior for known features', () => {
expect(clientFeatureFlags.parseFeatures(' Chain_Of_Custody , release_readiness '))
.toEqual(['chain_of_custody', 'release_readiness']);
});
it('returns [] for empty / undefined / null input', () => {
expect(clientFeatureFlags.parseFeatures('')).toEqual([]);
// @ts-expect-error intentional undefined
expect(clientFeatureFlags.parseFeatures(undefined)).toEqual([]);
// @ts-expect-error intentional null
expect(clientFeatureFlags.parseFeatures(null)).toEqual([]);
});
it('ignores unknown feature names', () => {
expect(
clientFeatureFlags.parseFeatures('chain_of_custody,not_a_real_flag,DROP TABLE'),
).toEqual(['chain_of_custody']);
});
});
describe('getBuildTimeFeatures', () => {
const originalEnv = { ...(import.meta as any).env };
afterEach(() => {
(import.meta as any).env = { ...originalEnv };
});
it('returns [] when VITE_FEATURES is not set', () => {
(import.meta as any).env = { ...originalEnv };
delete (import.meta as any).env.VITE_FEATURES;
expect(clientFeatureFlags.getBuildTimeFeatures()).toEqual([]);
});
it('parses VITE_FEATURES using the same rules as parseFeatures', () => {
(import.meta as any).env = {
...originalEnv,
VITE_FEATURES: ' Chain_Of_Custody , release_readiness ',
};
expect(clientFeatureFlags.getBuildTimeFeatures()).toEqual([
'chain_of_custody',
'release_readiness',
]);
});
});
describe('fetchEnabledFeatures', () => {
const originalFetch = global.fetch;
afterEach(() => {
global.fetch = originalFetch;
vi.restoreAllMocks();
});
it('returns [] when the response status is not 200', async () => {
global.fetch = vi.fn().mockResolvedValue({
ok: false,
status: 500,
json: vi.fn(),
});
const features = await clientFeatureFlags.fetchEnabledFeatures();
expect(features).toEqual([]);
});
it('returns [] on network errors', async () => {
global.fetch = vi.fn().mockRejectedValue(new Error('network error'));
const features = await clientFeatureFlags.fetchEnabledFeatures();
expect(features).toEqual([]);
});
it('returns parsed feature list on 200 responses', async () => {
global.fetch = vi.fn().mockResolvedValue({
ok: true,
status: 200,
json: vi.fn().mockResolvedValue({ features: ['chain_of_custody'] }),
});
const features = await clientFeatureFlags.fetchEnabledFeatures();
expect(features).toEqual(['chain_of_custody']);
});
});
});
```
These tests assume:
- `parseFeatures` in `src/utils/featureFlags.ts` mirrors the server behavior (normalization, unknown-name filtering).
- `getBuildTimeFeatures` reads `import.meta.env.VITE_FEATURES` and delegates to `parseFeatures`.
- `fetchEnabledFeatures` uses `fetch`, returns `[]` on non-OK statuses or errors, and returns a feature list (or `[]`) on success.
Adjust the exact expectations (response shape, env var name, etc.) if your current implementation differs.
</issue_to_address>
### Comment 3
<location path="spectracleanse-engineering/docs/release-risk-and-packaging.md" line_range="73" />
<code_context>
+
+The tiering below is a working hypothesis to guide entitlement architecture, **not a committed packaging decision.** Final names, bundles, and tiers are deferred to the Packaging & Monetization Report (§4a) after Phase 2.
+
+Value grouped by *which risks we help you retire*, with clear personas and upgrade triggers. (Pricing intentionally omitted.)
+
+| Tier | Persona | Value: risks addressed | Representative features |
</code_context>
<issue_to_address>
**suggestion (typo):** Consider adding a verb so this sentence reads more clearly (e.g., "Value is grouped by …").
The clause “Value grouped by *which risks we help you retire*” is grammatically a fragment. Consider “Value is grouped by *which risks we help you retire*” (or similar) to make it a complete sentence while preserving the meaning.
```suggestion
Value is grouped by *which risks we help you retire*, with clear personas and upgrade triggers. (Pricing intentionally omitted.)
```
</issue_to_address>
### Comment 4
<location path="spectracleanse-engineering/docs/release-risk-and-packaging.md" line_range="77" />
<code_context>
+
+| Tier | Persona | Value: risks addressed | Representative features |
+|---|---|---|---|
+| **Free — Scan** | Curious / first-time creator | See *that* risk exists | Basic release scan, verdict + top issues, limited scans/month, no export, metadata cleanse (existing) |
+| **Creator** | Active independent / AI-assisted creator | Retire metadata + disclosure risk | Full readiness reports, metadata validation, AI disclosure guidance, unlimited scans, export, batch-lite |
+| **Pro** (today's "Studio") | Serious / frequent releaser | Retire rights + platform risk | Everything in Creator + rights validation, platform-compliance packs, advanced/exportable reports, full batch |
</code_context>
<issue_to_address>
**suggestion (typo):** Slightly adjust the Free tier description to read more naturally (e.g., "See that a risk exists").
The current phrasing is slightly ungrammatical. Updating it to “See *that a* risk exists” would make the description read more smoothly while preserving the meaning.
```suggestion
| **Free — Scan** | Curious / first-time creator | See *that a* risk exists | Basic release scan, verdict + top issues, limited scans/month, no export, metadata cleanse (existing) |
```
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 7f8b06d858
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…oundation # Conflicts: # .env.example # app.tsx # server.js
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 2ea176de2e
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
CI: the npm audit gate failed on high/critical CVEs in build/test tooling (vite, vitest, esbuild) that never execute in the production runtime, plus real runtime highs (multer, nodemailer). - Reclassify build/test/frontend-only packages as devDependencies (verified the runtime server requires none of them; dist/ is prebuilt in the Docker builder stage). Also slims the production image. - Scope the audit gate to production deps: `npm audit --omit=dev --audit-level=high`. - Patch runtime highs: multer ^2.2.0 (non-breaking), nodemailer ^9.0.1. Review feedback: - ruleRegistry: warn on unknown rule operators instead of silently disabling. - readinessClient: throw ReadinessApiError carrying HTTP status so the dashboard logs out on 401 (expired token), matching the cleanse flow. - ReleaseReadiness: pass file analysis into the check so AI-marker / parse findings fire; key 401 handling off status. - Add client-side feature-flag tests (parse/build-time/fetch). - Doc grammar fixes in release-risk-and-packaging.md. Tests: 65 passing, 2 skipped (sqlite store test, CI/Node 20 only). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The whitespace check compared the already-trimmed value against its own trimmed form, so leading/trailing spaces were never caught. Compare the raw field value vs. trimmed (and check doubled spaces / control chars on raw), so titles/artists/copyright like ' My Song ' now correctly warn. Adds a regression test. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a3e4efe6e9
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
|
||
| app.post('/api/releases', requireAuth, requireFeature('release_readiness'), (req, res) => { | ||
| const { title, platform, metadata, analysis, targets } = req.body || {}; | ||
| const context = { metadata: metadata || {}, analysis: analysis || null, targets: targets || null }; |
There was a problem hiding this comment.
Bound persisted release context size
When release_readiness is enabled, this copies arbitrary metadata, analysis, and targets from the global 10 MB JSON body into context_json; running a check then persists a report that includes the same context as inputsSnapshot. Because these new write routes have no plan/usage cap, an authenticated user can repeatedly POST large targets/analysis payloads and grow the SQLite database until the disk fills, even without uploading files. Whitelist the expected scalar fields or add a much smaller per-release size/retention limit before persisting.
Useful? React with 👍 / 👎.
| # the production runtime. Build/test tooling (vite, vitest, esbuild, etc.) | ||
| # lives in devDependencies and never executes in the deployed server, so | ||
| # it is excluded here to keep the gate focused on real production risk. | ||
| run: npm audit --omit=dev --audit-level=high |
There was a problem hiding this comment.
Audit browser-shipped dependencies before deploy
This CI gate now runs npm audit --omit=dev, and I checked npm audit --help where --omit <dev|optional|peer> is the option that excludes dev dependencies. In this same change, browser-executed packages such as browser-id3-writer and music-metadata are under devDependencies but are imported by src/utils/metadata.js and bundled into the production SPA, so high/critical CVEs in code shipped to users' browsers no longer block the CI workflow that cd.yml waits on before deployment. Keep a separate frontend audit or don't omit those bundled runtime packages from the blocking audit.
Useful? React with 👍 / 👎.
…ach) Resolves conflicts in package.json / package-lock.json after PR #54 (root audit fix) merged to main. main is canonical: keep its dependency upgrades (vite 7, vitest 4, @vitejs/plugin-react 5, multer 2.2, nodemailer 9) and its full `npm audit --audit-level=high` gate. Deliberately drops this branch's earlier divergent audit approach (--omit=dev + dependency reclassification) so the merge does NOT revert #54's hardening or weaken the CI gate. All release-readiness feature code on this branch is kept. Verified: node --check server.js OK, tsc --noEmit OK, vitest 66 passed / 2 skipped (sqlite store test, CI-only), npm audit --audit-level=high exit 0.
What this is
The foundation for SpectraCleanse's evolution from a metadata utility into a Release Readiness Intelligence Platform — the system a creator runs before distribution to answer: "Can I safely release this today, and if not, exactly what should I fix first?"
Everything here is additive and gated behind feature flags that are off by default, so production behavior is unchanged until flags are enabled.
main(the live app: cleanse, billing, auth) is untouched.What changed
Phase 0 — feature flags + safe migration
server/featureFlags.js+src/utils/featureFlags.ts(off by default, unknown names ignored),GET /api/features,FEATURES/VITE_FEATURESenv.Phase 1 — intelligence framework (
server/readiness/)eval), report orchestrator, and a flag-gated provider registry that modules plug into.releases+release_reports, additive tables) and flag-gated API:POST /api/releases,GET /api/releases,GET /api/releases/:id,POST /api/releases/:id/check.Phase 2 — metadata validation provider
server/readiness/providers/metadata.js+registerProviders.js, gated bymetadata_validation. Findings are authored as risk (business impact, plain-English why, concrete fix), not technical nitpicks. With both flags on, the dashboard produces a real scored verdict.Specs / docs
release-readiness-spec.md(findings schema, scoring, verdict, report, rule registry — founder-approved decisions),release-risk-and-packaging.md(risk/readiness/scoring definitions; packaging deferred, Stripe frozen),feature-flags-and-migration.md.Why
Platforms are shifting from "hide AI" toward disclosure, rights verification, and release compliance. The durable, defensible product helps creators pass release checks — reframing the existing engine (which already does marker detection, hashing, verification) around trust and readiness, under the same sovereignty ethos (the creator gets the knowledge and control).
Reviewer notes
FEATURESset ⇒ identical to today. Auth/billing/cleanse are not gated on any flag.better-sqlite3is built for the Node 20 runtime and can't load under a newer local Node ABI; they run on CI (Node 20). For the same reason the server wasn't booted locally (onlynode --check+tsc --noEmit, both clean).release-risk-and-packaging.md§4a). No Stripe IDs, subscribers, or plan names are touched.release-readiness-spec.md→server/readiness/(findings → scoring → verdict → report) →providers/metadata.js→ the dashboard insrc/components/ReleaseReadiness.tsx.🤖 Generated with Claude Code
Summary by Sourcery
Introduce a flag-gated Release Readiness intelligence framework, including backend storage, APIs, scoring and verdict engines, and a new dashboard UI, all disabled by default to keep existing production behavior unchanged.
New Features:
Enhancements:
Tests: