[LOADED_DOCS: 3 files, no new loads]
Expand META-TODO scope; add BINARY_TODO entries; doc updates - Broadened ACCORE-META-T-F8R3 to cover SKILL.md/registry text drift, not just summary staleness - Added concrete SKILL.md drift examples and clarified fix direction - Added BINARY_TODOs for Id detection (convention/attribute) and serializer-native ignore attribute - Updated SIGNALR_BINARY_PROTOCOL_TODO.md and ADR 0001 to clarify deferral of decorator base/handshake TODOs - Minor topic code length and JSON-in-Binary tech debt clarifications - Synced references and cross-links with latest protocol decisions
This commit is contained in:
parent
fc63be3226
commit
8e9a0b47c1
File diff suppressed because one or more lines are too long
|
|
@ -74,10 +74,10 @@ Skill stays read-only; reports findings; user approves any patch (Rule #5).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## ACCORE-META-T-F8R3: `Current protocol state` summary auto-staleness detection
|
## ACCORE-META-T-F8R3: Static-text auto-staleness detection — `Current protocol state` summary AND SKILL.md / registry text drift
|
||||||
|
|
||||||
**Priority:** P3 · **Type:** Skill enhancement · **Status:** Open · **Area:** `protocol-audit/SKILL.md` (or `docs-check/SKILL.md`)
|
**Priority:** P3 · **Type:** Skill enhancement · **Status:** Open · **Area:** `protocol-audit/SKILL.md` (or `docs-check/SKILL.md`)
|
||||||
**Origin:** Phase 6 + LLMP-DEC-60 summary refresh
|
**Origin:** Phase 6 + LLMP-DEC-60 summary refresh; **scope broadened by LLMP-DEC-64** to include SKILL.md text drift (parallel class)
|
||||||
|
|
||||||
### Description
|
### Description
|
||||||
|
|
||||||
|
|
@ -90,18 +90,25 @@ Drift instances observed during the Phase 1-6 migration (all caught only at Phas
|
||||||
- Skill version labels (`v2.2`, `v1.0`) violating LLMP-DEC-47.
|
- Skill version labels (`v2.2`, `v1.0`) violating LLMP-DEC-47.
|
||||||
- `protocol-audit` description still mentioned `references/REPOS.md` central read after LLMP-DEC-58 redesigned to runtime discovery.
|
- `protocol-audit` description still mentioned `references/REPOS.md` central read after LLMP-DEC-58 redesigned to runtime discovery.
|
||||||
|
|
||||||
Manual refresh worked (LLMP-DEC-60), but a periodic/automated reminder would catch drift earlier.
|
**Parallel drift class — SKILL.md / invariant text staleness** (LLMP-DEC-64 broadened the scope):
|
||||||
|
- `protocol-audit/SKILL.md` C4 invariant said "all three skills" + `[LOADED_DOCS]` counts "4 for primary, 5 for inherit" — outdated since LLMP-DEC-43 introduced 5-skill 2-reactive/3-user-gated matrix (correct counts: 3/4).
|
||||||
|
- `protocol-audit/SKILL.md` X1 invariant said "all three bullets must be listed" — same root cause.
|
||||||
|
- Caught 2026-04-27 by a re-audit using the post-Phase-5.1 skill itself (positive self-evidence).
|
||||||
|
|
||||||
|
Manual refresh worked (LLMP-DEC-60 for summary; LLMP-DEC-64 for SKILL.md), but a periodic/automated reminder would catch drift earlier.
|
||||||
|
|
||||||
### Fix direction
|
### Fix direction
|
||||||
|
|
||||||
Two complementary options (pick one or combine):
|
Two complementary options (pick one or combine), now covering BOTH drift classes:
|
||||||
|
|
||||||
**(a) `protocol-audit` invariant `M1`** (meta-summary integrity, optional):
|
**(a) `protocol-audit` invariant `M1`** (meta-text integrity, optional) — applies to:
|
||||||
- Parse `Current protocol state` section claims (date, file count, skill list, ID format).
|
- `LLM_PROTOCOL_DECISIONS.md` `Current protocol state` section claims (date, file count, skill list, ID format).
|
||||||
- Compare against ground truth (filesystem walk, file content).
|
- `protocol-audit/SKILL.md` invariant texts that describe protocol-state quantities (e.g., skill counts in C4, bullet-list expectations in X1).
|
||||||
- Surface drift with suggested patch.
|
- Other SKILL.md / registry texts describing "current" state (`docs-check/SKILL.md`, `docs-discovery/SKILL.md`, `REPO_PREFIXES.md`, `TOPIC_CODES.md` cross-link counts).
|
||||||
|
|
||||||
**(b) `docs-check` skill** flags the summary section as stale if N (e.g., N ≥ 3) new LLMP-DEC entries have been added since the summary's last refresh date.
|
Compare against ground truth (filesystem walk + file content) and surface drift with suggested patch.
|
||||||
|
|
||||||
|
**(b) `docs-check` skill** flags any of the above as stale if N (e.g., N ≥ 3) new LLMP-DEC entries have been added since the file's last touch — heuristic but catches the common drift pattern.
|
||||||
|
|
||||||
### Acceptance criteria
|
### Acceptance criteria
|
||||||
|
|
||||||
|
|
@ -117,6 +124,335 @@ Two complementary options (pick one or combine):
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## ACCORE-META-T-B6V2: General-purpose `scheduler` skill — workspace-side scheduled-task abstraction
|
||||||
|
|
||||||
|
**Priority:** P3 · **Type:** New skill · **Status:** Open · **Area:** new skill (proposed: `AyCode.Core/.github/skills/scheduler/`)
|
||||||
|
**Origin:** 2026-04-26 — discussion about `MIGRATION_ID_MAPPING.md` 2026-05-10 deletion-review (LLMP-DEC-58)
|
||||||
|
|
||||||
|
### Description
|
||||||
|
|
||||||
|
The current LLMP-DEC-58 retention pattern uses **passive LLM-self-reminding** (any LLM reading the file from 2026-05-10 onward surfaces a deletion prompt). Works only if the file gets read; not guaranteed timing. Anthropic's host environment offers a `schedule` skill (one-time/recurring remote agents) — useful for the specific 2026-05-10 case, but ties the workspace process to a specific host's capability.
|
||||||
|
|
||||||
|
A **workspace-side `scheduler` skill** would generalize the pattern:
|
||||||
|
- File-based registry (e.g., `AyCode.Core/.github/SCHEDULED_TASKS.md` or similar) — declared scheduled events: due-date / cadence + trigger-action + description.
|
||||||
|
- Skill scans the registry on session start; surfaces "due / overdue" tasks to the user (gated, not spammy).
|
||||||
|
- Host-agnostic (Claude Code, Copilot, future LLM hosts).
|
||||||
|
- Optional integration: when the host has a native scheduler, the skill can offer to register the task there too — workspace-side registry stays source of truth.
|
||||||
|
|
||||||
|
Use cases beyond migration retention:
|
||||||
|
- Archive-eligibility scans (`docs-archive` periodic runs).
|
||||||
|
- Periodic `protocol-audit` runs.
|
||||||
|
- Planned-deletion of transient artifacts (more migrations, generated outputs, etc.).
|
||||||
|
- Deadline-bound TODOs that should auto-surface near their date.
|
||||||
|
|
||||||
|
### Fix direction
|
||||||
|
|
||||||
|
1. Create skill: `AyCode.Core/.github/skills/scheduler/SKILL.md` (with `references/` for the registry).
|
||||||
|
2. Define the registry format — likely simple table (`task-id | type (one-time/recurring) | due-date or cadence | trigger-action | description | status`).
|
||||||
|
3. Skill triggers:
|
||||||
|
- **Reactive** (session-start scan, like `docs-discovery` / `docs-check`) — surface only if there ARE due/overdue tasks.
|
||||||
|
- **User-invoked**: "list scheduled tasks", "what's due", "schedule a task" (interview-style add).
|
||||||
|
4. Migrate the 2026-05-10 `MIGRATION_ID_MAPPING.md` deletion-review to this system once it exists; the per-file LLM-self-reminder header note then trims to a cross-ref pointing at the registry.
|
||||||
|
|
||||||
|
### Acceptance criteria
|
||||||
|
|
||||||
|
- Registry-driven, host-agnostic.
|
||||||
|
- Session-start surfacing of due / overdue tasks (low-friction, not noise).
|
||||||
|
- Optional one-click "register at host-native scheduler too" for hosts that support it.
|
||||||
|
- The 2026-05-10 deletion-review event migrated to this system as the inaugural entry.
|
||||||
|
|
||||||
|
### Related
|
||||||
|
|
||||||
|
- LLMP-DEC-58 (introduces the per-file LLM-self-reminder pattern this would replace).
|
||||||
|
- LLMP-DEC-43 (5-skill matrix — adding a 6th skill is a workspace-meta change; will need an LLMP-DEC entry when implemented).
|
||||||
|
- Anthropic-host `schedule` skill (parallel capability to integrate with, not replace).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ACCORE-META-T-M5T8: TL;DR / "When to read this file" block on every topic README
|
||||||
|
|
||||||
|
**Priority:** P2 · **Type:** Convention update + bulk doc edit · **Status:** Open · **Area:** All topic `<TOPIC>/README.md` files
|
||||||
|
**Origin:** 2026-04-26 — Cross-source convergence (3/3 Copilot reviewers + my own analysis); user-reframing strengthened the case (correctness > token-economy)
|
||||||
|
|
||||||
|
### Description
|
||||||
|
|
||||||
|
Topic README files (`LOGGING/README.md`, `BINARY/README.md`, etc.) currently vary in opening style. The agent has to scan the whole README to decide "is this topic relevant?", "which sister docs to load deeper?", "when is this NOT what I'm looking for?". A consistent **TL;DR / "When to read" block** at the top would make load-decisions cheaper and more accurate. **3-of-3 Copilot reviewer convergence** is unusually strong signal.
|
||||||
|
|
||||||
|
Anti-hallucination angle: the block helps agents pick the RIGHT topic to load, not just MORE topics — relevance still matters even under "load broader" preference (`ACCORE-META-T-R2W6`).
|
||||||
|
|
||||||
|
### Fix direction
|
||||||
|
|
||||||
|
Standardize a block at the top of every topic README (after `# Title`, before first `## ` section):
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
> **Mikor olvasd ezt a fájlt:** ha a kérdés érinti `<X>`, `<Y>`, `<Z>`.
|
||||||
|
> **Először olvass:** `<TOPIC>_<sub>.md`, `<TOPIC>_<sub2>.md` (a 80% case-eket fedik).
|
||||||
|
> **Mikor NE olvasd:** ha pl. csak `<other-concern>` → `<other-topic>/README.md`.
|
||||||
|
```
|
||||||
|
|
||||||
|
Apply to all ACCORE topic READMEs (BINARY, LOGGING, SIGNALR, SBP, TOON, XCUT, AUTH). Pattern propagates to higher-layer repos as their topic READMEs are written.
|
||||||
|
|
||||||
|
### Acceptance criteria
|
||||||
|
|
||||||
|
- Every ACCORE topic README has the block, formatted consistently.
|
||||||
|
- The block is referenced in `docs-discovery/SKILL.md` as a load-decision aid.
|
||||||
|
- Optional: `docs-check` invariant flags missing block on new topic READMEs.
|
||||||
|
|
||||||
|
### Related
|
||||||
|
|
||||||
|
- Cross-source: 3-of-3 Copilot reviewer convergence (Set 2 #1+#2, Set 3 #2)
|
||||||
|
- LLMP-DEC-21 (Pattern B docs layout — established the topic-README pattern)
|
||||||
|
- `ACCORE-META-T-Y4Q9` (Hot-path tagging — finer-grained version of the same idea)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ACCORE-META-T-Y4Q9: Hot-path / Fast-path tagging in topic READMEs
|
||||||
|
|
||||||
|
**Priority:** P3 · **Type:** Convention update · **Status:** Open · **Area:** Topic README files
|
||||||
|
**Origin:** 2026-04-26 — Set 3 #3 reviewer suggestion; user-reframing recalibrated importance to **secondary** (less critical when default is "load more")
|
||||||
|
|
||||||
|
### Description
|
||||||
|
|
||||||
|
The TL;DR (`ACCORE-META-T-M5T8`) lists the FIRST 2-3 docs to load. A `### Fast-path docs` sub-section (or `@fastpath` tag) declaratively marks the docs covering the 80% case — distinct from "Először olvass" (which is conversational guidance) by being machine-friendly + parsable.
|
||||||
|
|
||||||
|
User's recalibration: with "load more upfront" preference (`ACCORE-META-T-R2W6`), agent will tend to load broader anyway → fast-path tagging becomes **secondary**, not critical. Still useful as a SIGNAL for which docs to start with when the agent does decide to be selective.
|
||||||
|
|
||||||
|
### Fix direction
|
||||||
|
|
||||||
|
Add a small `### Fast-path docs` section in topic READMEs listing 2-3 docs that suffice for 80% of agent queries. Example:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
### Fast-path docs
|
||||||
|
- `BINARY_FORMAT.md` — wire format spec (most queries land here)
|
||||||
|
- `BINARY_OPTIONS.md` — configuration reference
|
||||||
|
- `BINARY_WRITERS.md` — IBufferWriter integration
|
||||||
|
```
|
||||||
|
|
||||||
|
`docs-discovery/SKILL.md` honours: when topic identified, fast-path docs load first; expanded set (full topic-pair + sister docs) on demand or per `R2W6` preference.
|
||||||
|
|
||||||
|
### Acceptance criteria
|
||||||
|
|
||||||
|
- ACCORE topic READMEs have the block where applicable.
|
||||||
|
- `docs-discovery/SKILL.md` parses and honours the fast-path.
|
||||||
|
- Marker convention defined and consistent.
|
||||||
|
|
||||||
|
### Related
|
||||||
|
|
||||||
|
- `ACCORE-META-T-M5T8` (TL;DR — predecessor / parent convention)
|
||||||
|
- Cross-source: Set 3 #3 (only 1 source mentioned this — weaker convergence than M5T8)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ACCORE-META-T-V7L2: ADR quality lint / structural validator
|
||||||
|
|
||||||
|
**Priority:** P3 · **Type:** Skill enhancement · **Status:** Open · **Area:** `adr-author/SKILL.md` Step 8 extension (or new Step 10) + optional `references/ADR_LINT.md`
|
||||||
|
**Origin:** 2026-04-26 — Set 1 #7 + 2-of-3 Copilot reviewer agreement (one explicit suggestion: warning, not hard-fail)
|
||||||
|
|
||||||
|
### Description
|
||||||
|
|
||||||
|
`adr-author/SKILL.md` Steps 5-8 produce ADRs but don't VALIDATE them post-write. A lint pass would catch:
|
||||||
|
- Missing required sections (`## Status`, `## Context`, `## Decision`, `## Consequences`, `## Alternatives considered`, `## Related`)
|
||||||
|
- Status field format: `Proposed (YYYY-MM-DD) | Accepted (...) | Superseded by ADR-XXXX | Rejected (...)`
|
||||||
|
- Alternatives section: ≥ 2 alternatives present (or explicit "no alternatives — implementation path" justification)
|
||||||
|
- Decision section: explicit decision criterion, not just "we'll do X" without why
|
||||||
|
- Cross-ref consistency: `Supersedes` / `Superseded by` / `Related ADRs` IDs valid (file exists, anchor exists)
|
||||||
|
|
||||||
|
### Fix direction
|
||||||
|
|
||||||
|
Add Step 10 to `adr-author/SKILL.md` (or extend Step 8): after Write, run lint over the generated ADR. Surface any FAIL as polite suggestion (**warning mode**, not blocker — per Copilot reviewer #2).
|
||||||
|
|
||||||
|
Could alternatively be a `protocol-audit` invariant `D2` (docs-link integrity) extended to validate ADR structure — synergy with `ACCORE-META-T-K2P7` (path/anchor mismatch detection).
|
||||||
|
|
||||||
|
### Acceptance criteria
|
||||||
|
|
||||||
|
- Generated ADRs pass lint by construction (if user fills in placeholders correctly).
|
||||||
|
- Lint reports are constructive (suggested fix, not just "FAIL").
|
||||||
|
- Doesn't block ADR creation — quality nudge, not gate.
|
||||||
|
|
||||||
|
### Related
|
||||||
|
|
||||||
|
- `adr-author/SKILL.md` Steps 5-8 (existing structure)
|
||||||
|
- `ACCORE-META-T-K2P7` (parallel "anchor mismatch" lint family — synergy candidate)
|
||||||
|
- Cross-source: Set 1 #7 + Copilot reviewer #2 ("warning, not hard fail")
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ACCORE-META-T-N8K3: `Last verified against commit` opt-in metadata field
|
||||||
|
|
||||||
|
**Priority:** P3 · **Type:** Convention (opt-in metadata) · **Status:** Open · **Area:** Long-form `docs/` files (opt-in only)
|
||||||
|
**Origin:** 2026-04-26 — Set 2 #3 + user anti-hallucination framing (drift detection prevents stale-context hallucination loops)
|
||||||
|
|
||||||
|
### Description
|
||||||
|
|
||||||
|
Drift between `.md` docs and source code is a real hallucination cause: agent reads a doc, trusts it, the doc was written 6 months ago against an old API → wrong answer. A simple `<!-- last-verified: <commit-hash> @ <YYYY-MM-DD> -->` HTML-comment metadata field would let the agent know:
|
||||||
|
|
||||||
|
- "This doc was verified against commit `cdd54d3` (2026-04-05) — current HEAD is later — possibly stale, double-check the relevant `.cs` file."
|
||||||
|
- vs. "Verified yesterday — trust it."
|
||||||
|
|
||||||
|
User's recalibration: anti-hallucination angle **strengthens** this entry's importance vs. the original "marginal utility" framing. Drift-detection IS hallucination prevention.
|
||||||
|
|
||||||
|
### Fix direction
|
||||||
|
|
||||||
|
**Opt-in only** — not all docs need this. Suggested for:
|
||||||
|
- Long-form architecture docs (`ARCHITECTURE.md`, `BINARY/README.md`, etc. — less frequent churn)
|
||||||
|
- Reference docs (`CONVENTIONS.md`, `GLOSSARY.md`)
|
||||||
|
- Stable API docs / wire-format docs
|
||||||
|
|
||||||
|
NOT for:
|
||||||
|
- TODO / ISSUES files (high churn — verification not meaningful)
|
||||||
|
- Decision Log / ADRs (immutable per LLMP-DEC governance)
|
||||||
|
|
||||||
|
Format: HTML-comment line at top: `<!-- last-verified: <commit-hash> @ <YYYY-MM-DD> -->`. Manual update by author on doc-modifying response. `docs-check` skill flags "doc with last-verified tag, age > N days, code path touched since" as a soft warning.
|
||||||
|
|
||||||
|
### Acceptance criteria
|
||||||
|
|
||||||
|
- Convention documented in `CONVENTIONS.md` or `META_ISSUES.md` reference section.
|
||||||
|
- Opt-in — never required, never auto-fail.
|
||||||
|
- `docs-check` skill respects the marker if present.
|
||||||
|
- Anti-hallucination value: agent expresses "stale, verifying" instead of guessing.
|
||||||
|
|
||||||
|
### Related
|
||||||
|
|
||||||
|
- Set 2 #3 (Copilot suggestion)
|
||||||
|
- `ACCORE-META-T-R2W6` (anti-hallucination framing motivates this)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ACCORE-META-T-P9F4: Pre-load expansion with foundational reference docs
|
||||||
|
|
||||||
|
**Priority:** P2 · **Type:** Protocol change (Session Setup section + Rule #1) · **Status:** Open · **Area:** `copilot-instructions.md` Session Setup + `docs-discovery/SKILL.md`
|
||||||
|
**Origin:** 2026-04-26 — User reframing (correctness > token-economy); current 3-file pre-load may be too narrow
|
||||||
|
|
||||||
|
### Description
|
||||||
|
|
||||||
|
Current session-start pre-load is **3 files** (per LLMP-DEC-43): the active repo's `copilot-instructions.md` + 2 reactive skill SKILL.md (`docs-discovery`, `docs-check`). The 3 user-gated skills lazy-load on demand.
|
||||||
|
|
||||||
|
User feedback: this is too narrow. Foundational reference docs (`ARCHITECTURE.md`, `GLOSSARY.md`, `CONVENTIONS.md`, top-level project `README.md`) get queried in nearly every session. Lazy-loading them per-turn means each topic-specific question pays the load cost separately. **Pre-loading at session-start amortizes once across the entire session.**
|
||||||
|
|
||||||
|
User cost function (explicit): 5-8K extra session-start tokens are CHEAPER than 1 hallucination loop caused by missing foundational context.
|
||||||
|
|
||||||
|
### Fix direction
|
||||||
|
|
||||||
|
Expand session-start pre-load to include foundational reference docs from active repo's `docs/` root (when they exist):
|
||||||
|
|
||||||
|
1. Active repo's `copilot-instructions.md` (current — keeps Rule #1 baseline)
|
||||||
|
2. `docs-discovery/SKILL.md` (current — reactive)
|
||||||
|
3. `docs-check/SKILL.md` (current — reactive)
|
||||||
|
4. Active repo's root `README.md` (NEW)
|
||||||
|
5. `docs/ARCHITECTURE.md` (NEW, if exists)
|
||||||
|
6. `docs/CONVENTIONS.md` (NEW, if exists)
|
||||||
|
7. `docs/GLOSSARY.md` (NEW, if exists — workspace-glossary)
|
||||||
|
|
||||||
|
Result: 4-7 files at session start (depending on which exist in the active repo). LOADED_DOCS prefix shows accurate count. `## Session Setup` section in `copilot-instructions.md` updated.
|
||||||
|
|
||||||
|
For inherit files: pre-load also AyCode.Core's foundational docs (since this repo doesn't host them) — adjust the count similarly.
|
||||||
|
|
||||||
|
### Acceptance criteria
|
||||||
|
|
||||||
|
- Session-start LOADED_DOCS count: 4-7 files (was 3).
|
||||||
|
- Foundational docs available without explicit user query → reduces "had to grep for context" loops.
|
||||||
|
- `docs-check`'s base context broader → more accurate drift detection.
|
||||||
|
- Skill descriptions and Session Setup section in `copilot-instructions.md` updated.
|
||||||
|
- LLMP-DEC entry recording the change (refines LLMP-DEC-43).
|
||||||
|
|
||||||
|
### Related
|
||||||
|
|
||||||
|
- LLMP-DEC-43 (current 5-skill matrix with 2 reactive pre-loaded)
|
||||||
|
- `ACCORE-META-T-R2W6` (user-preference statement — same anti-hallucination motivation)
|
||||||
|
- LLMP-DEC-58 / 60 (Framework-First retightening — pre-load expansion respects this: only same-or-lower-layer docs)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ACCORE-META-T-R2W6: Workspace user-preference statement (correctness > token-economy)
|
||||||
|
|
||||||
|
**Priority:** P2 · **Type:** Protocol change (new sub-rule or `## Workspace Load-Policy Preference` section) · **Status:** Open · **Area:** `copilot-instructions.md`
|
||||||
|
**Origin:** 2026-04-26 — User reframing of cost function: *"felesleges kör költsége >> tokenköltség. inkább több, mint hibás."*
|
||||||
|
|
||||||
|
### Description
|
||||||
|
|
||||||
|
The current AI AGENT CORE PROTOCOL implicitly assumes "minimize tokens" as a calibration goal. User's actual cost function is different:
|
||||||
|
|
||||||
|
> "Felesleges kör költsége >> tokenköltség. Inkább több, mint hibás."
|
||||||
|
|
||||||
|
Agent should **prefer reading docs over guessing** when uncertain. The amortization principle (LLMP-DEC-43 era) partially codifies this for session-start; doesn't extend explicitly to per-turn decisions.
|
||||||
|
|
||||||
|
Without an explicit preference statement, agents calibrate toward "minimum loading" defaults observed in their training. The workspace's actual preference is opposite: **load broader, ask when unsure, never guess critical context**.
|
||||||
|
|
||||||
|
### Fix direction
|
||||||
|
|
||||||
|
Add a new section / Rule sub-bullet to `copilot-instructions.md` (location TBD: Rule #2 extension, new Rule #7, or a `## Workspace Load-Policy Preference` section between `## Session Setup` and the Rules). Suggested text:
|
||||||
|
|
||||||
|
```
|
||||||
|
**WORKSPACE LOAD-POLICY PREFERENCE:** This workspace prioritizes correctness
|
||||||
|
over token-economy. When uncertain whether you have current/sufficient
|
||||||
|
context on a topic, READ the relevant `.md` file first — do NOT guess from
|
||||||
|
memory or attempt to infer purely from code-search. Loading 5-10K extra
|
||||||
|
tokens upfront is cheaper than a wrong answer that requires user correction
|
||||||
|
+ retry. The amortization principle extends to ALL turns, not just session
|
||||||
|
start: every turn has the choice between (a) read-then-answer and (b)
|
||||||
|
guess-then-maybe-be-wrong; default to (a).
|
||||||
|
|
||||||
|
This explicit preference shifts agent calibration toward "load when uncertain".
|
||||||
|
It does NOT mean "load everything" — relevance still matters (Rule #2's docs-
|
||||||
|
before-code stays). It means "within the relevance scope, prefer broader
|
||||||
|
loading over narrower".
|
||||||
|
```
|
||||||
|
|
||||||
|
### Acceptance criteria
|
||||||
|
|
||||||
|
- Rule text in AyCode.Core canonical `copilot-instructions.md` (inherited by reference for non-Core primary files, or duplicated to all 5 — TBD per protocol-audit invariant style).
|
||||||
|
- `docs-discovery/SKILL.md` and `docs-check/SKILL.md` reference this preference where it affects skill behaviour (cap revisit per `ACCORE-META-T-C5J7`).
|
||||||
|
- LLMP-DEC entry recording the codification.
|
||||||
|
|
||||||
|
### Related
|
||||||
|
|
||||||
|
- `ACCORE-META-T-P9F4` (pre-load expansion — direct application)
|
||||||
|
- `ACCORE-META-T-C5J7` (cap revision — also direct application)
|
||||||
|
- LLMP-DEC-43 (amortization principle — this preference extends it)
|
||||||
|
- `ACCORE-META-T-N8K3` (drift-detection — anti-hallucination companion)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ACCORE-META-T-C5J7: `docs-check` and `docs-discovery` cap revision (revisit fixed limits)
|
||||||
|
|
||||||
|
**Priority:** P3 · **Type:** Skill behaviour tuning · **Status:** Open · **Area:** `docs-check/SKILL.md` (volume cap), `docs-discovery/SKILL.md` (per-topic limit)
|
||||||
|
**Origin:** 2026-04-26 — User reframing; LLMP-DEC-27 (volume cap = 3) calibration revisit
|
||||||
|
|
||||||
|
### Description
|
||||||
|
|
||||||
|
`docs-check/SKILL.md` has a "max 3 items per response" volume cap (per LLMP-DEC-27, originally calibrated for "noise-aversion / silence beats noise"). `docs-discovery/SKILL.md` has per-topic file limits implicit in glob patterns.
|
||||||
|
|
||||||
|
User's recalibration: the noise-averse 3-cap may be too conservative under the "correctness > token-economy" preference (`ACCORE-META-T-R2W6`). Options:
|
||||||
|
|
||||||
|
1. **Higher fixed default** (e.g., 5 or 7) — simple, predictable.
|
||||||
|
2. **Confidence-based** (no cap if all proposals are high-confidence with quotable evidence; cap kicks in only when speculation creeps in) — better quality control but harder to calibrate.
|
||||||
|
3. **Configurable per session** via a marker — flexible but adds state.
|
||||||
|
|
||||||
|
NOT dynamic complexity-based tuning (rejected per Set 1 #1 skip rationale — inconsistent across agents).
|
||||||
|
|
||||||
|
### Fix direction
|
||||||
|
|
||||||
|
Two-step exploration:
|
||||||
|
1. Update `docs-check/SKILL.md` volume cap: try `max 5` instead of `max 3`. Observe over a few sessions: is signal/noise ratio better, worse, same?
|
||||||
|
2. If "better" → codify as new default with LLMP-DEC entry refining LLMP-DEC-27. If "worse" → revert and consider confidence-based alternative.
|
||||||
|
|
||||||
|
For `docs-discovery/SKILL.md`: revisit per-topic file limit. Currently glob loads paired main+ISSUES+TODO. Consider expanding to load related sub-topic docs by default if they exist (e.g., `LOGGING/README.md` triggers also loads `LOGGING_SERVER.md` if present).
|
||||||
|
|
||||||
|
### Acceptance criteria
|
||||||
|
|
||||||
|
- Cap is documented numerically (no vague "as many as needed").
|
||||||
|
- `docs-check` skill respects new cap.
|
||||||
|
- LLMP-DEC entry recording the cap change (refines LLMP-DEC-27).
|
||||||
|
- A short session-log validates the new cap doesn't introduce noise (signal/noise ≥ pre-change).
|
||||||
|
|
||||||
|
### Related
|
||||||
|
|
||||||
|
- LLMP-DEC-27 (original 3-cap rationale — "silence beats noise")
|
||||||
|
- `ACCORE-META-T-R2W6` (preference shift — motivates this)
|
||||||
|
- `ACCORE-META-T-P9F4` (pre-load expansion — parallel "load more" direction)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## TODO entry template
|
## TODO entry template
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -16,6 +16,7 @@ To make IDs like `ACCORE-LOG-I-K7M2`, `ACCORE-SIG-B-3R9P`, `ACCORE-XCUT-I-A4B7`
|
||||||
| `AUTH` | AUTH | User authentication: bearer tokens, JWT, login flow, hub authorization | `AyCode.Core/docs/AUTH/` |
|
| `AUTH` | AUTH | User authentication: bearer tokens, JWT, login flow, hub authorization | `AyCode.Core/docs/AUTH/` |
|
||||||
| `SIG` | SIGNALR | SignalR transport: tags, client base, dispatch, session | `AyCode.Core/AyCode.Services/docs/SIGNALR/` (+ variant in `AyCode.Services.Server`) |
|
| `SIG` | SIGNALR | SignalR transport: tags, client base, dispatch, session | `AyCode.Core/AyCode.Services/docs/SIGNALR/` (+ variant in `AyCode.Services.Server`) |
|
||||||
| `SBP` | SIGNALR_BINARY_PROTOCOL | Binary wire protocol over SignalR: framing, chunking, argument read | `AyCode.Core/AyCode.Services/docs/SIGNALR_BINARY_PROTOCOL/` |
|
| `SBP` | SIGNALR_BINARY_PROTOCOL | Binary wire protocol over SignalR: framing, chunking, argument read | `AyCode.Core/AyCode.Services/docs/SIGNALR_BINARY_PROTOCOL/` |
|
||||||
|
| `SIGDS` | SIGNALR_DATASOURCE | Client-server DataSource on SignalR transport: change tracking, rollback, sync state, load lifecycle, IList<T> wrapper | `AyCode.Core/AyCode.Services.Server/docs/SIGNALR_DATASOURCE/` |
|
||||||
| `BIN` | BINARY | AcBinary serializer: features, format, writers, source generator | `AyCode.Core/AyCode.Core/docs/BINARY/` |
|
| `BIN` | BINARY | AcBinary serializer: features, format, writers, source generator | `AyCode.Core/AyCode.Core/docs/BINARY/` |
|
||||||
| `TOON` | TOON | Toon serializer: LLM-optimized format with @meta/@types/@data sections | `AyCode.Core/AyCode.Core/docs/TOON/` |
|
| `TOON` | TOON | Toon serializer: LLM-optimized format with @meta/@types/@data sections | `AyCode.Core/AyCode.Core/docs/TOON/` |
|
||||||
| `XCUT` | cross-cutting | Issues / TODOs spanning ≥2 ACCORE topics — one canonical home, referenced from each affected topic | `AyCode.Core/AyCode.Core/docs/XCUT/` |
|
| `XCUT` | cross-cutting | Issues / TODOs spanning ≥2 ACCORE topics — one canonical home, referenced from each affected topic | `AyCode.Core/AyCode.Core/docs/XCUT/` |
|
||||||
|
|
@ -65,7 +66,7 @@ The framework (this file) does NOT enumerate higher-layer topics — that would
|
||||||
## Registry maintenance — adding a new ACCORE topic
|
## Registry maintenance — adding a new ACCORE topic
|
||||||
|
|
||||||
To add a new topic code **for AyCode.Core specifically**:
|
To add a new topic code **for AyCode.Core specifically**:
|
||||||
1. Propose the code (2-5 uppercase chars), short and mnemonic, scoped to ACCORE's domain (framework concerns only).
|
1. Propose the code (2-6 uppercase chars), short and mnemonic, scoped to ACCORE's domain (framework concerns only).
|
||||||
2. Check it doesn't collide with C# class-name prefixes (`Ac*` / `Mg*`) — the topic code should be visually distinct in mixed code/markdown content.
|
2. Check it doesn't collide with C# class-name prefixes (`Ac*` / `Mg*`) — the topic code should be visually distinct in mixed code/markdown content.
|
||||||
3. Check it doesn't collide with existing ACCORE topic codes in the table above.
|
3. Check it doesn't collide with existing ACCORE topic codes in the table above.
|
||||||
4. Add a row to the "Framework's own (ACCORE) topic codes" table.
|
4. Add a row to the "Framework's own (ACCORE) topic codes" table.
|
||||||
|
|
@ -80,7 +81,7 @@ C# code conventions in this workspace:
|
||||||
- `Ac*` — AyCode.Core framework types (e.g., `AcLoggerBase`, `AcBinarySerializer`)
|
- `Ac*` — AyCode.Core framework types (e.g., `AcLoggerBase`, `AcBinarySerializer`)
|
||||||
- `Mg*` — Mango company types (e.g., `MgGrid`, `MgDbTableBase`, `MgEntityBase`)
|
- `Mg*` — Mango company types (e.g., `MgGrid`, `MgDbTableBase`, `MgEntityBase`)
|
||||||
|
|
||||||
Topic codes intentionally avoid these 2-char prefixes (`Ac`, `Mg`) to prevent visual confusion in mixed content. Topic codes are 2-5 chars and SHOULD NOT start with `Ac` or `Mg`. (Example principle: a hypothetical 2-char `MG` topic code would visually collide with `Mg*` class names; choose a more distinctive ≥3-char code.)
|
Topic codes intentionally avoid these 2-char prefixes (`Ac`, `Mg`) to prevent visual confusion in mixed content. Topic codes are 2-6 chars and SHOULD NOT start with `Ac` or `Mg`. (Example principle: a hypothetical 2-char `MG` topic code would visually collide with `Mg*` class names; choose a more distinctive ≥3-char code.)
|
||||||
|
|
||||||
## Examples (ACCORE only)
|
## Examples (ACCORE only)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -56,8 +56,14 @@ For each `"<dep-name>: <relative-path>"` entry, resolve `<repo-root>/<relative-p
|
||||||
**C3. `@repo.prefix` has valid format**
|
**C3. `@repo.prefix` has valid format**
|
||||||
The `prefix` value must be uppercase, 4-12 chars, alphanumeric only (no hyphens / underscores / spaces / lowercase). It must NOT collide with `Ac*` / `Mg*` C# class-name prefixes (must be ≥ 4 chars, see `REPO_PREFIXES.md`).
|
The `prefix` value must be uppercase, 4-12 chars, alphanumeric only (no hyphens / underscores / spaces / lowercase). It must NOT collide with `Ac*` / `Mg*` C# class-name prefixes (must be ≥ 4 chars, see `REPO_PREFIXES.md`).
|
||||||
|
|
||||||
**C4. `## Session Setup` section present with all three skills**
|
**C4. `## Session Setup` section present with reactive + user-gated skill classification**
|
||||||
Header `## Session Setup` must appear. The section body must reference all three skills: `docs-discovery/SKILL.md`, `docs-check/SKILL.md`, and `protocol-audit/SKILL.md`. For inherit files, the section must additionally reference loading the canonical `copilot-instructions.md` (from the host repo — e.g., AyCode.Core). Expected first-response `[LOADED_DOCS]` counts: 4 for primary, 5 for inherit.
|
Header `## Session Setup` must appear. The section body must reference:
|
||||||
|
- 2 **reactive** skills (mandatory pre-load at session start): `docs-discovery/SKILL.md`, `docs-check/SKILL.md`.
|
||||||
|
- 3 **user-gated** skills (lazy-loaded on demand, listed for trigger-recognition): `protocol-audit/SKILL.md`, `adr-author/SKILL.md`, `docs-archive/SKILL.md`.
|
||||||
|
|
||||||
|
For inherit files, the section must additionally reference loading the canonical `copilot-instructions.md` (from the host repo — e.g., AyCode.Core).
|
||||||
|
|
||||||
|
Expected first-response `[LOADED_DOCS]` counts: **3 for primary** (this `copilot-instructions.md` + 2 reactive `SKILL.md`), **4 for inherit** (this file + canonical `copilot-instructions.md` + 2 reactive `SKILL.md`). Lazy-loaded skills add to the count only when first invoked. Updated per LLMP-DEC-43 (5-skill 2-reactive/3-user-gated matrix) and LLMP-DEC-64 (text-drift fix from initial 3-skill wording).
|
||||||
|
|
||||||
### 3B — Primary-only invariants (applied to files classified as **primary** in REPOS.md)
|
### 3B — Primary-only invariants (applied to files classified as **primary** in REPOS.md)
|
||||||
|
|
||||||
|
|
@ -105,8 +111,8 @@ Substring `LLM_PROTOCOL_DECISIONS.md` present (via the Protocol History section
|
||||||
|
|
||||||
The **canonical protocol host** is the repo designated in `references/REPOS.md` as housing the shared skills, Decision Log, and registries (typically the first row labeled the "host" in REPOS.md). That host does not need to reference itself. These X invariants apply to every other file registered in REPOS.md.
|
The **canonical protocol host** is the repo designated in `references/REPOS.md` as housing the shared skills, Decision Log, and registries (typically the first row labeled the "host" in REPOS.md). That host does not need to reference itself. These X invariants apply to every other file registered in REPOS.md.
|
||||||
|
|
||||||
**X1. `## Shared Agent Skills` section present with all three skills**
|
**X1. `## Shared Agent Skills` section present with all five skills**
|
||||||
Header `## Shared Agent Skills` must appear. All three bullets must be listed: `docs-discovery`, `protocol-audit`, `docs-check`.
|
Header `## Shared Agent Skills` must appear. All five bullets must be listed: `docs-discovery` (reactive), `docs-check` (reactive), `protocol-audit` (user-gated), `adr-author` (user-gated), `docs-archive` (user-gated). Updated per LLMP-DEC-43 + LLMP-DEC-64.
|
||||||
|
|
||||||
**X2. `## Protocol History` section present**
|
**X2. `## Protocol History` section present**
|
||||||
Header `## Protocol History` must appear AND it must reference the Decision Log at the canonical host's location (e.g., `<HOST_REPO>/.github/LLM_PROTOCOL_DECISIONS.md`; the concrete path is resolvable from REPOS.md).
|
Header `## Protocol History` must appear AND it must reference the Decision Log at the canonical host's location (e.g., `<HOST_REPO>/.github/LLM_PROTOCOL_DECISIONS.md`; the concrete path is resolvable from REPOS.md).
|
||||||
|
|
|
||||||
|
|
@ -62,3 +62,37 @@ After ACCORE-BIN-T-W9F1 lands, JIT of generated `WriteProperties` / `ScanObject`
|
||||||
**Acceptance:**
|
**Acceptance:**
|
||||||
- Benchmark a realistic entity graph (≥ 3 referenced child types) and show first-call time within ~10% of steady-state after ACCORE-BIN-T-W9F1 + chosen mitigation(s).
|
- Benchmark a realistic entity graph (≥ 3 referenced child types) and show first-call time within ~10% of steady-state after ACCORE-BIN-T-W9F1 + chosen mitigation(s).
|
||||||
- Document which combination is recommended for SignalR hot-path workloads vs. batch serialization.
|
- Document which combination is recommended for SignalR hot-path workloads vs. batch serialization.
|
||||||
|
|
||||||
|
## ACCORE-BIN-T-Z3K8: Replace `IId<T>` interface dependency with convention/attribute-based Id detection
|
||||||
|
**Priority:** P1 · **Type:** Refactor
|
||||||
|
|
||||||
|
The binary serializer currently detects Id-tracking properties via the `IId<T>` interface (`AyCode.Interfaces`). This couples the serializer to a framework-specific abstraction and forces consumer types to implement the interface for tracking participation. Move to a POCO-friendly detection scheme:
|
||||||
|
|
||||||
|
- **`IdDetectionMode.Convention`** (default) — convention-based; any property named `Id` is treated as the tracking key. Zero-friction onboarding.
|
||||||
|
- **`IdDetectionMode.Attribute`** — explicit; only properties marked with a serializer-native `[Id]` (or similar) attribute are tracked.
|
||||||
|
- **`[IgnoreId]`** attribute — escape hatch in `Convention` mode to exclude an Id-named property from tracking when the developer wants explicit opt-out.
|
||||||
|
|
||||||
|
**Implicit contract for `Convention` mode:** within a single class, the `Id` property must be type-level unique. Whether it semantically represents a primary key or a sequence number is irrelevant — the tracker keys by `(Type, Id)`, so per-type uniqueness is the only requirement. Violating this invariant typically signals a domain-modelling problem, not a serializer bug. Design rationale discussed in conversation 2026-04-27.
|
||||||
|
|
||||||
|
**Acceptance:**
|
||||||
|
- Binary serializer no longer references `IId<T>` in any execution path (no interface checks, no `where T : IId<TKey>` constraints in the serializer surface).
|
||||||
|
- Wire format unchanged.
|
||||||
|
- Existing consumers using `IId<T>`-implementing types still work transparently in `Convention` mode (their `Id` property is detected via convention).
|
||||||
|
- New consumers can use plain POCOs with no `AyCode.Interfaces` dependency.
|
||||||
|
- `IdDetectionMode` exposed on `AcBinaryOptions` (or successor options class post-rebrand).
|
||||||
|
- Default mode = `Convention`.
|
||||||
|
|
||||||
|
## ACCORE-BIN-T-N7V1: Replace `[JsonIgnore]` dependency with serializer-native ignore attribute
|
||||||
|
**Priority:** P2 · **Type:** Refactor
|
||||||
|
|
||||||
|
Property exclusion from binary serialization currently relies on `[JsonIgnore]` (Newtonsoft.Json). This couples the binary serializer to a third-party JSON library's attribute and is conceptually wrong — a binary serializer should not consult a JSON-specific marker for its exclusion semantics.
|
||||||
|
|
||||||
|
Define a serializer-native ignore attribute (working name `[BinaryIgnore]`; final name TBD pending broader rebrand). For backward compatibility during transition, also continue recognizing `[JsonIgnore]` with a deprecation note.
|
||||||
|
|
||||||
|
**Possible cross-cutting consideration:** if `Toon` and other future serializers also need property-exclusion, a single shared attribute (e.g., `[SerializerIgnore]` in a common abstractions package) may be cleaner than per-serializer attributes. Decide before naming finalizes — this may belong in `XCUT_TODO.md` rather than purely BINARY scope.
|
||||||
|
|
||||||
|
**Acceptance:**
|
||||||
|
- Native ignore attribute defined in the binary serializer's namespace (or shared abstractions package, pending the cross-cutting decision above).
|
||||||
|
- Both native attribute and `[JsonIgnore]` recognized during a transitional period; native attribute takes precedence on conflict.
|
||||||
|
- `[JsonIgnore]` recognition flagged for removal in a future major version (track in a follow-up cleanup TODO once consumer projects have migrated).
|
||||||
|
- No new code dependency on Newtonsoft.Json for property-exclusion logic.
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,259 @@
|
||||||
|
# SignalR DataSource
|
||||||
|
|
||||||
|
Change-tracked real-time collection on top of the SignalR transport layer. Source: `SignalRs/AcSignalRDataSource.cs` in `AyCode.Services.Server`.
|
||||||
|
|
||||||
|
> For the underlying transport (tag system, wire protocol, client base) see `AyCode.Services/docs/SIGNALR/README.md`.
|
||||||
|
> For server hub infrastructure see `../SIGNALR/README.md`.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
`AcSignalRDataSource` is an **abstract** generic `IList<T>` that synchronizes with the server via CRUD tags. It handles change tracking, rollback, sync state, and pluggable Binary/JSON+GZip deserialization — so consuming code works with a regular list while the DataSource manages communication.
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public abstract class AcSignalRDataSource<TDataItem, TId, TIList> : IList<TDataItem>, IList, IReadOnlyList<TDataItem>
|
||||||
|
where TDataItem : class, IId<TId> // entity with ID
|
||||||
|
where TId : struct // Guid, int, etc.
|
||||||
|
where TIList : class, IList<TDataItem> // List<T> or AcObservableCollection<T>
|
||||||
|
```
|
||||||
|
|
||||||
|
Consumers must subclass — the class is abstract:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public class MySampleDataSource : AcSignalRDataSource<MyEntity, Guid, AcObservableCollection<MyEntity>>
|
||||||
|
{
|
||||||
|
public MySampleDataSource(AcSignalRClientBase client, SignalRCrudTags tags)
|
||||||
|
: base(client, tags) { }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Constructor:**
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
new MySampleDataSource(
|
||||||
|
AcSignalRClientBase signalRClient, // transport client
|
||||||
|
SignalRCrudTags crudTags, // 5 tags for CRUD operations
|
||||||
|
object[]? contextIds = null // optional server-side filter context
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
## SignalRCrudTags
|
||||||
|
|
||||||
|
A `sealed class` that bundles **5 independent tag integers** — one per CRUD operation. Tags are NOT sequential offsets; each tag is independently assigned:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public sealed class SignalRCrudTags(
|
||||||
|
int getAllTag,
|
||||||
|
int getItemTag,
|
||||||
|
int addTag,
|
||||||
|
int updateTag,
|
||||||
|
int removeTag)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Usage (consuming project — domain-agnostic example):**
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public abstract class MyAppSignalRTags : AcSignalRTags
|
||||||
|
{
|
||||||
|
public const int SampleGetAll = 300;
|
||||||
|
public const int SampleGetItem = 301;
|
||||||
|
public const int SampleAdd = 302;
|
||||||
|
public const int SampleUpdate = 303;
|
||||||
|
public const int SampleRemove = 304;
|
||||||
|
}
|
||||||
|
|
||||||
|
var crudTags = new SignalRCrudTags(
|
||||||
|
MyAppSignalRTags.SampleGetAll,
|
||||||
|
MyAppSignalRTags.SampleGetItem,
|
||||||
|
MyAppSignalRTags.SampleAdd,
|
||||||
|
MyAppSignalRTags.SampleUpdate,
|
||||||
|
MyAppSignalRTags.SampleRemove
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
**Tag lookup:** `GetMessageTagByTrackingState(TrackingState)` maps tracking state to the corresponding tag via switch expression.
|
||||||
|
|
||||||
|
## Serializer Selection
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public AcSerializerType SerializerType { get; set; } = AcSerializerType.Binary;
|
||||||
|
```
|
||||||
|
|
||||||
|
Controls the deserialization format on the **raw byte[]** load path (see Data Loading). Must match the server's `SerializerOptions.SerializerType` for that endpoint. Default: `Binary`. Override to `JsonGZip` for JSON datasources.
|
||||||
|
|
||||||
|
## Data Loading
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
await dataSource.LoadDataSource(clearChangeTracking: true); // sync-wait transport
|
||||||
|
await dataSource.LoadDataSourceAsync(clearChangeTracking: true); // async callback path
|
||||||
|
await dataSource.LoadDataSource(fromSource, refreshDataFromDbAsync: false,
|
||||||
|
setSourceToWorkingReferenceList: false, clearChangeTracking: true); // in-memory load (no transport)
|
||||||
|
await dataSource.LoadDataSourceFromResponseData(responseData, serializerType,
|
||||||
|
refreshDataFromDbAsync: false, setSourceToWorkingReferenceList: false,
|
||||||
|
clearChangeTracking: true); // pre-fetched payload
|
||||||
|
await dataSource.LoadItem(id); // single item by ID
|
||||||
|
// fires OnDataSourceItemChanged
|
||||||
|
// with TrackingState.Get
|
||||||
|
```
|
||||||
|
|
||||||
|
**Three deserialization paths** in `LoadDataSourceFromResponseData`:
|
||||||
|
|
||||||
|
1. **Raw byte[]** (`responseData is byte[]`) — consumer-side deserialize:
|
||||||
|
- `AcSerializerType.Binary` + `IAcObservableCollection`: `BeginUpdate()` → `AcBinaryDeserializer.PopulateMerge` → `EndUpdate()` (single batched UI notification).
|
||||||
|
- `AcSerializerType.Binary` + plain `IList<T>`: `AcBinaryDeserializer.Populate`.
|
||||||
|
- `AcSerializerType.JsonGZip` + `IAcObservableCollection`: `GzipHelper.DecompressToString` → `PopulateFromJson`.
|
||||||
|
- `AcSerializerType.JsonGZip` + plain `IList<T>`: `GzipHelper.DecompressToString` → `JsonTo`.
|
||||||
|
|
||||||
|
2. **Typed object** (`responseData is TIList`) — protocol already deserialized:
|
||||||
|
- `IAcObservableCollection`: `BeginUpdate` → `Clear` + `foreach Add` → `EndUpdate`.
|
||||||
|
- Otherwise: direct `Clear` + `foreach Add`.
|
||||||
|
|
||||||
|
3. **Fallback re-serialize** (responseData neither `byte[]` nor `TIList` — e.g., bare `List<T>` in test scenarios without protocol): `AcBinarySerializer.Serialize(responseData)` → bytes → process as Path 1 (Binary).
|
||||||
|
|
||||||
|
**Context/Filtering:** `ContextIds` (`object[]`) and `FilterText` (`string`) are sent with every GetAll request for server-side filtering.
|
||||||
|
|
||||||
|
## Change Tracking
|
||||||
|
|
||||||
|
Each modified item is wrapped in `TrackingItem<TDataItem, TId>`:
|
||||||
|
|
||||||
|
| Field | Purpose |
|
||||||
|
|---|---|
|
||||||
|
| `TrackingState` | `Add`, `Update`, `Remove`, `Get` |
|
||||||
|
| `CurrentValue` | Current item reference |
|
||||||
|
| `OriginalValue` | Clone for rollback (`TrackingItemHelpers.JsonClone` or `ReflectionClone`) |
|
||||||
|
|
||||||
|
The `ChangeTracking<TDataItem, TId>` class manages the list of tracked items.
|
||||||
|
|
||||||
|
### CRUD Operations
|
||||||
|
|
||||||
|
```
|
||||||
|
Add(item): → TrackingState.Add + InnerList.Add(item)
|
||||||
|
Add(item, autoSave): → optionally calls SaveItem after
|
||||||
|
AddOrUpdate(item, autoSave): → exists? Update : Add (TrackingState auto-determined)
|
||||||
|
Insert(index, item): → TrackingState.Add + InnerList.Insert(index, item)
|
||||||
|
Insert(index, item, autoSave): → optionally calls SaveItem after
|
||||||
|
Update(i, item, autoSave): → TrackingState.Update + clone original + InnerList[i] = item
|
||||||
|
Update(item, autoSave): → as Update(i, ...) by item.Id lookup
|
||||||
|
Remove(id, autoSave): → TrackingState.Remove + clone original + InnerList.RemoveAt
|
||||||
|
Remove(item, autoSave): → as Remove(id) by item.Id
|
||||||
|
TryRemove(id, out item): → no-throw remove (returns bool)
|
||||||
|
RemoveAt(index, autoSave): → by index
|
||||||
|
```
|
||||||
|
|
||||||
|
`autoSave` parameter — if true, immediately calls `SaveItem()` for that single change after the local mutation.
|
||||||
|
|
||||||
|
**Manual tracking:** `SetTrackingStateToUpdate(item)` marks an existing item as modified without replacing it — useful when properties are mutated in-place.
|
||||||
|
|
||||||
|
### Additional collection helpers
|
||||||
|
|
||||||
|
| Member | Returns | Purpose |
|
||||||
|
|---|---|---|
|
||||||
|
| `AddRange(IEnumerable)` | `void` | batch insert (observable-aware: `IAcObservableCollection.AddRange` or `List<T>.AddRange`, fallback per-item `Add`) |
|
||||||
|
| `TryGetValue(id, out item)` | `bool` | ID-keyed lookup |
|
||||||
|
| `TryGetIndex(id, out index)` | `bool` | index-keyed lookup |
|
||||||
|
| `IndexOf(id)` / `IndexOf(item)` | `int` | -1 if not found |
|
||||||
|
| `Contains(item)` | `bool` | by ID |
|
||||||
|
| `AsReadOnly()` | `ReadOnlyCollection<TDataItem>` | wrap |
|
||||||
|
| `BinarySearch(...)` | `int` | currently throws `NotImplementedException` (see SIGNALR_DATASOURCE_ISSUES.md) |
|
||||||
|
|
||||||
|
### Events
|
||||||
|
|
||||||
|
| Event | Signature | Fires when |
|
||||||
|
|---|---|---|
|
||||||
|
| `OnDataSourceItemChanged` | `Func<ItemChangedEventArgs<T>, Task>?` | After each item is saved or `LoadItem` completes (TrackingState in args) |
|
||||||
|
| `OnDataSourceLoaded` | `Func<Task>?` | After `LoadDataSource*` completes |
|
||||||
|
| `OnSyncingStateChanged` | `Action<bool>?` | On 0→1 (true) and 1→0 (false) sync transitions |
|
||||||
|
|
||||||
|
`ItemChangedEventArgs<T>` carries `Item: T` and `TrackingState: TrackingState`.
|
||||||
|
|
||||||
|
## SaveChanges
|
||||||
|
|
||||||
|
| Method | Returns | Transport pattern | Use case |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `SaveChanges()` | `Task<List<TrackingItem>>` (remaining failures) | Sync-wait via `ContinueWith` | Caller needs to know what failed |
|
||||||
|
| `SaveChangesAsync()` | `Task` (void) | Fire-and-forget callback (`Action<SignalResponseDataMessage>`) | Background save, no result inspection |
|
||||||
|
| `SaveItem(id)` | `Task<TDataItem>` | Sync-wait | Save a single tracked item |
|
||||||
|
| `SaveItem(id, trackingState)` | `Task<TDataItem>` | Sync-wait | Save with explicit state |
|
||||||
|
| `SaveItem(item, trackingState)` | `Task<TDataItem>` | Sync-wait | Save without lookup |
|
||||||
|
|
||||||
|
```
|
||||||
|
Both batch flows:
|
||||||
|
BeginSync()
|
||||||
|
for each tracked item:
|
||||||
|
tag = CrudTags.GetMessageTagByTrackingState(state)
|
||||||
|
response = SignalRClient.PostDataAsync(tag, item)
|
||||||
|
on success: ProcessSavedResponseItem → CopyTo InnerList item, remove tracking,
|
||||||
|
fire OnDataSourceItemChanged
|
||||||
|
on failure: TryRollbackItem() → restore OriginalValue
|
||||||
|
EndSync()
|
||||||
|
```
|
||||||
|
|
||||||
|
## Rollback
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
bool TryRollbackItem(TId id, out TDataItem? originalValue);
|
||||||
|
void Rollback(); // reverts all tracked changes
|
||||||
|
```
|
||||||
|
|
||||||
|
`TryRollbackItem` restores `OriginalValue` to `InnerList`:
|
||||||
|
- `TrackingState.Add`: removes item entirely (no original existed).
|
||||||
|
- `TrackingState.Update` / `Remove`: re-applies `OriginalValue.CopyTo(InnerList[index])`, or re-adds if missing.
|
||||||
|
- The tracking entry is removed in either case.
|
||||||
|
|
||||||
|
`Rollback()` iterates and calls `TryRollbackItem` for every tracked item.
|
||||||
|
|
||||||
|
## Sync State
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
private int _activeSyncOperations; // Interlocked counter
|
||||||
|
|
||||||
|
BeginSync(): Interlocked.Increment → fires OnSyncingStateChanged(true) on 0→1
|
||||||
|
EndSync(): Interlocked.Decrement → fires OnSyncingStateChanged(false) on 1→0
|
||||||
|
IsSyncing: _activeSyncOperations > 0
|
||||||
|
```
|
||||||
|
|
||||||
|
UI binds to `IsSyncing` to show loading indicators. The counter supports nested sync operations.
|
||||||
|
|
||||||
|
## Working Reference List
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
dataSource.SetWorkingReferenceList(externalList);
|
||||||
|
// Now dataSource operates directly on externalList — same reference, no copy
|
||||||
|
TIList innerList = dataSource.GetReferenceInnerList();
|
||||||
|
bool hasRef = dataSource.HasWorkingReferenceList;
|
||||||
|
```
|
||||||
|
|
||||||
|
Useful when the UI already has a bound collection and you want the DataSource to manage it in-place.
|
||||||
|
|
||||||
|
## Locking Strategy
|
||||||
|
|
||||||
|
| Lock | Scope | Used by |
|
||||||
|
|---|---|---|
|
||||||
|
| `object _syncRoot` | Synchronous | Count, Contains, IndexOf, GetEnumerator, Add (no-save), Insert (no-save), CopyTo |
|
||||||
|
| `SemaphoreSlim _asyncLock` | Asynchronous | LoadDataSource*, AddOrUpdate, Update, Remove (with save), SaveItem, SaveChanges* |
|
||||||
|
|
||||||
|
`GetEnumerator()` returns `InnerList.ToList().GetEnumerator()` — safe copy to avoid mutation during iteration.
|
||||||
|
|
||||||
|
## Relationship to Transport
|
||||||
|
|
||||||
|
The DataSource is a **consumer** of the SignalR transport, not part of it:
|
||||||
|
|
||||||
|
```
|
||||||
|
DataSource.SaveChanges()
|
||||||
|
→ CrudTags.GetMessageTagByTrackingState(state) → tag
|
||||||
|
→ AcSignalRClientBase.PostDataAsync(tag, item) ← transport layer
|
||||||
|
→ OnReceiveMessage(tag, bytes, requestId) ← wire protocol
|
||||||
|
→ Server method with [SignalR(tag)] ← tag dispatch
|
||||||
|
```
|
||||||
|
|
||||||
|
Projects can also call the transport directly without DataSource — see `AyCode.Services/docs/SIGNALR/README.md`.
|
||||||
|
|
||||||
|
> **Cross-cutting:** `ACCORE-SIG-I-T7S2` (GetAll raw byte[] for populate/merge) lives in `../SIGNALR/SIGNALR_ISSUES.md` because it spans transport + DataSource concerns equally.
|
||||||
|
|
||||||
|
## Key Source Files
|
||||||
|
|
||||||
|
| Component | Path |
|
||||||
|
|---|---|
|
||||||
|
| DataSource (abstract base) | `SignalRs/AcSignalRDataSource.cs` (in `AyCode.Services.Server`) |
|
||||||
|
| Tracking helpers (`JsonClone` / `ReflectionClone`) | `SignalRs/TrackingItemHelpers.cs` (in `AyCode.Services.Server`) |
|
||||||
|
| CRUD tags | `SignalRs/SignalRCrudTags.cs` (in `AyCode.Services`) |
|
||||||
|
| Transport client | `SignalRs/AcSignalRClientBase.cs` (in `AyCode.Services`) |
|
||||||
|
|
@ -0,0 +1,56 @@
|
||||||
|
# SignalR DataSource — Known Issues
|
||||||
|
|
||||||
|
Issues specific to `AcSignalRDataSource` (state management, change tracking, public API surface). Issues affecting the underlying SignalR transport itself live in `../SIGNALR/SIGNALR_ISSUES.md` (`SIG` topic).
|
||||||
|
|
||||||
|
Cross-cutting transport+DataSource concerns intentionally remain under `SIG` — see `../SIGNALR/SIGNALR_ISSUES.md#accore-sig-i-t7s2` (GetAll raw byte[] for populate/merge).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ACCORE-SIGDS-B-T4K9: SaveChangesAsync silently drops per-item save Tasks
|
||||||
|
|
||||||
|
**Status:** Open
|
||||||
|
**Affects:** `AcSignalRDataSource.SaveTrackingItemUnsafeAsync` (line 1001-1002)
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
private async Task SaveTrackingItemUnsafeAsync(TrackingItem<TDataItem, TId> trackingItem)
|
||||||
|
=> SaveItemUnsafeAsync(trackingItem.CurrentValue, trackingItem.TrackingState);
|
||||||
|
```
|
||||||
|
|
||||||
|
`async Task` with an expression body that produces another `Task` discards the inner Task — the outer state machine wraps a void completion, so the per-item save is fire-and-forget. The caller in `SaveChangesAsync` (line 950) `await SaveTrackingItemUnsafeAsync(trackingItem)` returns immediately; the foreach proceeds to the next item without waiting for the previous server response.
|
||||||
|
|
||||||
|
If this is the intended fire-and-forget batch behaviour, the method should drop `async` and return the inner Task explicitly (consumers can opt into await). If sequential save was the intent, the body needs `await SaveItemUnsafeAsync(...)`.
|
||||||
|
|
||||||
|
**Possible fix direction:**
|
||||||
|
```csharp
|
||||||
|
// Option A — explicit fire-and-forget (rename + drop async):
|
||||||
|
private void DispatchTrackingItemUnsafeAsync(TrackingItem<TDataItem, TId> ti)
|
||||||
|
=> SaveItemUnsafeAsync(ti.CurrentValue, ti.TrackingState).Forget();
|
||||||
|
|
||||||
|
// Option B — sequential (proper await):
|
||||||
|
private Task SaveTrackingItemUnsafeAsync(TrackingItem<TDataItem, TId> ti)
|
||||||
|
=> SaveItemUnsafeAsync(ti.CurrentValue, ti.TrackingState);
|
||||||
|
// (drop `async`; caller `await` waits the inner Task)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ACCORE-SIGDS-I-W2H6: BinarySearch is public but throws NotImplementedException
|
||||||
|
|
||||||
|
**Status:** Open
|
||||||
|
**Affects:** `AcSignalRDataSource.BinarySearch` (lines 1179, 1184, 1185)
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public int BinarySearch(int index, int count, TDataItem item, IComparer<TDataItem>? comparer)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException($"BinarySearch");
|
||||||
|
}
|
||||||
|
public int BinarySearch(TDataItem item) => BinarySearch(0, Count, item, null);
|
||||||
|
public int BinarySearch(TDataItem item, IComparer<TDataItem>? comparer) => BinarySearch(0, Count, item, comparer);
|
||||||
|
```
|
||||||
|
|
||||||
|
All three overloads are `public` (visible via IntelliSense, callable by consumers) but throw at runtime. Sort-dependent operation — DataSource ordering is not enforced by the framework, so a meaningful implementation requires an explicit sort contract.
|
||||||
|
|
||||||
|
**Possible fix directions** (mutually exclusive):
|
||||||
|
- Implement (delegating to `InnerList.BinarySearch` if `InnerList is List<TDataItem>`; for `IAcObservableCollection` document the comparer requirement).
|
||||||
|
- Mark `[Obsolete("Not implemented")]` + change visibility to `protected internal` (keeps source compatibility, hides from external callers).
|
||||||
|
- Remove the three overloads outright and rely on consumers using `IndexOf` + manual sort if needed.
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
# SignalR DataSource — TODO
|
||||||
|
|
||||||
|
Forward-looking work specific to `AcSignalRDataSource`. Transport-side TODOs live in `../SIGNALR/SIGNALR_TODO.md`.
|
||||||
|
|
||||||
|
## Priority legend
|
||||||
|
- **P0** blocker · **P1** important · **P2** nice-to-have · **P3** idea
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ACCORE-SIGDS-T-N5R8: ChangeTracking._trackingItems → Dictionary<TId, TrackingItem>
|
||||||
|
**Priority:** P3 · **Type:** Performance refactor · **Status:** Open
|
||||||
|
|
||||||
|
Pre-existing `//TODO: Dictionary... - J.` comment in `AcSignalRDataSource.cs:68`:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
private readonly List<TrackingItem<TDataItem, TId>> _trackingItems = [];
|
||||||
|
```
|
||||||
|
|
||||||
|
`ChangeTracking.FindIndex(newValue)` and `TryGetTrackingItem(id)` both linear-scan `_trackingItems`. For DataSources with many concurrent tracked changes (large grids with frequent edits), this is O(N) per operation. A `Dictionary<TId, TrackingItem<TDataItem, TId>>` would give O(1) lookup; `ToList()` enumeration (the only external read path) is order-agnostic, so no behavioural regression.
|
||||||
|
|
||||||
|
Trade-off: per-tracking-item dictionary overhead; negligible for typical workload.
|
||||||
|
|
||||||
|
**Acceptance:**
|
||||||
|
- `_trackingItems` switched to `Dictionary<TId, TrackingItem<TDataItem, TId>>`.
|
||||||
|
- `FindIndex` removed (caller paths in `AddTrackingItem` re-flow as TryGetValue + Add/Update).
|
||||||
|
- `TryGetTrackingItem` becomes single dict lookup.
|
||||||
|
- `ToList()` returns `_trackingItems.Values.ToList()`.
|
||||||
|
- `Remove(trackingItem)` → `Remove(trackingItem.CurrentValue.Id)`.
|
||||||
|
- All existing tests pass without change to public API surface.
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -58,7 +58,7 @@ Zero first-message overhead, fully explicit. Both sides advertise their send-mod
|
||||||
These ideas were captured because the **wider binary-protocol market is currently silent** on these features (no major .NET binary serializer ships built-in encryption / compression / tracing). Real differentiation potential, but only if executed correctly.
|
These ideas were captured because the **wider binary-protocol market is currently silent** on these features (no major .NET binary serializer ships built-in encryption / compression / tracing). Real differentiation potential, but only if executed correctly.
|
||||||
|
|
||||||
## ACCORE-SBP-T-H7M5: Optional payload encryption (`AcEncryptionOptions`)
|
## ACCORE-SBP-T-H7M5: Optional payload encryption (`AcEncryptionOptions`)
|
||||||
**Priority:** P3 — IDEA · **Type:** Feature (NuGet competitiveness) · **Status:** Open
|
**Priority:** P3 — IDEA · **Type:** Feature (NuGet competitiveness) · **Status:** Open · **Umbrella ADR:** [`adr/0001`](../adr/0001-acbinary-decorator-feature-stack-design.md)
|
||||||
|
|
||||||
### Niches where TLS alone is insufficient
|
### Niches where TLS alone is insufficient
|
||||||
- **SignalR backplane / Azure SignalR Service** — relay sees plaintext; payload encryption gives true end-to-end client↔client.
|
- **SignalR backplane / Azure SignalR Service** — relay sees plaintext; payload encryption gives true end-to-end client↔client.
|
||||||
|
|
@ -84,7 +84,7 @@ These ideas were captured because the **wider binary-protocol market is currentl
|
||||||
- Decorator API sketch reviewed.
|
- Decorator API sketch reviewed.
|
||||||
|
|
||||||
## ACCORE-SBP-T-N9F3: Optional message compression with `MinSize` threshold
|
## ACCORE-SBP-T-N9F3: Optional message compression with `MinSize` threshold
|
||||||
**Priority:** P3 — IDEA · **Type:** Feature (NuGet competitiveness) · **Status:** Open
|
**Priority:** P3 — IDEA · **Type:** Feature (NuGet competitiveness) · **Status:** Open · **Umbrella ADR:** [`adr/0001`](../adr/0001-acbinary-decorator-feature-stack-design.md)
|
||||||
|
|
||||||
### Niches
|
### Niches
|
||||||
- **Large structured payloads** (orders, product lists, shipping documents) — typical 50-90% compression on text-heavy DTOs.
|
- **Large structured payloads** (orders, product lists, shipping documents) — typical 50-90% compression on text-heavy DTOs.
|
||||||
|
|
@ -148,7 +148,7 @@ In `AsyncSegment` mode the total message size is unknown until `CHUNK_END`, so a
|
||||||
- Fallback / negotiation strategy: what happens if peer can't decompress? (Suggested: wire-marker `0x00` always works since it means "uncompressed" — sender can downgrade per-message if peer signals incompatibility, similar to HTTP `Accept-Encoding` semantics.)
|
- Fallback / negotiation strategy: what happens if peer can't decompress? (Suggested: wire-marker `0x00` always works since it means "uncompressed" — sender can downgrade per-message if peer signals incompatibility, similar to HTTP `Accept-Encoding` semantics.)
|
||||||
|
|
||||||
## ACCORE-SBP-T-J5W8: OpenTelemetry tracing integration
|
## ACCORE-SBP-T-J5W8: OpenTelemetry tracing integration
|
||||||
**Priority:** P3 — IDEA · **Type:** Feature (NuGet competitiveness) · **Status:** Open
|
**Priority:** P3 — IDEA · **Type:** Feature (NuGet competitiveness) · **Status:** Open · **Umbrella ADR:** [`adr/0001`](../adr/0001-acbinary-decorator-feature-stack-design.md)
|
||||||
|
|
||||||
### Niches
|
### Niches
|
||||||
- **Distributed tracing** is a modern .NET observability standard — gRPC has it, MessagePack/MemoryPack/Protobuf-net don't.
|
- **Distributed tracing** is a modern .NET observability standard — gRPC has it, MessagePack/MemoryPack/Protobuf-net don't.
|
||||||
|
|
@ -166,7 +166,7 @@ In `AsyncSegment` mode the total message size is unknown until `CHUNK_END`, so a
|
||||||
- Hot-path branch verification — when `ActivitySource == null`, JIT must eliminate the tracing code completely.
|
- Hot-path branch verification — when `ActivitySource == null`, JIT must eliminate the tracing code completely.
|
||||||
|
|
||||||
## ACCORE-SBP-T-B3K6: Optional HMAC signing (without encryption)
|
## ACCORE-SBP-T-B3K6: Optional HMAC signing (without encryption)
|
||||||
**Priority:** P3 — IDEA · **Type:** Feature (NuGet competitiveness) · **Status:** Open
|
**Priority:** P3 — IDEA · **Type:** Feature (NuGet competitiveness) · **Status:** Open · **Umbrella ADR:** [`adr/0001`](../adr/0001-acbinary-decorator-feature-stack-design.md)
|
||||||
|
|
||||||
### Niches
|
### Niches
|
||||||
- **Tamper detection where confidentiality is acceptable** — audit log forwarding, telemetry where data isn't sensitive but integrity must be provable.
|
- **Tamper detection where confidentiality is acceptable** — audit log forwarding, telemetry where data isn't sensitive but integrity must be provable.
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ Adopt the **decorator-chain pattern** for composing optional features onto `AcBi
|
||||||
|
|
||||||
### `AcHubProtocolDecoratorBase` abstract base
|
### `AcHubProtocolDecoratorBase` abstract base
|
||||||
|
|
||||||
A new abstract class in `AyCode.Services/SignalRs/` (concrete API spec is impl-level, tracked as a forthcoming TODO in `SIGNALR_BINARY_PROTOCOL_TODO.md`, no ID assigned yet):
|
A new abstract class in `AyCode.Services/SignalRs/` (concrete API spec is impl-level — see Follow-up #1 for tracking):
|
||||||
|
|
||||||
- Delegates passthrough `IHubProtocol` members (`Name`, `Version`, `TransferFormat`, `IsVersionSupported`, `GetMessageBytes`) to the wrapped inner protocol.
|
- Delegates passthrough `IHubProtocol` members (`Name`, `Version`, `TransferFormat`, `IsVersionSupported`, `GetMessageBytes`) to the wrapped inner protocol.
|
||||||
- Exposes `protected IHubProtocol Inner { get; }` to derived classes.
|
- Exposes `protected IHubProtocol Inner { get; }` to derived classes.
|
||||||
|
|
@ -72,7 +72,7 @@ At connection setup (1 application-level handshake roundtrip after SignalR's pro
|
||||||
|
|
||||||
**Mismatch handling**: asymmetric registration (one side has decorator, other doesn't) → handshake fails fast with a logged reason at `Error` level. Per-message asymmetry (handshake-skip artefacts) is handled by the decorator-failure unified protocol below.
|
**Mismatch handling**: asymmetric registration (one side has decorator, other doesn't) → handshake fails fast with a logged reason at `Error` level. Per-message asymmetry (handshake-skip artefacts) is handled by the decorator-failure unified protocol below.
|
||||||
|
|
||||||
The app-level handshake mechanism's concrete shape (Option A: a special `__ac_handshake` HubMessage as first message; Option B: wire-level `WriteHandshake` virtual on `AcHubProtocolDecoratorBase`) is **deferred to a follow-up** — the umbrella commits to "handshake-based" but does not pick the mechanism. Tracked as part of the forthcoming TODO.
|
The app-level handshake mechanism's concrete shape (Option A: a special `__ac_handshake` HubMessage as first message; Option B: wire-level `WriteHandshake` virtual on `AcHubProtocolDecoratorBase`) is **deferred to a follow-up** — the umbrella commits to "handshake-based" but does not pick the mechanism. See Follow-up #3.
|
||||||
|
|
||||||
### Per-message `IsCompressed` flag — semantic (A): standalone byte
|
### Per-message `IsCompressed` flag — semantic (A): standalone byte
|
||||||
|
|
||||||
|
|
@ -186,9 +186,9 @@ A v1 client (no decorators registered) connecting to a v2 server (decorators in
|
||||||
|
|
||||||
### Follow-ups required
|
### Follow-ups required
|
||||||
|
|
||||||
1. **`AcHubProtocolDecoratorBase` implementation** — abstract class added to `AyCode.Services/SignalRs/`; tracked as a new forthcoming TODO entry in `SIGNALR_BINARY_PROTOCOL_TODO.md` (no ID assigned yet). Implementation deferred until at least one leaf ADR (0002-0005) reaches `Status: Accepted`.
|
1. **`AcHubProtocolDecoratorBase` implementation** — abstract class to be added to `AyCode.Services/SignalRs/`. **Implementation is deferred** until at least one leaf ADR (0002-0005) reaches `Status: Accepted` and triggers the actual feature work. **At that point**, a `SIGNALR_BINARY_PROTOCOL_TODO.md` entry will be added with a freshly-generated `ACCORE-SBP-T-<RAND>` ID per the `docs-check` skill Step 5 procedure, and this follow-up's references throughout the ADR will be updated.
|
||||||
2. **Stacking-order validation in DI** — `AddAcEncryption` / `AddAcSigning` / `AddAcCompression` extension methods should warn or throw if registered in a non-canonical order (concrete impl part of the forthcoming TODO).
|
2. **Stacking-order validation in DI** — `AddAcEncryption` / `AddAcSigning` / `AddAcCompression` extension methods should warn or throw if registered in a non-canonical order. Concrete implementation lands together with Follow-up #1 once that work begins.
|
||||||
3. **App-level handshake mechanism design** — Option A (`__ac_handshake` HubMessage as first message) vs Option B (`WriteHandshake` virtual on `AcHubProtocolDecoratorBase`). Tracked as part of the forthcoming TODO.
|
3. **App-level handshake mechanism design** — choose between Option A (`__ac_handshake` HubMessage as first message) vs Option B (`WriteHandshake` virtual on `AcHubProtocolDecoratorBase`). Decided + implemented together with Follow-up #1 when that work begins.
|
||||||
4. **Benchmark scaffolding** — message-granularity vtable cost measurement for the "negligible" claim. Each leaf ADR's acceptance criteria includes verifying this in their feature's context.
|
4. **Benchmark scaffolding** — message-granularity vtable cost measurement for the "negligible" claim. Each leaf ADR's acceptance criteria includes verifying this in their feature's context.
|
||||||
5. **Cross-references** — Step 8 of the `adr-author` skill: this ADR links to the 4 forthcoming leaf ADRs (0002-0005); `../SIGNALR_BINARY_PROTOCOL/README.md` gets a new `## Related ADRs` section pointing back to `0001-acbinary-decorator-feature-stack-design.md`.
|
5. **Cross-references** — Step 8 of the `adr-author` skill: this ADR links to the 4 forthcoming leaf ADRs (0002-0005); `../SIGNALR_BINARY_PROTOCOL/README.md` gets a new `## Related ADRs` section pointing back to `0001-acbinary-decorator-feature-stack-design.md`.
|
||||||
|
|
||||||
|
|
@ -210,6 +210,6 @@ A v1 client (no decorators registered) connecting to a v2 server (decorators in
|
||||||
|
|
||||||
## Related
|
## Related
|
||||||
|
|
||||||
- Related TODOs: `../SIGNALR_BINARY_PROTOCOL/SIGNALR_BINARY_PROTOCOL_TODO.md` ACCORE-SBP-T-H7M5 (encryption), ACCORE-SBP-T-N9F3 (compression+MinSize), ACCORE-SBP-T-J5W8 (tracing), ACCORE-SBP-T-B3K6 (signing); the forthcoming `AcHubProtocolDecoratorBase` impl + handshake-mechanism TODO (to be added in Step 8 cross-reference round).
|
- Related TODOs: `../SIGNALR_BINARY_PROTOCOL/SIGNALR_BINARY_PROTOCOL_TODO.md` ACCORE-SBP-T-H7M5 (encryption), ACCORE-SBP-T-N9F3 (compression+MinSize), ACCORE-SBP-T-J5W8 (tracing), ACCORE-SBP-T-B3K6 (signing). The `AcHubProtocolDecoratorBase` impl + handshake-mechanism TODO is **not yet created** — see Follow-up #1 for the conditions under which it will be added.
|
||||||
- Forthcoming ADRs: 0002 (payload encryption), 0003 (message compression with MinSize), 0004 (OpenTelemetry tracing), 0005 (HMAC signing-only) — all will declare `Depends on: ADR-0001`.
|
- Forthcoming ADRs: 0002 (payload encryption), 0003 (message compression with MinSize), 0004 (OpenTelemetry tracing), 0005 (HMAC signing-only) — all will declare `Depends on: ADR-0001`.
|
||||||
- Topic-folder cross-ref: `../SIGNALR_BINARY_PROTOCOL/README.md` to add `## Related ADRs` section in Step 8.
|
- Topic-folder cross-ref: `../SIGNALR_BINARY_PROTOCOL/README.md` to add `## Related ADRs` section in Step 8.
|
||||||
|
|
|
||||||
|
|
@ -100,7 +100,6 @@ Adopt a layered bearer-token authentication architecture across all client and s
|
||||||
- **Per-tag DI policy registry** (rejected): too much complexity for Layer 0; mixes consumer-specific tag lists into framework.
|
- **Per-tag DI policy registry** (rejected): too much complexity for Layer 0; mixes consumer-specific tag lists into framework.
|
||||||
- **Custom token middleware from scratch** (rejected): non-standard, no `[Authorize]` interop, no out-of-the-box JWT validation.
|
- **Custom token middleware from scratch** (rejected): non-standard, no `[Authorize]` interop, no out-of-the-box JWT validation.
|
||||||
- *Future flexibility:* locks us out of standard OAuth2 / external-IdP integration when ADR `0002+` extends scope; the chosen `JwtBearer` pipeline keeps that door open.
|
- *Future flexibility:* locks us out of standard OAuth2 / external-IdP integration when ADR `0002+` extends scope; the chosen `JwtBearer` pipeline keeps that door open.
|
||||||
- *Future flexibility:* locks out standard OAuth2 / external-IdP integration when ADR `0002+` extends scope; the chosen `JwtBearer` pipeline keeps that door open.
|
|
||||||
|
|
||||||
## Related
|
## Related
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue