prometheus: scaffold the standalone service — pages, routing, static assets
New top-level Prometheus/ directory holds the Node service that will own
prometheus.x once the on-chain record flips from the silentmode.st
redirect to ip+tls. Nothing dynamic yet — this milestone delivers the
site skeleton and proves the family's visual conventions port cleanly to
a dynamic server.
Shape:
Prometheus/service/server.js node:http listener + map-based router
Prometheus/service/layout.js document shell, topnav, footer
Prometheus/service/pages.js home / browse / submit / publish / boundaries
Prometheus/public/style.css ported silentmode.st tokens + category grid
Prometheus/public/favicon.svg flame mark
Routes: /, /browse, /submit, /publish, /boundaries, /static/*.
Dynamic routes (/api/upload, /api/manifest, /api/publish, /api/pay,
/admin/*) land here in later milestones per the architecture doc.
Launch config .claude/launch.json gets a prometheus-service entry on
port 3700 so the preview harness can run it.
The pre-pivot WordPress-based scaffold at site-prometheus-x/ is left
in place for the next session to delete deliberately — the architecture
doc marks it as abandoned for prometheus.x but keeps the Sirius Press
infra fixes it carried in.
2026-10-05 22:22:47 +02:00
/ * *
* The pages Prometheus . X renders .
*
* Each export is ` (req?) => {title, description, body} ` . The server is the one
* that wraps them in the layout — these just return the inner content .
*
* The copy was lifted from site / prometheus / index . html ( the ICANN teaser ) and
* split by concern so each page is one topic a reader opened on purpose . The
* single - scroll version lives at silentmode . st / prometheus and will stay there
* as the SEO - friendly mirror .
* /
prometheus: browse pages — categories and per-listing detail
/c/<slug> renders one of the eight categories with every active listing
in it, newest first, as a grid of cards. Empty category shows
a "be the first" prompt rather than a dead page.
/app/<pub>/<slug> renders a listing: icon + name + publisher + version
chip, a prominent Download card with size, hash chip,
signed scan badge (green/clean | red/infected |
neutral/unknown), description, homepage link, who
signed, and an endorsements placeholder for when APS1
lands.
The home-page and /browse grids now read real counts from the DB — the
hardcoded "0 listings" placeholders are gone. A new helper
`renderCategoriesGrid()` is the one place category cards are rendered,
so the two surfaces always agree.
404s for unknown categories (`/c/nope`) and unknown listings
(`/app/foo/bar`) go through the same notFound page and now actually
return HTTP 404 instead of 200 — pages opt in by returning the
notFound object, server detects it by title and sets the status.
CATEGORIES is the single source of truth for slug/icon/title/blurb;
adding a new category now means one row here plus one entry in the
listings allow-list.
Verified end-to-end with three listings across three categories: counts
update, cards render, detail page shows the signed scan badge, 404s
return 404.
2026-10-08 01:56:52 +02:00
import { renderCategoriesGrid , countsByCategory } from './pages-browse.js' ;
import { countActive } from './db.js' ;
/ * *
* The "Browse by category" section used on the home page . Lives here so the
* home page ' s inline template reads cleanly , but delegates the actual grid to
* pages - browse . js so a single row of counts drives both surfaces .
* /
function renderHomeBrowseSection ( ) {
const total = countActive ( ) ;
return ` <section class="plain">
< h2 > Browse by category < / h 2 >
< p class = "lede" > On a conventional store , one company decides which shelf an app
belongs on . Here the publisher labels their own listing , and your trust list
decides whether to show it . The categories below are the initial shape ; a new
one gets proposed by publishing a listing against it . < / p >
$ { renderCategoriesGrid ( { verbose : true } ) }
$ { total === 0 ? `
< div class = "note warn" style = "margin-top:1.6rem" > < b > No live listings yet . < / b >
The on - chain publication protocol ( < code class = "inline" > APS1 < / c o d e > ) i s i n
progress ; in the meantime the site , the scanner , the publisher - signed
listing endpoint and the publishers ' walkthrough are up . First real
listings appear in this grid as they publish . < / d i v >
` : ''}
< / s e c t i o n > ` ;
}
prometheus: scaffold the standalone service — pages, routing, static assets
New top-level Prometheus/ directory holds the Node service that will own
prometheus.x once the on-chain record flips from the silentmode.st
redirect to ip+tls. Nothing dynamic yet — this milestone delivers the
site skeleton and proves the family's visual conventions port cleanly to
a dynamic server.
Shape:
Prometheus/service/server.js node:http listener + map-based router
Prometheus/service/layout.js document shell, topnav, footer
Prometheus/service/pages.js home / browse / submit / publish / boundaries
Prometheus/public/style.css ported silentmode.st tokens + category grid
Prometheus/public/favicon.svg flame mark
Routes: /, /browse, /submit, /publish, /boundaries, /static/*.
Dynamic routes (/api/upload, /api/manifest, /api/publish, /api/pay,
/admin/*) land here in later milestones per the architecture doc.
Launch config .claude/launch.json gets a prometheus-service entry on
port 3700 so the preview harness can run it.
The pre-pivot WordPress-based scaffold at site-prometheus-x/ is left
in place for the next session to delete deliberately — the architecture
doc marks it as abandoned for prometheus.x but keeps the Sirius Press
infra fixes it carried in.
2026-10-05 22:22:47 +02:00
/* ------------------------------- home ------------------------------- */
export function home ( ) {
return {
title : 'Apps and programs on the decentralized web' ,
description :
'Prometheus is a home for apps, programs, and dApps published on the Bitcoin Cash chain. Listings live on chain, binaries live on Sia, endorsements are signed. Anyone can publish; nobody can gatekeep.' ,
here : '/' ,
body : `
< header class = "hero" >
prometheus: standalone identity + first-wave Silent Mode listings
Four changes, one coherent pass:
1. Prometheus gets its own flame mark (SVG, not an emoji). Used in the
topnav brand, in the hero at 88 px, and as the favicon. Replaces the
🔥 glyph in the service and in site/prometheus/index.html.
2. silentmode.st/prometheus is now a brief introduction with two big
buttons (Open prometheus.x · Open apps.silentmode.st mirror) and a
short "what / who / status" block, instead of the full category
grid that duplicated what prometheus.x already renders. The /prometheus
page's job on silentmode.st is "point you at the real site," not host
the content.
3. Footer on the Prometheus service drops the "Silent Mode — a Deviant
project" byline and reframes the sibling-x links as "Also live in the
decentralized web:" so Prometheus reads as its own site rather than a
Silent Mode subpage. Keeps the attribution via the MPL-2.0 + source
link on the final line. aegis.x and pithos.x added to the sibling row.
4. Four Silent Mode apps seeded as first-wave listings
(scripts/seed-listings.json + scripts/seed.mjs). Theseus Navigator,
Ariadne's Thread (Windows + Android), Pithos. Each carries the
project's own SVG icon from public/icons/, pulled from the project's
own site. The seed is a direct-DB insert (not through /api/listing)
with signer_address = chipnet operator and signature = sentinel
`SM_OPERATOR_SEED`; scan_report is an operator-attested envelope
signed by the service key so the display pipeline is unchanged. Run
with `node scripts/seed.mjs`; idempotent (skips existing (publisher,
slug)).
Deliberately NOT seeded: Theseus extensions that have no standalone
form — pdf-editor, docx-editor, shield-blocker, bergamot provider.
They ship via Theseus's own OTA, not as Prometheus listings. Aegis
is in the same boat today (a Theseus bundled add-on without a
standalone binary); its icon is included in public/icons/ for when
it has its own distribution.
Real file sizes + SHAs come from the current dl.silentmode.st manifest
(theseus 0.3.89, ariadne resolver 0.1.13, ariadne android 0.22,
pithos-desktop 0.3.26). When APS1 lands cross-session, these listings
migrate to real on-chain records signed by each project's own publisher
key and the sentinel values go away.
2026-10-09 01:23:21 +02:00
< div class = "mark" style = "width:88px;height:88px;margin:0 auto 1rem" >
< svg viewBox = "0 0 64 64" width = "88" height = "88" aria - hidden = "true" style = "display:block" >
< path d = "M32 6 C 38 18 46 24 46 36 C 46 48 40 56 32 56 C 24 56 18 48 18 36 C 18 28 24 22 20 14 C 26 18 30 20 32 6 Z" fill = "#d6ff3d" / >
< path d = "M32 20 C 36 28 40 32 40 40 C 40 48 36 52 32 52 C 28 52 24 48 24 40 C 24 34 28 30 32 20 Z" fill = "#0b0e14" / >
< circle cx = "32" cy = "44" r = "3" fill = "#d6ff3d" / >
< / s v g >
< / d i v >
< h1 > Prometheus < span class = "g" > . X < / s p a n > < / h 1 >
prometheus: scaffold the standalone service — pages, routing, static assets
New top-level Prometheus/ directory holds the Node service that will own
prometheus.x once the on-chain record flips from the silentmode.st
redirect to ip+tls. Nothing dynamic yet — this milestone delivers the
site skeleton and proves the family's visual conventions port cleanly to
a dynamic server.
Shape:
Prometheus/service/server.js node:http listener + map-based router
Prometheus/service/layout.js document shell, topnav, footer
Prometheus/service/pages.js home / browse / submit / publish / boundaries
Prometheus/public/style.css ported silentmode.st tokens + category grid
Prometheus/public/favicon.svg flame mark
Routes: /, /browse, /submit, /publish, /boundaries, /static/*.
Dynamic routes (/api/upload, /api/manifest, /api/publish, /api/pay,
/admin/*) land here in later milestones per the architecture doc.
Launch config .claude/launch.json gets a prometheus-service entry on
port 3700 so the preview harness can run it.
The pre-pivot WordPress-based scaffold at site-prometheus-x/ is left
in place for the next session to delete deliberately — the architecture
doc marks it as abandoned for prometheus.x but keeps the Sirius Press
infra fixes it carried in.
2026-10-05 22:22:47 +02:00
< p class = "tag" > A home for apps , programs , dApps , and libraries — < b > listed on the
Bitcoin Cash chain , hosted on Sia , endorsed by signature < / b > . A n y o n e c a n p u b l i s h .
Nobody can gatekeep . Named for the one who gave fire to humans and did not ask
the gods first . < / p >
< div class = "cta" >
< a class = "btn acid" href = "/browse" > Browse apps < / a >
< a class = "btn ghost" href = "/submit" > Submit an app < / a >
< / d i v >
< / h e a d e r >
< div class = "wrap" >
prometheus: browse pages — categories and per-listing detail
/c/<slug> renders one of the eight categories with every active listing
in it, newest first, as a grid of cards. Empty category shows
a "be the first" prompt rather than a dead page.
/app/<pub>/<slug> renders a listing: icon + name + publisher + version
chip, a prominent Download card with size, hash chip,
signed scan badge (green/clean | red/infected |
neutral/unknown), description, homepage link, who
signed, and an endorsements placeholder for when APS1
lands.
The home-page and /browse grids now read real counts from the DB — the
hardcoded "0 listings" placeholders are gone. A new helper
`renderCategoriesGrid()` is the one place category cards are rendered,
so the two surfaces always agree.
404s for unknown categories (`/c/nope`) and unknown listings
(`/app/foo/bar`) go through the same notFound page and now actually
return HTTP 404 instead of 200 — pages opt in by returning the
notFound object, server detects it by title and sets the status.
CATEGORIES is the single source of truth for slug/icon/title/blurb;
adding a new category now means one row here plus one entry in the
listings allow-list.
Verified end-to-end with three listings across three categories: counts
update, cards render, detail page shows the signed scan badge, 404s
return 404.
2026-10-08 01:56:52 +02:00
$ { renderHomeBrowseSection ( ) }
prometheus: scaffold the standalone service — pages, routing, static assets
New top-level Prometheus/ directory holds the Node service that will own
prometheus.x once the on-chain record flips from the silentmode.st
redirect to ip+tls. Nothing dynamic yet — this milestone delivers the
site skeleton and proves the family's visual conventions port cleanly to
a dynamic server.
Shape:
Prometheus/service/server.js node:http listener + map-based router
Prometheus/service/layout.js document shell, topnav, footer
Prometheus/service/pages.js home / browse / submit / publish / boundaries
Prometheus/public/style.css ported silentmode.st tokens + category grid
Prometheus/public/favicon.svg flame mark
Routes: /, /browse, /submit, /publish, /boundaries, /static/*.
Dynamic routes (/api/upload, /api/manifest, /api/publish, /api/pay,
/admin/*) land here in later milestones per the architecture doc.
Launch config .claude/launch.json gets a prometheus-service entry on
port 3700 so the preview harness can run it.
The pre-pivot WordPress-based scaffold at site-prometheus-x/ is left
in place for the next session to delete deliberately — the architecture
doc marks it as abandoned for prometheus.x but keeps the Sirius Press
infra fixes it carried in.
2026-10-05 22:22:47 +02:00
< section >
< h2 > How Prometheus works , in a nutshell < / h 2 >
< ol class = "steps" >
< li > < b > A listing is a signed on - chain record . < / b >
< span > Publishing an app writes a small < code class = "inline" > APS1 < / c o d e >
record to Bitcoin Cash — same protocol shape as a BNS name registration ,
different prefix . The payload is a hash pointing at a full manifest stored
on Sia . Nobody approves the record ; the transaction is the publish . < / s p a n > < / l i >
< li > < b > The binary lives on Sia . < / b >
< span > App files are uploaded to the Sia storage network — encrypted on
the publisher ' s machine , sharded across roughly 30 independent hosts ,
held by nobody in particular . The listing carries the SHA - 256 the file
must match . < / s p a n > < / l i >
< li > < b > Scan reports are signed and attached . < / b >
< span > Prometheus runs an automated malware scan on every binary it hosts
and publishes the signed report alongside the listing . The chain still
goes up ; the report tells your client whether to warn . < / s p a n > < / l i >
< li > < b > Install verifies against the chain . < / b >
< span > Clients pull the file , check the SHA - 256 against the on - chain
record , check the signing certificate against the fingerprint , and only
then install . The chain is the truth , not the mirror . < / s p a n > < / l i >
< / o l >
< p style = "margin-top:1.2rem" > < a href = "/publish" > The full publishers ' walkthrough ,
step by step → < / a > < / p >
< / s e c t i o n >
< / d i v > ` ,
} ;
}
/* ------------------------------- publish ---------------------------- */
export function publish ( ) {
return {
title : "Publishers' walkthrough" ,
description :
'The end-to-end flow of publishing an app on Prometheus: name, hosting, manifest, listing, verification, updates, withdrawal, and endorsements.' ,
here : '/publish' ,
body : `
< header class = "hero compact" >
< h1 > Publishers ' walkthrough < / h 1 >
< p class = "tag" > Pick a name , host the binary , write the manifest , sign the listing ,
verify it resolves , ship an update when it 's time. Here' s the whole shape . < / p >
< / h e a d e r >
< div class = "wrap" >
< section class = "plain" >
< p class = "lede" > Publishing an app on Prometheus is a sequence of signed records
pointing at files stored where you keep them . The whole thing — identity ,
hosting , the listing itself — is yours ; nothing here grants anyone else
permission to pull your app down , raise your price , or take a cut of what
people pay you for it . < / p >
< p > Steps are tagged < span class = "pill p-live" > available today < / s p a n > o r
< span class = "pill p-designed" > designed < / s p a n > s o i t i s c l e a r w h a t y o u c a n d o
now and what is waiting on the APS1 record format landing . < / p >
< / s e c t i o n >
< section >
< h2 > What you need before you start < / h 2 >
< div class = "grid" >
< div class = "card" >
< span class = "pill p-live" > available today < / s p a n >
< h3 > A BNS name < / h 3 >
< p > Any name registered through < a href = "//sirius.x/" > Sirius < / a > w o r k s . T h e
name is what signs the listing , so it is also what readers trust the
listing by . Fractions of a cent to register , no monthly bill . < / p >
< / d i v >
< div class = "card" >
< span class = "pill p-live" > available today < / s p a n >
< h3 > Somewhere to put the binary < / h 3 >
< p > Prometheus can host for you — upload here and we put it on Sia , signed
and scanned . If you ' d rather self - host on your own URL , the listing just
records the URL and the hash and will fetch from wherever you say . < / p >
< / d i v >
< div class = "card" >
< span class = "pill p-live" > available today < / s p a n >
< h3 > A signing key < / h 3 >
< p > The private key for the BNS name . If the name is in a wallet you already
use for Bitcoin Cash , that key already exists ; publishing re - uses it . < / p >
< / d i v >
< div class = "card" >
< span class = "pill p-live" > available today < / s p a n >
< h3 > A pinch of BCH < / h 3 >
< p > One listing is one transaction . The transaction fee is cents , denominated
in sats . During the free wave , that is the only cost . Later , a flat
listing fee covers the Sia storage . < / p >
< / d i v >
< / d i v >
< / s e c t i o n >
< section >
< h2 > The walkthrough < / h 2 >
< ol class = "steps" >
< li > < b > 1. Register a name — or use one you already own . < / b >
< span > The listing is signed by the name ' s key . A reader who follows your
name will see your listing with the publisher chip set to that name ; a
reader who does not will see it marked unsigned by anyone they trust .
One name can carry any number of listings , so a studio with three games
publishes under one identity . < / s p a n > < / l i >
< li > < b > 2. Upload the binary ( or point us at your URL ) . < / b >
< span > Prometheus runs a malware scan on upload . If it passes , the file
goes into a Sia bucket Silent Mode controls , the signed scan report is
prepared , and the manifest fields are prefilled . If you ' d rather host
the file yourself , give us the URL and the expected SHA - 256 ; the manifest
records both , the scan still runs against whatever the URL serves , and
the client refuses to install a byte - mismatched download . < / s p a n > < / l i >
< li > < b > 3. Fill in the manifest . < / b >
< span > Name , description , category , version , icon , download URL ,
< code class = "inline" > sha256 < / c o d e > , a n d t h e s i g n i n g - c e r t f i n g e r p r i n t w h e n
the binary itself is signed ( it should be , if the platform supports it ) .
Icons are referenced by URL , not embedded . A 2 KB manifest with a 500 KB
icon beside it is the right shape ; a 500 KB manifest is not . < / s p a n > < / l i >
< li > < b > 4. Sign the APS1 record with your BNS key . < / b >
< span > One transaction . The on - chain record carries the manifest hash ,
the category label , and the signature from your name ' s key . From the
moment the transaction confirms , every client that follows the chain can
see your listing — no approval queue , no editorial review , no publisher
dashboard waiting for a human on the other end . < / s p a n > < / l i >
< li > < b > 5. Verify it resolves . < / b >
< span > Open one of the Ariadne clients and look for your listing in the
category you labelled it under . The client should pull the manifest from
Sia , show the fields you wrote , surface the scan report , and offer the
download . Clicking install should fetch the binary , check the SHA - 256 ,
check the signing certificate , and only then run . < / s p a n > < / l i >
< li > < b > 6. Ship updates with another signed record . < / b >
< span > A new version is another APS1 transaction referencing the same app
id , signed by the same key , pointing at a new manifest hash . Clients
already following the app see the update offered the next time they
check . Nobody can push a hostile update unless they have your key . < / s p a n > < / l i >
< li > < b > 7. Withdraw a listing when it is time . < / b >
< span > A withdrawal is a signed record that marks the app id as retired .
Clients hide the listing from browse and refuse to install it , though
anyone with the manifest hash and the binary can still side - load it —
the chain is a signal , not a lock . < / s p a n > < / l i >
< / o l >
< / s e c t i o n >
< section >
< h2 > A note on endorsements < / h 2 >
< p > A listing is one signed record . An < em > endorsement < / e m > i s a n o t h e r — a s h o r t
on - chain statement from any name saying "I vouch for this app." Readers whose
trust list includes the endorser ' s name will see your listing marked as
endorsed by them ; readers who do not follow that name will not . Endorsements
are not reviews , and the client does not aggregate them into a star rating .
They are statements : who said what , when , signed . < / p >
< div class = "note" > < b > You can endorse too . < / b > A n y n a m e c a n p u b l i s h a n e n d o r s e m e n t
of any listing , and most publishers end up endorsing a handful of apps they
use themselves . The trust graph that emerges is the directory ' s real curation
layer ; the categories are just filing . < / d i v >
< / s e c t i o n >
< section >
< h2 > What this costs < / h 2 >
< div class = "grid" >
< div class = "card" >
< h3 > Per listing < / h 3 >
< p > One Bitcoin Cash transaction . Fractions of a cent at current rates . Paid
once , lives on chain . < / p >
< / d i v >
< div class = "card" >
< h3 > Per update < / h 3 >
< p > Another transaction . Same shape , same cost . There is no "update bundle"
discount because there is no central party to collect one . < / p >
< / d i v >
< div class = "card" >
< h3 > Per month of hosting < / h 3 >
< p > Free during the opening wave while Prometheus underwrites the storage .
Afterwards , a flat per - listing fee ( planned around $1 at current rates )
covers Sia storage for the typical binary . Your own HTTPS host costs
what it costs you . < / p >
< / d i v >
< div class = "card" >
< h3 > Per download < / h 3 >
< p > Zero to the directory . The reader pulls the binary from wherever the
manifest points ; Prometheus never meters the traffic . < / p >
< / d i v >
< / d i v >
< / s e c t i o n >
< section >
< div class = "note warn" > < b > Where this stands today . < / b > N a m e s , S i a h o s t i n g a n d
BCH signing exist and are in use . The < code class = "inline" > APS1 < / c o d e > r e c o r d
format and the client - side browse / install flow are being wired . This page
describes the end state ; the first live listings will go up as soon as the
submit path lands on this site and the Ariadne clients learn to read it . < / d i v >
< / s e c t i o n >
< / d i v > ` ,
} ;
}
/* ------------------------------- browse ----------------------------- */
export function browse ( ) {
prometheus: browse pages — categories and per-listing detail
/c/<slug> renders one of the eight categories with every active listing
in it, newest first, as a grid of cards. Empty category shows
a "be the first" prompt rather than a dead page.
/app/<pub>/<slug> renders a listing: icon + name + publisher + version
chip, a prominent Download card with size, hash chip,
signed scan badge (green/clean | red/infected |
neutral/unknown), description, homepage link, who
signed, and an endorsements placeholder for when APS1
lands.
The home-page and /browse grids now read real counts from the DB — the
hardcoded "0 listings" placeholders are gone. A new helper
`renderCategoriesGrid()` is the one place category cards are rendered,
so the two surfaces always agree.
404s for unknown categories (`/c/nope`) and unknown listings
(`/app/foo/bar`) go through the same notFound page and now actually
return HTTP 404 instead of 200 — pages opt in by returning the
notFound object, server detects it by title and sets the status.
CATEGORIES is the single source of truth for slug/icon/title/blurb;
adding a new category now means one row here plus one entry in the
listings allow-list.
Verified end-to-end with three listings across three categories: counts
update, cards render, detail page shows the signed scan badge, 404s
return 404.
2026-10-08 01:56:52 +02:00
const total = countActive ( ) ;
const totalLabel = total === 0
? ` No listings yet — the eight categories are set and ready to receive publications. `
: ` ${ total } ${ total === 1 ? 'listing' : 'listings' } across ${ Object . values ( countsByCategory ( ) ) . filter ( ( n ) => n > 0 ) . length } ${ Object . values ( countsByCategory ( ) ) . filter ( ( n ) => n > 0 ) . length === 1 ? 'category' : 'categories' } . ` ;
prometheus: scaffold the standalone service — pages, routing, static assets
New top-level Prometheus/ directory holds the Node service that will own
prometheus.x once the on-chain record flips from the silentmode.st
redirect to ip+tls. Nothing dynamic yet — this milestone delivers the
site skeleton and proves the family's visual conventions port cleanly to
a dynamic server.
Shape:
Prometheus/service/server.js node:http listener + map-based router
Prometheus/service/layout.js document shell, topnav, footer
Prometheus/service/pages.js home / browse / submit / publish / boundaries
Prometheus/public/style.css ported silentmode.st tokens + category grid
Prometheus/public/favicon.svg flame mark
Routes: /, /browse, /submit, /publish, /boundaries, /static/*.
Dynamic routes (/api/upload, /api/manifest, /api/publish, /api/pay,
/admin/*) land here in later milestones per the architecture doc.
Launch config .claude/launch.json gets a prometheus-service entry on
port 3700 so the preview harness can run it.
The pre-pivot WordPress-based scaffold at site-prometheus-x/ is left
in place for the next session to delete deliberately — the architecture
doc marks it as abandoned for prometheus.x but keeps the Sirius Press
infra fixes it carried in.
2026-10-05 22:22:47 +02:00
return {
title : 'Browse' ,
description :
'Browse apps and programs published on Prometheus by category. Signed listings on the Bitcoin Cash chain; scan reports attached; your trust list filters.' ,
here : '/browse' ,
body : `
< header class = "hero compact" >
< h1 > Browse < / h 1 >
< p class = "tag" > Signed listings on chain , scan reports attached , your trust list
decides what to render . < / p >
< / h e a d e r >
< div class = "wrap" >
< section class = "plain" >
prometheus: browse pages — categories and per-listing detail
/c/<slug> renders one of the eight categories with every active listing
in it, newest first, as a grid of cards. Empty category shows
a "be the first" prompt rather than a dead page.
/app/<pub>/<slug> renders a listing: icon + name + publisher + version
chip, a prominent Download card with size, hash chip,
signed scan badge (green/clean | red/infected |
neutral/unknown), description, homepage link, who
signed, and an endorsements placeholder for when APS1
lands.
The home-page and /browse grids now read real counts from the DB — the
hardcoded "0 listings" placeholders are gone. A new helper
`renderCategoriesGrid()` is the one place category cards are rendered,
so the two surfaces always agree.
404s for unknown categories (`/c/nope`) and unknown listings
(`/app/foo/bar`) go through the same notFound page and now actually
return HTTP 404 instead of 200 — pages opt in by returning the
notFound object, server detects it by title and sets the status.
CATEGORIES is the single source of truth for slug/icon/title/blurb;
adding a new category now means one row here plus one entry in the
listings allow-list.
Verified end-to-end with three listings across three categories: counts
update, cards render, detail page shows the signed scan badge, 404s
return 404.
2026-10-08 01:56:52 +02:00
< p class = "lede" > $ { totalLabel } < / p >
$ { renderCategoriesGrid ( { verbose : false } ) }
prometheus: scaffold the standalone service — pages, routing, static assets
New top-level Prometheus/ directory holds the Node service that will own
prometheus.x once the on-chain record flips from the silentmode.st
redirect to ip+tls. Nothing dynamic yet — this milestone delivers the
site skeleton and proves the family's visual conventions port cleanly to
a dynamic server.
Shape:
Prometheus/service/server.js node:http listener + map-based router
Prometheus/service/layout.js document shell, topnav, footer
Prometheus/service/pages.js home / browse / submit / publish / boundaries
Prometheus/public/style.css ported silentmode.st tokens + category grid
Prometheus/public/favicon.svg flame mark
Routes: /, /browse, /submit, /publish, /boundaries, /static/*.
Dynamic routes (/api/upload, /api/manifest, /api/publish, /api/pay,
/admin/*) land here in later milestones per the architecture doc.
Launch config .claude/launch.json gets a prometheus-service entry on
port 3700 so the preview harness can run it.
The pre-pivot WordPress-based scaffold at site-prometheus-x/ is left
in place for the next session to delete deliberately — the architecture
doc marks it as abandoned for prometheus.x but keeps the Sirius Press
infra fixes it carried in.
2026-10-05 22:22:47 +02:00
< / s e c t i o n >
< / d i v > ` ,
} ;
}
/* ------------------------------- submit ----------------------------- */
export function submit ( ) {
return {
title : 'Submit an app' ,
description :
'Upload an app to Prometheus. We scan it, generate the manifest and the APS1 record, you sign with your BNS key and the listing goes on chain.' ,
here : '/submit' ,
body : `
< header class = "hero compact" >
< h1 > Submit an app < / h 1 >
< p class = "tag" > Upload , scan , sign , publish . Four screens ; your key stays with you . < / p >
< / h e a d e r >
< div class = "wrap" >
< section class = "plain" >
prometheus: upload + ClamAV scan + signed scan reports
POST /api/upload accepts a multipart file, spools it to a temp path while
hashing to SHA-256, hands the on-disk copy to clamd via INSTREAM, keeps
the file at uploads/<sha256>.bin on a clean verdict and discards it on
anything else. The response carries the (hash, size, verdict) tuple
signed by the service's ed25519 key — the artifact the manifest step
will attach to the APS1 record later.
For local dev on a box without ClamAV, MOCK_SCAN=clean|infected|error
short-circuits the TCP call while still draining the bytes end-to-end.
CLAMD_HOST / CLAMD_PORT / PROMETHEUS_MAX_UPLOAD_BYTES override the
defaults (127.0.0.1:3310, 2 GiB).
Service key lives at .keys/service.key (gitignored, 0600), generated on
first start if absent. The matching public key is served at
/.well-known/prometheus-service-key.json so clients can cache it per
origin. ed25519 chosen over BIP-137 because the scan report is a service
attestation, not a Bitcoin thing — the key never signs value, never
signs for a user, never touches the chain.
Canonicalisation walks the payload recursively (sorted keys at every
level, no whitespace). The first draft used JSON.stringify with a keys
array, which only serialises top-level keys from the array — nested
objects came out as {}, so the signature verified nothing useful.
/healthz pings clamd (or the mock) so monitors can tell when the scanner
is down.
The /submit page gets a working upload form that drives the endpoint
and renders the full signed response, so the whole chain is testable in
the browser as well as via curl.
One dependency added: busboy. Rolling our own multipart parser was the
obvious alternative; busboy is one small, maintained library with no
runtime deps of its own, and multipart RFC 7578 edge cases are not
places this project earns anything by rediscovering.
2026-10-05 22:41:55 +02:00
< div class = "note warn" > < b > Milestone 1 of 4 : upload + scan . < / b > T h i s s t e p i s
live and testable . The next milestone wires the manifest and APS1 record
( see < a href = "/publish" > the publishers ' walkthrough < / a > ) ; p a y m e n t a n d t h e
on - chain publish come after that . < / d i v >
prometheus: scaffold the standalone service — pages, routing, static assets
New top-level Prometheus/ directory holds the Node service that will own
prometheus.x once the on-chain record flips from the silentmode.st
redirect to ip+tls. Nothing dynamic yet — this milestone delivers the
site skeleton and proves the family's visual conventions port cleanly to
a dynamic server.
Shape:
Prometheus/service/server.js node:http listener + map-based router
Prometheus/service/layout.js document shell, topnav, footer
Prometheus/service/pages.js home / browse / submit / publish / boundaries
Prometheus/public/style.css ported silentmode.st tokens + category grid
Prometheus/public/favicon.svg flame mark
Routes: /, /browse, /submit, /publish, /boundaries, /static/*.
Dynamic routes (/api/upload, /api/manifest, /api/publish, /api/pay,
/admin/*) land here in later milestones per the architecture doc.
Launch config .claude/launch.json gets a prometheus-service entry on
port 3700 so the preview harness can run it.
The pre-pivot WordPress-based scaffold at site-prometheus-x/ is left
in place for the next session to delete deliberately — the architecture
doc marks it as abandoned for prometheus.x but keeps the Sirius Press
infra fixes it carried in.
2026-10-05 22:22:47 +02:00
< ol class = "steps" style = "margin-top:1.4rem" >
< li > < b > 1. Upload the binary . < / b >
prometheus: upload + ClamAV scan + signed scan reports
POST /api/upload accepts a multipart file, spools it to a temp path while
hashing to SHA-256, hands the on-disk copy to clamd via INSTREAM, keeps
the file at uploads/<sha256>.bin on a clean verdict and discards it on
anything else. The response carries the (hash, size, verdict) tuple
signed by the service's ed25519 key — the artifact the manifest step
will attach to the APS1 record later.
For local dev on a box without ClamAV, MOCK_SCAN=clean|infected|error
short-circuits the TCP call while still draining the bytes end-to-end.
CLAMD_HOST / CLAMD_PORT / PROMETHEUS_MAX_UPLOAD_BYTES override the
defaults (127.0.0.1:3310, 2 GiB).
Service key lives at .keys/service.key (gitignored, 0600), generated on
first start if absent. The matching public key is served at
/.well-known/prometheus-service-key.json so clients can cache it per
origin. ed25519 chosen over BIP-137 because the scan report is a service
attestation, not a Bitcoin thing — the key never signs value, never
signs for a user, never touches the chain.
Canonicalisation walks the payload recursively (sorted keys at every
level, no whitespace). The first draft used JSON.stringify with a keys
array, which only serialises top-level keys from the array — nested
objects came out as {}, so the signature verified nothing useful.
/healthz pings clamd (or the mock) so monitors can tell when the scanner
is down.
The /submit page gets a working upload form that drives the endpoint
and renders the full signed response, so the whole chain is testable in
the browser as well as via curl.
One dependency added: busboy. Rolling our own multipart parser was the
obvious alternative; busboy is one small, maintained library with no
runtime deps of its own, and multipart RFC 7578 edge cases are not
places this project earns anything by rediscovering.
2026-10-05 22:41:55 +02:00
< span > Pick a file ( any file — we don ' t care what the format is , only
what ClamAV says about the bytes ) . The server scans it , hashes it ,
and hands back a signed scan report . < / s p a n > < / l i >
prometheus: scaffold the standalone service — pages, routing, static assets
New top-level Prometheus/ directory holds the Node service that will own
prometheus.x once the on-chain record flips from the silentmode.st
redirect to ip+tls. Nothing dynamic yet — this milestone delivers the
site skeleton and proves the family's visual conventions port cleanly to
a dynamic server.
Shape:
Prometheus/service/server.js node:http listener + map-based router
Prometheus/service/layout.js document shell, topnav, footer
Prometheus/service/pages.js home / browse / submit / publish / boundaries
Prometheus/public/style.css ported silentmode.st tokens + category grid
Prometheus/public/favicon.svg flame mark
Routes: /, /browse, /submit, /publish, /boundaries, /static/*.
Dynamic routes (/api/upload, /api/manifest, /api/publish, /api/pay,
/admin/*) land here in later milestones per the architecture doc.
Launch config .claude/launch.json gets a prometheus-service entry on
port 3700 so the preview harness can run it.
The pre-pivot WordPress-based scaffold at site-prometheus-x/ is left
in place for the next session to delete deliberately — the architecture
doc marks it as abandoned for prometheus.x but keeps the Sirius Press
infra fixes it carried in.
2026-10-05 22:22:47 +02:00
< li > < b > 2. Fill the manifest . < / b >
< span > Name , description , category , version , icon URL . Scan verdict is
prometheus: upload + ClamAV scan + signed scan reports
POST /api/upload accepts a multipart file, spools it to a temp path while
hashing to SHA-256, hands the on-disk copy to clamd via INSTREAM, keeps
the file at uploads/<sha256>.bin on a clean verdict and discards it on
anything else. The response carries the (hash, size, verdict) tuple
signed by the service's ed25519 key — the artifact the manifest step
will attach to the APS1 record later.
For local dev on a box without ClamAV, MOCK_SCAN=clean|infected|error
short-circuits the TCP call while still draining the bytes end-to-end.
CLAMD_HOST / CLAMD_PORT / PROMETHEUS_MAX_UPLOAD_BYTES override the
defaults (127.0.0.1:3310, 2 GiB).
Service key lives at .keys/service.key (gitignored, 0600), generated on
first start if absent. The matching public key is served at
/.well-known/prometheus-service-key.json so clients can cache it per
origin. ed25519 chosen over BIP-137 because the scan report is a service
attestation, not a Bitcoin thing — the key never signs value, never
signs for a user, never touches the chain.
Canonicalisation walks the payload recursively (sorted keys at every
level, no whitespace). The first draft used JSON.stringify with a keys
array, which only serialises top-level keys from the array — nested
objects came out as {}, so the signature verified nothing useful.
/healthz pings clamd (or the mock) so monitors can tell when the scanner
is down.
The /submit page gets a working upload form that drives the endpoint
and renders the full signed response, so the whole chain is testable in
the browser as well as via curl.
One dependency added: busboy. Rolling our own multipart parser was the
obvious alternative; busboy is one small, maintained library with no
runtime deps of its own, and multipart RFC 7578 edge cases are not
places this project earns anything by rediscovering.
2026-10-05 22:41:55 +02:00
attached automatically . < em > Not wired yet . < / e m > < / s p a n > < / l i >
prometheus: scaffold the standalone service — pages, routing, static assets
New top-level Prometheus/ directory holds the Node service that will own
prometheus.x once the on-chain record flips from the silentmode.st
redirect to ip+tls. Nothing dynamic yet — this milestone delivers the
site skeleton and proves the family's visual conventions port cleanly to
a dynamic server.
Shape:
Prometheus/service/server.js node:http listener + map-based router
Prometheus/service/layout.js document shell, topnav, footer
Prometheus/service/pages.js home / browse / submit / publish / boundaries
Prometheus/public/style.css ported silentmode.st tokens + category grid
Prometheus/public/favicon.svg flame mark
Routes: /, /browse, /submit, /publish, /boundaries, /static/*.
Dynamic routes (/api/upload, /api/manifest, /api/publish, /api/pay,
/admin/*) land here in later milestones per the architecture doc.
Launch config .claude/launch.json gets a prometheus-service entry on
port 3700 so the preview harness can run it.
The pre-pivot WordPress-based scaffold at site-prometheus-x/ is left
in place for the next session to delete deliberately — the architecture
doc marks it as abandoned for prometheus.x but keeps the Sirius Press
infra fixes it carried in.
2026-10-05 22:22:47 +02:00
< li > < b > 3. Sign with your BNS key . < / b >
prometheus: upload + ClamAV scan + signed scan reports
POST /api/upload accepts a multipart file, spools it to a temp path while
hashing to SHA-256, hands the on-disk copy to clamd via INSTREAM, keeps
the file at uploads/<sha256>.bin on a clean verdict and discards it on
anything else. The response carries the (hash, size, verdict) tuple
signed by the service's ed25519 key — the artifact the manifest step
will attach to the APS1 record later.
For local dev on a box without ClamAV, MOCK_SCAN=clean|infected|error
short-circuits the TCP call while still draining the bytes end-to-end.
CLAMD_HOST / CLAMD_PORT / PROMETHEUS_MAX_UPLOAD_BYTES override the
defaults (127.0.0.1:3310, 2 GiB).
Service key lives at .keys/service.key (gitignored, 0600), generated on
first start if absent. The matching public key is served at
/.well-known/prometheus-service-key.json so clients can cache it per
origin. ed25519 chosen over BIP-137 because the scan report is a service
attestation, not a Bitcoin thing — the key never signs value, never
signs for a user, never touches the chain.
Canonicalisation walks the payload recursively (sorted keys at every
level, no whitespace). The first draft used JSON.stringify with a keys
array, which only serialises top-level keys from the array — nested
objects came out as {}, so the signature verified nothing useful.
/healthz pings clamd (or the mock) so monitors can tell when the scanner
is down.
The /submit page gets a working upload form that drives the endpoint
and renders the full signed response, so the whole chain is testable in
the browser as well as via curl.
One dependency added: busboy. Rolling our own multipart parser was the
obvious alternative; busboy is one small, maintained library with no
runtime deps of its own, and multipart RFC 7578 edge cases are not
places this project earns anything by rediscovering.
2026-10-05 22:41:55 +02:00
< span > The service shows the APS1 payload . Sign in Theseus , paste a
BIP - 137 signature from any BCH wallet , or import your name ' s mnemonic
once in the page ( wiped on close ) . < em > Not wired yet . < / e m > < / s p a n > < / l i >
prometheus: scaffold the standalone service — pages, routing, static assets
New top-level Prometheus/ directory holds the Node service that will own
prometheus.x once the on-chain record flips from the silentmode.st
redirect to ip+tls. Nothing dynamic yet — this milestone delivers the
site skeleton and proves the family's visual conventions port cleanly to
a dynamic server.
Shape:
Prometheus/service/server.js node:http listener + map-based router
Prometheus/service/layout.js document shell, topnav, footer
Prometheus/service/pages.js home / browse / submit / publish / boundaries
Prometheus/public/style.css ported silentmode.st tokens + category grid
Prometheus/public/favicon.svg flame mark
Routes: /, /browse, /submit, /publish, /boundaries, /static/*.
Dynamic routes (/api/upload, /api/manifest, /api/publish, /api/pay,
/admin/*) land here in later milestones per the architecture doc.
Launch config .claude/launch.json gets a prometheus-service entry on
port 3700 so the preview harness can run it.
The pre-pivot WordPress-based scaffold at site-prometheus-x/ is left
in place for the next session to delete deliberately — the architecture
doc marks it as abandoned for prometheus.x but keeps the Sirius Press
infra fixes it carried in.
2026-10-05 22:22:47 +02:00
< li > < b > 4. Pay the listing fee ( eventually ) . < / b >
prometheus: upload + ClamAV scan + signed scan reports
POST /api/upload accepts a multipart file, spools it to a temp path while
hashing to SHA-256, hands the on-disk copy to clamd via INSTREAM, keeps
the file at uploads/<sha256>.bin on a clean verdict and discards it on
anything else. The response carries the (hash, size, verdict) tuple
signed by the service's ed25519 key — the artifact the manifest step
will attach to the APS1 record later.
For local dev on a box without ClamAV, MOCK_SCAN=clean|infected|error
short-circuits the TCP call while still draining the bytes end-to-end.
CLAMD_HOST / CLAMD_PORT / PROMETHEUS_MAX_UPLOAD_BYTES override the
defaults (127.0.0.1:3310, 2 GiB).
Service key lives at .keys/service.key (gitignored, 0600), generated on
first start if absent. The matching public key is served at
/.well-known/prometheus-service-key.json so clients can cache it per
origin. ed25519 chosen over BIP-137 because the scan report is a service
attestation, not a Bitcoin thing — the key never signs value, never
signs for a user, never touches the chain.
Canonicalisation walks the payload recursively (sorted keys at every
level, no whitespace). The first draft used JSON.stringify with a keys
array, which only serialises top-level keys from the array — nested
objects came out as {}, so the signature verified nothing useful.
/healthz pings clamd (or the mock) so monitors can tell when the scanner
is down.
The /submit page gets a working upload form that drives the endpoint
and renders the full signed response, so the whole chain is testable in
the browser as well as via curl.
One dependency added: busboy. Rolling our own multipart parser was the
obvious alternative; busboy is one small, maintained library with no
runtime deps of its own, and multipart RFC 7578 edge cases are not
places this project earns anything by rediscovering.
2026-10-05 22:41:55 +02:00
< span > Free during the opening wave ; a BCH invoice for the per - listing
fee once storage costs cross a threshold . < em > Not wired yet . < / e m > < / s p a n > < / l i >
prometheus: scaffold the standalone service — pages, routing, static assets
New top-level Prometheus/ directory holds the Node service that will own
prometheus.x once the on-chain record flips from the silentmode.st
redirect to ip+tls. Nothing dynamic yet — this milestone delivers the
site skeleton and proves the family's visual conventions port cleanly to
a dynamic server.
Shape:
Prometheus/service/server.js node:http listener + map-based router
Prometheus/service/layout.js document shell, topnav, footer
Prometheus/service/pages.js home / browse / submit / publish / boundaries
Prometheus/public/style.css ported silentmode.st tokens + category grid
Prometheus/public/favicon.svg flame mark
Routes: /, /browse, /submit, /publish, /boundaries, /static/*.
Dynamic routes (/api/upload, /api/manifest, /api/publish, /api/pay,
/admin/*) land here in later milestones per the architecture doc.
Launch config .claude/launch.json gets a prometheus-service entry on
port 3700 so the preview harness can run it.
The pre-pivot WordPress-based scaffold at site-prometheus-x/ is left
in place for the next session to delete deliberately — the architecture
doc marks it as abandoned for prometheus.x but keeps the Sirius Press
infra fixes it carried in.
2026-10-05 22:22:47 +02:00
< / o l >
< / s e c t i o n >
prometheus: upload + ClamAV scan + signed scan reports
POST /api/upload accepts a multipart file, spools it to a temp path while
hashing to SHA-256, hands the on-disk copy to clamd via INSTREAM, keeps
the file at uploads/<sha256>.bin on a clean verdict and discards it on
anything else. The response carries the (hash, size, verdict) tuple
signed by the service's ed25519 key — the artifact the manifest step
will attach to the APS1 record later.
For local dev on a box without ClamAV, MOCK_SCAN=clean|infected|error
short-circuits the TCP call while still draining the bytes end-to-end.
CLAMD_HOST / CLAMD_PORT / PROMETHEUS_MAX_UPLOAD_BYTES override the
defaults (127.0.0.1:3310, 2 GiB).
Service key lives at .keys/service.key (gitignored, 0600), generated on
first start if absent. The matching public key is served at
/.well-known/prometheus-service-key.json so clients can cache it per
origin. ed25519 chosen over BIP-137 because the scan report is a service
attestation, not a Bitcoin thing — the key never signs value, never
signs for a user, never touches the chain.
Canonicalisation walks the payload recursively (sorted keys at every
level, no whitespace). The first draft used JSON.stringify with a keys
array, which only serialises top-level keys from the array — nested
objects came out as {}, so the signature verified nothing useful.
/healthz pings clamd (or the mock) so monitors can tell when the scanner
is down.
The /submit page gets a working upload form that drives the endpoint
and renders the full signed response, so the whole chain is testable in
the browser as well as via curl.
One dependency added: busboy. Rolling our own multipart parser was the
obvious alternative; busboy is one small, maintained library with no
runtime deps of its own, and multipart RFC 7578 edge cases are not
places this project earns anything by rediscovering.
2026-10-05 22:41:55 +02:00
< section >
< h2 > Upload a file to try it < / h 2 >
< p class = "lede" > Pick any file to send through the scanner . The upload is
accepted , SHA - 256 ' d , and either kept ( clean ) or discarded ( infected or
error ) . Nothing is published ; this is just the scan step . < / p >
< form id = "up" style = "display:grid;gap:12px;max-width:520px;margin-top:1rem" >
< input type = "file" name = "file" required
style = "padding:12px;background:var(--panel);border:1px solid var(--line);border-radius:10px;color:var(--ink)" >
< button class = "btn acid" type = "submit" > Upload & amp ; scan < / b u t t o n >
< div id = "status" style = "color:var(--mut);font-size:14px;min-height:1.4em" > < / d i v >
< / f o r m >
< pre id = "result" style = "display:none;max-height:480px;overflow:auto;margin-top:1.2rem" > < / p r e >
< / s e c t i o n >
< script >
( ( ) => {
const form = document . getElementById ( 'up' ) ;
const status = document . getElementById ( 'status' ) ;
const result = document . getElementById ( 'result' ) ;
form . addEventListener ( 'submit' , async ( ev ) => {
ev . preventDefault ( ) ;
const file = form . file . files [ 0 ] ;
if ( ! file ) return ;
status . textContent = \ ` Uploading \$ {file.name} ( \$ {fmtBytes(file.size)})… \` ;
result . style . display = 'none' ;
result . textContent = '' ;
const body = new FormData ( ) ;
body . append ( 'file' , file ) ;
const started = performance . now ( ) ;
let json ;
try {
const r = await fetch ( '/api/upload' , { method : 'POST' , body } ) ;
json = await r . json ( ) ;
const elapsed = Math . round ( performance . now ( ) - started ) ;
const v = json ? . scan ? . verdict ;
const emoji = v === 'clean' ? '✅' : v === 'infected' ? '🚨' : '⚠️' ;
status . innerHTML = \ ` \$ {emoji} <b> \$ {v || 'done'}</b> — \$ {elapsed} ms round trip. \` ;
} catch ( err ) {
status . innerHTML = '⚠️ <b>network error</b> — ' + String ( err ? . message || err ) ;
return ;
}
result . style . display = 'block' ;
result . textContent = JSON . stringify ( json , null , 2 ) ;
} ) ;
function fmtBytes ( n ) {
if ( n < 1024 ) return n + ' B' ;
if ( n < 1024 * 1024 ) return ( n / 1024 ) . toFixed ( 1 ) + ' KiB' ;
if ( n < 1024 * 1024 * 1024 ) return ( n / 1024 / 1024 ) . toFixed ( 1 ) + ' MiB' ;
return ( n / 1024 / 1024 / 1024 ) . toFixed ( 2 ) + ' GiB' ;
}
} ) ( ) ;
< / s c r i p t >
prometheus: scaffold the standalone service — pages, routing, static assets
New top-level Prometheus/ directory holds the Node service that will own
prometheus.x once the on-chain record flips from the silentmode.st
redirect to ip+tls. Nothing dynamic yet — this milestone delivers the
site skeleton and proves the family's visual conventions port cleanly to
a dynamic server.
Shape:
Prometheus/service/server.js node:http listener + map-based router
Prometheus/service/layout.js document shell, topnav, footer
Prometheus/service/pages.js home / browse / submit / publish / boundaries
Prometheus/public/style.css ported silentmode.st tokens + category grid
Prometheus/public/favicon.svg flame mark
Routes: /, /browse, /submit, /publish, /boundaries, /static/*.
Dynamic routes (/api/upload, /api/manifest, /api/publish, /api/pay,
/admin/*) land here in later milestones per the architecture doc.
Launch config .claude/launch.json gets a prometheus-service entry on
port 3700 so the preview harness can run it.
The pre-pivot WordPress-based scaffold at site-prometheus-x/ is left
in place for the next session to delete deliberately — the architecture
doc marks it as abandoned for prometheus.x but keeps the Sirius Press
infra fixes it carried in.
2026-10-05 22:22:47 +02:00
< / d i v > ` ,
} ;
}
/* ------------------------------- boundaries ------------------------- */
export function boundaries ( ) {
return {
title : 'Boundaries' ,
description :
'What Prometheus does and does not do. Not a curator. Not a moderator. Storage costs money. Readers decide; the chain carries signed records.' ,
here : '/boundaries' ,
body : `
< header class = "hero compact" >
< h1 > Boundaries < / h 1 >
< p class = "tag" > What this directory is not , and why . < / p >
< / h e a d e r >
< div class = "wrap" >
< section class = "plain" >
< p class = "lede" > A permissionless app directory earns the right to call itself
permissionless by being honest about what that costs . Here is the shape of
the trade . < / p >
< / s e c t i o n >
< section >
< h2 > What Prometheus is not < / h 2 >
< div class = "grid" >
< div class = "card" >
< h3 > Not a curator < / h 3 >
< p > Prometheus does not decide what is on it . Malware can be published — as
it can on any store . The scan report tells your client to warn ; the
trust lists you subscribe to filter the view . If nobody you trust
endorses it , your client says so . < / p >
< / d i v >
< div class = "card" >
< h3 > Not a moderator < / h 3 >
< p > Content that is illegal in your jurisdiction is your responsibility to
avoid , same as any other permissionless network . Silent Mode operates
infrastructure ; it does not decide what runs on it . < / p >
< / d i v >
< div class = "card" >
< h3 > Not a payment processor < / h 3 >
< p > Payments in an app go publisher - to - reader . The listing may carry a BCH
receive address or a wallet - connect URL ; Prometheus never routes a
transaction . There is no refund desk because there is no middleman to
issue a refund . < / p >
< / d i v >
< div class = "card" >
< h3 > Not an identity provider < / h 3 >
< p > The only identity on Prometheus is the BNS name a listing is signed by .
Readers decide which names count via their own trust lists . There is no
Prometheus account , no "verified publisher" badge , no sign - in flow for
readers . < / p >
< / d i v >
< / d i v >
< / s e c t i o n >
< section >
< h2 > What Silent Mode does control < / h 2 >
< div class = "grid" >
< div class = "card" >
< h3 > The scan report < / h 3 >
< p > Our ClamAV + VirusTotal pipeline runs on every binary we host . The
signed report is attached to the listing . We cannot stop a listing from
being on chain ; we can tell readers what we found . < / p >
< / d i v >
< div class = "card" >
< h3 > Which binaries we host < / h 3 >
< p > Silent Mode ' s Sia bucket . Operators can refuse to host something and
require the publisher to self - host at their own URL . The listing still
exists ; the scan report says we did not host it . < / p >
< / d i v >
< div class = "card" >
< h3 > The default sort < / h 3 >
< p > What the Browse page shows first is a choice . We publish the sort order
and let clients override it . The chain holds the data ; the directory is
one lens . < / p >
< / d i v >
< div class = "card" >
< h3 > The publish mode < / h 3 >
< p > Three modes exist ( append - only , scan - first - blocking , human - review ) .
Operators pick which one is active per category . The current mode is
always visible on this page ; a change is announced before it takes
effect . < / p >
< / d i v >
< / d i v >
< / s e c t i o n >
< section >
< h2 > Storage costs money < / h 2 >
< p > Sia hosting is inexpensive but not free — publishers pay for the bytes
their apps take . There is no advertising model , no data harvesting , no
subscription . Storage is the only cost , and it is small . During the
opening wave Silent Mode absorbs it ; afterwards it is a flat per - listing
fee paid in BCH . < / p >
< / s e c t i o n >
< / d i v > ` ,
} ;
}
/* ------------------------------- 404 -------------------------------- */
export function notFound ( req ) {
return {
title : 'Not here' ,
description : 'The requested page does not exist on Prometheus.' ,
here : '' ,
body : `
< header class = "hero compact" >
< h1 > Not here < / h 1 >
< p class = "tag" > Nothing lives at < code class = "inline" > $ { escapeHtml ( req ? . url || '' ) } < / c o d e > . < / p >
< / h e a d e r >
< div class = "wrap" >
< section class = "plain" >
< p > Try the < a href = "/" > home page < / a > , t h e < a h r e f = " / b r o w s e " > b r o w s e < / a > o r t h e
< a href = "/publish" > publishers ' walkthrough < / a > . < / p >
< / s e c t i o n >
< / d i v > ` ,
} ;
}
/* ------------------------------- helpers ---------------------------- */
function escapeHtml ( s ) {
return String ( s )
. replaceAll ( '&' , '&' )
. replaceAll ( '<' , '<' )
. replaceAll ( '>' , '>' )
. replaceAll ( '"' , '"' )
. replaceAll ( "'" , ''' ) ;
}