Skip to content

Develop - #1128

Merged
rcbjBlueMars merged 169 commits into
mainfrom
develop
Oct 28, 2026
Merged

Develop#1128
rcbjBlueMars merged 169 commits into
mainfrom
develop

Conversation

@rcbjBlueMars

Copy link
Copy Markdown
Contributor

No description provided.

rcbjBlueMars and others added 30 commits October 26, 2026 10:07
The operator broke out of its wait on a final-looking word anywhere in the
scheduler's answer, and started t-post-cl's module at 16:36:36.703 while
that realm's rotation was still writing generations until 16:36:37.159.
The module then read a JWKS with no new signing key. It now waits for the
run's own state and then for a signing kid the JWKS did not have before.
In t-jwt-cl, oidcc-claims-essential timed out, and creating the next module
met the suite still stopping it ("setStatus() called but status is the same:
INTERRUPTED -> INTERRUPTED"), so the plan lost every remaining module. The
stop is now waited on, and a creation that still meets it is made once more.
oidcc-claims-essential timed out once in t-jwt-cl on 2026-10-26, in one plan
of twenty-four, with no request on the service and no error from the browser.
The module's status and the browser's asked and visited addresses are logged
before it is stopped, and a module that timed out is run once more; one that
hangs again is reported as before.
…ery person (#985).

On an installation of fifty million people every `users` query in the
reports application met the ten-second cap, `username equals rcbj`
included: the reader built the group index, paged every person, fetched
each entry again and applied the filter only as rows were offered, and a
row that did not match kept the walk going. The store already answers
the question through the keyed digests of uid, mail and entryUUID and
the trigram index over the RDN value, which sign-in and the portal's
delegate search use.

The catalog now says which operators the store indexes for each field
of `users` (`indexed`: username for eq, in, contains and startsWith;
mail and entryUuid for eq and in), published on the catalog API, the
OpenAPI document and the source page. The users reader reads the plan's
filter first: an indexed leaf, one arm of an and, or every arm of an or
becomes a lookup through the directory's own doors, only those rows are
offered, and the whole filter still applies to them. A synchronous read
that would still have to see everybody on a windowed directory is
refused before the walk starts (STS-REPORT-0036, result_too_large,
naming the indexed fields), and in auto mode the API queues it as a job
instead. The memory directory walks as before.

tests/report_query.js drives the planner, every lookup door, the
refusal, the auto and job distinction and the memory fallback against a
counted directory double. docs/reporting.md and report/CLAUDE.md carry
the rule.
…n, growing filter rows and a save box (#986).

Six things rcbj asked for on 2026-10-26, every one drawn by the browser
runtime from a /report-api answer, as the surface rule has it.

The bar starts with the trust realm the page is showing, on the
console's switcher shape: a select of the realms and a Go button where
the service has several, the one realm's name otherwise. A new GET
/report-api/realms answers the current realm and, where realms are
active, every realm with its id, name and prefix. Choosing another realm
leaves the page for that realm's report application at the same path,
where the runtime signs in as any first load does, because the token in
hand is audienced to the realm that issued it.

Under the bar a link opens the reporting page of the documentation
site. Under the card the service version is drawn from the index, which
now carries common/version.js's record as the console's shell does;
tests/version.js lists report_api.ts among the drawers.

The query form starts with one filter row and a + beside the last row
adds the next: the runtime clones the row, renames its controls to the
next index, empties them and moves the +. The document reader takes as
many rows as the form holds, up to the sixteen an and may carry. A Save
query box reveals the Name field and the Save button; unticked, the form
only runs.

Sign out moves from the bar into the user menu, which is therefore
drawn for every signed-in person; its account-switching section still
appears only with several accounts on.

tests/report_query.js holds the realms view, the realm field's two
shapes, the footer, a row with and without the +, the variable reader
over a form double and the OpenAPI entries. The browser's cloning and
the navigation are not held in process.
…query once Save query is ticked (#986).

The realm field, the language chooser and the user menu move into a
smaller right-justified bar above the navigation bar, which keeps the
pages and Refresh.

Enter in the Name box ran the query instead of saving it: a form
submitted that way activates its default button, the first submit
button in document order that is not disabled, and a hidden button still
counts. On 8081 that run then became a job, and the job page's row count
was what came back. Save is now first in the document and disabled as
well as hidden until the Save query box is ticked; a CSS order rule still
draws it after Run.
… a date and weekdays, and draw the top bar as one (#986).

Every job now records why it was queued (every-person, asked, limit,
source, run or schedule), the view carries it, and the job page says it
in a sentence: an auto-mode query whose filter the directory cannot
answer from an index reads every person, and until now the page only
showed that it had become a job.

A definition's owner is a subject, which no reader can place; the list
and the page show the display name or the username, resolved through
the subject resolver and the entry, and the subject stays on the API.

The schedule was a free box for a cron expression. It is a date-time in
UTC and seven weekday boxes now: weekdays ticked compose a weekly
expression at that time, none composes that date every year, and the
sentence under the controls says which and shows the expression the API
keeps, so one written by hand that the editor cannot draw is still seen.

The top row is drawn as a bar like the navigation bar, and one rule
gives its selects, buttons and the user menu's summary the same box: the
language chooser came out with the browser's own background and the
menu with its own shape.
#499 moved the MEDIUM line to 3 and left the evaluators' factors alone, so a
single address signal lifts a familiar sign-in to MEDIUM less often than it
did: a Tor exit (×5) from a model score of 0.6, where a line at 1 needed
0.2. The ticket offered three ways out — raise the factors in proportion,
move the HIGH line with the MEDIUM one, or decide on the signal itself —
and this takes the third. The factors are evidence about the sign-in and
θ is the population's prior odds (Freeman et al., Eq. (4)); rescaling one
by the other would count the prior twice. And what a loaded list says is
about the ADDRESS, not about this person's habits, so what to do about it
is a decision, and decisions are policy.

The built-in role-issuance document gains two template parameters and the
rules they build. `stepUpSignals` (default `tor-exit, reputation,
operator-deny`; `none` builds no rule) adds `risk-signal-step-up` after the
two MEDIUM rules: below MEDIUM, a sign-in carrying one of them is refused
until it carries a second factor. `refuseSignals` (empty by default, for
#226's reason) adds `risk-signal-refuse` after `risk-high`: a sign-in
carrying one is refused at any level, however many factors it presented.
The MEDIUM rules exclude the refuse list, so the risk rules stay mutually
exclusive and a Deny still carries one instruction. The applications
`neverLockOut` names are stepped up or alarmed on either list through the
same `elevated` test as HIGH, never refused; STS-RISK-0038's description
says so.

tests/risk_decisions.js B8–B16 hold it: LOW from each listed signal asks
for a second factor and is permitted once one is carried, a signal off the
list leaves LOW to the roles, `none` builds no rule, the list is the
realm's, a refuse list refuses at LOW and at MEDIUM, the console is never
refused on either, and the rules sit where ordered-deny-overrides needs
them. The step-up rule removed is caught by seven assertions.
docs/risk-scoring.md and risk/CLAUDE.md carry the reasoning.
The route was registered by the realm switch and left undescribed, which
the metadata drift check reports; the row says what the application
draws from it.
…ps, give a group an address, and edit the query with the New Query controls (#989).

The definition page's groups box becomes the console's search-and-results
pane: a text search over the realm's groups at GET /report-api/groups,
five a page with Previous and Next, an Add beside each hit, and the
chosen groups in a list with a Remove each. Save posts the definition and
then the list to the share endpoint. The query is edited with the New
Query form's own controls, filled from the saved document; a filter the
rows cannot draw is shown as JSON and carried back until a row is filled
in. Save and Delete are buttons at the foot, in a two-column editor.

The shared groups are also who is emailed the result. A group entry may
carry one mail attribute, given at the create or set and cleared through
the group page's Email address form and POST /admin-api/groups/set-mail;
the mail channel gains a group recipient, resolved by the directory, so
the address never comes from a request. When a job finishes with Notify
on, each shared group with an address is sent the notice, and each
without is STS-REPORT-0037, logged and on the audit row, while the others
are still sent to.

tests/report_query.js holds the search, the recipients split, the form's
prefill and the pane; tests/mail.js the group recipient; and
tests/directory_group_writes.js the address on the entry. The browser's
Add, Remove and search, and a notice to a real transport, are not held
in process.
…make the editor's columns one height (#989).

Every field of the reports application carries a title on its label, the
console's shape, from sentences in the seven languages. Under the
definition page's Run box a sentence names the parameters the saved
filter holds, or says it holds none and the box is ignored, and gives
the shape of a parameter leaf and one example run object; the two JSON
examples ride in as message values, since a brace in a catalog message
is a parameter to the message format. The editor's two columns stretch
to one height, each drawn as a panel.
…e it took, and why a run did not happen (#989).

A schedule was set and no job was seen, and the page gave no way to tell
"not due yet" from "skipped" or "off". The definition view now carries
nextRunAt and lastSlotAt beside the notes the schedule job leaves, and
the page draws them under the editor with one sentence on how a schedule
runs: an enabled schedule is checked about once a minute, the most
recent occurrence not yet taken is queued as a job, the first within a
minute of saving, and a save with Enabled clear keeps the schedule and
never runs.
…dules (#992).

Every definition that carries a schedule, with its next run, the
occurrence it last took, the notes a skipped run left and its most
recent scheduled jobs. A report:use token sees the definitions it owns
or is shared; report:admin sees the whole realm and may name one owner,
by subject or by username. A report:use token that names another owner
is refused (STS-REPORT-0038) rather than narrowed to its own, so an
answer never means something other than what was asked.
…ubble (#989).

Download of a completed job said only that something went wrong. The
page's API helper read every application/json answer as the API's own
JSON, so a result saved in JSON format came back parsed with no bytes to
hand to the browser. A result is now read as bytes whatever its type,
a refusal still as JSON for its reason, and a failed download names the
reason or the HTTP status.

Every act now reports in a status bubble at the top of the card, the
console's own: saved, deleted, queued as a job, a refused run or save, a
failed download. A refusal no longer repaints the page, so the form stays
as it was and the reason sits above it.
…ires.

sts_passkey_report created its person with invent: false and nothing else,
so in the single-node (product) mode the realm's attribute policy refused
the create with 400 and the job failed on 2026-10-26; the memory mode had
hidden it. The four attributes every person must hold are passed, as the
other passkey jobs do.
A PDF downloaded from the jobs tab clipped every cell at its column's
edge and drew the rest of the value nowhere, which a reader could not
tell from a short value. A cell is now laid out on as many lines as its
column needs: broken at a space where one falls within the line, a word
longer than the line broken where the line ends, a CR or LF in the value
a break of its own. A cell draws at most 256 WinAnsi bytes, after which
it is cut and ends in `...` inside the cap, so a cut is visible too.

A row is as tall as its tallest cell, and a page holds forty LINES of
data rather than forty rows, so forty single-line rows still fill one. A
row that does not fit in what is left of a page starts the next page
whole; a row taller than a whole page continues on the next, because a
row cut at the page's foot is the same clipping seen from below. The
header wraps as well, and its extra lines come out of every page's data
budget so the table never runs into the page number. The page cap is
applied to the pages the layout needs, so a wrapped report can be
refused where a clipped one was not; the alternative is a page count
that lies. The layout is a character count over Helvetica's average
advance, the heuristic the clipping already used, because the file
carries no font metrics.

tests/report_query.js holds it: a long value drawn whole on several
lines, none wider than the column; a two-line row moving a report onto
a second page, and starting that page rather than splitting; a value
over 256 bytes cut to 253 and ending in `...`; a 256-byte cell in a
three-character column drawn whole across three pages; a blank cell, a
line feed, and the forty-line page. With no wrapping at all, six of
them go red. docs/reporting.md and report/CLAUDE.md say the rest.
…wers (#672).

"Times you have signed in" on /portal said 0 for everybody since the portal's
first day (2026-09-06). admin_stats.userDetail() answers { user, tokens,
artifacts } and keeps the count on user.authentications, the row /admin/users
draws, which was right all along; the Overview read detail.authentications, a
member that shape never had, so Number(undefined || 0) was the figure on every
account page. Nothing failed: the page drew a number, the number was a number,
and the console beside it disagreed quietly.

The view now reads detail.user.authentications, and null with no row as
before, so the page draws no figure rather than a zero it cannot stand behind.

Two tests hold it. tests/portal_overview_count.js builds the Overview's view
from the real register after real recordAuthentication() calls, in process,
because the claim is about the shape one module answers and another reads:
three sign-ins are three, a fourth makes four, a person with no row gets null,
and the count is on user and nowhere else. tests/protocol/sts_portal_sessions.js
asserts over HTTP, after the owner's real sign-in, that the operation's JSON
and the drawn page both carry a figure of at least one, with a bounded wait for
a worker that is a change-log pull behind.
…#507).

The session-ID cache has refused a resumption under another server name
since 2026-10-07 (RFC 6066 section 3), and TLS 1.2 session tickets went
on resuming under any name: node resumes a ticket inside OpenSSL with no
event, OpenSSL compares no name on a hit, and nothing node exposes carries
the name a ticket was made under — `servername` is false after a TLS 1.2
hit and the session's DER holds no name, with or without an SNI callback
answering a context (shown by hand against a bare node server). What a
resumed TLS 1.2 session does keep is the master secret of the session it
resumes.

So the listener records, at the end of every full TLS 1.2 handshake, the
name the hello offered under a SHA-256 of that session's master secret
(read from getSession()'s SSL_SESSION, never the secret itself), and on
every resumed TLS 1.2 connection looks it up and closes the connection
when the name differs (STS-TLS-0052) or this node has no record
(STS-TLS-0053). Closed rather than resumed afresh, because by
secureConnection the handshake is over; prepended, so nothing reads the
connection first. The record is per listener and per process, bounded by
4096 entries and the listener's session lifetime, and a replicated shared
store (tls.ticketNames, a CELL-tier store beside tls.presentedChains)
carries it to the node that did not seal a ticket under the cluster's
shared key; nothing waits for a row, since a mismatch must close at once.

Of the ticket's four options this is the first. Turning TLS 1.2 tickets
off makes TLS 1.3 tickets stateful and every TLS 1.3 resumption a
per-process one; node exposes no ticket callback to carry the name in;
and no hook permits a full-handshake fallback once OpenSSL has accepted
the ticket. TLS 1.3 is left alone on purpose: RFC 8446 section 4.2.11
removes the server's check and section 4.6.1 binds the client to one
name, and OpenSSL resumes a TLS 1.3 PSK under another name.

The tlsfuzzer plan's stale entries: the session-ID exceptions of
test-sessionID-resumption and test-extended-master-secret-extension are
the cluster mode's only now (the cache resumes on one node), and
test-resumption-with-wrong-ciphers stays out for the right reason — every
probe offers only CBC suites, which the BCP 195 list refuses before any
session exists; the cipher check on a resumption is OpenSSL's, which
answers illegal_parameter as the script expects.

tests/listener_tls_policy.js L5d and L5e: a ticket made under one name
is resumed and then closed unanswered under another or under none, and
resumed and answered under its own; a TLS 1.3 ticket is resumed and
answered under another name.
# Conflicts:
#	docs/error-codes.md
…ole pages its tables (#997).

Both lists showed everything on one page; the jobs API paged by a cursor
the page never followed. GET /report-api/definitions and GET
/report-api/jobs now take page and per (fifty unless given, at most two
hundred) and answer page, pages, per and total beside the rows; the jobs
list still answers a cursor walk with after and limit.

The two pages draw the console's pager above and below the table: first
and previous, a window of seven page numbers with the current one marked,
next and last, the line saying page x of y and rows a to b of n, and a
rows-per-page choice. Every page is a link under the list's own address
with page and per in the query, so a page can be linked to and the back
button works; the choice of size moves to page one the same way.
admin_stats.js's users register is realms.map({ persist: 'admin_stats.users',
merge: 'own' }), and that facade journals a set(), a delete() and a clear()
and nothing else. A record was set once, in userRecord() at creation, and
then edited where it stood: recordAuthentication() incremented
authentications, pushed the event, moved lastAt and bumped the protocol and
method counts; noteKnownIdentity() wrote knownBy. So the row in the store
was the record as it stood at the first flush after its creation, one
authentication, and it was never rewritten.

On 8081 (product mode, postgres, two request workers) an account with
sixty-two accepted credentials in the audit log read authentications 1, with
firstAt and lastAt equal and twelve days old. The audit rows' own
authenticationsSoFar climbed 2, 3, ... 8 within one worker's life and read 2
again after each restart, because the restart restored the frozen row and
the other worker read the same frozen row through the fan-in. sts_changes
held six rows for the store in two days, all record creations. The portal's
"Times you have signed in" (#672) and the console's user page both draw the
figure, so #672's fix to the page changed nothing a person could see there.
Development mode on the memory store never restarts and never persists,
which is why #672's tests passed.

touchUserRecord() re-sets the key after the edits, once per sign-in, the
answer persistence/CLAUDE.md gives for a store of mutable rows and the one
the CAEP and RISC registers already use. The cost is one minted-own change
row per sign-in, which the read barrier does not wait on. The eviction past
MAX_USERS is unchanged.

tests/user_register_journal.js holds it against the minted journal and a
stub store: the first sign-in writes the row; a second leaves the journal
dirty and the next flush writes the row again, with two; the written record
carries both events and a moved lastAt; a fresh module state restoring from
the store answers the stored count; a person made by hand is written with
the knownBy set after creation. Without the re-set the second flush writes
nothing for the person and the stored count stays 1. The admin console and
user-facing pages docs say what the count now is.
A new report source, `credentialHolders`: one row per person, per
mechanism, per role, read from what the directory already keeps. A
passkey's role is the one chosen at its enrolment (`primary` or `mfa`,
the key's own `role`, which sign-in enforces); its algorithm and lane
are `keyAlgorithm()`'s. An authenticator app and an emailed code are
`mfa`; a certificate and a Kerberos key are `primary`. The row carries
how many credentials of that kind the person holds, their distinct
algorithms and lanes, the earliest enrolment, the latest use, the
RFC 8176 methods they have signed in with, the last multi-factor
sign-in, and their groups.

Nothing new is stored or indexed and nothing changes what is sealed. A
filter on `mechanism` reads that mechanism's holders through the
store's asked-names index (`holdersAsync()`, now on the directory
module's exports as well as the credential store's), bounded by the
holders; a filter on `memberOf` reads the group's members by name. A
query naming neither reads every mechanism's holders, which a windowed
directory refuses synchronously (STS-REPORT-0036) and `auto` mode turns
into a job. Recovery codes and app passwords are not listed, because
their attributes are outside that index.

The census is the engine's count: `aggregate: "count"` with `groupBy`
on `mechanism`, `role`, `algorithms` or `lanes`, narrowed to a community
by a `memberOf` filter. `tests/report_query.js`'s `holders()` drives it
against the directory double; `docs/reporting.md` and `report/CLAUDE.md`
describe it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
rcbjBlueMars and others added 29 commits October 27, 2026 12:55
…s for a certificate; the coverage notes, the crypto page and the reasoning in ldap/CLAUDE.md say the same.
…ificate (#1061).

identityOf()/checkSocket() take { stored: true }: a certificate whose chain
runs through no authority this service holds is the one person or
application entry of the realm storing its DER in userCertificate, found by
the SHA-256 through the directory store's value index (windowed) or a
checked index on its own clock (resident), never by common name or subject.
Every door that reads a certificate as a person or application asks that
way: /tls/sign-in and the sign-in screen, SCIM, the XACML gates, Shared
Signals, WS-Trust and LDAP SASL EXTERNAL. The handshake no longer seeds a
directory entry from an outside certificate. The console's /admin-api twin
stores and removes a certificate (add-certificate, remove-certificate,
GET /users/certificates). New codes per door; STS-LDAP-0182 retired.
… docs.

The person page draws Stored certificates (Credentials, Factors) over GET
/admin-api/users/certificates with Store and Remove; six translations.
Coverage notes and the SCIM, LDAP and setting prose say an outside
certificate is the one entry storing it. tests/stored_certificates.js holds
the mapping, the admin actions and /tls/sign-in, the sign-in screen, SCIM and
the XACML gates over real handshakes; the SSF, WS-Trust and LDAP SASL tests
gain the outside cases; revocation_status and credential_seeds_no_entry
store the foreign leaves they present or assert that nothing is seeded.
pep-credential.js gains storeOnEntry()/--store-on, and the XACML, console
bootstrap, global logout and pooling jobs store their leaves. The docs pages
of every door carry the rule, how to store a certificate and the upgrade
note.
…anged prose.

The resident index is rebuilt whenever its clock moved, not only on a miss:
it answers how many entries store a certificate, and a second holder left the
first hit good and the count wrong. The revocation case presents a stored
certificate whose CRL cannot be fetched, under hard-fail. The ldap, scim,
settings and stsmetadata catalogs carry the new English and its six
translations (and zh-Hant-HK's two).
…at the next realm load, not after ten minutes of STS-STORE-0088.
Every listener sent the leaf, the Issuing CA and the Intermediate, and left
the Root out, which RFC 8446 section 4.4.2 allows. Every Root is called
CN=sts Root CA, O=sts, so a client never handed the current one saw only a
name and could match it to an older Root it already held.

common/pki.js chainThroughRoot() is now the one place a reported chain is
completed: the leaf, the stored chain, and the Root that signs its top,
checked by signature. tls/tls_server.js presentedBundlePem() builds every
socket's bundle from it, and the main port, the debugger, the custom and
realm listeners, LDAPS, the channel between cells and SPIFFE's gRPC
listeners all take their bundle from there. What a certificate is stored
with (chainPem) is unchanged.
…1064).

common/pki.js chainThroughRoot() completes every chain this service reports
about its own certificates: the leaf, the stored chain, and the Root that
signs its top, checked by signature. It accepts PEM or bare base64.

* JOSE: every published JWK x5c (the realm JWKS and its standby keys, the
  pinned and grouped signers, the certified key pairs, /crypto/metadata)
  goes through helpers.x5cOf(); jose_certificate_header.js takes its
  token-header chains from chainThroughRoot() under its existing memo.
* XML: helpers.x509DataXml() writes every ds:X509Data, the leaf first.
  Every signature's KeyInfo (crypto.js signXml() takes it as keyInfoXml,
  handed over by the seven signers) and every metadata KeyDescriptor,
  signing and encryption (SAML 2.0, WS-Federation, the back channel's
  listener keys, a federation relationship's own metadata) carry the chain
  and the Root. The vendored xmldsig.js is unchanged.
* The remote PEP's listener certificate bundle and the console's chain
  download gain the Root too.

HAIP 1.0 forbids the trust anchor in the x5c of a credential, a Status List
Token and a signed OpenID4VP request; those keep it out, and the OpenID4VP
verifier's x5c now says so at its call into chainThroughRoot().
…es nothing: the disable ended what it had to, and the enable superseded it.
…drive it by its documented 400 in the read sweep (#1061).
).

tests/tls_presented_root.js completes a real handshake on the bundle every
listener presents and counts four certificates arriving, the last of them
this process's Root by fingerprint, verified by a client trusting that Root
alone. It holds chainThroughRoot() (matched by signature, a foreign chain
answered as it came, withoutRoot, bare base64), a published x5c and an XML
X509Data ending at the Root with the leaf first, an XML signature carrying
the chain still verifying here and under xml-crypto reading KeyInfo, and the
listeners taking their bundle from presentedBundlePem().

docs/configure-tls.md says what the handshake sends and how to compare
thumbprints; docs/pki.md lists every place a chain is reported and the four
signed objects that keep the Root out. tls/CLAUDE.md and common/CLAUDE.md
(3w) carry the reasoning.
tests/pki.js and the local protocol job sts_jwt_bearer_grant.js counted
three members in an issued key pair's x5c; with the Root it is four.
tests/tls_presented_root.js certifies the XML key itself before reading its
chain, as tests/jose_certificate_header.js does, so it passes on its own and
not only after an earlier file has certified it.
…ed, a refusal floats (#1066).

The simplified view of /admin/users/new had no uid box, and nothing showed
which fields the realm's attribute policy requires. The username box moves
into the field grid as the first cell of the Name group, in both views. It
still posts `username` and never `field.uid`, so one form cannot name two
uids. Every field the policy requires carries fg-required: a red rule and
an asterisk, beside the word "required", in the shared grid cell, so a
person's Attributes tab outlines the same fields. The tile counts the
policy's required attributes; it was a constant 1. A refused create floats
in kit.flash(), as the application pages' refusals do.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ner (#1066).

WebKit.refusalBanner() draws a refusal as the application pages do: a
kit.flash() warning with a heading and the reasons as a list. A refused
create, a refused Fill, the Service account tab's owners and destination,
and the shared owner-protection block now use it. That block also sits on
the Groups, Roles and Applications Owners tabs, which float the same way. A
refusal that came back as ?error= already floated through
ConsoleRuntime.draw().

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…n force (#1066).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rid (#1066).

rcbj withdrew the uid cell: the username box at the top of the form is
the entry's uid. The form is drawn as it was on develop. The note under
the username no longer calls it the only required field, because the
attribute policy always requires cn and sn as well, outlined in the grid
below. The outlined required fields, the policy-counted tile and the
floating refusals stay.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
5d19086 described the endpoint on the metadata page without rewriting the
endpoints and stsmetadata catalogs, so server_prose_catalogs failed on the
English alone. The English is regenerated from the source and the two keys
carry their six translations (es, fil, fr, sv, zh-Hans, zh-Hant).
…of the request-walk guard (#1061).

certificateHolders() rebuilds its index over every entry when its clock moved,
and GET /admin-api/users/certificates and the add/remove operations reach it
from a request. The walk is the memory branch only: a windowed process visits
directoryWindow.eachResident() — the resident containers — and asks the store's
usercertificatesha256 index for the people and devices. Run #5's memory mode
failed no_full_directory_walk_in_requests on it.
…s secrets have Copy buttons (#1068).

A page shown once was drawn with no `up`, so its section was the last
crumb, which is never a link. The page after a create had no way back to
Directory → Users. ConsoleRuntime now hands the shell
`once.up || { href: active, leaf: title }`, so every one-time page links to
its section. A create and a credential reset answer
WebAnswers.personTrail(): Admin console › Users › <person> › <title>, with
the person's page linked through trailBar()'s new `via` crumbs.
WebAnswers.secretBlock() draws each value shown once (a password, an
activation or reset link, an app password, a new realm's password) with
WebKit.copyButton() under it, which draw() already reveals and wires.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… a read of the data-key rows, not dropped as malformed (#1070).

applyChange() reported every change whose sealed name named a data
encryption key the pulling process had not read yet as STS-STORE-0014 at
error and dropped it: a new realm's seed rows, ~1,400 lines a full run.
keystore.namesUnheldDek() now tells that apart from a malformed name; the
data-key rows are read again (once per page, in prefetch()) and the change
applied. A key still not held after a read is skipped under the new
STS-STORE-0089, said once per key.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@rcbjBlueMars
rcbjBlueMars merged commit a253924 into main Oct 28, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant