Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
82ec432
feat(storage): fast pod quota — DuSizeReporter + FastQuotaStrategy (A+B)
Aug 15, 2026
3eefb4b
docs: add pod storage quota design & verification (POD-STORAGE-QUOTA.md)
Aug 15, 2026
94695fe
feat(storage): design C — incremental per-pod byte counter (O(1) quota)
Aug 15, 2026
d8c1135
fix(quota): skip delta tracking for /.internal paths (avoids IDP lock…
Aug 16, 2026
6692db8
fix(quota): exempt /.internal paths from quota validation (avoids IDP…
Aug 16, 2026
00b4cda
docs: document /.internal lock-expiry incident and fix (POD-STORAGE-Q…
Aug 16, 2026
5e54d02
fix(quota): match /.internal on URL pathname, not raw identifier.path
Aug 16, 2026
4e6ab3d
docs: correct §9 root cause — ResourceIdentifier.path is a URL (5e54d02)
Aug 16, 2026
bf46bce
fix(deploy): run design C quota in prod.json — quota-counter config w…
Aug 16, 2026
13c1882
fix(deploy): run design C quota in suffix.json too (same lock-expiry …
Aug 16, 2026
c4a854e
Revert "fix(deploy): run design C quota in prod.json — quota-counter …
Aug 16, 2026
4a97109
Revert "fix(deploy): run design C quota in suffix.json too (same lock…
Aug 16, 2026
086ca6f
fix(locking): raise expiring lock 6s→30s via long-expiry.json (wired …
Aug 16, 2026
66f0485
fix(quota): discover subdomain pods (metadata before root-container t…
Aug 17, 2026
786fe35
test(quota): add subdomain pod-discovery regression test; adapt FastQ…
Aug 17, 2026
2860a5f
test(jest): ts-jest isolatedModules — transpile-only so tests run wit…
Aug 17, 2026
bcda86c
mashlib@2.3.2
bourgeoa Aug 21, 2026
babf0d0
Merge branch 'pod-quota-counter' of https://github.com/solid-contrib/…
bourgeoa Aug 21, 2026
b537190
Merge branch 'main' into pod-quota-counter
bourgeoa Aug 23, 2026
4c46845
fix: port CSS lock/sweep hardening
bourgeoa Aug 30, 2026
d5e6428
fix: move file-locker override out of shared config
bourgeoa Aug 30, 2026
4eacc9d
merged fix/clear-lock an pod-quota-counter branches
bourgeoa Aug 31, 2026
efb3aca
fix(config): exclude nested .internal from quota walks and align IDP …
bourgeoa Sep 1, 2026
0d9d33a
fix(config): exclude nested .internal from quota walks and align IDP …
bourgeoa Sep 1, 2026
7e7dd4e
fix(config): align IDP adapter sweep timeout
bourgeoa Sep 1, 2026
5eec704
Merge branch 'pod-quota-counter' into clearLock-quotaCounter
bourgeoa Sep 1, 2026
4752798
Merge branch 'fix/clear-lock' into clearLock-quotaCounter
bourgeoa Sep 1, 2026
8a02418
feat(storage): guard the profile card of registered WebIDs
bourgeoa Sep 9, 2026
cb41a9d
Merge branch 'clearLock-quotaCounter' into webid-guard
bourgeoa Sep 9, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
538 changes: 538 additions & 0 deletions POD-STORAGE-QUOTA.md

Large diffs are not rendered by default.

16 changes: 4 additions & 12 deletions config/customise-me.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@
"https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^7.0.0/components/context.jsonld",
"https://linkedsoftwaredependencies.org/bundles/npm/@solid/pivot/^1.0.0/components/context.jsonld"
],
"import": [
"pivot:config/storage/backend/quota-counter-file.json",
"pivot:config/storage/resource-locker/long-expiry.json"
],
"@graph": [
{
"comment": "The settings of your email server.",
Expand Down Expand Up @@ -31,18 +35,6 @@
"templateFolder": "templates/pod"
}
},
{
"comment": "Sets the maximum size of a single pod to 70MB.",
"@type": "Override",
"overrideInstance": {
"@id": "urn:solid-server:default:QuotaStrategy"
},
"overrideParameters": {
"@type": "PodQuotaStrategy",
"limit_amount": 70000000,
"limit_unit": "bytes"
}
},
{
"comment": "Serve Databrowser as default representation",
"@id": "urn:solid-server:default:DefaultUiConverter",
Expand Down
4 changes: 3 additions & 1 deletion config/dev-http-subdomain.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,16 @@
"css:config/storage/key-value/resource-store.json",
"css:config/storage/location/pod.json",
"pivot:config/storage/middleware/default.json",
"pivot:config/storage/profile-card-guard.json",
"css:config/util/auxiliary/acl.json",
"css:config/util/identifiers/subdomain.json",

"css:config/util/logging/winston.json",
"css:config/util/representation-conversion/default.json",
"css:config/util/resource-locker/file.json",
"css:config/util/variables/default.json",
"pivot:config/pivot-overrides.json"
"pivot:config/pivot-overrides.json",
"pivot:config/pivot-file-locker-overrides.json"
],
"@graph": [
{
Expand Down
4 changes: 3 additions & 1 deletion config/dev-http-suffix.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,16 @@
"css:config/storage/key-value/resource-store.json",
"css:config/storage/location/pod.json",
"pivot:config/storage/middleware/default.json",
"pivot:config/storage/profile-card-guard.json",
"css:config/util/auxiliary/acl.json",
"css:config/util/identifiers/suffix.json",

"css:config/util/logging/winston.json",
"css:config/util/representation-conversion/default.json",
"css:config/util/resource-locker/file.json",
"css:config/util/variables/default.json",
"pivot:config/pivot-overrides.json"
"pivot:config/pivot-overrides.json",
"pivot:config/pivot-file-locker-overrides.json"
],
"@graph": [
{
Expand Down
20 changes: 20 additions & 0 deletions config/pivot-file-locker-overrides.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"comment": "Overrides specific to configs that use the file-system resource locker. Imported only by prod/dev configs, NOT by test.json (which uses the memory locker).",
"@context": [
"https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^7.0.0/components/context.jsonld",
"https://linkedsoftwaredependencies.org/bundles/npm/@solid/pivot/^1.0.0/components/context.jsonld"
],
"@graph": [
{
"comment": "Bound the lock acquisition retries so a contended lock fails fast instead of spinning the event loop and filesystem threadpool forever (~30s max wait).",
"@type": "Override",
"overrideInstance": { "@id": "urn:solid-server:default:FileSystemResourceLocker" },
"overrideParameters": {
"@type": "FileSystemResourceLocker",
"attemptSettings_retryCount": 600,
"attemptSettings_retryDelay": 50,
"attemptSettings_retryJitter": 30
}
}
]
}
76 changes: 76 additions & 0 deletions config/pivot-overrides.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,82 @@
"templateEngine": { "@id": "urn:solid-server:default:TemplateEngine" },
"template": "./templates/main.html.ejs"
}
},
{
"@type": "Override",
"overrideInstance": { "@id": "urn:solid-server:default:AccountStorage" },
"overrideParameters": {
"@type": "SafeBaseLoginAccountStorage",
"storage": { "@id": "urn:solid-server:default:IndexedStorage" }
}
},
{
"comment": "Scope the cookie sweep to its own container so entries()/sweeps do not walk the entire /.internal/ tree (sustained high CPU with many accounts).",
"@type": "Override",
"overrideInstance": { "@id": "urn:solid-server:default:CookieStorage" },
"overrideParameters": {
"@type": "WrappedExpiringStorage",
"source": {
"@type": "MaxKeyLengthStorage",
"source": {
"@type": "JsonResourceStorage",
"source": { "@id": "urn:solid-server:default:ResourceStore_Backend" },
"baseUrl": { "@id": "urn:solid-server:default:variable:baseUrl" },
"container": "/.internal/accounts/cookies/"
}
}
}
},
{
"comment": "Scope the forgot-password sweep to its own container.",
"@type": "Override",
"overrideInstance": { "@id": "urn:solid-server:default:ForgotPasswordStorage" },
"overrideParameters": {
"@type": "WrappedExpiringStorage",
"source": {
"@type": "MaxKeyLengthStorage",
"source": {
"@type": "JsonResourceStorage",
"source": { "@id": "urn:solid-server:default:ResourceStore_Backend" },
"baseUrl": { "@id": "urn:solid-server:default:variable:baseUrl" },
"container": "/.internal/accounts/forgot-password/"
}
}
}
},
{
"comment": "Scope the token sweep to its own container.",
"@type": "Override",
"overrideInstance": { "@id": "urn:solid-server:default:ExpiringTokenStorage" },
"overrideParameters": {
"@type": "WrappedExpiringStorage",
"source": {
"@type": "MaxKeyLengthStorage",
"source": {
"@type": "JsonResourceStorage",
"source": { "@id": "urn:solid-server:default:ResourceStore_Backend" },
"baseUrl": { "@id": "urn:solid-server:default:variable:baseUrl" },
"container": "/.internal/idp/tokens/"
}
}
}
},
{
"comment": "Scope the adapter sweep to its own container. Requires the @id added to the installed CSS adapter-factory.json by the patch-package override.",
"@type": "Override",
"overrideInstance": { "@id": "urn:solid-server:default:ExpiringAdapterStorage" },
"overrideParameters": {
"@type": "WrappedExpiringStorage",
"source": {
"@type": "MaxKeyLengthStorage",
"source": {
"@type": "JsonResourceStorage",
"source": { "@id": "urn:solid-server:default:ResourceStore_Backend" },
"baseUrl": { "@id": "urn:solid-server:default:variable:baseUrl" },
"container": "/.internal/idp/adapter/"
}
}
}
}
]
}
9 changes: 8 additions & 1 deletion config/prod.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,15 @@
"css:config/storage/key-value/resource-store.json",
"css:config/storage/location/pod.json",
"pivot:config/storage/middleware/default.json",
"pivot:config/storage/profile-card-guard.json",
"css:config/util/auxiliary/acl.json",
"css:config/util/identifiers/subdomain.json",
"css:config/util/logging/winston.json",
"css:config/util/representation-conversion/default.json",
"css:config/util/resource-locker/file.json",
"css:config/util/variables/default.json",
"pivot:config/pivot-overrides.json"
"pivot:config/pivot-overrides.json",
"pivot:config/pivot-file-locker-overrides.json"
],
"@graph": [
{
Expand Down Expand Up @@ -69,6 +71,11 @@
"@id": "urn:solid-server:default:ExpiringTokenStorage",
"@type": "WrappedExpiringStorage",
"timeout": 1
},
{
"@id": "urn:solid-server:default:ExpiringAdapterStorage",
"@type": "WrappedExpiringStorage",
"timeout": 1
}
]
}
88 changes: 88 additions & 0 deletions config/storage/backend/quota-counter-file.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
{
"comment": "Design C: incremental per-pod byte counter. QuotaCounter + IncrementalSizeReporter + QuotaDeltaDataAccessor (delta hook) + FastQuotaStrategy. Writes are O(1) after bootstrap; a full du/Node walk only seeds/repairs a pod's counter.",
"@context": [
"https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^7.0.0/components/context.jsonld",
"https://linkedsoftwaredependencies.org/bundles/npm/@solid/pivot/^1.0.0/components/context.jsonld"
],
"@graph": [
{
"comment": "The shared per-pod counter (in-memory + sidecar + recount engine).",
"@id": "urn:solid-server:default:QuotaCounter",
"@type": "QuotaCounter",
"fileIdentifierMapper": {
"@id": "urn:solid-server:default:FileIdentifierMapper"
},
"rootFilePath": {
"@id": "urn:solid-server:default:variable:rootFilePath"
},
"ignoreFolders": [
"(^|/)\\.internal$"
]
},
{
"comment": "SizeReporter backed by the counter: O(1) pod reads, single stat for resources.",
"@type": "Override",
"overrideInstance": {
"@id": "urn:solid-server:default:SizeReporter"
},
"overrideParameters": {
"@type": "IncrementalSizeReporter",
"counter": {
"@id": "urn:solid-server:default:QuotaCounter"
}
}
},
{
"comment": "Delta hook: wraps the accessor chain, feeds per-write deltas to the counter. Preserves the content-length filter.",
"@type": "Override",
"overrideInstance": {
"@id": "urn:solid-server:default:FileDataAccessor"
},
"overrideParameters": {
"@type": "QuotaDeltaDataAccessor",
"fileIdentifierMapper": {
"@id": "urn:solid-server:default:FileIdentifierMapper"
},
"identifierStrategy": {
"@id": "urn:solid-server:default:IdentifierStrategy"
},
"counter": {
"@id": "urn:solid-server:default:QuotaCounter"
},
"accessor": {
"@type": "FilterMetadataDataAccessor",
"accessor": {
"@id": "urn:solid-server:default:ValidatingFileDataAccessor"
},
"filters": [
{
"@type": "FilterPattern",
"predicate": "http://www.w3.org/2011/http-headers#content-length"
}
]
}
}
},
{
"comment": "Pod quota strategy that computes the available space once per write (no per-chunk walk).",
"@type": "Override",
"overrideInstance": {
"@id": "urn:solid-server:default:QuotaStrategy"
},
"overrideParameters": {
"@type": "FastQuotaStrategy",
"limit_amount": 70000000,
"limit_unit": "bytes",
"reporter": {
"@id": "urn:solid-server:default:SizeReporter"
},
"identifierStrategy": {
"@id": "urn:solid-server:default:IdentifierStrategy"
},
"accessor": {
"@id": "urn:solid-server:default:AtomicFileDataAccessor"
}
}
}
]
}
50 changes: 50 additions & 0 deletions config/storage/backend/quota-fast-file.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
{
"comment": "Fast per-pod quota: DuSizeReporter (du + TTL cache) and FastQuotaStrategy (no per-chunk pod walk).",
"@context": [
"https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^7.0.0/components/context.jsonld",
"https://linkedsoftwaredependencies.org/bundles/npm/@solid/pivot/^1.0.0/components/context.jsonld"
],
"@graph": [
{
"comment": "SizeReporter backed by du with a per-path TTL cache, measuring apparent bytes (portable, user-manageable). Falls back to the Node walk when du is unavailable.",
"@type": "Override",
"overrideInstance": {
"@id": "urn:solid-server:default:SizeReporter"
},
"overrideParameters": {
"@type": "DuSizeReporter",
"fileIdentifierMapper": {
"@id": "urn:solid-server:default:FileIdentifierMapper"
},
"rootFilePath": {
"@id": "urn:solid-server:default:variable:rootFilePath"
},
"ignoreFolders": [
"^/\\.internal$"
],
"ttl": 5000
}
},
{
"comment": "Pod quota strategy that computes the available space once per write instead of per stream chunk.",
"@type": "Override",
"overrideInstance": {
"@id": "urn:solid-server:default:QuotaStrategy"
},
"overrideParameters": {
"@type": "FastQuotaStrategy",
"limit_amount": 70000000,
"limit_unit": "bytes",
"reporter": {
"@id": "urn:solid-server:default:SizeReporter"
},
"identifierStrategy": {
"@id": "urn:solid-server:default:IdentifierStrategy"
},
"accessor": {
"@id": "urn:solid-server:default:AtomicFileDataAccessor"
}
}
}
]
}
48 changes: 48 additions & 0 deletions config/storage/profile-card-guard.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
{
"@context": [
"https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^7.0.0/components/context.jsonld",
"https://linkedsoftwaredependencies.org/bundles/npm/@solid/pivot/^1.0.0/components/context.jsonld"
],
"comment": "Guards the profile card of WebIDs registered on this server, keeping the `solid:oidcIssuer` triple.",
"@graph": [
{
"comment": "Guards registered WebID cards from being deleted or losing their `solid:oidcIssuer` triple.",
"@id": "urn:solid-server:default:ResourceStore_CardGuard",
"@type": "ProfileCardGuard",
"source": { "@id": "urn:solid-server:default:ResourceStore_Converting" },
"webIdStore": { "@id": "urn:solid-server:default:WebIdStore" },
"baseUrl": { "@id": "urn:solid-server:default:variable:baseUrl" },
"converter": { "@id": "urn:solid-server:default:RepresentationConverter" },
"relativeWebIdPaths": [
"/profile/card#me"
]
},
{
"comment": "WebID store that also exposes whether a WebID is registered (hasWebId), as required by the card guard.",
"@type": "Override",
"overrideInstance": { "@id": "urn:solid-server:default:WebIdStore" },
"overrideParameters": {
"@type": "GuardedWebIdStore",
"storage": { "@id": "urn:solid-server:default:AccountStorage" }
}
},
{
"comment": "Insert the card guard between pivot's RDF patching store and the converting store, so PUT/POST as well as PATCH results are all validated.",
"@type": "Override",
"overrideInstance": { "@id": "urn:solid-server:default:ResourceStore_RdfPatching" },
"overrideParameters": {
"@type": "RdfPatchingStore",
"source": { "@id": "urn:solid-server:default:ResourceStore_CardGuard" }
}
},
{
"comment": "Route the internal key-value storage (accounts, WebID links, ...) through the converting store directly, bypassing the guard.",
"@type": "Override",
"overrideInstance": { "@id": "urn:solid-server:default:JsonResourceStorage" },
"overrideParameters": {
"@type": "JsonResourceStorage",
"source": { "@id": "urn:solid-server:default:ResourceStore_Converting" }
}
}
]
}
Loading
Loading