Changelog
What changed, and when.
Every release, in the words of the engineers who shipped it. The same file ships inside the product.
All notable changes to the Needset platform. Dates are release dates.
2026-09-30
Edge hardening
- Unauthenticated signup, sign-in link and lead posts are throttled per address (10/min, burst 20, configurable); repeated bad credentials lock an address out (20 failures in 10 minutes, 15-minute lockout, logged as
auth.lockout); public reads go through the per-address limiter;/readyzis cached for 3 seconds. - The database thread has a bounded queue (
NEEDSET_MAX_PENDING, default 64): beyond it requests get 503busywith Retry-After instead of queueing behind one abusive client. Long-running operations (compile, quote, deliver, scan, optimize, verify, activate, retire, download) are capped per workspace (NEEDSET_HEAVY_IN_FLIGHT, default 2). Downloads stream on a separate I/O pool; byte-range reads of unoptimized sources are single ranged reads, never a spool of the whole object. - Sign-in links: a duplicate signup answers exactly like a new one and sends the existing user a sign-in link; each address receives at most 5 sign-in mails per 15 minutes whoever asked; unknown addresses take as long as a send;
NEEDSET_PUBLIC_URLis required in production. Multipart text fields capped (16 fields, 1 MB each). - Tenant-supplied URLs (webhooks, storage endpoints) must resolve to public addresses; redirects are not followed;
allow_insecurewebhooks are refused in production (needset/netsafety.py). - Gateway: body cap 16 MB on every route but upload; unexpected methods 405; HSTS preload, COOP/CORP, Permissions-Policy; the site's CSP has no inline scripts (
script-src 'self'); the.htmlredirect is absolute on our host. Local 8443 listener bound to loopback. Build context excludes secrets,.env, deploy files and scripts.bin/harden-droplet.sh: firewall, key-only SSH, fail2ban, unattended security upgrades, off-box backup copy.GO_LIVE.mdcovers Cloudflare in front of both hostnames. - Marketing site rebuilt: plain-language home page with a plan receipt and portal screenshots, outcome-first product page, engineer-facing technology page, public changelog, downloadable SDK. process-wide counters.
The joint plan (schema needset.plan.v4)
The compiler now decides selection and execution together by minimising one objective,
J = a*stored_bytes + b*moved_bytes + g*decode_ms + d*stall_ms + e*energy_j + l*quality_risk, at weights
the request sets (cost_model; defaults reproduce the previous selection exactly). Spec: JOINT_PLAN_V4.md.
- Joint execution placement. Every selected candidate carries an
executiondecision inside the signed body:decode_at(consumer or service, chosen by cost or pinned),route(transfer, partial_reuse, reuse),codec,tier,target_memory, and the moved bytes, decode, stall, energy and quality-risk terms it costs. Chunks the consumer already holds (delivery ledger) or the plan already moves cost nothing to move again.metrics.objectivetotals the terms.executionrequest section:consumer,decode_at,target_memory,stall_ms_per_mib,decode_stall_fraction,cold_first_byte_ms,service_decode_factor, energy rates. - Compile context in the plan.
contextrecords the consumer, the held chunks and validations that are in the pool, each source's tier and the calibration used, soverify_planrecompiles from the plan body alone.NeedsetPolicy.from_bodyrebuilds a policy from a stored plan for offline verifiers. - Fidelity contracts and dual identity. Candidates have
representation(exact|semantic), and semantic ones namesubstitutes_for(the exact content hash) with asubstitution_error. The request'sfidelity(exactdefault,bounded_errorwithmax_error,task_validatedwith optionaltask) decides eligibility; a plan never selects both identities of one content (substituted). New exclusion reasons:fidelity_contract,fidelity_error_bound,fidelity_unvalidated,substituted.POST /v1/validations(approver) records a task validation for a semantic candidate;:revokewithdraws it;GET /v1/validations. - Request compiler: ranges and records.
POST /v1/data:delivertakesrequestsof asset ids or{asset_id, ranges: [[start, end]], records: [ordinal]}and returns the fewest chunks that reconstruct exactly that (needset.delivery.v2, withbytes_requested, per-assetchunks_in_asset,requestsspans). Records resolve throughPOST /v1/data/assets/{id}:index-records(non-empty newline records, 16 bytes per record, keyed by source digest).Range: bytes=a-bon:downloadreads only the covering chunks (206). At most 100,000 spans per delivery; inverted, empty or out-of-range spans are refused. - Chunk transport honours the decision.
GET /v1/data/chunks/{sha256}?encoding=storedreturns stored bytes withX-Needset-Codecfor consumers that decode themselves; the default decodes and verifies on the service. - Dependency-aware schedule and prefetch. Candidates declare
depends_on; the plan carriesschedule(dependencies first, otherwise selection order; cycles are refused;metrics.unmet_dependenciesnames in-pool dependencies the budget excluded). Delivery by plan lists chunks in schedule order. The reader (needset.torch) follows the schedule, prefetches within a bounded window across asset boundaries, decodes where the plan says, and reports the run when it completes. - Telemetry loop. `POST /v1/runs:report {consumer, run_id, plan_id, bytes_moved, decode_ms, stall_ms, energy_j, quality, samples, marginal_quality_gain, run_cost_usd, minimum_gain_per_usd}` stores one report (idempotent on run_id), returns the calibration the next plan for that consumer will use (median observed over declared decode time, median stall per MiB moved) and, with gain and cost, a
continue|stopverdict. A report by the plan's own consumer under a task-validated plan whose quality is below the plan'sminimum_qualityrevokes that plan's substitutions (validation.revokedwebhook).GET /v1/runs. - Closed-loop re-representation.
optimizetakescodec(auto|identity|zlib-6);GET /v1/data/rerepresentationnames assets whose active codec should change from selection frequency and measured decode density. The new representation goes through verify, approve, activate; rollback reverses it. - Asset kinds.
kindon ingest, upload and register (dataset,checkpoint,index,embedding,other); the storage report groups by kind. - Isolation hardening. Asset ids are validated (
[A-Za-z0-9][A-Za-z0-9._/-]{0,255}, no.., no leading slash); hosted object keys must live under the tenant's prefix; hosted prefix scans see only the tenant's prefix and name assets relative to it. - SDK:
plans.validate,plans.revoke_validations,plans.validations,plans.report_run,plans.runs,data.index_records,data.rerepresentation_advice,data.optimize(codec),data.deliver(requests=),data.open(byte_range=),data.chunk(encoded=). Portal: objective, consumer, fidelity and per-asset execution in the explain view; "Moves" column on plans. Runtime accepts v3 and v4 plans. - Not built (roadmap, stated as such on the site): token-span requests, storage-side and DPU decode.
Evaluation-set fencing
- Candidates and data assets carry a role (
training|evaluation). Evaluation candidates, and any candidate whose source or lineage is an evaluation asset, are refused by the compiler with reasonevaluation_fencedand listed underfenced_evaluationin the signed plan body. Verification recompiles, so a plan that drops the fence fails. Savings proofs restate the fenced list. The role is its own column (added by an additive migration), so no other write can drop it. Changing a role is an approver action and is audited with the approver.POST /v1/data/assets/{id}:role,Needset.data.set_role, portal toggle on Data assets.
Cross-run reuse
- Per-tenant delivery ledger:
POST /v1/data:deliver {consumer, asset_ids | plan_id, dry_run}returns the chunks a consumer (job, cluster, region) has not already received and records the delivery. Consumers fetch exactly those chunks throughGET /v1/data/chunks/{sha256}(served from a per-tenant chunk index) and read chunk order fromGET /v1/data/assets/{id}:representation; repeated deliveries transfer nothing. Whole (unoptimized) objects are ledgered by source digest. Every delivery writes adelivery_eventsrow with bytes transferred and bytes skipped; savings proofs sum those rows for their window. Consumer names are limited to[A-Za-z0-9._-].GET /v1/data/reuse,DELETE /v1/data/reuse/{consumer},needset_reuse_bytes_heldin Prometheus (label values escaped). Portal: Reuse panel and Deliver on plans.
Demand-driven retention
GET /v1/data/retention?window_days=names assets no plan selected, nothing delivered and nothing changed within the window, with the evidence for every asset kept. Evaluation assets are never candidates. Retirement stays behind the approver-gated retire flow. Portal: Retention panel.
Decode-aware placement
GET /v1/plans/{id}:placementtiers a plan's assets hot/warm/cold from selection frequency and the representation's measured decode cost (verify now recordsdecode_ms).POST /v1/data/placement:apply(approver) sets S3 storage classes by in-place copy, limited to STANDARD and STANDARD_IA (archive classes are not reversible in place); objects over 5 GiB are skipped with a note; copy errors returned inside a 200 body are detected. Unsupported stores report so. Portal: Placement.
Adoption
needset.torch:plan_records()(framework-free) andNeedsetPlanDataset(torch IterableDataset, sharded across workers) assemble a plan's assets from chunks through a local, digest-verified chunk cache and record the delivery only when every shard of an epoch has finished.POST /v1/plans:quote: the same deterministic plancompilewould store (shared policy path), plus a cost quote, stored nowhere, readable by viewers.Needset.plans.quote,Needset.data.open,.chunk,.representation.- Test fixtures run the whole suite on SQLite or, with
NEEDSET_TEST_DATABASE_URL, on a live PostgreSQL (testing.py); both are green.
2026-09-29
Self-serve (signup, billing, storage connections)
- Workspaces can be created from the portal (
/signup): company, name, work email. Free plan is active instantly; Enterprise requests are created pending and approved by a platform admin. - Passwordless sign-in with one-time email links (15 minutes, single use) and HttpOnly session cookies; cookie-authenticated writes require the portal header (CSRF). API keys and OIDC unchanged.
- Plans and quotas enforced in the service (
needset/accounts.py): bytes under management after deduplication, seats, keys, schedules, webhooks. Over quota returns HTTP 402quota_exceeded. - Stripe billing without the SDK (
needset/billing.py): one-time Checkout for Assessment and Pilot, metered subscription for Platform (daily meter events), Customer Portal, signed webhooks with idempotent processing.bin/stripe-setup.pycreates the catalog. - Customer-owned storage: a tenant connects its own S3-compatible bucket from the portal; credentials are Fernet-encrypted at rest; the lifecycle resolves the tenant store per asset (
source.store == "tenant"). Hosted uploads keep using the deployment's store. - Team management: invites by email with roles, removal revokes sessions.
- Platform admin surface (
/v1/admin/*, portal Admin tab): tenants, approval, plan and quota, leads. Website contact form posts leads to/v1/leads(CORS-limited to the site origin). - Transactional email over SMTP (
needset/mail.py); local runs without SMTP return the sign-in link in the response for development. - Nightly
pg_dumpbackup service incompose.yaml; optional secrets as empty files. - Portal: sign-in and signup screens, onboarding checklist, Storage, Billing, Access (team + keys) and Admin views.
Deployment
- Gateway configuration split into
deploy/Caddyfile(local) anddeploy/Caddyfile.hosted(public hostname, automatic certificates), selected withNEEDSET_CADDYFILE; the previous single file used placeholders Caddy could not parse for thetlsdirective. - Optional marketing site served by the same gateway (
deploy/site.caddy,NEEDSET_SITE_DOMAIN,NEEDSET_SITE_DIR) with clean URLs, www redirect, 404 page and a strict CSP. - Gateway attached to a non-internal
frontendnetwork so ports 80/443 are reachable. bin/needset-upwrites secrets readable by the unprivileged service user (directory 700, files 644); the previous umask made the container fail to read/run/secrets/*..env.exampledocumentsNEEDSET_ALLOW_API_KEY_APPROVAL=1for NDA/demo tenants.
Added
demo_seed.py: populates a tenant with a coherent, idempotent demonstration pilot through the public API.DEMO.md: laptop walkthrough and hosted reviewer-portal setup with per-reviewer revocable keys.- Gateway takes
NEEDSET_DOMAINfor hosted deployments and obtains public certificates automatically. - Upload progress card with stage and percentage; buttons show a busy state while an operation runs.
- Connections overview: browser upload, S3-compatible storage, API and SDK, scheduled jobs and SSO, each with live status. Only integrations the product implements are listed.
Fixed
- SDK uploads never sent the closing multipart boundary, so the server waited indefinitely.
- Re-creating an existing pilot returned an internal error; it is now idempotent for an identical definition and a 409 conflict otherwise.
- Content-Security-Policy header construction is compatible with Python 3.11 as well as 3.12.
- Compose project name no longer embeds a release version.
- The Windows start script copies the administrator key to the clipboard only when it creates one.
- Large-dataset guidance: trees beyond the browser file limit are uploaded as one archive or registered in place with
data-scan.
2026-09-23.2
Fixed
- Declared the
scikit-learndependency used by the semantic representation graph and made its import lazy; the service starts without it. - Startup refuses a bootstrap tenant that differs from the tenant the configured API key already belongs to, instead of proceeding under the other tenant.
- Added clear startup errors when a Docker secret path is missing, empty, or is a directory instead of a file.
- Aligned the Compose bootstrap defaults with
.env.example. - Published the application only on the Windows host loopback interface for straightforward local testing.
Added
- Windows PowerShell setup and diagnostic scripts with cryptographically secure secret generation, validation, readiness checks, and actionable failure output.
- Windows quick-start and extracted-folder upload instructions.
2026-09-23
Added
- Portal single sign-on: OpenID Connect authorization-code flow with PKCE, executed in the browser against the configured issuer. Enabled by
NEEDSET_OIDC_CLIENT_ID. - Evaluation import: lm-eval-harness results and per-sample files, and any JSONL with a label and a score, become failure signals (
needset/evalimport.py,POST /v1/failures:batch,needsetctl failures-import, portal Demand tab). - Source prefix scan: register every object under a bucket or directory prefix in place, idempotently, reporting conflicts rather than overwriting (
POST /v1/data/assets:scan,needsetctl data-scan, S3 ListObjectsV2 support). - Plan explanation: per-candidate reasons, the failures that drove each selection, and the change that would admit each exclusion (
GET /v1/plans/{id}:explain, portal "Why"). - Metering: Prometheus exposition endpoint and savings CSV export.
- Python SDK (
needset/client.py), standard library only, streaming uploads and downloads. - Schedules for recurring compile and scan jobs; HMAC-signed webhooks for plan, pilot, scan and savings events.
- Multi-file and folder upload as a deterministic tar container, written in a single pass while parts arrive.
- Content-Security-Policy with per-load nonces on the portal; inline script forbidden on API responses.
- Signed audit anchors and attestation to detect chain truncation or rewriting.
- Streaming upload and download endpoints; browser and CLI uploads of any size within the configured limit.
- Operator portal served by the service itself; role-aware; covers the full lifecycle, demand, planning, pilots, economics, audit and key management.
- Scoped API keys with least-privilege roles; key issuance and revocation.
- Deployment: one-command bring-up (
bin/needset-up), environment template, Kubernetes manifest with S3 and spool volumes, operator and pilot runbooks.
Changed
- Compiler: single deterministic greedy over an explicit selection state; coverage floors are hard constraints; exact lazy evaluation with a block-overlap index (verified identical to exhaustive greedy on random pools). Plan schema
needset.plan.v3. - Plan verification is deterministic recompilation plus optional HMAC signature.
- Chunking: one windowed gear-hash content-defined chunker used by every path.
- Codec selection is deterministic (nominal cost model); measured selection is available for benchmarking only.
- All catalog, lifecycle, pilot and identity-provider calls run on a dedicated database thread; the event loop never blocks.
- Object reads are memory-mapped; S3 objects are spooled by ranged reads. Peak memory is independent of object size.
- Audit log rows carry an HMAC; pre-2026-09-21 catalogs are refused at startup.
- Approvals record the authenticated principal; production requires an OIDC identity for approvals unless explicitly overridden.
- Scout (
needset_cli.py) measures block-level deduplication with the production chunker and codec instead of file-level gzip. - RAG workload benchmark rebuilt so routing never sees the query's ground-truth label; both arms use an inverted index.
- GPU benchmark compares matched codecs on a realistic payload;
evidence.pyrejects cross-codec results.
Removed
- Separate in-memory block store and its CLI benchmark; the lifecycle store is the only storage path.
- The separate Next.js portal and its third-party hosted sign-in dependency.
- A bounded brute-force "novelty" comparison that could not support a claim.