HLPS-DRAFT: Split Dorc.Api into Linux-compatible + Windows-only worker (#423)#706
HLPS-DRAFT: Split Dorc.Api into Linux-compatible + Windows-only worker (#423)#706benhegartysefe wants to merge 15 commits into
Conversation
Draft HLPS scoping the split of Dorc.Api into a Linux-compatible primary and a Windows-only worker. Deliberately excludes the bulk class-rename that bundled with PR #424 (the renames misread CLAUDE.md's naming principle as a banned-words blacklist and produced grab-bag names). Research carried over from PR #424: - WINDOWS_DEPENDENCIES_ANALYSIS.md (file-level Windows-API inventory) - ARCHITECTURE_API_SPLIT.md (PR #424's proposed topology, informational) - REGISTRY_UPGRADE_EXAMPLE.md - FILES_TO_MOVE_ANALYSIS.md - DEPLOYMENT_DEPENDENCY_ANALYSIS.md Three blocking unknowns flagged for architecture-owner sign-off before moving to IS: worker process topology, AD migration direction, inter-API auth propagation. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Architecture-owner decisions on 2026-05-28: - D-1 (was U-1): HTTP loopback worker. Separate ASP.NET Core process bound to 127.0.0.1. In-proc plugin defeats the Linux-deployment goal; queue sidecar is the wrong shape for synchronous request-path work. - D-2 (was U-2): Replace System.DirectoryServices with Microsoft Graph in the primary. AD code is deleted, not moved to the worker. Worker shrinks to genuinely non-portable surface (WMI, registry, password reset). Entra tenant becomes a hard customer prerequisite. - D-3 (was U-3): Loopback-only + shared-secret header. Worker rejects calls missing X-Worker-Key. Authz happens entirely in the primary using Graph claims before the worker is called; worker trusts. For password reset, worker uses its own service account; caller identity is forwarded in the body for audit only. JWT/mTLS add machinery without material security benefit on loopback. Scope and Success Criteria updated to reflect: Graph migration in scope, AD removal in scope, customer Entra prerequisite documented. Next Steps section now sketches concrete IS S-001..S-009 shape. Two new non-blocking unknowns added (U-9 Graph permission set, U-10 on-prem-only customer migration path) for the IS step that does the Graph swap. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Blocking unknowns resolved with architecture owner on 2026-05-28. Status bumped to IN REVIEW.
Knock-on changes in the HLPS:
Next Steps section now sketches a concrete IS shape: S-001..S-009 with explicit milestones (worker scaffolding → contract → first endpoint family proof-of-pattern → WMI → password reset → Graph migration → drop Windows-only deps → installer → docs). Ready for adversarial review. |
…-wide Graph migration
Adversarial panel raised 8 HIGH findings, 8 MEDIUM, 5 LOW. Full
disposition in Appendix A of the HLPS.
Substantive changes:
- Title broadened to "Replace AD with Microsoft Graph + Split". The
Graph migration is the load-bearing piece; the split rides on it.
- D-2 expanded codebase-wide. AD code (in Dorc.Core, Dorc.PersistentData,
Dorc.Api) is deleted, not moved to the worker. AzureEntraSearcher's
incorrect [SupportedOSPlatform("windows")] attribute is removed.
- Parity matrix added to §4 enumerating the load-bearing AD behaviours
the Graph implementation must match (user/group search, identity
resolution via objectId, transitive group membership, disabled-account
detection, display name/email). Out-of-parity items called out
(foreign security principals, well-known SIDs, local-machine SIDs).
- Windows authentication scheme removal (Negotiate, WinAuth*) added to
scope (Scope E + S-007). Previously missed.
- Next Steps reordered: S-001 is now the Graph migration spike (the
riskiest work). Worker scaffold drops to S-002. Reduces risk of
stranded plumbing if Graph parity proves harder than D-2 assumes.
- D-3 augmented with threat model, secret protection class, and
rotation policy.
- SC-1 expanded to cover Dorc.Core + Dorc.PersistentData (not just
Dorc.Api) with a CI gate for Linux build.
- SC-4 reworded as documented behavioural envelope (not a parity claim).
- SC-8 split into SC-8a (LDAP injection — eliminated by S-001) and
SC-8b (log injection — addressed by carved-out SPECs).
- SC-9 added: parity matrix is a living artefact with test coverage.
- C-1 reworded: "no client-side compile-time change" + documented
behavioural envelope on Linux installs.
- C-7 added: installer-side secret-handling requires security review.
- U-10 owner = Product, with explicit default position recorded.
- Frontmatter codebase_anchor field added (aab79d1) so file paths
and class names are pinned.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Round 1 revision pushed (commit 41c933c). Status: REVISION → IN REVIEW (Round 2). Adversarial panel raised 8 HIGH, 8 MED, 5 LOW findings. Full disposition in Appendix A of the HLPS. Substantive changes from Round 1:
Round 2 panel kicking off now. |
…untName resolution)
Round 2 panel verified the Round 1 fixes but found two genuine new
HIGH issues:
NH-1: Parity matrix omitted resolution of legacy AD SIDs (the
AccessControl.Sid column persisted from existing customer installs).
Current AzureEntraSearcher.GetUserDataById only does Users[id]/Groups[id]
which assumes Entra object IDs; existing AccessControl.Sid rows
(populated from pre-migration AD lookups, see EnvironmentsPersistentSource
lines 165/166/203/373/422/728/932 + AccessControlPersistentSource
lines 77/180/196) would silently 404 post-migration.
NH-2: GetGroupSidIfUserIsMemberRecursive treats the userName parameter
as an Entra object ID/UPN and calls graphClient.Users[userName],
so all sAMAccountName-only callers silently return empty.
Both are codebase-level (not SPEC-level) concerns — they determine
whether D-2 ("AD deleted, not moved") is feasible without breaking
every existing install.
Revision:
- Parity matrix in §4 expanded from 7 rows to 9 (P-4 SID lookup,
P-5 sAMAccountName resolution, P-7 dual-ID claims emission) with
explicit ❌-gap markers + Graph strategy per gap (onPremisesSecurityIdentifier
filter, onPremisesSamAccountName filter, transitiveMemberOf with id+SID select).
- Scope A bullets added for the three gap fixes; S-001 step description
in §9 names the gaps explicitly.
- D-2 customer-implication text extended: Entra Connect (or Cloud Sync)
is now an explicit prerequisite for any customer with existing
AccessControl.Sid rows.
- U-10 reframed with Cohort A (Entra Connect present → transparent
upgrade) vs Cohort B (pure on-prem AD → hard break).
- New SC-10: existing AccessControl.Sid rows resolve correctly with
Entra Connect present.
- Scope A test requirement tightened (NM-2): integration tests against
a Graph SDK fake required per parity-matrix row, not just
IActiveDirectorySearcher-boundary mocks. SC-9 reinforced.
- New non-blocking U-11: worker-absence detection mechanism on Linux
(recommended: explicit WindowsWorker:Enabled config flag).
- NM-1 audit-trail fix: don't claim [SupportedOSPlatform] was "inherited";
state it's wrong, fix it.
- NL-1: S-001 description explicitly calls out the Linux build CI gate.
- Appendix A extended with Round 2 disposition table.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Round 2 revision pushed (commit 0865902). Status: REVISION → IN REVIEW (Round 3). Round 2 panel verified the Round 1 fixes but raised two genuine new HIGHs:
Both verified in code. These are codebase-level (not SPEC-level) concerns: they determine whether D-2 ("AD deleted, not moved") is feasible without breaking every existing install. Revision (Round 3):
Round 3 verification kicking off now. Per CLAUDE.md max 3 rounds — if Round 3 finds new HIGHs that aren't trivial, I escalate. |
Round 3 reviewer verified all Round 2 NH/NM findings closed; no new showstoppers. Residual DOMAIN\ prefix handling for sAMAccountName resolution deferred to S-001 SPEC (appropriate scope). Per CLAUDE.md, this is the post-HLPS checkpoint. Next: draft IS-api-split.md ordering S-001..S-010. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
HLPS APPROVED ✅ (Round 3 verification passed) Round 3 reviewer verdict: APPROVE. All Round 2 NH-1 / NH-2 / NM-1..3 findings closed; no new showstoppers. Residual Full disposition trail in Appendix A of the HLPS (Round 1 + Round 2 tables). Per CLAUDE.md this is the post-HLPS checkpoint. Moving to IS-api-split.md draft next. PR #706 will get the IS commit + a fresh adversarial-review cycle on it. |
Translates approved HLPS into 10 atomic, reviewable steps spanning
multiple PRs:
- S-001 Graph migration spike (the riskiest work, sequenced first)
- S-002 Worker project scaffold + loopback host + shared-secret auth
- S-003 IWindowsWorkerClient contract + null-impl for Linux + worker-
absence detection (resolves U-6/U-7/U-11)
- S-004 Move RefDataServers registry probing to worker (proof-of-pattern)
- S-005 Move WMI service-status probe path to worker
- S-006 Move ResetAppPassword impersonation to worker + SC-8b carve-out
- S-007 Remove Negotiate scheme + WinAuth* (after S-001)
- S-008 MSI installer wiring + shared-secret provisioning
- S-009 Remaining log-injection SPEC carve-outs (parallel)
- S-010 Documentation + Linux container smoke test (closeout)
Step dependency graph documented. Parallelism explicit (S-001 and S-002
fully independent; S-004/S-005/S-006 parallel after S-003; S-009 parallel
throughout). Risk shape documented (S-001 highest, S-006 second,
S-008 third, rest mechanical).
Cross-cutting acceptance table maps every SC-1..SC-10 to the step(s)
that satisfy it.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Round 1 reviewer raised 3 HIGH, 3 MED, 2 LOW findings. HIGH fixes: - H-1: S-006 was bundling worker-move + log-injection security fix. Split: S-006 is worker-move only; the ResetAppPasswordController log-injection fix moves to S-009 alongside the other three log-injection items. Security-sensitive diffs now reviewed independently of mechanical moves. - H-2: S-005 was declared independent of S-001, but DaemonStatusProbe lives in Dorc.Core which S-001 reshapes. Added S-001 dependency to S-005 (and clarified the same applies to S-004 and S-006 in the parallelism note). - H-3: SC-5 was asserted in the cross-cutting table without a step's verification covering it. Added explicit "Dorc.Api.Client builds unchanged" assertion to S-001 and S-007 verification intents. MED fixes: - M-1: Named the typed-exception throw-timing decision in S-003 as an open SPEC-level question (synchronous-at-call vs startup-probe), with recommendation. - M-2: Promoted Windows-Service-vs-IIS hosting decision to a new IS-level Lookahead unknown U-12, with recommendation. Removed the "decision in SPEC" language from S-008. - M-3: Added explicit fixture-capture-on-main sentence to S-004 (applies to S-005 and S-006 too — they reuse the S-004 fixture pattern). LOW fixes: - L-1: Added SID-based ACL parity check to S-007 verification. - L-2: Parallelism note clarified — S-004/5/6 mutually independent at code level, but all depend on S-001. Status: DRAFT -> IN REVIEW (Round 2). Cross-cutting acceptance table updated to match. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Round 2 review confirmed all Round 1 fixes except H-2 was only partially addressed: the S-005 body still read "Independent of S-001" contradicting the Step Index and Parallelism note (both correctly listing S-001 as a dependency). One-line fix to bring the S-005 Dependencies subsection into line with the index. No other defects. Status: IN REVIEW (Round 2) -> APPROVED. Per CLAUDE.md this is the post-IS checkpoint. Next: JIT SPEC for S-001 (Graph migration spike), adversarial review of that SPEC, then test-first execution. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
IS APPROVED ✅ (Round 2 verification) Round 1 reviewer raised 3 HIGH, 3 MED, 2 LOW findings. Round 2 reviewer verified all are addressed except H-2 was partially addressed — the S-005 body still contradicted the Step Index. Trivial one-line fix applied; no other defects. Status: DRAFT → IN REVIEW (R1) → REVISION → IN REVIEW (R2) → APPROVED. Per CLAUDE.md we are at the post-IS checkpoint. Both HLPS and IS are approved. The next move is JIT SPEC for S-001 (Graph migration spike) — the riskiest step, sequenced first. S-001 in plain English: promote Estimated diff size for S-001 alone: substantial — likely several hundred LOC across ~15 files, plus a sizable test suite. |
Pseudocode-level spec for S-001: - AzureEntraSearcher attribute removal + 3 parity-gap fixes (P-4, P-5, P-7) - DI switch in ApiRegistry to direct AzureEntraSearcher registration - AD-code deletions (7 files; folder cleanup if needed) - System.DirectoryServices* package-ref drops from Dorc.Core + Dorc.PersistentData - Linux build CI gate (negative SC-1 enforcement) - Graph fake harness via NSubstitute against IRequestAdapter - Per-row parity-matrix test plan (P-1..P-9 + SC-10 acceptance + negatives) - 6 risks flagged including R-3 ClaimsTransformer CA1416 transient warning Two open SPEC-level decisions named (test-seam refactor approach, CI job placement) — to resolve during adversarial review. Status: DRAFT. Will go to adversarial review before code execution. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Round 1 reviewer raised 3 HIGH, 3 MED, 3 LOW findings. HIGH fixes: - H-1: §2.6 missed ClaimsPrincipalReaderFactory as a second consumer of IUserGroupsReaderFactory (uses both GetWinAuthUserGroupsReader AND GetOAuthUserGroupsReader). Added as a touch site with explicit resolution; verified in code. - H-2: §2.2's ordered-lookup language could read as "duplicate the existing Users[pid] call". Rewrote as augmentation: the SID-shaped filter fallback runs *after* the existing 404s, not as a new starting point. Implementer told explicitly not to duplicate. - H-3: §5 bullet 9 "ClaimsTransformer compiles" was not objectively scriptable. Replaced with concrete dotnet build invocation that covers both consumer-update edits. MED fixes: - M-1: §2.4 disambiguated — GetMemberGroups call is *replaced* (not augmented) by TransitiveMemberOf.GraphGroup. - M-2: §2.3 sentinel value pinned to string.Empty (matches existing semantics observed by CachedUserGroupReader.GetGroupSidIfUserIsMember line 42's sid != null check). Implementer told explicitly: do not return null. - M-3: §3.1 test-seam refactor justified against HLPS SC-9 + Round-2 NM-2 rejection of boundary mocks. Now in-scope by direct reference, with Option A (Func<GraphServiceClient> factory) recommended and Option B (recorded HTTP harness) as documented alternative. LOW fixes: - L-1: P-8 test split into two cases that match actual control flow (disabled-user-only falls through to group lookup; both miss throws). - L-2: §4 / §5 grep overlap clarified (one enforces, the other audits). - L-3: §4 now tells implementer to check TreatWarningsAsErrors before applying R-3's #pragma suppression. Status: DRAFT -> APPROVED. Ready for code execution. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
SPEC-S-001 APPROVED ✅ (Round 2 verification) Round 1 raised 3 HIGH, 3 MED, 3 LOW findings — all addressed in commit 28b4a8c. Round 2 verifier confirmed each fix and reported no new showstoppers. Status: DRAFT → APPROVED. Per CLAUDE.md this is the pre-execution checkpoint for S-001. The spec is at Stopping here for the user's go-ahead before writing code. The execution will touch ~15 files, drop two package refs, add Graph-fake tests, and produce a substantial PR. |
Implements SPEC-S-001 §2 in full:
- AzureEntraSearcher: [SupportedOSPlatform("windows")] attribute removed;
added internal test-seam ctor (Func<GraphServiceClient>); P-4 (legacy
AD SID lookup via $filter=onPremisesSecurityIdentifier), P-5
(sAMAccountName resolution with DOMAIN\ and (External) prefix
handling), P-7 (TransitiveMemberOf emitting both Pid and on-prem SID)
gap fixes in place.
- ApiRegistry: switched from composite/factory pattern to direct
registration of AzureEntraSearcher as the singleton
IActiveDirectorySearcher; IUserGroupReader registered directly;
EntraDirectorySearchService registered for IDirectorySearchService.
- EntraDirectorySearchService (new): Graph-backed implementation of
IDirectorySearchService (preserves DirectorySearchController's
UserSearchResult/GroupSearchResult REST surface — SPEC §2.6 missed
this consumer; added per implementation discovery).
- ClaimsTransformer: ctor now injects IUserGroupReader directly
(was IUserGroupsReaderFactory).
- ClaimsPrincipalReaderFactory: same direct-injection switch; both
internal readers now wrap the single shared IUserGroupReader.
- Program.cs ConfigureOAuth: dropped IUserGroupsReaderFactory
registration line (no longer needed).
- PropertyValuesController, RequestController: ctor switched from
IDirectorySearcherFactory to IActiveDirectorySearcher directly.
Files deleted (SPEC §2.6 + the additional IUserGroupsReaderFactory
discovered during execution):
- Dorc.Core/ActiveDirectorySearcher.cs
- Dorc.Core/CompositeDirectorySearcher.cs
- Dorc.Core/IdentityServer/IdentityServerSearcher.cs
- Dorc.Core/Interfaces/IDirectorySearcherFactory.cs
- Dorc.Api/Services/DirectorySearcherFactory.cs
- Dorc.Api/Services/UserGroupReaderFactory.cs
- Dorc.Api/Services/ActiveDirectorySearchService.cs
- Dorc.Api/Interfaces/IUserGroupsReaderFactory.cs
Package refs dropped (SPEC §2.7):
- Dorc.Core.csproj: System.DirectoryServices, System.DirectoryServices.AccountManagement
- Dorc.PersistentData.csproj: same two
Verification at this commit:
- Dorc.Api, Dorc.Core, Dorc.PersistentData, Dorc.Api.Tests,
Dorc.Core.Tests, Dorc.Monitor, Dorc.Monitor.Tests, Dorc.Api.Client
all build clean (zero errors).
- `git grep "System\.DirectoryServices" src/` returns zero hits.
Follow-up commits on this same PR:
- Graph fake test harness + parity-matrix tests P-1..P-9 + SC-10
- Linux build CI gate workflow
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds .github/workflows/linux-build-gate.yml enforcing HLPS SC-1: - Runs on ubuntu-latest for every push and PR to main/develop - Guard step greps for System.DirectoryServices* and System.Management* in src/Dorc.Api, Dorc.Core, Dorc.PersistentData; fails fast with a clear "SC-1 regression" message if any reappear. - Then restores + builds each of the three projects in Release config. This is the negative SC-1 gate. The positive gate (Linux container actually runs DORC) is S-010's smoke test, not this commit. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…+ SC-10 Implements SPEC-S-001 §3 in full. Test harness (Dorc.Core.Tests/Graph/): - MockHttpHandler.cs — HttpMessageHandler that matches by predicate and returns canned JSON; unmatched calls fall through to 404 so missing mocks surface as failing tests rather than hangs. - GraphTestClient.cs — Factory that constructs a real GraphServiceClient over the mock handler via HttpClientRequestAdapter + AnonymousAuthenticationProvider. This means tests exercise the real Kiota deserialization path, satisfying HLPS SC-9's requirement that parity tests use a Graph SDK fake rather than IActiveDirectorySearcher- boundary mocks. Tests (Dorc.Core.Tests/AzureEntraSearcherTests.cs) — 12 tests, all green: - P1 Search user by display name - P2 Search group by display name - P3 GetUserDataById resolves Entra object id - P4 GetUserDataById resolves legacy AD SID via onPremisesSecurityIdentifier - P4 GetUserDataById falls through user→group when user filter misses - P5 GetGroupSidIfUserIsMemberRecursive resolves sAMAccountName via filter - P5 GetGroupSidIfUserIsMemberRecursive strips DOMAIN\ prefix - P5 GetGroupSidIfUserIsMemberRecursive returns empty string (not null) on miss — preserves CachedUserGroupReader cache semantics - P7 GetSidsForUser emits both Pid and Sid (self + groups) - P8 GetUserDataById falls through to group when user is disabled - P8 GetUserDataById throws ArgumentException when both miss - SC10 AccessControlSidRow_ResolvesViaOnPremisesIdentifier — integration test simulating an existing customer's persisted AccessControl.Sid row Production-code fix surfaced by the tests: - The original AzureEntraSearcher caught Microsoft.Graph.ServiceException with NotFound status to recover from 404s; Graph SDK 5.x replaced ServiceException with Microsoft.Kiota.Abstractions.ApiException. The original catch was dead code — never matched in production. Replaced ServiceException → ApiException throughout, and switched .Result to .GetAwaiter().GetResult() so Kiota's ApiException isn't wrapped in AggregateException before the catch sees it. This is a latent bug the original codebase had since the Graph 4→5 upgrade. Test results at this commit: - Dorc.Core.Tests: 132 / 132 pass (12 new + 120 existing) - Dorc.Api.Tests: 190 / 190 pass (no regressions) Remaining S-001 work: - SC-8a SAST re-scan against the diff (manual verification step; PR-#424's LDAP-injection findings should no longer trigger because the entire LDAP code path is gone). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
S-001 execution complete ✅ Three commits land the Graph migration spike per SPEC-S-001:
Test results at HEAD:
Notable findings surfaced by the test work:
HLPS SC coverage:
Outstanding for S-001 closeout:
PR #706 is now a full deliverable: HLPS + IS + SPEC-S-001 + S-001 implementation. Next IS step (S-002 — worker project scaffold) would be a separate PR. |
SC-1 gates against PackageReferences, not against source usages of the namespace. Initial workflow tripped on: - A comment in EntraDirectorySearchService.cs that explains the file is a replacement for the System.DirectoryServices-backed predecessor - using System.Management in WmiUtil.cs — but WmiUtil moves to the worker in S-005, not S-001, so System.Management source usages are expected in the primary at this point in the IS sequence. Narrowed the grep to PackageReference Include="System.DirectoryServices*" on csproj files only. System.Management package-ref check belongs in the workflow gate added by S-005's PR. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Linux build CI gate passes on commit |
- CodeQL (workflow permissions): added `permissions: contents: read` to linux-build-gate.yml so GITHUB_TOKEN is minimised. - CodeQL (PII exposure via GetUserEmail call graph): removed the user-id template argument from AzureEntraSearcher.GetSidsForUser's best-effort warning log. The log still says we couldn't resolve the on-prem SID for the requested user; identifying the user across log lines now requires correlating other request-scoped context. - Code-quality (HttpResponseMessage not disposed): MockHttpHandler now overrides Dispose(disposing) and disposes the captured HttpRequestMessage instances. The returned HttpResponseMessage ownership intentionally transfers to the Kiota request adapter. - Code-quality (foreach-with-break -> Where/FirstOrDefault): replaced the rule-matching loop in MockHttpHandler.SendAsync with _rules.FirstOrDefault(r => r.Match(request)). Test results at this commit: - 12 / 12 AzureEntraSearcherTests pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
| var rule = _rules.FirstOrDefault(r => r.Match(request)); | ||
| if (rule != null) | ||
| { | ||
| var response = new HttpResponseMessage(rule.Status); |
| var fallback = new HttpResponseMessage(HttpStatusCode.NotFound) | ||
| { | ||
| Content = new StringContent($"{{\"error\":{{\"code\":\"itemNotFound\",\"message\":\"No mock for {request.Method} {request.RequestUri}\"}}}}", Encoding.UTF8, "application/json") | ||
| }; |
|
All bot findings on the prior commit addressed in |
Summary
Dorc.Apiinto a Linux-compatible primary and a Windows-only worker), scoped to the split only — explicitly excluding the bulk class-rename that bundled into the superseded Split Dorc API into Linux-compatible and Windows-specific APIs with complete naming compliance #424.docs/api-split/research/so the investigation isn't lost.This PR is documentation-only — no code changes, no installer changes. It is the first checkpoint of the HLPS → IS → SPEC process for #423.
Why this exists
#424 was closed unmerged (rationale) because it bundled the structural split with:
Properties,Requests,Operations).deployschema with PascalCase, add RBAC + audit, fix UX bugs #649) landed onmain— ~340 silently semantic-conflicted files.This HLPS unwinds (1) and (3) and asks the architecture owner to resolve the unknowns before any code is written.
Status
DRAFT— per CLAUDE.md, HLPS progresses DRAFT → IN REVIEW → REVISION → APPROVED. Only the adversarial review panel can approve.Test plan
docs/api-split/IS-api-split.mdordering atomic steps; second checkpoint there.🤖 Generated with Claude Code