Files
CHORUS/vendor/github.com/ipld/go-ipld-prime/node/basicnode/HACKME.md
anthonyrawlins 9bdcbe0447 Integrate BACKBEAT SDK and resolve KACHING license validation
Major integrations and fixes:
- Added BACKBEAT SDK integration for P2P operation timing
- Implemented beat-aware status tracking for distributed operations
- Added Docker secrets support for secure license management
- Resolved KACHING license validation via HTTPS/TLS
- Updated docker-compose configuration for clean stack deployment
- Disabled rollback policies to prevent deployment failures
- Added license credential storage (CHORUS-DEV-MULTI-001)

Technical improvements:
- BACKBEAT P2P operation tracking with phase management
- Enhanced configuration system with file-based secrets
- Improved error handling for license validation
- Clean separation of KACHING and CHORUS deployment stacks

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-06 07:56:26 +10:00

6.5 KiB

hackme

Design rationale are documented here.

This doc is not necessary reading for users of this package, but if you're considering submitting patches -- or just trying to understand why it was written this way, and check for reasoning that might be dated -- then it might be useful reading.

scalars are just typedefs

This is noteworthy because in codegen, this is typically not the case: in codegen, even scalar types are boxed in a struct, such that it prevents casting values into those types.

This casting is not a concern for the node implementations in this package, because

  • A) we don't have any kind of validation rules to make such casting worrying; and
  • B) since our types are unexported, casting is still blocked by this anyway.

about builders for scalars

The assembler types for scalars (string, int, etc) are pretty funny-looking. You might wish to make them work without any state at all!

The reason this doesn't fly is that we have to keep the "wip" value in hand just long enough to return it from the NodeBuilder.Build method -- the NodeAssembler contract for Assign* methods doesn't permit just returning their results immediately.

(Another possible reason is if we expected to use these assemblers on slab-style allocations (say, []plainString)... however, this is inapplicable at present, because A) we don't (except places that have special-case internal paths anyway); and B) the types aren't exported, so users can't either.)

Does this mean that using NodeBuilder for scalars has a completely unnecessary second allocation, which is laughably inefficient? Yes. It's unfortunate the interfaces constrain us to this. But: one typically doesn't actually use builders for scalars much; they're just here for completeness. So this is less of a problem in practice than it might at first seem.

More often, one will use the "any" builder (which is has a whole different set of design constraints and tradeoffs); or, if one is writing code and knows which scalar they need, the exported direct constructor function for that kind (e.g., String("foo") instead of Prototype__String{}.NewBuilder().AssignString("foo")) will do the right thing and do it in one allocation (and it's less to type, too).

maps and list keyAssembler and valueAssemblers have custom scalar handling

Related to the above heading.

Maps and lists in this package do their own internal handling of scalars, using unexported features inside the package, because they can more efficient.

when to invalidate the 'w' pointers

The 'w' pointer -- short for 'wip' node pointer -- has an interesting lifecycle.

In a NodeAssembler, the 'w' pointer should be intialized before the assembler is used. This means either the matching NodeBuilder type does so; or, if we're inside recursive structure, the parent assembler did so.

The 'w' pointer is used throughout the life of the assembler.

Setting the 'w' pointer to nil is one of two mechanisms used internally to mark that assembly has become "finished" (the other mechanism is using an internal state enum field). Setting the 'w' pointer to nil has two advantages: one is that it makes it impossible to continue to mutate the target node; the other is that we need no additional memory to track this state change. However, we can't use the strategy of nilling 'w' in all cases: in particular, when in the NodeBuilder at the root of some construction, we need to continue to hold onto the node between when it becomes "finished" and when Build is called; otherwise we can't actually return the value! Different stratgies are therefore used in different parts of this package.

Maps and lists use an internal state enum, because they already have one, and so they might as well; there's no additional cost to this. Since they can use this state to guard against additional mutations after "finish", the map and list assemblers don't bother to nil their own 'w' at all.

During recursion to assemble values inside maps and lists, it's interesting: the child assembler wrapper type takes reponsibility for nilling out the 'w' pointer in the child assembler's state, doing this at the same time as it updates the parent's state machine to clear proceeding with the next entry.

In the case of scalars at the root of a build, we took a shortcut: we actually don't fence against repeat mutations at all. You can actually use the assign method more than once. We can do this without breaking safety contracts because the scalars all have a pass-by-value phase somewhere in their lifecycle (calling nb.AssignString("x"), then n := nb.Build(), then nb.AssignString("y") won't error if nb is a freestanding builder for strings... but it also won't result in mutating n to contain "y", so overall, it's safe).

We could normalize the case with scalars at the root of a tree so that they error more aggressively... but currently we haven't bothered, since this would require adding another piece of memory to the scalar builders; and meanwhile we're not in trouble on compositional correctness.

Note that these remarks are for the basicnode package, but may also apply to other implementations too (e.g., our codegen output follows similar overall logic).

NodePrototypes are available through a singleton

Every NodePrototype available from this package is exposed as a field in a struct of which there's one public exported instance available, called 'Prototype'.

This means you can use it like this:

nbm := basicnode.Prototype.Map.NewBuilder()
nbs := basicnode.Prototype.String.NewBuilder()
nba := basicnode.Prototype.Any.NewBuilder()
// etc

(If you're interested in the performance of this: it's free! Methods called at the end of the chain are inlinable. Since all of the types of the structures on the way there are zero-member structs, the compiler can effectively treat them as constants, and thus freely elide any memory dereferences that would otherwise be necessary to get methods on such a value.)

NodePrototypes are (also) available as exported concrete types

The 'Prototype' singleton is one way to access the NodePrototype in this package; their exported types are another equivalent way.

basicnode.Prototype.Map = basicnode.Prototype.Map

It is recommended to use the singleton style; they compile to identical assembly, and the singleton is syntactically prettier.

We may make these concrete types unexported in the future. A decision on this is deferred until some time has passed and we can accumulate reasonable certainty that there's no need for an exported type (such as type assertions, etc).