Skip to content

License split: Apache-2.0 for contract/ and ts/workflow-core/ (stacked on #3)#4

Closed
r1marcus wants to merge 41 commits into
mainfrom
chore/license-split-apache-wire-format
Closed

License split: Apache-2.0 for contract/ and ts/workflow-core/ (stacked on #3)#4
r1marcus wants to merge 41 commits into
mainfrom
chore/license-split-apache-wire-format

Conversation

@r1marcus

Copy link
Copy Markdown
Collaborator

Summary

Two-tier license model for edge-agents:

Tier Components License
Wire format / headless model contract/, ts/workflow-core/ Apache-2.0
Engine + visual builder go/, ts/workflow-builder/, ts/app/ AGPL-3.0-only + commercial

Why split: Make wire format and headless workflow model maximally reusable (third-party Python/Rust/Java clients become possible) while keeping the engine and visual builder protected under copyleft.

Stacked on #3. Base branch is feat/fh-agent-cli, not main. Merge #3 first, then this.

What's in this PR (12 commits, 11 license-related + 1 dead-code cleanup that snuck in)

Apache-2.0 additions

  • contract/LICENSE + contract/NOTICE
  • ts/workflow-core/LICENSE + NOTICE + README.md (new package README)
  • ts/workflow-core/package.json"license": "Apache-2.0"

AGPL preservation (workflow-builder hygiene)

  • ts/workflow-builder/LICENSE (full AGPL text — was missing!)
  • ts/workflow-builder/NOTICE (explains dependency on Apache workflow-core)
  • ts/workflow-builder/package.json"license": "AGPL-3.0-only" + NOTICE in files

Documentation

  • README.md License section → 5-row component-by-license table
  • Top-level NOTICE → documents two-tier model + references subdir LICENSEs
  • THIRD_PARTY_NOTICES intro → updated for two-tier
  • .github/CONTRIBUTING.md → new "Which license your contribution falls under" section
  • .github/PULL_REQUEST_TEMPLATE.md CLA checkbox → mentions both tiers

Legal model

  • contract/ Apache LICENSE = verbatim apache.org SHA256 cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30
  • ts/workflow-core/ Apache LICENSE = same verbatim hash
  • ts/workflow-builder/ AGPL = exact copy of top-level LICENSE
  • CLA in CONTRIBUTING.md (untouched) covers both tiers — relicense rights extend to either path
  • Contributors checked via git log: only Daniel Maier and Daniel Konegen on contract/ + ts/workflow-core/ — CLA-coverage trivial

Out of scope

  • SPDX file headers (LICENSE_HEADER template at root remains unused — pre-existing, not regressed)
  • ts/v0.1.1 git tag (CHANGELOG link points to tree/main/ts until tags get adopted for TS releases)

Test plan

  • go build ./... + go test ./... green
  • cd ts && npm run typecheck green
  • cd ts/workflow-core && npm run build && npm run test (72/72 tests pass)
  • cd ts/workflow-core && npm pack --dry-run shows LICENSE + NOTICE + README in tarball
  • cd ts/workflow-builder && npm pack --dry-run shows LICENSE + NOTICE in tarball
  • License hash verification: sha256sum contract/LICENSE ts/workflow-core/LICENSE matches apache.org verbatim
  • Final dev/legal sign-off on Apache split for wire format

🤖 Authored with Claude Code

MarcusBot and others added 30 commits May 31, 2026 00:13
A Go CLI under go/cmd/fh-agent that turns a high-level site.spec.yaml
into a Docker Compose stack ready to scp + 'docker compose up' on a
Raspberry Pi 5, NVIDIA Jetson Orin Nano, x86 NUC, STM32MP25, or Bosch
Rexroth ctrlX CORE.

Pipeline: spec → plan → validate → build.

  spec     authoring & schema-export (read/write spec only)
  targets  introspection over 5 embedded hardware profiles
  models   suggest --target X --capability Y, RAM-filtered
  plan     deterministic compile of spec × target into 6 artifacts
           (workflow, mapping, resources, manifest, local-models, meta)
  validate cross-check the build dir (channels↔mapping↔resources↔manifest,
           model RAM vs target)
  build    render Compose stack (engine + llama-server + optional
           mosquitto) + README + .env.example, optional --tar

Designed for agents:
  - JSON-first I/O, identical diagnostic shape across commands
  - Documented exit codes (0/1/2/64)
  - Deterministic plan (sorted keys, hashed node ids) so an iterating
    agent sees only meaningful diffs
  - 'targets describe' and 'spec schema' double as documentation so
    Claude doesn't need to load 900 lines of contract YAML

Bundle uses the official ghcr.io engine image with configs mounted as
volumes — no per-customer Docker build, no AGPL-derivative-work question.

v1 limits documented in the README: no contract-schema validation
(separately run fh-workflow validate), no provisioning/OTA/HIL.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
fh-agent validate now shells out to `fh-builder validate <wf> --json`
to catch contract violations the Go-side cross-checks can't see (missing
required params, wrong arg names, expression shape). Diagnostics merge
into fh-agent's existing JSON array — same shape, same exit codes.

Resilient: if fh-builder is not in PATH (e.g. on a customer device
without Node), validate emits one warn diagnostic and continues.
`--skip-workflow-check` silences it explicitly.

Required TS-side change:
- ts/app/cli/validate.ts: new `--json` flag emits a flat diagnostics
  array on stdout (was human text only). Same field names as
  fh-agent's Diagnostic.

Plan fixes uncovered by the new check:
- Ticker: emit intervalValue + intervalUnit, not legacy intervalMs
- ReadPin/WritePin/OnPinEdge: use `pinReference`, not `channelReference`
- agentTask edges: now carry the required `prompt` Expression
- Expression literals: non-empty default per dataType ("0"/"false"/"")

Example bundle (muellers-haus) now passes both Go cross-checks AND
fh-builder contract validation end-to-end.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Scoped roadmap for go/cmd/fh-agent/ — principles, shipped versions
(v1.0 + v1.1), v1.x hardening backlog ordered by value/effort, v2
phase candidates, and explicit out-of-scope items.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The entire file was commented out with only the package declaration
active. No code referenced RetryWithResilienceContext anywhere in the
tree. Drop the dead stub.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Configure weekly Dependabot updates for the Go module under /go,
the npm workspace under /ts, and GitHub Actions workflows. Minor
and patch updates are grouped per ecosystem to reduce PR noise.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Enable GitHub Sponsors button for the ForestHubAI org. Other funding
platforms are listed commented-out as a reference for later.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Route reviews per subtree to @ForestHubAI/maintainers:
- /go/, /ts/    — language subtrees
- /contract/    — critical: drift between go/ts implementations
- /.github/     — CI, workflows, repo meta
- *             — default fallback

Note: the @ForestHubAI/maintainers team must be created in
GitHub org settings (Org Settings → Teams) for the mentions to
resolve. Until then GitHub will show "Unknown owner" warnings on
the branch-protection UI but rules silently no-op.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pin per-language indentation and whitespace defaults so editors without
project-aware tooling agree with gofmt, Prettier, and YAML conventions.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The auth middleware compared the configured shared secret against the
incoming Authorization header with `!=`, which short-circuits on the
first differing byte. A network-adjacent attacker could in principle use
response-time differences to learn the secret prefix-by-prefix.

Switch to `crypto/subtle.ConstantTimeCompare` after a length check
(length is not secret information, so an early length-mismatch return
does not weaken the guarantee). Add table-style tests covering the
empty-secret, missing-header, wrong-token and correct-token cases.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Keep-a-Changelog 1.1.0 format. Backfills go/v1.0.1 and ts/0.1.1
release lines from git history; opens an Unreleased section
covering the fh-agent CLI, dependabot, CODEOWNERS, and README
rewrite work landed since the tag.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Lints contract/*.yaml on PR + push to main with stoplightio/spectral-action.
Baseline ruleset extends spectral:oas; first run surfaces 12 errors (mostly
no-$ref-siblings + missing paths on llmproxy/workflow) and 31 warnings.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The shared-secret bearer middleware ran on every operation including
Healthz, breaking the standard container-orchestrator pattern where a
kubelet, Docker Compose healthcheck or ECS agent probes /healthz
unauthenticated. The probe would 401 and the orchestrator would mark
the engine unhealthy or refuse to route traffic to it.

Short-circuit the middleware when the strict-handler operationID is
"Healthz" so the probe reaches the handler without credentials. The
response only reports "is the runner attached" — no workflow content
or node state — so the exemption is safe to disclose. Other operations
(Deploy, Stop) continue to require the bearer.

Tests cover Healthz bypass with both a configured and an empty secret.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The vendored backend httpclient used &http.Client{} with no timeout.
Per-call context.WithTimeout is used on the boot/heartbeat paths
(BootCallbackTimeout=10s, HeartbeatTimeout=5s, ProviderLoadTimeout=10s),
but Engine.Deploy → memory.Restore → Snapshot inherits an uncapped ctx
from context.WithCancel(Background) and would hang on an unreachable
backend. RAG/LLM-chat call sites from runner nodes are likewise uncapped.

A 30s defaultTimeout on the http.Client gives defense-in-depth without
interfering with the tighter per-call timeouts.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds .github/workflows/lint.yml with two parallel jobs:
- go-lint: golangci/golangci-lint-action@v6 against go/, Go version from go.mod
- ts-lint: npm ci && npm run lint in ts/ (eslint flat config already present)

Separate from ci.yml to keep lint-failures non-blocking for test runs and to
isolate concerns. permissions: contents: read only.

Also adds go/.golangci.yml with sensible defaults:
- enabled: govet, errcheck, staticcheck, ineffassign, unused, gosimple, gosec
- gosec noisy rules excluded (G104, G304, G404)
- govet: enable-all minus fieldalignment + shadow
- test files exempt from gosec/errcheck
- generated go/api/ tree excluded

Local verification:
- ts lint runs clean (0 findings) via `npm run lint`
- golangci-lint not installed locally; YAML syntax verified for both files
- Go lint findings: unknown until first CI run (follow-up: fix or relax)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Disables blank issues and routes off-topic intents (questions, security
reports, commercial license inquiries) to Discussions, private security
advisories, and mailto:root@foresthub.ai respectively.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…gistry

Removes the GitHub Packages registry pin (which forced consumers to set up an
auth token even for read access) and points the package at the public npm
registry instead. Adds publishConfig.access=public so npm treats the scoped
@foresthubai/* package as openly published on first publish.

Tightens the files whitelist to dist + README + LICENSE; the previous list
included src, which double-shipped TypeScript sources alongside the compiled
dist/ output (npm pack now reports ~87 kB / 265 files for the tarball).

Follow-up: no release workflow exists today, but when one is added it must
not pin registry-url to https://npm.pkg.github.com.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… registry

Mirrors the workflow-core switch: drops the GitHub Packages registry pin so
consumers can install without an auth token, and sets access=public so the
scoped package is published openly.

Tightens the files whitelist: dist + tailwind-preset.ts + README + LICENSE
plus src/styles/ (load-bearing — the exports map points "./styles/index.css"
at "./src/styles/index.css", so consumers' CSS import would break if the
whole src/ tree were excluded). The rest of src/ (TS sources) no longer
ships. Tarball is now ~212 kB / 394 files.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…nodes

Hero now leads with a concrete, verified number (~32 MB compressed image,
measured by cross-compiling cmd/engine for linux/arm64 and linux/amd64
with CGO_ENABLED=0 and -ldflags='-s -w' on top of distroless/static).
Replaces the previous ~15 MB figure which was not reproducible from a
fresh build. Drops OPC-UA from the pitch — it appears only as a
ctrlX-OS pass-through note in a target manifest, not as an engine node
(go/engine/driver and go/engine/transport implement GPIO/ADC/DAC/PWM,
UART/serial, and MQTT). Pi Zero 2W is replaced by generic "Raspberry
Pi" wording since no Pi Zero target is in cmd/fh-agent/targets.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Publishes ghcr.io/foresthubai/edge-agents/engine on every `go/vX.Y.Z` tag —
the same tag that releases the Go module — so the README's `docker run ...`
quickstart actually resolves once the next tag is pushed.

- linux/amd64 + linux/arm64 via buildx + QEMU (cross-compile in builder
  stage, only the final COPY into distroless is emulated)
- docker/metadata-action strips the `go/` prefix so the image gets clean
  semver tags (1.2.3, 1.2, 1) plus `latest`
- keyless cosign signing via GitHub OIDC, signs each tag-by-digest
- SLSA build provenance + SBOM attestations pushed to the registry
- workflow_dispatch with optional tag override for one-off rebuilds

Triggered exclusively on `go/v*` tag-push and manual dispatch; no rolling
build on main, to keep `:latest` aligned with a real release tag.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds four shields.io badges that work today: Go version (resolved from
go/go.mod, currently 1.25), GitHub stars, last commit, and open issues.
Each URL was probed (HTTP 200, real SVG content) before being added.

Skipped: release badge (no GitHub releases published yet — only the
go/v1.0.1 git tag exists), Docker Pulls (no public image), Discord
(no server), Codecov (not wired).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Mirrors the publishConfig switch on both TS packages: replaces the GitHub
Packages walkthrough (consumer .npmrc + FH_PACKAGES_TOKEN) with the npmjs.org
flow (npm login on the publish machine, no consumer setup required). Drops
the "going public" footnote since that is now the steady state.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Runs the GitHub Advanced Security CodeQL analyzer on push to main, on every
PR to main, and on a weekly Sunday schedule (catches CVEs disclosed against
already-merged code). Matrix covers go (autobuild via go/go.mod) and
javascript-typescript (the modern combined identifier; supersedes the split
javascript + typescript languages). security-and-quality query pack runs the
security suite plus the quality rules — slightly noisier than security-only
but worth it for a public repo.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
New section frames edge-agents against three projects users frequently
evaluate alongside it (LangGraph, n8n, Dify). Comparison axes are
container size, offline mode, hardware I/O, MQTT, visual builder,
local SLMs, and license.

Container sizes are sourced: n8n ~368 MB and Dify ~900 MB from current
Docker Hub tags; edge-agents ~32 MB from the same cross-compile +
distroless/static measurement used in the hero. OpenClaw was considered
but dropped — no authoritative size data and the project is not
comparable in scope.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Runs gitleaks-action on every push and PR, with fetch-depth: 0 so the scan
covers full git history (not just the latest diff) — historic leaks block
the merge just like fresh ones.

No GITLEAKS_LICENSE configured: the paid tier is unnecessary for a public
repo. If the default ruleset throws false positives on third-party notices,
generated code, or vendored snippets we'll add a .gitleaks.toml allowlist
in a follow-up commit — the first real CI run on this branch will surface
what (if anything) needs ignoring.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…N-Schema)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…g block

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The contract/ directory defines the language-neutral wire format
(OpenAPI 3.0.3 schemas) for the edge-agents platform. Splitting its
license to Apache-2.0 allows third-party clients, SDKs, and tooling to
implement the wire format without inheriting the AGPL terms that apply
to the engine and builder.

Verbatim text from https://www.apache.org/licenses/LICENSE-2.0.txt
(sha256 cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Clarifies that contract/ is licensed under Apache-2.0 while the
surrounding repository (engine, llmproxy, ts/workflow-builder, app)
remains AGPL-3.0 / commercial. The NOTICE matches the wording style
of the top-level NOTICE for consistency.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
MarcusBot and others added 10 commits May 31, 2026 16:47
…kage

The workflow-core package is the headless wire-format binding consumers
embed in their own tooling, so it carries Apache-2.0 separately from the
repository-level AGPL-3.0 license that covers the engine and the React
workflow builder. LICENSE is the verbatim Apache-2.0 text; NOTICE points
readers back to the repository LICENSE for the AGPL terms covering the
rest of the tree.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sets the npm "license" field to Apache-2.0 and adds NOTICE to the
publishable "files" allowlist so the Apache-2.0 NOTICE ships in the
tarball alongside LICENSE. The package itself stays self-describing
on the npm registry without the repository-level AGPL terms leaking
into downstream consumers' license scans.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a short README so the package has a landing page on npm covering
what it is (headless workflow model + validator, no React, no DOM),
how to install and validate a workflow, the three-layer architecture,
and the Apache-2.0 / repo AGPL-3.0 license split. Linked from the
"files" allowlist already shipping in the tarball.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces the AGPL-only dual-license paragraph with a component-by-
component table that maps contract/ and ts/workflow-core to Apache-2.0
and keeps engine, workflow-builder, and app under AGPL-3.0/commercial.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Top-level NOTICE now explicitly states the two-tier license split and
points to contract/LICENSE/NOTICE and ts/workflow-core/LICENSE/NOTICE
for the Apache-licensed parts, while keeping the AGPL/commercial wording
scoped to the remaining components.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Updates the License and Contributor Agreement intro to name the two
tiers explicitly, and adds a closing "Which license your contribution
falls under" subsection so contributors know whether a PR ships under
Apache-2.0 or AGPL-3.0-only based on the path it touches.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ckage

The workflow-builder package.json files-whitelist already references LICENSE
but the file was missing, which would publish the package to npm with no
license file in the tarball. Add the full AGPL-3.0 text (copied verbatim from
the top-level LICENSE) and a workflow-builder-specific NOTICE that mirrors
the wording used by workflow-core/NOTICE and clarifies that this package is
AGPL while its workflow-core dependency is Apache-2.0.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…e.json

Without an explicit license field, npmjs.com displays "no license" on the
package page even though LICENSE ships in the tarball. Set the SPDX
identifier explicitly and add NOTICE to the files whitelist so attribution
ships alongside the license text (matching workflow-core's pattern).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The intro claimed edge-agents was AGPL-only, which is stale after the
license split. Restate the model (Apache-2.0 for contract/ and
workflow-core/, AGPL-3.0-only or commercial for the rest) and clarify
that the listed third-party licenses are compatible with both tiers.
The component list itself is untouched.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The CLA checkbox only mentioned AGPL-3.0 relicensing, which made the
template misleading for PRs that touch only contract/ or workflow-core/
(both Apache-2.0). Restate both tiers so contributors agree to the
license applicable to the paths they actually touched.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@konegen konegen changed the base branch from feat/fh-agent-cli to main June 1, 2026 07:25
@konegen konegen closed this in #6 Jun 1, 2026
@r1marcus r1marcus deleted the chore/license-split-apache-wire-format branch June 3, 2026 06:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants