Repository navigation
[Product Gap] Remove buyer-demo shell and hard-coded demo authority from production UI #317
Description
Buyer-visible outcome
Replace Clearfolio's production-facing buyer-demo authority with a real tenant-aware document workspace and fail-closed service contract. Development/demo fixtures may remain available only through an explicit non-production profile or separately packaged test/demo artifact; they must not define production identity, permissions, evidence, or success semantics.
Fresh protected-main evidence — 2026-08-10
At protected main 83ec6f7fe2b04bdcd28bf98ec350e41e55730a18 the gap exists at root UI, document-viewer JavaScript, demo JavaScript, and service layers:
ViewerUiController.home()returns the historical demo shell and the root page still exposesLoad demo story, seeded recovery/KPI controls and demonstration-specific copy;src/main/resources/static/assets/viewer/demo.jscontains hard-codedbuyer-demotenant/subject/permission headers, loads/assets/viewer/demo-fixtures.json, and browser session history uses theclearfolio-demo-history-v1key;src/main/resources/static/assets/viewer/viewer.jsalso definesDEMO_AUTH_HEADERSwithbuyer-demo/buyer-demo-operatorand automatically sends those headers fromfetchJson()for status and viewer-bootstrap requests whenever the URL does not carry a signedartifactToken. This is the production document-viewer asset, so the demo-authority problem is not confined to the root demo shell;- the same
viewer.jshas a safer external-integration path that accepts a server-issued signedartifactTokenand directly renders the artifact without tenant-header bootstrap. That proves a current no-demo-authority byte-delivery path exists, but it does not by itself supply the missing authenticated status/bootstrap workspace contract; DefaultDocumentConversionService.submit(file, overrideRequest)constructsTenantContext(DEMO_TENANT_ID, DEMO_SUBJECT_ID, Set.of());- the three-argument
submit(..., tenantContext)path silently substitutes the same demo tenant/subject whentenantContext == null; - current security parent fix(security): harden audit pseudonymization and refresh Netty evidence #270 still carries the legacy two-argument demo fallback, so this cannot be dismissed as stale UI-only code;
- the
demo.js/dom-utils.jspaths are also the current work surface for fix(accessibility): make asynchronous viewer actions nested-safe #264's reusable nested-safe accessibility behavior and must not be raced by a competing writer; viewer.jsitself is not currently in fix(accessibility): make asynchronous viewer actions nested-safe #264 or fix(security): harden audit pseudonymization and refresh Netty evidence #270's changed-file set, but removing its demo headers without first supplying a real server-controlled session/bootstrap authority would break the existing unsigned status/bootstrap flow. Therefore the next source slice must establish the production auth/bootstrap contract rather than merely deleting constants and silently making the viewer unusable.
This is a product/security boundary problem, not a naming nit: a production distribution must not create identity or authorization context from hard-coded demonstration values when real authority is absent.
Active bounded work / dependency boundary
Do not race or duplicate active work:
- fix(security): harden audit pseudonymization and refresh Netty evidence #270 owns current signed artifact/download/privacy parent work;
- fix(accessibility): make asynchronous viewer actions nested-safe #264 owns the nested-safe async accessibility primitive currently demonstrated in
demo.js/dom-utils.js; - fix(security): require signed tenant claims on admin endpoints #268/[Product Gap] Complete tenant-safe document lifecycle and artifact deletion #263 own tenant-safe lifecycle/deletion substrate and end-to-end deletion UX;
- [Product Gap] Add production OIDC/JWT identity federation with tenant-scoped authorization #314 owns production OIDC/JWT identity federation;
- [Product Gap] Publish a complete versioned API/schema and naruon compatibility contract #315 owns the complete versioned API/schema and naruon client contract;
- Draft fix(viewer): remove hard-coded external company branding #318 owns only the two
ViewerUiControllerbranding replacements and their focused regression. It overlaps fix(security): harden audit pseudonymization and refresh Netty evidence #270 at the Java-file level and must remain behind the parent; it does not complete this issue.
First stabilize/integrate #270 and reconcile #264. Do not copy their source paths onto a competing branch. The production workspace may initially consume the existing signed tenant-header adapter behind a server-controlled bootstrap boundary, but browser or service code must not manufacture demo tenant IDs or permissions.
Required product architecture
Separate the surfaces explicitly:
-
Production workspace
- real authenticated/tenant-scoped document intake and history;
- no seeded demo story or fake KPI/recovery evidence;
- permissions derived from verified server-side identity/bootstrap authority, never browser constants;
- the document viewer receives only server-authorized session/bootstrap state or a scoped signed artifact token; it must not synthesize tenant headers in JavaScript;
- service methods that require tenant authority fail closed when it is absent rather than constructing demo authority;
- real conversion job/status/view/download/deletion states only;
- accessible nested-safe async controls using the integrated fix(accessibility): make asynchronous viewer actions nested-safe #264 primitive;
- versioned API client aligned with issue [Product Gap] Publish a complete versioned API/schema and naruon compatibility contract #315;
- deterministic controlled errors and non-enumerating tenant behavior.
-
Development/demo fixture surface
- opt-in non-production profile, test fixture, Storybook-like harness, or separately packaged demo asset;
- clearly labelled synthetic data;
- no production credentials or shared production storage;
- cannot be accidentally enabled by omission of a secret/profile setting;
- legacy convenience overloads that inject demo authority, if retained at all, are confined to explicit test/demo wiring and are impossible to select from production Spring composition;
- any demo-auth header injector is confined to this explicit non-production surface, not
viewer.jsor another production asset; - excluded from production navigation and release acceptance except when explicitly testing the demo profile.
-
Reusable primitives
- move generic busy-state, DOM restoration, accessible naming, status rendering and API-client behavior out of
demo.jsnaming/ownership where appropriate; - keep production code free of buyer-specific hard-coded identity, valuation, diligence assumptions and demo authority.
- move generic busy-state, DOM restoration, accessible naming, status rendering and API-client behavior out of
Security and privacy invariants
- no hard-coded production tenant, subject, permission, token, secret or approver identity in browser assets or service fallbacks;
- browser code must not mint or infer authorization claims;
viewer.jsand other production assets must not automatically attach demo tenant/subject/permission headers;- null/missing tenant authority fails closed before validation, dedupe, persistence, worker enqueue, job lookup or artifact access in production entrypoints;
- production requests use a reviewed identity/session/bootstrap contract and preserve same-tenant concealment;
- signed artifact-token authority remains independent of general tenant authentication;
- no raw document content, tokens, user identifiers or tenant identifiers are stored in local browser history unless the product retention/privacy contract explicitly requires it;
- synthetic fixture data is unmistakably synthetic and isolated from real customer data;
- development fallbacks fail closed in production rather than silently selecting demo authority.
Test-first acceptance
On the stable integrated baseline, establish RED evidence before implementation. Require at least:
- production
/does not containbuyer-demo,Load demo story, seeded diligence/KPI fixture controls, or hard-coded demo identity; - production
viewer.jsand all other production static JavaScript contain noDEMO_AUTH_HEADERS,buyer-demo,buyer-demo-operator, or equivalent hard-coded tenant/subject/permission authority; - unsigned viewer status/bootstrap requests use a reviewed server-controlled authenticated session/bootstrap fixture or fail closed; deleting demo headers without a replacement authority is not an acceptable GREEN;
- signed
artifactTokenviewer mode continues to render the scoped artifact without requiring tenant-header synthesis and preserves token expiry/scope/revocation/checksum/read-audit authority; - production service submission with absent/null tenant context fails closed and does not validate, deduplicate, persist or enqueue a job under demo authority;
- production wiring exposes no legacy overload that can silently create
DEMO_TENANT_ID/DEMO_SUBJECT_ID; - explicit development/demo wiring still supports deterministic synthetic fixtures without being selectable by production omission/misconfiguration;
- production workspace successfully uses a verified tenant/session/bootstrap fixture without embedding credential values in source;
- missing/invalid production identity fails closed before document/job data is read;
- cross-tenant job probes remain concealed;
- real upload → poll → viewer workflow test;
- real successful artifact access through the integrated signed-delivery contract;
- lifecycle/deletion UI only after [Product Gap] Complete tenant-safe document lifecycle and artifact deletion #263 substrate is available, with truthful pending/failure/completion states;
- keyboard/touch/screen-reader/focus restoration and nested concurrent-action tests using the fix(accessibility): make asynchronous viewer actions nested-safe #264 primitive;
- production profile cannot enable the demo fixture surface accidentally;
- browser storage tests prove tenant/session data does not leak between identities;
- exact 100% owned production statement/branch coverage for new production code and complete beginner-readable public docs/docstrings;
- exact-head CI, Security Scan, SAST, applicable JS coverage/accessibility tests, independent review and repository protection.
Documentation / release updates
Update canonical PRD/TRD/Architecture, UML, API contract, Security/Threat Model, Test Strategy, Operability, Traceability, Acquisition Diligence, README/AGENTS/CLAUDE and CHANGELOG with truthful maturity. Demote dated seeded buyer-demo artifacts to historical/demo evidence rather than deleting useful provenance.
Release evidence must prove the production distribution cannot silently expose either browser-side or service-layer demo authority.
Suggested implementation order
- Integrate/reconcile fix(security): harden audit pseudonymization and refresh Netty evidence #270 and fix(accessibility): make asynchronous viewer actions nested-safe #264; extract reusable async/DOM primitives without demo-specific ownership.
- Define a real server-controlled viewer/session bootstrap contract that can replace
viewer.jsdemo header synthesis without breaking status/bootstrap access. - Add RED browser/service tests for hard-coded viewer auth and missing/null tenant authority; remove/confine demo-authority paths on the stable baseline.
- Add the production workspace bootstrap/auth contract to the versioned API client.
- Move seeded browser fixtures behind an explicit non-production surface/profile.
- Replace the root buyer-demo shell with the production workspace.
- Add end-to-end tenant isolation, real upload/view, signed-token viewer, accessibility and production-profile negative tests.
- Align the production client with [Product Gap] Publish a complete versioned API/schema and naruon compatibility contract #315 and later [Product Gap] Add production OIDC/JWT identity federation with tenant-scoped authorization #314 without embedding provider-specific auth logic in UI.
Do not create a competing demo.js writer while #264 is active, and do not remove viewer.js demo headers as a standalone deletion patch that leaves the production viewer without a legitimate authority path.
Activity
seonghobae commentedon Aug 10, 2026
Active bounded slice: PR #318 owns only
ViewerUiControllerbranding integrity and deliberately does not touchdemo.js/dom-utils.jswhile #264 owns those paths. Test-only head2b7483a428c54baf41afb4d5d024189ecb891317produced the intended RED: both/and/viewer/{docId}still rendered the unrelatedHYOSUNGownership claim. Current #318 head076574d5145640c881290f5db4aed3ae5fdf8c1freplaces only those two footer claims withClearfolio Viewer · Apache-2.0; CI31351475453, Security31351475466, SAST31351475434, and fuzz31351475440are green. #318 remains Draft pending independent review. This does not complete issue #317: the buyer-demo root workflow, seeded controls, browser-side demo authority, and production-workspace/bootstrap split remain dependency-gated as described above.seonghobae commentedon Aug 10, 2026
Fresh protected-main evidence expands this product-boundary gap beyond browser assets. At
main83ec6f7fe2b04bdcd28bf98ec350e41e55730a18,DefaultDocumentConversionService.submit(file, overrideRequest)constructsTenantContext(DEMO_TENANT_ID, DEMO_SUBJECT_ID, Set.of()), and the three-argument submit path also falls back to the same demo identity whentenantContext == null. Current #270 head0f9db3903526bdec795cffb228aa76844921ac6fstill contains the two-argument demo fallback. This means the production service layer itself still has a hard-coded buyer-demo authority path; it is not only a UI naming problem.Treat this as part of issue #317 rather than opening a duplicate issue. On the stable #270/#264 baseline, add RED tests proving production/service entrypoints cannot silently create a demo tenant/subject when tenant authority is absent, then remove or explicitly confine legacy/demo overloads behind a non-production fixture/profile boundary. Missing tenant context must fail closed in production. Keep test-only fixture convenience separate from shipped service authority, and preserve tenant-scoped authorization work already owned by #270/#268/#314.