Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions .cursor/rules/living-documentation.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
---
description: Document new types and members — purpose, usage, problem solved
alwaysApply: true
---

# Living Documentation for Types and Members

Good code documents **why** it exists, not only **what** it does. When you add or materially change a type (class, struct, enum, type alias) or a non-obvious member, add brief comments at the definition site.

## Required for new/changed definitions

For each new type or group of related helpers, document:

1. **Purpose** — what problem this solves in the subsystem.
2. **Usage** — who calls it, lifetime/ownership expectations, borrow vs own.
3. **Scope** — file-local helper vs public API; thread/context constraints if relevant.

For non-obvious members (handles, caches, flags, borrowed pointers, `unique_ptr` sinks), add a comment **on the line above the member** unless the type-level comment already makes it obvious. Do not use trailing end-of-line comments for member documentation.

## Style in this repo

- **Public headers** (`cpp-lib/include/`, generated interfaces): use existing `/*! ... */` block style where siblings do.
- **Implementation-only helpers** (anonymous namespaces, `.cpp` RAII guards): `//` block above the type; `//` line above each non-obvious member.
- Match **line density** of the surrounding file — do not add blank lines after every line.
- Do not restate the identifier in prose (“`m_foo` is the foo”) unless it disambiguates ownership or invariants.
- **Document each type or member in place.** Do not defer to another definition with “same as”, “like”, “see”, or “mirrors” — if one changes, cross-references go stale. Repeat purpose, usage, and ownership at every site that needs it.

## Examples

```cpp
// Snapshot of inbound invoke metadata after envelope decode.
class InboundInvokeRejectContext {
public:
// Invoke ID from the decoded envelope (99999 = event).
AsnIntType m_invokeId = 0;
};

/*! Owned inbound response matched to a pending outbound invoke. */
std::unique_ptr<SNACC::ROSEMessage> m_pAnswerMessage;
```

## When comments are not required

- Trivial locals, loop indices, obvious parameters (`invokeID`, `strResponse`).
- Generated code unless the generator template is being changed.
- Renames with unchanged semantics.

## Agent workflow

When introducing structures or members as part of a fix or feature, **include documentation in the same change**. Do not defer “comment pass” to a follow-up unless the user explicitly asks for code-only first.
53 changes: 53 additions & 0 deletions .cursor/rules/test-specification-first.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
---
description: Tests specify public API behavior — never tune tests or product code to go green
alwaysApply: true
---

# Tests Define the Public API

**Hard constraint for the snacc ecosystem (compiler, runtimes, generated stubs, samples).**

Tests document the **expected behavior** of the public API. They are the specification callers and integrators may rely on — not a mirror of whatever the code happens to do today.

## Non-negotiable rules

1. **Write tests to specify the API.** The goal is a precise, reviewable contract for public behavior (encoders, decoders, ROSE runtime, generated handlers, etc.) — not merely a green test count.

2. **NEVER tune test code** to make the suite pass. Do not weaken, delete, skip, or rewrite assertions because the current implementation fails. Do not change expected return codes, context identity, telemetry shape, or error semantics to match existing behavior.

3. **NEVER tune production or library code** (compiler, `cpp-lib/`, `c-lib/`, generators, runtimes) merely to make tests pass. Do not add shortcuts, bypasses, test-only branches, or special cases whose purpose is to turn a failing spec test green without implementing the specified behavior.

4. **NEVER tune both sides** in the same change to "meet in the middle."

5. **When writing tests, do not change test or product code** beyond adding the new test files and neutral test infrastructure (fixtures, CMake wiring) that does not alter what is asserted.

6. **Only exception:** the user **explicitly asks** to tune test code, tune product code, or revise the spec. Until then, leave failing tests failing and report what failed and why.

7. **Test-first workflow:** when asked to add tests before fixes, write the **full** scenario matrix (happy + failure paths), run it, and **stop**. Do not fix product code in the same task unless the user asks for that phase.

8. **Harness and fakes are for observation only.** Test support (`cpp-lib/tests/test_support/`, mocks, loopback transports) may record state and drive scenarios. It must **not** bypass the API under test or smuggle in behavior the real runtime would not satisfy.

9. **If the spec is unclear, ask.** Do not guess by aligning test and code. Document locked-in semantics in `cpp-lib/tests/runtime_correctness_notes.md`.

## When a spec test fails (default behavior)

- **Report** the failure and which spec assertion is unmet.
- **Do not** change the test or product code unless the user explicitly requests it.
- Neutral test infrastructure (fixtures, capture helpers) is allowed if it does not change what is asserted.

## Forbidden unless the user explicitly asks

- Weakening or removing assertions
- Changing expected values to match current output
- Harness workarounds that fake green results
- Production hacks (`if (testing)`, alternate code paths for tests)
- `DISABLED` / skipped tests without user approval

## Primary paths

- C++ runtime tests: `cpp-lib/tests/`
- Semantics notes: `cpp-lib/tests/runtime_correctness_notes.md`
- ROSE runtime: `cpp-lib/src/SnaccROSEBase.cpp`
- TypeScript samples: `samples/ts-microservice/`

**The test specifies what the public API must do. Neither tests nor product code are tuned to go green unless the user specially asks.**
12 changes: 12 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,18 @@ Requirements and IDE-specific steps are in [ReadMe.md](ReadMe.md). CMake minimum
- Prefer the smallest correct change; match surrounding code style.
- Generated output shape is defined by ASN.1 inputs plus the relevant `compiler/back-ends/*-gen` implementation — read both before changing behavior.
- When documentation and code disagree, verify against `samples/` and tests before updating docs.
- **Document new types and members** at the definition site (purpose, usage, ownership). See [.cursor/rules/living-documentation.mdc](.cursor/rules/living-documentation.mdc).

### Tests define the public API (hard constraint)

See [.cursor/rules/test-specification-first.mdc](.cursor/rules/test-specification-first.mdc).

- Tests specify the **expected behavior** of the snacc ecosystem public API — they are the spec, not a mirror of current code.
- **NEVER tune test code** to match broken or incomplete implementation.
- **NEVER tune product or library code** merely to turn a failing spec test green.
- **NEVER tune both** in one change.
- When writing tests, focus on specifying the API — not on getting a green suite.
- **Only exception:** the user explicitly asks to change tests, change product code, or revise the spec.

## Related repositories

Expand Down
33 changes: 18 additions & 15 deletions compiler/back-ends/c++-gen/gen-code.c
Original file line number Diff line number Diff line change
Expand Up @@ -1178,10 +1178,10 @@ static void PrintROSEOnInvokeHandler(FILE* src, int bEvents, Module* mod, ValueD
const char* pszHandlerPrefix = bEvents ? "RoseEvent" : "RoseInvoke";

fprintf(src, "// [%s] OPID_%s\n", __FUNCTION__, vd->definedName);
fprintf(src, "static long %s_OPID_%s(SNACC::ROSEMessage* pMsg, SNACC::ROSEInvoke* pInvoke, SnaccROSESender* pBase, %sInterface* pInt, SnaccInvokeContext& ctx, std::string& strResponseData)\n", pszHandlerPrefix, vd->definedName, mod->ROSEClassName);
fprintf(src, "static long %s_OPID_%s(std::unique_ptr<SNACC::ROSEMessage>& pMsg, SnaccROSESender* pBase, %sInterface* pInt, SnaccInvokeContext& ctx, std::string& strResponseData)\n", pszHandlerPrefix, vd->definedName, mod->ROSEClassName);
fprintf(src, "{\n");
fprintf(src, "\t%s argument;\n", pszArgument);
fprintf(src, "\tlong lRoseResult = pBase->DecodeInvoke(pMsg, &argument);\n");
fprintf(src, "\tlong lRoseResult = pBase->DecodeInvoke(*pMsg, &argument);\n");
fprintf(src, "\tif (lRoseResult == ROSE_NOERROR)\n");
const bool bIsDeprecated = IsDeprecatedFlaggedOperation(mod, vd->definedName);
const bool bIsMultiLine = bIsDeprecated || pszResult;
Expand All @@ -1202,12 +1202,12 @@ static void PrintROSEOnInvokeHandler(FILE* src, int bEvents, Module* mod, ValueD
{
fprintf(src, "\t\t%s error;\n", pszError);
fprintf(src, "\t\tauto invokeResult = pInt->OnInvoke_%s(&argument, &result, &error, ctx);\n", vd->definedName);
fprintf(src, "\t\tlRoseResult = pBase->HandleOnInvokeResult(invokeResult, pInvoke, ctx, strResponseData, &result, &error);\n");
fprintf(src, "\t\tlRoseResult = pBase->HandleOnInvokeResult(invokeResult, *pMsg->invoke, ctx, strResponseData, &result, &error);\n");
}
else
{
fprintf(src, "\t\tauto invokeResult = pInt->OnInvoke_%s(&argument, &result, ctx);\n", vd->definedName);
fprintf(src, "\t\tlRoseResult = pBase->HandleOnInvokeResult(invokeResult, pInvoke, ctx, strResponseData, &result);\n");
fprintf(src, "\t\tlRoseResult = pBase->HandleOnInvokeResult(invokeResult, *pMsg->invoke, ctx, strResponseData, &result);\n");
}
}
else
Expand Down Expand Up @@ -1239,7 +1239,7 @@ static void PrintROSEOnInvokeswitchCase(FILE* src, int bEvents, Module* mod, Val
const char* pszHandlerPrefix = bEvents ? "RoseEvent" : "RoseInvoke";

fprintf(src, "\tcase OPID_%s:\n", vd->definedName);
fprintf(src, "\t\treturn %s_OPID_%s(pMsg, pInvoke, pBase, pInt, ctx, strResponseData);\n", pszHandlerPrefix, vd->definedName);
fprintf(src, "\t\treturn %s_OPID_%s(pMsg, pBase, pInt, ctx, strResponseData);\n", pszHandlerPrefix, vd->definedName);
}
}
} /* PrintROSEOnInvokeswitchCase */
Expand Down Expand Up @@ -2039,8 +2039,6 @@ void PrintChoiceDefCodeBerDecodeContent(FILE* src, FILE* hdr, Module* m, CxxRule
}

varName = cxxtri->fieldName;
/* set choice id for to this elment */
fprintf(src, "\t\t\t%s = %s;\n", r->choiceIdFieldName, cxxtri->choiceIdSymbol);

/* alloc elmt if nec */
if (cxxtri->isPtr)
Expand Down Expand Up @@ -2079,6 +2077,8 @@ void PrintChoiceDefCodeBerDecodeContent(FILE* src, FILE* hdr, Module* m, CxxRule
fprintf(src, "%s", getAccessor(cxxtri->isPtr));
fprintf(src, "B%s(_b, tag, elmtLen%d, bytesDecoded);\n", r->decodeContentBaseName, elmtLevel);
}
/* set choice id only after the arm decoded successfully */
fprintf(src, "\t\t\t%s = %s;\n", r->choiceIdFieldName, cxxtri->choiceIdSymbol);

/* decode Eoc (s) */
for (i = elmtLevel - 1; i >= 0; i--)
Expand All @@ -2096,10 +2096,10 @@ void PrintChoiceDefCodeBerDecodeContent(FILE* src, FILE* hdr, Module* m, CxxRule
if (extensionsExist)
{
fprintf(src, "\t\t\tAsnAny extAny;\n");
fprintf(src, "\t\t\textension = new AsnExtension;\n");
fprintf(src, "\t\t\tchoiceId = extensionCid;\n");
fprintf(src, "\t\t\textAny.BDecContent(_b, tag, elmtLen0, bytesDecoded);\n");
fprintf(src, "\t\t\textension = new AsnExtension;\n");
fprintf(src, "\t\t\textension->extList.insert( extension->extList.end(), extAny );\n");
fprintf(src, "\t\t\t%s = extensionCid;\n", r->choiceIdFieldName);
fprintf(src, "\t\t\tbreak;\n");
}
else
Expand Down Expand Up @@ -2279,16 +2279,19 @@ void PrintChoiceDefCodeJsonDec(FILE* src, FILE* hdr, Module* m, CxxRules* r, Typ
fprintf(src, "\telse if (b.isMember(\"%s\"))\n", varName);
fprintf(src, "\t{\n");

fprintf(src, "\t\t%s = %s;\n", r->choiceIdFieldName, cxxtri->choiceIdSymbol);
if (cxxtri->isPtr)
{
fprintf(src, "\t\tdelete %s;\n", varName);
fprintf(src, "\t\t%s = new %s;\n", varName, cxxtri->className);
fprintf(src, "\t\tif (!%s->JDec(b[\"%s\"]))\n", varName, varName);
fprintf(src, "\t\t\tthrow InvalidTagException(typeName(), \"decode failed: %s\", STACK_ENTRY);\n", varName);
}
else
{
fprintf(src, "\t\tif (!%s.JDec(b[\"%s\"]))\n", varName, varName);
fprintf(src, "\t\t\tthrow InvalidTagException(typeName(), \"decode failed: %s\", STACK_ENTRY);\n", varName);
fprintf(src, "\t\t\tthrow InvalidTagException(typeName(), \"decode failed: %s\", STACK_ENTRY);\n", varName);
}
fprintf(src, "\t\t%s = %s;\n", r->choiceIdFieldName, cxxtri->choiceIdSymbol);

fprintf(src, "\t}\n");
}
Expand Down Expand Up @@ -4620,11 +4623,11 @@ void PrintROSECode(FILE* src, FILE* hdr, FILE* hdrInterface, ModuleList* mods, M

// generate the InvokeHandler
fprintf(hdr, "\t// The main Invoke Dispatcher\n");
fprintf(hdr, "\tstatic long OnInvoke(SNACC::ROSEMessage* pMsg, SnaccROSESender* pBase, %sInterface* pInt, SnaccInvokeContext& ctx, std::string& strResponseData);\n", m->ROSEClassName);
fprintf(src, "long %s::OnInvoke(SNACC::ROSEMessage* pMsg, SnaccROSESender* pBase, %sInterface* pInt, SnaccInvokeContext& ctx, std::string& strResponseData)\n", m->ROSEClassName, m->ROSEClassName);
fprintf(hdr, "\tstatic long OnInvoke(std::unique_ptr<SNACC::ROSEMessage>& pMsg, SnaccROSESender* pBase, %sInterface* pInt, SnaccInvokeContext& ctx, std::string& strResponseData);\n", m->ROSEClassName);
fprintf(src, "long %s::OnInvoke(std::unique_ptr<SNACC::ROSEMessage>& pMsg, SnaccROSESender* pBase, %sInterface* pInt, SnaccInvokeContext& ctx, std::string& strResponseData)\n", m->ROSEClassName, m->ROSEClassName);
fprintf(src, "{\n");
fprintf(src, "\tauto pInvoke = pMsg->invoke;\n");
fprintf(src, "\tswitch (pInvoke->operationID)\n");
fprintf(src, "\tauto& invoke = *pMsg->invoke;\n");
fprintf(src, "\tswitch (invoke.operationID)\n");
fprintf(src, "\t{\n");

// Rose Interface class
Expand Down
2 changes: 1 addition & 1 deletion cpp-lib/include/SNACCROSE.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
//
// SNACCROSE.h - class definitions for ASN.1 module SNACC-ROSE
//
// This file was generated by estos esnacc (V7.0.2, 16.04.2026)
// This file was generated by estos esnacc (V7.0.5, 03.07.2026)
// based on Coral WinSnacc written by Deepak Gupta
// NOTE: This is a machine generated file - editing not recommended
//
Expand Down
Loading
Loading