diff --git a/.agents/guidelines b/.agents/guidelines
new file mode 120000
index 0000000..6f9d966
--- /dev/null
+++ b/.agents/guidelines
@@ -0,0 +1 @@
+shared/guidelines
\ No newline at end of file
diff --git a/.agents/project.md b/.agents/project.md
new file mode 120000
index 0000000..7e0bf9b
--- /dev/null
+++ b/.agents/project.md
@@ -0,0 +1 @@
+../docs/project.md
\ No newline at end of file
diff --git a/.agents/scripts b/.agents/scripts
new file mode 120000
index 0000000..96bf06e
--- /dev/null
+++ b/.agents/scripts
@@ -0,0 +1 @@
+shared/scripts
\ No newline at end of file
diff --git a/.agents/shared b/.agents/shared
new file mode 160000
index 0000000..2369ab8
--- /dev/null
+++ b/.agents/shared
@@ -0,0 +1 @@
+Subproject commit 2369ab816bb5d0feee562bfd6395b0b2852a3a9d
diff --git a/.agents/skills b/.agents/skills
new file mode 120000
index 0000000..f14734d
--- /dev/null
+++ b/.agents/skills
@@ -0,0 +1 @@
+shared/skills
\ No newline at end of file
diff --git a/.claude/agents b/.claude/agents
new file mode 120000
index 0000000..18e96c9
--- /dev/null
+++ b/.claude/agents
@@ -0,0 +1 @@
+../.agents/shared/claude/agents
\ No newline at end of file
diff --git a/.claude/commands b/.claude/commands
new file mode 120000
index 0000000..ad85cd8
--- /dev/null
+++ b/.claude/commands
@@ -0,0 +1 @@
+../.agents/shared/claude/commands
\ No newline at end of file
diff --git a/.claude/settings.json b/.claude/settings.json
new file mode 100644
index 0000000..3c7da68
--- /dev/null
+++ b/.claude/settings.json
@@ -0,0 +1,106 @@
+{
+ "$schema": "https://json.schemastore.org/claude-code-settings.json",
+ "plansDirectory": ".claude/plans",
+ "permissions": {
+ "allow": [
+ "Edit(version.gradle.kts)",
+ "Bash(./gradlew:*)",
+ "Bash(./config/gradlew:*)",
+ "Bash(git status:*)",
+ "Bash(git diff:*)",
+ "Bash(git log:*)",
+ "Bash(git show:*)",
+ "Bash(git branch:*)",
+ "Bash(git switch:*)",
+ "Bash(git checkout:*)",
+ "Bash(git add:*)",
+ "Bash(git restore:*)",
+ "Bash(git stash:*)",
+ "Bash(git fetch:*)",
+ "Bash(git push:*)",
+ "Bash(git rev-parse:*)",
+ "Bash(git ls-files:*)",
+ "Bash(git mv:*)",
+ "Bash(git submodule status:*)",
+ "Bash(ls:*)",
+ "Bash(cat:*)",
+ "Bash(head:*)",
+ "Bash(tail:*)",
+ "Bash(wc:*)",
+ "Bash(find:*)",
+ "Bash(rg:*)",
+ "Bash(grep:*)",
+ "Bash(mkdir:*)",
+ "Bash(touch:*)",
+ "Bash(python3 .agents/skills/update-copyright/scripts/update_copyright.py:*)",
+ "Bash(.agents/skills/version-bumped/scripts/version-bumped.sh)",
+ "Bash(./config/pull)",
+ "Bash(./config/migrate)",
+ "Skill(pre-pr)",
+ "Skill(pre-pr:*)"
+ ],
+ "deny": [
+ "Bash(git reset --hard:*)",
+ "Bash(git clean -fdx:*)",
+ "Bash(rm -rf /:*)",
+ "Bash(rm -rf ~:*)",
+ "Bash(gh pr merge:*)",
+ "Bash(gh release create:*)"
+ ],
+ "ask": [
+ "Bash(git commit:*)",
+ "Bash(git rebase:*)",
+ "Bash(git merge:*)",
+ "Bash(git cherry-pick:*)",
+ "Bash(./gradlew publish:*)",
+ "Bash(./gradlew uploadArtifacts:*)",
+ "Bash(./gradlew clean:*)"
+ ]
+ },
+ "hooks": {
+ "SessionStart": [
+ {
+ "hooks": [
+ {
+ "type": "command",
+ "command": "$CLAUDE_PROJECT_DIR/init-submodules"
+ }
+ ]
+ }
+ ],
+ "PreToolUse": [
+ {
+ "matcher": "Bash",
+ "hooks": [
+ {
+ "type": "command",
+ "command": "$CLAUDE_PROJECT_DIR/.agents/scripts/secret-scan-gate.sh"
+ },
+ {
+ "type": "command",
+ "command": "$CLAUDE_PROJECT_DIR/.agents/scripts/pre-pr-gate.sh"
+ },
+ {
+ "type": "command",
+ "command": "$CLAUDE_PROJECT_DIR/.agents/scripts/publish-version-gate.sh"
+ }
+ ]
+ }
+ ],
+ "PostToolUse": [
+ {
+ "matcher": "Edit|Write|MultiEdit",
+ "hooks": [
+ {
+ "type": "command",
+ "command": "$CLAUDE_PROJECT_DIR/.agents/scripts/sanitize-source-code.sh"
+ },
+ {
+ "type": "command",
+ "command": "$CLAUDE_PROJECT_DIR/.agents/scripts/update-copyright.sh"
+ }
+ ]
+ }
+ ]
+ }
+}
diff --git a/.claude/skills b/.claude/skills
new file mode 120000
index 0000000..2b7a412
--- /dev/null
+++ b/.claude/skills
@@ -0,0 +1 @@
+../.agents/skills
\ No newline at end of file
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..aa71b2f
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,66 @@
+# Set the default behavior, in case people don't have core.autocrlf set.
+* text=auto
+
+# Explicitly declare text files you want to always be normalized and converted
+# to native line endings on checkout.
+
+# Common formats
+*.html text
+*.xml text
+*.css text
+*.scss text
+*.svg text
+*.js text
+*.properties text
+*.rtf text
+*.yaml text
+*.yml text
+*.md text
+
+LICENSE text
+
+# SQL scripts
+*.sql text
+
+# Java sources
+*.java text
+
+# Kotlin sources
+*.kt text
+*.kts text
+
+# Python sources
+*.py text
+
+# Gradle build files
+*.gradle text
+
+# Google protocol buffers
+*.proto text
+
+# Miscellaneous
+*.rb text
+
+# Declare files that will always have CRLF line endings on checkout.
+*.bat text eol=crlf
+
+# Declare files that will always have LF line endings on checkout.
+*.sh text eol=lf
+gradlew text eol=lf
+pull text eol=lf
+
+# Denote all files that are truly binary and should not be modified.
+*.png binary
+*.jpg binary
+*.gif binary
+*.swf binary
+*.jar binary
+*.desc binary
+
+*.scpt binary
+*.scssc binary
+
+# Encrypted files
+*.enc binary
+*.gpg binary
+*.weis binary
diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md
new file mode 100644
index 0000000..039657b
--- /dev/null
+++ b/.github/copilot-instructions.md
@@ -0,0 +1,45 @@
+# GitHub Copilot Instructions
+
+## Repository context
+
+This repository is part of the Spine SDK organisation (~40 repos).
+
+Universal agent instructions are in [`AGENTS.md`](../AGENTS.md) at the
+repository root — read it first.
+
+If `.agents/project.md` exists, read it before reviewing. It provides the
+language, architecture, role, and code review checklist for this specific repo.
+
+Additional guidelines are in `.agents/guidelines/` — see
+`.agents/guidelines/_TOC.md` for the index.
+
+## Do not review
+
+Never review `gradlew` or `gradlew.bat` in any repository, including `config`.
+These files are provided by Gradle and are not edited manually.
+
+If the current repository is `config`, review its files normally unless noted
+above: they are authoritative there. In other repositories, the following files are managed by
+the `config` submodule and must be reviewed in the `config` repository, not
+here. In those consumer repositories, skip them without comment:
+
+- `AGENTS.md`, `CLAUDE.md`, `CONTRIBUTING.md`, `CODE_OF_CONDUCT.md`
+- `.agents/**` (except `.agents/project.md`)
+- `.claude/**`, `.idea/**`, `.junie/**`
+- `.github/copilot-instructions.md`
+- `buildSrc/**` (except `buildSrc/src/main/kotlin/module.gradle.kts`)
+- `gradle/`, `gradlew`, `gradlew.bat`
+- `.codecov.yml`, `.gitignore`, `gradle.properties`, `lychee.toml`
+- `.github/workflows/` — unless the workflow was introduced by this repo
+
+## Universal rules
+
+**Do not suggest:**
+- Any git history operation — `git commit`, `git push`, `git tag`,
+ `git rebase`, `git merge`, `git cherry-pick`, `gh pr merge`, or any other
+ command that writes to history — leave these to the developer.
+- Auto-updating dependency versions outside a dedicated update task.
+- Feature flags, backwards-compatibility shims, or fallbacks for scenarios
+ that cannot occur in the current codebase.
+- Analytics, telemetry, or tracking code.
+- Reflection or unsafe code without explicit approval.
diff --git a/.github/workflows/secret-scan.yml b/.github/workflows/secret-scan.yml
new file mode 100644
index 0000000..6f0130e
--- /dev/null
+++ b/.github/workflows/secret-scan.yml
@@ -0,0 +1,70 @@
+name: Secret scan
+
+# Defense-in-depth behind the local `secret-scan` pre-commit hook and the
+# `.gitignore` secret patterns: if a credential is committed despite those, this
+# fails the pull request before it can merge. Distributed to every Spine repo by
+# `./config/pull`.
+
+on:
+ pull_request:
+ push:
+ branches:
+ - master
+ - main
+
+permissions:
+ contents: read
+
+jobs:
+ gitleaks:
+ name: gitleaks
+ runs-on: ubuntu-latest
+ env:
+ # Pinned gitleaks version — bump through the usual dependency-update process.
+ GITLEAKS_VERSION: "8.21.2"
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v6
+ with:
+ # Full history so a pull request's commit range can be scanned.
+ fetch-depth: 0
+
+ - name: Install gitleaks
+ # Run gitleaks as the runner user against the checkout it owns — no
+ # container, so no "dubious ownership" git error and no GitHub Action
+ # org-licence requirement.
+ run: |
+ curl -sSfL "https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz" \
+ | tar -xzf - gitleaks
+ ./gitleaks version
+
+ - name: Scan
+ env:
+ EVENT: ${{ github.event_name }}
+ BASE: ${{ github.event.pull_request.base.sha }}
+ HEAD: ${{ github.event.pull_request.head.sha }}
+ BEFORE: ${{ github.event.before }}
+ AFTER: ${{ github.sha }}
+ run: |
+ if [ "$EVENT" = pull_request ]; then
+ # Scan the PR's own commit RANGE: a secret added in one commit and
+ # deleted in a later commit of the same PR is still caught (a
+ # working-tree scan would miss it, yet merging keeps the secret-bearing
+ # commit reachable), while already-rotated secrets in older history
+ # outside base..head are not re-flagged.
+ ./gitleaks git --log-opts="$BASE..$HEAD" --redact --verbose --exit-code=1 .
+ else
+ # Push to a default branch: scan the pushed commit RANGE (before..after)
+ # so an add-then-remove batch is caught here too, not only on PRs — the
+ # leaked commit would otherwise stay reachable on the default branch. A
+ # branch's first push reports an all-zero `before` (no range); fall back
+ # to a working-tree scan then.
+ if [ -n "$BEFORE" ] && [ "$BEFORE" != "0000000000000000000000000000000000000000" ]; then
+ ./gitleaks git --log-opts="$BEFORE..$AFTER" --redact --verbose --exit-code=1 .
+ else
+ # Branch's first push (all-zero `before`): no range to diff against, so
+ # scan the whole history reachable from the pushed tip as the initial
+ # import — an add-then-remove within those commits is still caught.
+ ./gitleaks git --log-opts="$AFTER" --redact --verbose --exit-code=1 .
+ fi
+ fi
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..18ef021
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,200 @@
+# >>> shared config (managed by ./config/pull -- do not edit inside this block) >>>
+#
+# Copyright 2025, TeamDev. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Redistribution and use in source and/or binary forms, with or without
+# modification, must retain the above copyright notice and the following
+# disclaimer.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#
+
+#
+# This file is used for two purposes:
+# 1. ignoring files in the `config` project.
+# 2. ignoring files in the projects that import `config` as a sub-module.
+#
+# Therefore, instructions below are superset of instructions required for all the projects.
+
+# Temporary output of AI agents.
+.output
+
+# `jenv` local configuration.
+.java-version
+
+# Internal tool directories.
+.fleet/
+.junie/memory/
+
+# Kotlin temp directories.
+**/.kotlin/
+
+# IntelliJ IDEA modules and interim config files.
+*.iml
+.idea/*.xml
+.idea/.name
+.idea/artifacts
+.idea/libraries
+.idea/modules
+.idea/shelf
+
+# `.idea/misc.xml` is intentionally NOT re-included below. It is project-local —
+# it holds the per-project JDK name and IDEA's own churn (entry-point list
+# indices, external-storage toggles) — so `.idea/*.xml` above keeps it ignored.
+# `./config/pull` (via `migrate`) untracks any copy an earlier pull committed.
+
+# Do not ignore the following IDEA settings
+!.idea/codeStyleSettings.xml
+!.idea/codeStyles/
+!.idea/copyright/
+
+# Ignore IDEA config files under `tests`
+/tests/.idea/**
+
+# Gradle interim configs
+**/.gradle/**
+
+# Temp directory for Gradle TestKit runners
+**/.gradle-test-kit/**
+
+# Integration test log files
+/tests/_out/**
+
+# Generated source code
+**/generated/**
+**/*.pb.dart
+**/*.pbenum.dart
+**/*.pbserver.dart
+**/*.pbjson.dart
+
+# Generated source code with custom path under `tests`
+/tests/**/proto-gen/**
+
+# Gradle build files
+**/build/**
+!**/src/**/build/**
+
+# Build files produced by the IDE
+**/out/**
+
+# Ignore Gradle GUI config
+gradle-app.setting
+
+# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
+!gradle-wrapper.jar
+
+# Cache of project
+.gradletasknamecache
+
+# # Work around https://youtrack.jetbrains.com/issue/IDEA-116898
+# gradle/wrapper/gradle-wrapper.properties
+
+# Spine internal directory for storing intermediate artifacts
+**/.spine/**
+
+# ---------------------------------------------------------------------------
+# Secrets — NEVER commit these.
+#
+# Encrypted credentials live under `.github/keys/*.gpg` and ARE committed.
+# `config/scripts/decrypt.sh` turns each into its PLAINTEXT twin at build / CI /
+# publish time (e.g. `spine-dev-framework-ci.json.gpg` -> `spine-dev.json`). The
+# decrypted twins below — and any private key or service-account file — must stay
+# out of Git. The shared `secret-scan` pre-commit hook is the backstop if one ever
+# slips past these patterns.
+# ---------------------------------------------------------------------------
+
+# Maven repository login details; each workstation defines its own.
+credentials.tar
+credentials.properties
+cloudrepo.properties
+deploy_key_rsa
+gcs-auth-key.json
+
+# Decrypted Google / GCP service-account keys (plaintext twins of *.gpg).
+spine-dev.json
+spine-dev-*.json
+maven-publisher.json
+firebase-sa.json
+*-sa.json
+*service-account*.json
+
+# Decrypted credential property files and portal / publisher secrets.
+*.secret.properties
+
+# Private SSH keys (public keys are *.pub and remain committable).
+*_rsa
+*_dsa
+*_ecdsa
+*_ed25519
+id_rsa
+id_dsa
+id_ecdsa
+id_ed25519
+
+# ...but always keep the committed ENCRYPTED forms.
+!*.gpg
+
+# Log files
+*.log
+
+# Package Files #
+*.war
+*.ear
+*.zip
+*.tar.gz
+*.rar
+
+# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
+hs_err_pid*
+
+.packages
+pubspec.lock
+
+# Ignore the `tmp` directory used for building dependant repositories.
+/tmp
+
+.gradle-test-kit/
+
+# Python cache
+__pycache__/
+*.pyc
+
+# Claude working files
+/.claude/worktrees/
+# Ephemeral plan-mode scratch (durable task docs live in `.agents/tasks/`).
+/.claude/plans/
+
+# Personal, per-developer Claude Code settings overrides (never committed;
+# the distributed `.claude/settings.json` is the shared, committed layer).
+/.claude/settings.local.json
+
+# Auto-downloaded Lychee binary used by the `check-links` skill.
+/.agents/skills/check-links/.cache/
+
+# Lychee link-checker cache (created by the `check-links` skill and
+# the `Check Links` workflow when run locally).
+.lycheecache
+
+# Hugo docs preview site build artifacts (used by the `check-links`
+# skill and the `Check Links` workflow in repos that contain a
+# `docs/_preview` Hugo site).
+docs/_preview/node_modules/
+docs/_preview/public/
+docs/_preview/resources/
+# <<< shared config <<<
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..b37014f
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,57 @@
+[submodule "base-libraries"]
+ path = base-libraries
+ url = https://github.com/SpineEventEngine/base-libraries.git
+[submodule "core-jvm"]
+ path = core-jvm
+ url = https://github.com/SpineEventEngine/core-jvm.git
+[submodule "validation"]
+ path = validation
+ url = https://github.com/SpineEventEngine/validation.git
+[submodule "tool-base"]
+ path = tool-base
+ url = https://github.com/SpineEventEngine/tool-base.git
+[submodule "delivery-server"]
+ path = delivery-server
+ url = https://github.com/SpineEventEngine/delivery-server.git
+[submodule "config"]
+ path = config
+ url = https://github.com/SpineEventEngine/config.git
+[submodule "compiler"]
+ path = compiler
+ url = https://github.com/SpineEventEngine/compiler.git
+[submodule "time"]
+ path = time
+ url = https://github.com/SpineEventEngine/time.git
+[submodule "logging"]
+ path = logging
+ url = https://github.com/SpineEventEngine/logging.git
+[submodule "jdbc-storage"]
+ path = jdbc-storage
+ url = https://github.com/SpineEventEngine/jdbc-storage.git
+[submodule "core-jvm-compiler"]
+ path = core-jvm-compiler
+ url = https://github.com/SpineEventEngine/core-jvm-compiler.git
+[submodule "ProtoTap"]
+ path = ProtoTap
+ url = https://github.com/SpineEventEngine/ProtoTap.git
+[submodule "base-types"]
+ path = base-types
+ url = https://github.com/SpineEventEngine/base-types.git
+[submodule "testlib"]
+ path = testlib
+ url = https://github.com/SpineEventEngine/testlib.git
+[submodule "change"]
+ path = change
+ url = https://github.com/SpineEventEngine/change.git
+[submodule "reflect"]
+ path = reflect
+ url = https://github.com/SpineEventEngine/reflect.git
+[submodule "money"]
+ path = money
+ url = https://github.com/SpineEventEngine/money.git
+[submodule ".agents/shared"]
+ path = .agents/shared
+ url = https://github.com/SpineEventEngine/agents.git
+ branch = master
+ update = merge
+ ignore = all
diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
new file mode 100644
index 0000000..f60c273
--- /dev/null
+++ b/.idea/codeStyles/Project.xml
@@ -0,0 +1,104 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ GETTERS_AND_SETTERS
+ KEEP
+
+
+ OVERRIDDEN_METHODS
+ KEEP
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml
new file mode 100644
index 0000000..0f7bc51
--- /dev/null
+++ b/.idea/codeStyles/codeStyleConfig.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/.idea/copyright/TeamDev_Open_Source.xml b/.idea/copyright/TeamDev_Open_Source.xml
new file mode 100644
index 0000000..cea7fed
--- /dev/null
+++ b/.idea/copyright/TeamDev_Open_Source.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/.idea/copyright/profiles_settings.xml b/.idea/copyright/profiles_settings.xml
new file mode 100644
index 0000000..0b8f9a1
--- /dev/null
+++ b/.idea/copyright/profiles_settings.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/.idea/dictionaries/common.xml b/.idea/dictionaries/common.xml
new file mode 100644
index 0000000..d1c3a7b
--- /dev/null
+++ b/.idea/dictionaries/common.xml
@@ -0,0 +1,71 @@
+
+
+
+ afghani
+ arraybuffer
+ aspx
+ bytebuffer
+ callees
+ closeables
+ cqrs
+ dartdocs
+ dataset
+ datastore
+ datastores
+ deserialized
+ dirham
+ enrichable
+ enrichments
+ escaper
+ flushables
+ googleapis
+ gradle
+ grpc
+ handshaker
+ hohpe
+ idempotency
+ jspecify
+ kotest
+ lempira
+ liskov
+ melnik
+ memoized
+ memoizes
+ memoizing
+ mergeable
+ mikhaylov
+ millisecs
+ multitenancy
+ multitenant
+ nullable
+ onclose
+ oneof
+ onmessage
+ onopen
+ parameterizing
+ plugable
+ processmanager
+ procman
+ proto's
+ protodata
+ protos
+ sfixed
+ stderr
+ stringifier
+ stringifiers
+ substituter
+ switchman
+ testutil
+ threeten
+ tuples
+ unicast
+ unregister
+ unregistering
+ unregisters
+ unregistration
+ websocket
+ workflows
+ yevsyukov
+
+
+
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
new file mode 100644
index 0000000..7be402d
--- /dev/null
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -0,0 +1,915 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/live-templates/README.md b/.idea/live-templates/README.md
new file mode 100644
index 0000000..9500667
--- /dev/null
+++ b/.idea/live-templates/README.md
@@ -0,0 +1,27 @@
+### Live Templates
+
+This directory contains two live template groups:
+
+1. `Spine.xml`: shortcuts for the repeated patterns used in the framework.
+2. `User.xml`: a single shortcut to generate TODO comments.
+
+### Installation
+
+Live templates are not picked up by IDEA automatically. They should be added manually.
+In order to add these templates, perform the following steps:
+
+1. Copy `*.xml` files from this directory to `templates` directory in the IntelliJ IDEA
+ [settings folder][settings_folder].
+2. Restart IntelliJ IDEA: `File -> Invalidate Caches -> Just restart`.
+3. Go to `Preferences -> Editor -> Live Templates`.
+4. Verify `User` and `Spine` template groups are present.
+
+[settings_folder]: https://www.jetbrains.com/help/idea/directories-used-by-the-ide-to-store-settings-caches-plugins-and-logs.html#config-directory
+
+### Configuring `User.todo` template
+
+1. Open the corresponding template: `Preferences -> Editor -> Live Templates -> User.todo`.
+2. Click on `Edit variables`.
+3. Set `USER` variable to your domain email address without `@teamdev.com` ending. For example,
+ for `jack.sparrow@teamdev.com` use the following expression `"jack.sparrow"`.
+4. Verify that the template generates expected comments: `// TODO:2022-11-03:jack.sparrow: <...>`.
diff --git a/.idea/live-templates/Spine.xml b/.idea/live-templates/Spine.xml
new file mode 100644
index 0000000..369b72d
--- /dev/null
+++ b/.idea/live-templates/Spine.xml
@@ -0,0 +1,58 @@
+
+
+
+
+
+
+
+
+
diff --git a/.junie/guidelines.md b/.junie/guidelines.md
new file mode 100644
index 0000000..7c1f866
--- /dev/null
+++ b/.junie/guidelines.md
@@ -0,0 +1,21 @@
+# Guidelines for Junie and AI Agent from JetBrains
+
+Read the `../.agents/guidelines/_TOC.md` file to understand:
+ - the agent responsibilities,
+ - project overview,
+ - coding guidelines,
+ - other relevant topics.
+
+Also follow the Junie-specific rules described below.
+
+## Junie Assistance Tips
+
+When working with Junie AI on the Spine family of projects:
+
+1. **Project Navigation**: Use `search_project` to find relevant files and code segments.
+2. **Code Understanding**: Request file structure with `get_file_structure` before editing.
+3. **Code Editing**: Make minimal changes with `search_replace` to maintain project consistency.
+4. **Testing**: Verify changes with `run_test` on relevant test files.
+5. **Documentation**: Follow KDoc style for documentation.
+6. **Kotlin Idioms**: Prefer Kotlin-style solutions over Java-style approaches.
+7. **Version Updates**: Remember to update `version.gradle.kts` for PRs.
diff --git a/.junie/skills b/.junie/skills
new file mode 120000
index 0000000..2b7a412
--- /dev/null
+++ b/.junie/skills
@@ -0,0 +1 @@
+../.agents/skills
\ No newline at end of file
diff --git a/AGENTS.md b/AGENTS.md
new file mode 100644
index 0000000..8c5f619
--- /dev/null
+++ b/AGENTS.md
@@ -0,0 +1,135 @@
+# 👋 Welcome, Agents!
+
+## Orientation
+
+If `.agents/project.md` exists in this repository, read it first — it describes
+the language, architecture, and role of this specific repo within the Spine SDK
+organisation. It is a symlink to `docs/project.md`; to create one, copy
+`.agents/guidelines/project.template.md` to `docs/project.md` and fill it in. If it
+links to a shared requirements file (e.g. `jvm-project.md`), read that too.
+
+- Start every session by reading `.agents/guidelines/quick-reference-card.md` (if present).
+- For specific tasks (code review, PR prep, dependency updates, docs, etc.),
+ prefer the matching skill from `.agents/skills/`.
+- Full standards reference: `.agents/guidelines/_TOC.md` (if present) — consult when a
+ skill doesn't cover the needed context.
+
+Shared skills, scripts, and guidelines come from the `.agents/shared` submodule (the
+[`agents`][agents-repo] repository) exposed via symlinks.
+`./config/pull` initializes and floats them automatically. But a fresh `git worktree`
+(and some shallow clones / cloud checkouts) start with NO submodules checked out, so
+those symlinks dangle and no skills are found. Bootstrap such a tree with
+**`./init-submodules`** — a root script that materializes the missing
+*config-managed* submodules at their pinned commits: `config` itself, plus every
+submodule that declares a tracked `branch` in `.gitmodules` (`.agents/shared`, and
+any shared submodule added later) — the same rule `./config/pull` uses to decide
+what it floats. Submodules the consumer owns (a Hugo theme, a vendored library,
+doc-example submodules, …) declare no tracked branch and are left untouched, so the
+automatic `SessionStart` run never tries to clone — or fail on credentials for — a
+submodule this project does not manage. It depends on no pre-existing `config`
+submodule, so it works before `./config/pull` (which lives inside the `config`
+submodule) can. Claude Code runs it automatically via a `SessionStart` hook; other
+agents and humans run it by hand, then `./config/pull` to float the shared submodules
+to their branch tips.
+
+## Commit and history safety
+
+**Do not commit, push, tag, rebase, merge, cherry-pick, or otherwise write to git history**
+unless one of the following is true *right now*:
+
+1. The currently active skill's `SKILL.md` has a `## Commit authorization` section
+ that explicitly permits the operation.
+2. The user's *current* prompt explicitly requests the operation.
+
+Authorization does not carry over between turns or sessions. When in doubt: stage
+changes, show the diff, and stop — let the user commit.
+
+See [`.agents/guidelines/safety-rules.md`](.agents/guidelines/safety-rules.md) → *Commits and history-writing*.
+
+## Other safety rules
+
+- All code must compile and pass static analysis.
+- Do not auto-update external dependencies outside a dedicated update task.
+- No analytics, telemetry, or tracking code.
+- No reflection or unsafe code without explicit approval.
+
+See [`.agents/guidelines/safety-rules.md`](.agents/guidelines/safety-rules.md) for the full list.
+
+## Moving files
+
+When moving or renaming tracked files, always use `git mv`. Do not simulate a
+move by deleting the old file and creating a new one — preserve Git history
+unless the user explicitly asks for a fresh replacement.
+
+If `git mv` fails due to permissions or sandbox restrictions, request approval;
+do not fall back to delete/create.
+
+## Memory
+
+Team-shared memory lives in `.agents/memory/` (checked into git). Use it for
+feedback rules, durable project rationale, and external system pointers.
+See `.agents/memory/README.md` for layout and write protocol.
+
+Review `.agents/memory/MEMORY.md` at the start of every session.
+Ruthlessly iterate until mistakes stop repeating.
+
+## Asking questions
+
+- Ask at most one question per message. If a decision has a small set of
+ options, include those options as part of that one question.
+- Do not bundle unrelated clarification questions. Ask the next question only
+ after the user answers the previous one.
+- Apply this rule both when the agent needs clarification and when the user's
+ prompt means "ask questions".
+- Prefer a reasonable assumption over another question when the answer would not
+ materially change the next step.
+
+## Verification & Quality
+
+- Never mark a task done without proof (tests, logs, diff vs main).
+- Ask: "Would a senior/staff engineer approve this?"
+- For non-trivial changes: pause and consider a more elegant solution.
+- Fix bugs autonomously — find root cause, no hand-holding, no band-aids.
+
+## Core Principles
+
+- Simplicity first: minimal code impact, minimal surface area.
+- No laziness: always find root causes.
+- Minimal side effects: avoid new bugs.
+- Prefer early returns and clear naming.
+- Challenge your own work before presenting it.
+
+## Task planning
+
+- Write plans to `.agents/tasks/.md` before coding.
+ See `.agents/tasks/README.md` for format and lifecycle.
+- Verify changes before marking a task done.
+- Update memory if lessons emerged.
+- Delete the task file on merge to master.
+
+## Code review
+
+Never review `gradlew` or `gradlew.bat` in any repository, including `config`.
+These files are provided by Gradle and are not edited manually.
+
+When reviewing a pull request or diff in a consumer repository, skip any
+file that the `config` module distributes. Those files belong in a review
+of the `config` repo, not the consumer repo — reviewing them there adds
+noise without value.
+
+Do **not** apply this skip rule when reviewing the `config` repository
+itself. In `config`, these files are source files owned by the current
+repo and must be reviewed normally, except `gradlew` and `gradlew.bat`.
+
+In consumer repositories, skip without comment any path matching:
+
+- `AGENTS.md`, `CLAUDE.md`, `CONTRIBUTING.md`, `CODE_OF_CONDUCT.md`
+- `.agents/**` (except `.agents/project.md`)
+- `.claude/**`, `.idea/**`, `.junie/**`
+- `.github/copilot-instructions.md`
+- `buildSrc/**` (except `buildSrc/src/main/kotlin/module.gradle.kts`)
+- `gradle/`, `gradlew`, `gradlew.bat`, `init-submodules`
+- `.codecov.yml`, `.gitignore`, `gradle.properties`, `lychee.toml`
+- `.github/workflows/` — unless the workflow was introduced by this repo
+
+[agents-repo]: https://github.com/SpineEventEngine/agents
diff --git a/CLAUDE.md b/CLAUDE.md
new file mode 100644
index 0000000..2ddd0b9
--- /dev/null
+++ b/CLAUDE.md
@@ -0,0 +1,16 @@
+@AGENTS.md
+
+## Claude Code-specific notes
+
+- Use Plan mode (`EnterPlanMode`) for architecture, refactoring, multi-file
+ changes, or lengthy documentation. Show the plan (`ExitPlanMode`) before
+ implementing.
+- Track live progress with `TaskCreate`.
+- In JVM repos: before reading library source code from `~/.gradle/caches`,
+ follow the `api-discovery` skill — never `unzip` JARs directly.
+- Per-developer memory lives in the built-in auto-memory dir. Use it for
+ personal preferences, ephemeral project state, and per-machine resources.
+ Litmus test: *would a teammate benefit from this next month?* → repo.
+ Otherwise → auto-memory.
+- This is living team memory. Update it regularly and keep it concise
+ (<120 lines / ~2.5k tokens).
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
new file mode 100644
index 0000000..0a1b5f2
--- /dev/null
+++ b/CODE_OF_CONDUCT.md
@@ -0,0 +1,128 @@
+# Contributor Covenant Code of Conduct
+
+## Our Pledge
+
+We as members, contributors, and leaders pledge to make participation in our
+community a harassment-free experience for everyone, regardless of age, body
+size, visible or invisible disability, ethnicity, sex characteristics, gender
+identity and expression, level of experience, education, socio-economic status,
+nationality, personal appearance, race, religion, or sexual identity
+and orientation.
+
+We pledge to act and interact in ways that contribute to an open, welcoming,
+diverse, inclusive, and healthy community.
+
+## Our Standards
+
+Examples of behavior that contributes to a positive environment for our
+community include:
+
+* Demonstrating empathy and kindness toward other people
+* Being respectful of differing opinions, viewpoints, and experiences
+* Giving and gracefully accepting constructive feedback
+* Accepting responsibility and apologizing to those affected by our mistakes,
+ and learning from the experience
+* Focusing on what is best not just for us as individuals, but for the
+ overall community
+
+Examples of unacceptable behavior include:
+
+* The use of sexualized language or imagery, and sexual attention or
+ advances of any kind
+* Trolling, insulting or derogatory comments, and personal or political attacks
+* Public or private harassment
+* Publishing others' private information, such as a physical or email
+ address, without their explicit permission
+* Other conduct which could reasonably be considered inappropriate in a
+ professional setting
+
+## Enforcement Responsibilities
+
+Community leaders are responsible for clarifying and enforcing our standards of
+acceptable behavior and will take appropriate and fair corrective action in
+response to any behavior that they deem inappropriate, threatening, offensive,
+or harmful.
+
+Community leaders have the right and responsibility to remove, edit, or reject
+comments, commits, code, wiki edits, issues, and other contributions that are
+not aligned to this Code of Conduct, and will communicate reasons for moderation
+decisions when appropriate.
+
+## Scope
+
+This Code of Conduct applies within all community spaces, and also applies when
+an individual is officially representing the community in public spaces.
+Examples of representing our community include using an official e-mail address,
+posting via an official social media account, or acting as an appointed
+representative at an online or offline event.
+
+## Enforcement
+
+Instances of abusive, harassing, or otherwise unacceptable behavior may be
+reported to the community leaders responsible for enforcement at
+developers@spine.io.
+All complaints will be reviewed and investigated promptly and fairly.
+
+All community leaders are obligated to respect the privacy and security of the
+reporter of any incident.
+
+## Enforcement Guidelines
+
+Community leaders will follow these Community Impact Guidelines in determining
+the consequences for any action they deem in violation of this Code of Conduct:
+
+### 1. Correction
+
+**Community Impact**: Use of inappropriate language or other behavior deemed
+unprofessional or unwelcome in the community.
+
+**Consequence**: A private, written warning from community leaders, providing
+clarity around the nature of the violation and an explanation of why the
+behavior was inappropriate. A public apology may be requested.
+
+### 2. Warning
+
+**Community Impact**: A violation through a single incident or series
+of actions.
+
+**Consequence**: A warning with consequences for continued behavior. No
+interaction with the people involved, including unsolicited interaction with
+those enforcing the Code of Conduct, for a specified period of time. This
+includes avoiding interactions in community spaces as well as external channels
+like social media. Violating these terms may lead to a temporary or
+permanent ban.
+
+### 3. Temporary Ban
+
+**Community Impact**: A serious violation of community standards, including
+sustained inappropriate behavior.
+
+**Consequence**: A temporary ban from any sort of interaction or public
+communication with the community for a specified period of time. No public or
+private interaction with the people involved, including unsolicited interaction
+with those enforcing the Code of Conduct, is allowed during this period.
+Violating these terms may lead to a permanent ban.
+
+### 4. Permanent Ban
+
+**Community Impact**: Demonstrating a pattern of violation of community
+standards, including sustained inappropriate behavior, harassment of an
+individual, or aggression toward or disparagement of classes of individuals.
+
+**Consequence**: A permanent ban from any sort of public interaction within
+the community.
+
+## Attribution
+
+This Code of Conduct is adapted from the [Contributor Covenant][homepage],
+version 2.0, available at
+https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
+
+Community Impact Guidelines were inspired by [Mozilla's code of conduct
+enforcement ladder](https://github.com/mozilla/diversity).
+
+[homepage]: https://www.contributor-covenant.org
+
+For answers to common questions about this code of conduct, see the FAQ at
+https://www.contributor-covenant.org/faq. Translations are available at
+https://www.contributor-covenant.org/translations.
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..2185ef6
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,37 @@
+How to contribute
+==================
+Thank you for wanting to contribute to Spine. The following links will help you get started:
+ * [Wiki home][wiki-home] — the home of the framework developer's documentation.
+ * [Getting started with Spine in Java][quick-start] — this guide will walk you through
+ a minimal client-server “Hello World!” application in Java.
+ * [Introduction][docs-intro] — this section of the Spine Documentation will help you understand
+ the foundation of the framework.
+
+Pull requests
+-------------
+The work on an improvement starts with creating an issue that describes a bug or a feature. The issue will be used for communications on the proposed improvements.
+If code changes are going to be introduced, the issue should also have a link to the corresponding Pull Request.
+
+Code contributions should:
+ * Be accompanied by tests.
+ * Be licensed under the Apache v2.0 license with the appropriate copyright header for each file.
+ * Formatted according to the code style. See [Wiki home][wiki-home] for the links to
+ style guides of the programming languages used in the framework.
+
+Contributor License Agreement
+-----------------------------
+Contributions to the code of Spine Event Engine framework and its libraries must be accompanied by
+Contributor License Agreement (CLA).
+
+ * If you are an individual writing original source code and you're sure you own
+ the intellectual property, then you'll need to sign an individual CLA.
+
+ * If you work for a company which wants you to contribute your work,
+ then an authorized person from your company will need to sign a corporate CLA.
+
+Please [contact us][legal-email] for arranging the paper formalities.
+
+[wiki-home]: https://github.com/SpineEventEngine/SpineEventEngine.github.io/wiki
+[quick-start]: https://spine.io/docs/quick-start
+[docs-intro]: https://spine.io/docs/introduction
+[legal-email]: mailto:legal@teamdev.com
diff --git a/ProtoTap b/ProtoTap
new file mode 160000
index 0000000..43ac1ac
--- /dev/null
+++ b/ProtoTap
@@ -0,0 +1 @@
+Subproject commit 43ac1acaa282398920c1706230241660a3817041
diff --git a/base-libraries b/base-libraries
new file mode 160000
index 0000000..76b2d00
--- /dev/null
+++ b/base-libraries
@@ -0,0 +1 @@
+Subproject commit 76b2d003f8e5711a50741bbd0b016302f399e82d
diff --git a/base-types b/base-types
new file mode 160000
index 0000000..ee191de
--- /dev/null
+++ b/base-types
@@ -0,0 +1 @@
+Subproject commit ee191de26a712f0ab318ee8e794929f8e7a89dbe
diff --git a/change b/change
new file mode 160000
index 0000000..53fe702
--- /dev/null
+++ b/change
@@ -0,0 +1 @@
+Subproject commit 53fe7029a18920b8cd5d79c8636affb79f77ce84
diff --git a/compiler b/compiler
new file mode 160000
index 0000000..313a647
--- /dev/null
+++ b/compiler
@@ -0,0 +1 @@
+Subproject commit 313a647055faf29942d94047fcbc256386a8ebdc
diff --git a/config b/config
new file mode 160000
index 0000000..6cee3cf
--- /dev/null
+++ b/config
@@ -0,0 +1 @@
+Subproject commit 6cee3cf52f3044f08a8433cf838713c804ea1084
diff --git a/core-jvm b/core-jvm
new file mode 160000
index 0000000..a408b0d
--- /dev/null
+++ b/core-jvm
@@ -0,0 +1 @@
+Subproject commit a408b0d70dafd603efc55b89c8b4b6f3e8c19d3b
diff --git a/core-jvm-compiler b/core-jvm-compiler
new file mode 160000
index 0000000..0eb8ec3
--- /dev/null
+++ b/core-jvm-compiler
@@ -0,0 +1 @@
+Subproject commit 0eb8ec332cbad5731193bea5b22a21759300e4f9
diff --git a/delivery-server b/delivery-server
new file mode 160000
index 0000000..27aef68
--- /dev/null
+++ b/delivery-server
@@ -0,0 +1 @@
+Subproject commit 27aef68d77c9b40c19f2f0dd253e351363f76377
diff --git a/docs/project.md b/docs/project.md
new file mode 100644
index 0000000..0fdaffc
--- /dev/null
+++ b/docs/project.md
@@ -0,0 +1,18 @@
+
+
+# Project:
+
+## Overview
+
+*One paragraph: what this repo is, what problem it solves, and its role in the
+Spine SDK organisation.*
+
+## Architecture
+
+*Role in the org: library / tool / Gradle plugin / application.
+Key patterns, public API boundaries, and constraints specific to this repo.*
+
+
diff --git a/init-submodules b/init-submodules
new file mode 100755
index 0000000..2ae143d
--- /dev/null
+++ b/init-submodules
@@ -0,0 +1,99 @@
+#!/usr/bin/env bash
+
+################################################################################
+#
+# Materialize the *config-managed* submodules a fresh working tree is missing, so
+# agent assets resolve.
+#
+# `git worktree add` — and some shallow CI / cloud checkouts — populate only the
+# superproject's own tracked files; registered submodules are left UNinitialized.
+# In a Spine repo that means the `config` and `.agents/shared` submodules are
+# empty, the `.agents/skills` -> `.agents/shared/skills` symlink dangles, and no
+# agent skills, scripts, or guidelines can be found.
+#
+# This script is the bootstrap that has to run BEFORE `./config/pull`: `pull`
+# lives inside the `config` submodule, so on a fresh worktree it does not yet
+# exist. `init-submodules`, by contrast, is a plain tracked file at the repo root
+# (distributed by `config`), so `git worktree add` always checks it out — it can
+# therefore bring `config` itself into existence.
+#
+# It initializes ONLY submodules that are BOTH:
+#
+# * not yet checked out — those `git submodule status` marks with a leading `-`,
+# at the commit the branch pins; and
+#
+# * config-managed — `config` itself (the bootstrap target `pull` lives inside,
+# which carries no tracked `branch` in a consumer's `.gitmodules`), plus every
+# submodule that declares a tracked `branch` in `.gitmodules`. This is exactly
+# the rule `./config/pull` uses to decide what it floats, so the two scripts
+# can never disagree about what is shared.
+#
+# Consumer-owned submodules (a Hugo theme, a vendored library, documentation
+# examples, ...) declare no tracked branch and are deliberately left untouched.
+# Because a `SessionStart` hook runs this script automatically on every session,
+# initializing them would mean trying to clone — or failing on credentials for —
+# a submodule this project does not manage, on every single start. They are
+# skipped (noted on stderr).
+#
+# Submodules already present are left exactly as they are, so a tree that floated
+# `config` / `.agents/shared` to a branch tip via `./config/pull` is never
+# silently rewound to the pin. That makes the script idempotent and safe to run on
+# every session start.
+#
+# It does NOT float submodules to their branch tips — run `./config/pull`
+# afterwards for that. Unlike `pull`, it depends on no pre-existing `config`
+# submodule, so it can bootstrap a bare worktree where `./config/pull` does not
+# yet exist.
+#
+################################################################################
+
+set -u
+
+root=$(git rev-parse --show-toplevel 2>/dev/null) || exit 0
+cd "$root" || exit 0
+
+# Nothing to do in a repo without submodules.
+[ -f .gitmodules ] || exit 0
+
+# The set of config-managed submodule paths: `config` itself (handled specially —
+# it carries no tracked branch, exactly as in `./config/pull`), plus every
+# submodule declaring a tracked `branch` in `.gitmodules`. Mirrors `pull`'s rule.
+config_managed_paths() {
+ printf '%s\n' 'config'
+ git config -f .gitmodules --get-regexp '^submodule\..*\.branch$' 2>/dev/null \
+ | while read -r key _branch; do
+ name=${key#submodule.}; name=${name%.branch}
+ git config -f .gitmodules --get "submodule.$name.path" 2>/dev/null
+ done
+}
+
+managed=$(config_managed_paths | sort -u)
+
+# `git submodule status` prefixes each uninitialized submodule with `-`; an
+# initialized one starts with a space (at the pinned commit) or `+` (ahead of it).
+# Act only on the `-` lines, taking the path from the second field, and only when
+# that path is config-managed.
+git submodule status 2>/dev/null | awk '$1 ~ /^-/ { print $2 }' | while read -r path; do
+ [ -n "$path" ] || continue
+ if printf '%s\n' "$managed" | grep -qxF -- "$path"; then
+ echo "init-submodules: initializing '$path'"
+ git submodule update --init --recursive -- "$path" \
+ || echo "init-submodules: WARNING — could not initialize '$path' (offline?)." >&2
+ else
+ echo "init-submodules: skipping consumer-owned '$path' (not config-managed)." >&2
+ fi
+done
+
+# Route Git hooks to the shared hooks directory so the secret-scan `pre-commit`
+# hook is active even in a brand-new worktree, before `./config/pull` runs. The
+# path floats with the `.agents/shared` submodule; until that submodule is
+# initialized the hook simply does not fire (Git skips a missing hook). Set only
+# when unset or already ours — never override a repo's own `core.hooksPath`.
+desired_hooks=".agents/scripts/git-hooks"
+current_hooks=$(git config --local --get core.hooksPath 2>/dev/null || true)
+if [ -z "$current_hooks" ] || [ "$current_hooks" = "$desired_hooks" ]; then
+ git config --local core.hooksPath "$desired_hooks" \
+ && echo "init-submodules: Git hooks routed to '$desired_hooks' (secret-scan pre-commit active)."
+fi
+
+exit 0
diff --git a/jdbc-storage b/jdbc-storage
new file mode 160000
index 0000000..1b77832
--- /dev/null
+++ b/jdbc-storage
@@ -0,0 +1 @@
+Subproject commit 1b778327a6d24c9834102af86e18f9c532b8ac98
diff --git a/logging b/logging
new file mode 160000
index 0000000..b883c8f
--- /dev/null
+++ b/logging
@@ -0,0 +1 @@
+Subproject commit b883c8f9707c105f6dafceb94b192dfd8a082662
diff --git a/money b/money
new file mode 160000
index 0000000..d8d5f6b
--- /dev/null
+++ b/money
@@ -0,0 +1 @@
+Subproject commit d8d5f6ba522e21b690b68ac0401d393ee226204d
diff --git a/reflect b/reflect
new file mode 160000
index 0000000..980720d
--- /dev/null
+++ b/reflect
@@ -0,0 +1 @@
+Subproject commit 980720d676e5bca7e85905267627b657e5b5ae01
diff --git a/testlib b/testlib
new file mode 160000
index 0000000..1abead1
--- /dev/null
+++ b/testlib
@@ -0,0 +1 @@
+Subproject commit 1abead10d847fef5d32cdc5787e3aa5e53c4ed7f
diff --git a/time b/time
new file mode 160000
index 0000000..57d3dd9
--- /dev/null
+++ b/time
@@ -0,0 +1 @@
+Subproject commit 57d3dd98fea8efcdc4a3843f91143acc2dce87dc
diff --git a/tool-base b/tool-base
new file mode 160000
index 0000000..cca5d03
--- /dev/null
+++ b/tool-base
@@ -0,0 +1 @@
+Subproject commit cca5d03ff5f313b5c03beb37c798c1fc65543919
diff --git a/validation b/validation
new file mode 160000
index 0000000..c737fc1
--- /dev/null
+++ b/validation
@@ -0,0 +1 @@
+Subproject commit c737fc15ff3b11e72bf3d2540d763015d8cd5908