Roadmap §§8–22: the conformance findings, the host strategy, a security review, and the handover - #20
Merged
Merged
Conversation
…an old wire Building the conformance gate the earlier analysis called for turned up two P0s in the shipped product, so ROADMAP.md gains a section §8 with the measurements and two corrections — one to its own §1 (the health crons watch each hop's success, and every hop succeeded while republishing a frozen artefact), one to the 2026-09-17 analysis that preceded the work (its recommendation to drop the 41 transpiled framework classes was wrong: none of them is shadowed by a hand-port, they are all fill-ins). docs/reference/protocol.md opened by claiming the wire format is "identical to abap2UI5". Measured against upstream's live Node runtime, it is not: the page documents S_FRONT.PARAMS, upstream now sends S_FRONT.S_ACTION action rows and has flattened two-way bindings out of MODEL.XX. The page is still accurate for debugging cap2UI5's own backend, so it keeps its content and gains a danger callout saying which of the two it describes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JoANwtaK8Jm9srGEqSxmUF
Adds ROADMAP §9: the draft-store and app-serializer seams that landed upstream, and the spike they made possible — a serializer written in plain JavaScript, installed through set_serializer( ), carried hi_world's app state across two roundtrips with no CALL TRANSFORMATION and no S-RTTI in the path. Also records what the spike does NOT prove, because the distinction decides whether anyone can plan on it: the probe kept the live container in a Map rather than projecting it to a persisted shape, so reconstruction from a real JSON document in a cold process is still untested. The §8 blocker is gone; the next unknown is one step further in. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JoANwtaK8Jm9srGEqSxmUF
Naht 3 (guarding the codepage fallback upstream) closes the second P0 from §8: the modified upstream was mirrored into builder-abap2UI5-js, re-transpiled and rebuilt, and the rebuilt core answers a roundtrip instead of hanging — so the hi_world fix from §8 reaches the package too. Naht 4 puts a version on the wire, which does not fix the protocol break but ends its silence. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JoANwtaK8Jm9srGEqSxmUF
ROADMAP §11. Measured with a transpiled ABAP app as the control on every run:
_bind( ) over a JS field resolves to {/NAME}, the two-way delta lands on the JS
object, and the state survives a roundtrip with app_cont's process buffer
cleared, so the second roundtrip comes back through the draft document.
The enabling detail is a declared 'static ATTRIBUTES' map in the shape a
transpiled ABAP class carries - without it the framework answers BINDING_ERROR.
So a cap2UI5 app is a plain class plus a schema declaration, which is an API
decision rather than a derivation.
Not proven: a genuinely cold second process. The default SQLite client is
in-memory only, so that test needs the CDS-backed store, which is the next item.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JoANwtaK8Jm9srGEqSxmUF
ROADMAP §12. A CDS entity plus a ~90-line JS implementation of z2ui5_if_ui5_draft_store, installed with one set_instance( ) call, puts the framework's session state in the project's own database under its connection, transactions and authorization. Proven across a SIGKILL: process A writes the draft, process B is a fresh boot with an empty app_cont buffer, and both the transpiled ABAP control app and a plain JS app answer correctly from the persisted row. That is what the two earlier spikes were explicitly flagged for not showing. Boundaries recorded too: the UI has never rendered in a browser here (the sandbox proxy blocks the UI5 CDN), no authorization was exercised, and the raw ATTRIBUTES map still wants an ergonomic wrapper. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JoANwtaK8Jm9srGEqSxmUF
ROADMAP §13. The ATTRIBUTES schema derives itself from the class's own typed fields, so an app is a plain class with plain values; and the app surface is synchronous, because the awaits were never real - _bind( ) and _event( ) await nothing but their own internal calls. Queries resolve before main( ), commands are recorded and replayed after it, and c.event( ) returns a token that is substituted at flush time. Also records that the prototype moved out of the scratchpad into builder-abap2UI5-js/docs/prototypes/open-abap-cap - 548 hand-written lines plus reproduction steps, with the transpiled output gitignored. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JoANwtaK8Jm9srGEqSxmUF
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JoANwtaK8Jm9srGEqSxmUF
… what needs a person Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JoANwtaK8Jm9srGEqSxmUF
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JoANwtaK8Jm9srGEqSxmUF
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JoANwtaK8Jm9srGEqSxmUF
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JoANwtaK8Jm9srGEqSxmUF
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JoANwtaK8Jm9srGEqSxmUF
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JoANwtaK8Jm9srGEqSxmUF
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JoANwtaK8Jm9srGEqSxmUF
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JoANwtaK8Jm9srGEqSxmUF
…it found Everything in this project had been reviewed for whether it worked. The owner binding - the one piece that decides who sees whose data - had never been reviewed for whether it could be DEFEATED. So I ran a security review over the cap2UI5 branch diff with my own code as the suspect. It found a real one, and a proof of concept confirmed it: all three ownership checks in draft-store.js were guarded by the TRUTHINESS of the stored owner rather than by its presence, so a Drafts row whose owner was NULL or "" belonged to everybody rather than to nobody. bob replayed alice's draft id against such a row and was handed alice's running app, complete with her model. The only difference between the refused request and the served one was the emptiness of one column. The section carries the measurement, the two realistic ways such a row arises (`?? ` does not catch "", and cds.User permits an empty id; and cap2ui5.Drafts is an ordinary entity in the project's model), the fix, and the three smaller findings fixed in the same pass - internal error text reaching the client, the body being buffered in front of the authorization guard, and CI running another repository's code with a write-capable GITHUB_TOKEN. It also records what the review CLEARED, because each looked like a finding until it was checked: cds.ql parameterizes, the event token cannot be forged from app state, and @abap2ui5/runtime is not a dependency-confusion target - the package is unpublished but the npm scope is claimed. The lesson, which outlives the fix: an authorization check compares presence, never truthiness. `&&` in front of a comparison in an access decision turns a missing value into a wildcard. I wrote that line three times in one file and reviewed it twice for whether it worked. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JoANwtaK8Jm9srGEqSxmUF
…DOVER
Three red checks across two PRs carried the same note: "fails identically on
main, therefore not this branch's". True, and verified - but that is an
ATTRIBUTION, not a diagnosis. It says whose the failure is and leaves the
reviewer to find out what it is. §21 does the second half.
devtoolsConsole: `git log` on both files ends at 1f2cccb (#2771), which is
main's HEAD and which added MAX_NODES and the test together - so the test was
merged red and has never passed. The cause is a property a JSON.stringify
replacer cannot have: the third assertion asks it to remove a KEY, and a
replacer answers a key's VALUE. Three-line patch proposed on the PR, verified
green (29/29) and verified to discriminate. Measured what the cap is worth
while I was there: 11% on the test's own input, 52% on the row-shaped lookup
the code comment names.
shared-file-gate: #2771's own commit message predicts one of the four drifts
("check:shared stays red until that follow-up lands"); the other three are
sync-shared.yaml against the samples repositories, from #2719 on 2026-09-05 -
a fortnight before this branch existed.
And a correction I owed. Re-measuring the builder-abap2UI5-js ratchet to check
my own proposed patch, I found I had read one of two arrays and reported the
total as if it were the one: it is 104 regressions and 57 fixed-but-still-
listed, not 161 and 0. 57 entries of a 131-entry list now pass, which makes
the re-baseline recommendation better than I described it. cs_event likewise
carries 42 constants on both sides, not 42 against 36 - six are renamed, which
my prose had said and my numbers had not. Both are posted where the wrong
numbers are.
HANDOVER.md is rewritten rather than patched. It had gone stale in a way that
mattered: it told a human to OPEN pull requests that are open, with suggested
bodies naming an interface that has been renamed (z2ui5_if_ui5_app_serializer),
a workflow job that was reworked (release.yaml -> backend-prebuilt.yaml), a
file that moved (node/package.json -> node/setup/runtime.package.json), and it
sent step 3 to the builder repo the plugin has since left. It also told them to
create an npm organisation that already exists and carries three packages. It
now says what to DO with each PR rather than repeating its body, carries both
root causes, and lists the facade slice as done instead of pending.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JoANwtaK8Jm9srGEqSxmUF
This morning I rewrote HANDOVER.md because it had gone stale, then wrote a step 4 for the repository cutover that said nothing about the documentation the cutover invalidates. Same failure I had just fixed, one section further down. Measured over docs/: 36 pages, 5,434 lines, 27 of them describing the port. Only ROADMAP.md, HANDOVER.md and one line of reference/database.md know the plugin exists. The sums check exactly against find/cat, and the three buckets add back to 36 pages and 5,434 lines. It is not stale paths. The guide teaches the port's app API, and the plugin's is a different API rather than a renamed one - synchronous main( ), c.isFirstRun and c.isDisplay in place of the two check_on_* predicates, c.bind / c.event, t.table( ) for state. So the split is 12 pages of structure and reference to write from scratch (1,949 lines), 15 pages of app-authoring API against defineApp (2,416), and 9 that survive with corrections (1,069). One finding stands on its own: docs/guide/samples.md is GENERATED and its generator breaks. scripts/gen-samples.mjs line 39 hardcodes .../cap2UI5/blob/main/core/srv/app/samples as its source, lines 122 and 188 write that path into the prose, and the plugin PR deletes that folder - so the page's 98 rows lose their input. It fails into a stale page, not into an error. I deliberately rewrote NONE of it. These pages are correct today: they describe the cap2UI5 that is published and working. Rewriting them into the future tense would swap accurate documentation for speculative documentation, and a rejected approach would leave the repository with neither. The trigger is step 4.3. The "where do the docs live" decision belongs before the rewrite, not after - it decides whether 3,400 lines land here or in the plugin repo. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JoANwtaK8Jm9srGEqSxmUF
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The written record of the work in cap2UI5/builder-abap2UI5-js#29, cap2UI5/cap2UI5#72 and the upstream seams PR, plus a page for whoever does the parts that need a person.
Important
This PR changes no documentation page. It adds ROADMAP sections and rewrites HANDOVER.md. The 36 pages under
docs/are left exactly as they are, on purpose — see §22 below.ROADMAP.md§§8–22Fifteen sections, each written when the thing it describes was measured, not afterwards:
build_coreproduced a hanging core.src/— all are fill-ins. The recommendation was withdrawn before anything was implemented.ownercoming outanonymouswas reported there as "no auth configured". It was not — the route was mounted outsidecds.middlewares.before, so the store could never see a user, and a draft answered to the wrong person. Fixed and tested.cap2ui5.Draftsnot reachable through it.main's own head), plus a correction to two numbers I had posted about thebuilder-abap2UI5-jsratchet.docs/reference/protocol.mdgains a danger callout: the page documentsS_FRONT.PARAMS, which is what cap2UI5 emits and no longer what abap2UI5 emits.HANDOVER.md— rewritten, not patchedIt had gone stale in a way that mattered: it told a human to open pull requests that are open, with suggested bodies naming an interface that has since been renamed, a workflow job that was reworked, and a file that moved — and it sent step 3 to the builder repository the plugin has since left. It also said to create an npm organisation that already exists and carries three packages.
It now says what to do with each pull request rather than repeating its body, carries the root cause of every red check a reviewer will see, and lists the facade slice as done instead of pending. The one warning that matters is unchanged: disable
update_capin builder-cap2UI5 before merging the plugin, or it overwrites it the next night.And the part step 4 had left out: what the cutover does to these pages
Merging the plugin invalidates most of this repository's documentation on the same day. Measured over
docs/— 36 pages, 5,434 lines, 27 of them describing the port. Only ROADMAP.md, HANDOVER.md and one line ofreference/database.mdknow the plugin exists.It is not stale paths. The whole guide teaches the port's app API, and the plugin's is a different API rather than a renamed one:
defineAppOne finding stands on its own:
docs/guide/samples.mdis generated and its generator breaks.scripts/gen-samples.mjsline 39 hardcodes…/cap2UI5/blob/main/core/srv/app/samplesas its source and lines 122/188 write that path into the prose. The plugin PR deletes that folder, so the page's 98 rows lose their input — silently, into a stale page rather than an error.Why nothing here is rewritten. These pages are correct today: they describe the cap2UI5 that is published and working. Rewriting them into the future tense would swap accurate documentation for speculative documentation, and a rejected approach would leave the repository with neither. The trigger is step 4.3 of the handover. The maintainer decision about where the docs live belongs before that rewrite, since it decides whether 3,400 lines land here or in the plugin repository.
🤖 Generated with Claude Code
https://claude.ai/code/session_01JoANwtaK8Jm9srGEqSxmUF
Generated by Claude Code