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
49 changes: 49 additions & 0 deletions .github/ISSUE_TEMPLATE/bug_report.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
name: Bug report
description: Report a defect in a FunctionFoundry package
labels: ["bug"]
body:
- type: dropdown
id: package
attributes:
label: Package
options:
- FunctionFoundry.Security
- FunctionFoundry.Storage
- FunctionFoundry.Integrity
- FunctionFoundry.Observability
- FunctionFoundry.Data
- FunctionFoundry.Text
- FunctionFoundry.Resilience
- FunctionFoundry.Networking
- FunctionFoundry.Distributed
- FunctionFoundry.Scheduling
- Repository / CI / docs
validations:
required: true
- type: input
id: version
attributes:
label: Package version
placeholder: 1.0.0
validations:
required: true
- type: textarea
id: description
attributes:
label: Description
description: What went wrong?
validations:
required: true
- type: textarea
id: repro
attributes:
label: Reproduction steps
description: Minimal code or steps to reproduce.
validations:
required: true
- type: textarea
id: expected
attributes:
label: Expected behavior
validations:
required: true
36 changes: 36 additions & 0 deletions .github/ISSUE_TEMPLATE/feature_request.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
name: Feature request
description: Propose a capability for a FunctionFoundry package
labels: ["enhancement"]
body:
- type: dropdown
id: package
attributes:
label: Package
options:
- FunctionFoundry.Security
- FunctionFoundry.Storage
- FunctionFoundry.Integrity
- FunctionFoundry.Observability
- FunctionFoundry.Data
- FunctionFoundry.Text
- FunctionFoundry.Resilience
- FunctionFoundry.Networking
- FunctionFoundry.Distributed
- FunctionFoundry.Scheduling
- New package / cross-cutting
validations:
required: true
- type: textarea
id: problem
attributes:
label: Problem
description: What specialized capability is missing that the BCL does not already provide well?
validations:
required: true
- type: textarea
id: proposal
attributes:
label: Proposal
description: API sketch, guarantees, and non-goals.
validations:
required: true
13 changes: 13 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
version: 2
updates:
- package-ecosystem: github-actions
directory: /
schedule:
interval: weekly
open-pull-requests-limit: 5

- package-ecosystem: nuget
directory: /
schedule:
interval: weekly
open-pull-requests-limit: 10
91 changes: 81 additions & 10 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,11 @@ env:

jobs:
build:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- name: Checkout
uses: actions/checkout@v4
Expand All @@ -25,26 +29,34 @@ jobs:
global-json-file: global.json

- name: Verify pinned SDK
shell: bash
run: |
SDK_VERSION=$(dotnet --version)
echo "Installed SDK: $SDK_VERSION"
test "$SDK_VERSION" = "10.0.301"
EXPECTED=$(python -c "import json; print(json.load(open('global.json'))['sdk']['version'])")
test "$SDK_VERSION" = "$EXPECTED"

- name: Restore (locked mode when lock files exist)
run: |
if find . -name 'packages.lock.json' | grep -q .; then
dotnet restore --locked-mode
else
dotnet restore
fi
- name: Restore (locked mode)
run: dotnet restore --locked-mode

- name: Verify formatting
if: matrix.os == 'ubuntu-latest'
run: dotnet format --verify-no-changes --verbosity diagnostic

- name: Build Release
run: dotnet build -c Release --no-restore

- name: Build samples
shell: bash
run: |
set -e
for proj in samples/*/*.csproj; do
echo "=== Building $proj ==="
dotnet build -c Release --no-restore "$proj"
done

- name: Test with coverage
shell: bash
run: |
set +e
FAILURES=0
Expand Down Expand Up @@ -78,12 +90,72 @@ jobs:
exit 1
fi

- name: Enforce line coverage threshold
if: matrix.os == 'ubuntu-latest'
shell: bash
run: |
python3 <<'PY'
import pathlib, sys, xml.etree.ElementTree as ET

threshold = 85.0
files = list(pathlib.Path("artifacts/test-results").rglob("coverage.cobertura.xml"))
if not files:
print("::error::No cobertura coverage files found.", file=sys.stderr)
sys.exit(1)

# Deduplicate by package name so multi-project Coverlet runs do not double-count.
package_stats: dict[str, tuple[int, int]] = {}
for path in files:
root = ET.parse(path).getroot()
for package in root.findall(".//package"):
name = package.attrib.get("name", "")
if not name or ".Tests" in name or ".Sample" in name or ".Benchmarks" in name:
continue
if "FunctionFoundry." not in name:
continue
covered = 0
valid = 0
for line in package.findall(".//line"):
valid += 1
if int(line.attrib.get("hits", "0")) > 0:
covered += 1
previous = package_stats.get(name)
if previous is None or valid > previous[1]:
package_stats[name] = (covered, valid)

if not package_stats:
# Fallback to document totals when package names are unavailable.
covered = valid = 0
for path in files:
root = ET.parse(path).getroot()
covered += int(float(root.attrib.get("lines-covered", "0")))
valid += int(float(root.attrib.get("lines-valid", "0")))
else:
covered = sum(c for c, _ in package_stats.values())
valid = sum(v for _, v in package_stats.values())
for name, (c, v) in sorted(package_stats.items()):
pct_pkg = 100.0 * c / v if v else 0.0
print(f" {name}: {pct_pkg:.1f}% ({c}/{v})")

if valid == 0:
print("::error::Coverage files contained zero valid lines.", file=sys.stderr)
sys.exit(1)

pct = 100.0 * covered / valid
print(f"Aggregated line coverage: {pct:.2f}% ({covered}/{valid}) across {len(package_stats) or len(files)} packages/files")
if pct + 1e-9 < threshold:
print(f"::error::Line coverage {pct:.2f}% is below required {threshold:.1f}%.", file=sys.stderr)
sys.exit(1)
PY

- name: Pack
if: matrix.os == 'ubuntu-latest'
run: |
mkdir -p artifacts/packages
dotnet pack -c Release --no-build -o artifacts/packages

- name: Upload artifacts
if: matrix.os == 'ubuntu-latest'
uses: actions/upload-artifact@v4
with:
name: packages-and-tests
Expand All @@ -100,4 +172,3 @@ jobs:
steps:
- uses: actions/checkout@v4
- uses: actions/dependency-review-action@v4
continue-on-error: true
72 changes: 72 additions & 0 deletions .github/workflows/publish-nuget.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
name: Publish NuGet

on:
workflow_dispatch:
inputs:
version:
description: SemVer package version to publish (e.g. 1.0.0)
required: true
default: "1.0.0"
release:
types: [published]

env:
DOTNET_NOLOGO: true
DOTNET_CLI_TELEMETRY_OPTOUT: true
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
CI: true
FF_RELEASE_PACK: true

jobs:
publish:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Resolve version
id: ver
shell: bash
run: |
if [ "${{ github.event_name }}" = "release" ]; then
TAG="${{ github.event.release.tag_name }}"
VERSION="${TAG#v}"
else
VERSION="${{ inputs.version }}"
fi
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
echo "Publishing version $VERSION"

- name: Setup .NET SDK
uses: actions/setup-dotnet@v4
with:
global-json-file: global.json

- name: Restore / build / pack (no -local suffix)
run: |
dotnet restore --locked-mode
dotnet build -c Release --no-restore -p:Version=${{ steps.ver.outputs.version }} -p:VersionSuffix=
mkdir -p artifacts/packages
dotnet pack -c Release --no-build -o artifacts/packages -p:Version=${{ steps.ver.outputs.version }} -p:VersionSuffix=

- name: Push to NuGet.org
env:
NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }}
shell: bash
run: |
if [ -z "$NUGET_API_KEY" ]; then
echo "::error::NUGET_API_KEY secret is not configured."
exit 1
fi
shopt -s nullglob
packages=(artifacts/packages/FunctionFoundry.*.${{ steps.ver.outputs.version }}.nupkg)
if [ ${#packages[@]} -eq 0 ]; then
echo "::error::No nupkg files found for version ${{ steps.ver.outputs.version }}."
exit 1
fi
for pkg in "${packages[@]}"; do
echo "Pushing $pkg"
dotnet nuget push "$pkg" --api-key "$NUGET_API_KEY" --source https://api.nuget.org/v3/index.json --skip-duplicate
done
27 changes: 24 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Changed

* Relicensed from MIT to **Apache License 2.0** with an attribution `NOTICE` file. Free use remains allowed; FunctionFoundry must be credited when used or redistributed.
* Pin .NET SDK **10.0.302** (maintenance upgrade from 10.0.301; roll-forward still disabled).
* Local packs keep the `-local` version suffix; CI and `FF_RELEASE_PACK=true` produce clean SemVer packages suitable for NuGet.org.
* CI now runs on Ubuntu and Windows, builds all samples, restores with lock files, and fails when aggregated line coverage is below 85%.
* `FunctionFoundry.Networking` downloader now adapts chunk size within min/max from observed throughput and performs corrupt-chunk / digest repair passes.
* Security reporting documents GitHub Security Advisories as the private channel.

### Added

* `.github/workflows/publish-nuget.yml` for clean version publishes (requires `NUGET_API_KEY`).
* Dependabot for GitHub Actions and NuGet.
* `CODE_OF_CONDUCT.md`.
* Package lock files (`packages.lock.json`) for reproducible restores.

### Fixed

* Documentation drift (test counts, tag naming, release/NuGet status) in `STATE.md` and `docs/FINAL_REPORT.md`.
* Changelog now records the Apache 2.0 relicense under 1.0.0 (was incorrectly left only under Unreleased).

## [1.0.0] - 2026-07-14

Expand All @@ -25,9 +41,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
* `FunctionFoundry.Networking` — resumable parallel downloader, mirror selector, transfer plan, streaming integrity verifier.
* `FunctionFoundry.Distributed` — weighted rendezvous hashing, version clocks, phi-accrual detector, quorum aggregator.
* `FunctionFoundry.Scheduling` — interval-set algebra, recurring availability, business calendars, critical-path scheduler.
* Repository engineering foundation (SDK 10.0.301 pin, analyzers, CI, ADRs, documentation).
* Repository engineering foundation (SDK pin, analyzers, CI, ADRs, documentation).

### Changed

* Licensed under the **Apache License 2.0** with an attribution `NOTICE` file. Free use remains allowed; FunctionFoundry must be credited when used or redistributed.

### Notes

* Feature 1 packages were hardened as the conceptual v0.5.0 baseline; Feature 2 completion is released as v1.0.0 local package artifacts.
* Feature 1 packages were hardened as the conceptual v0.5.0 baseline; Feature 2 completion is released as v1.0.0.
* No mandatory aggregate package. Core packages have zero third-party runtime dependencies.
* An accidental NuGet publish of `1.0.0-local` should be replaced by a clean `1.0.0` using the Publish NuGet workflow.
28 changes: 28 additions & 0 deletions CODE_OF_CONDUCT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Code of Conduct

## Our pledge

We pledge to make participation in FunctionFoundry a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, experience level, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation.

## Our standards

Examples of behavior that contributes to a positive environment include:

* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community

Examples of unacceptable behavior include:

* Harassment, trolling, insulting or derogatory comments
* Publishing others' private information without permission
* Other conduct which could reasonably be considered inappropriate in a professional setting

## Enforcement

Report unacceptable behavior privately using the channels in [SECURITY.md](SECURITY.md) for sensitive matters, or contact the repository maintainers via GitHub for conduct issues. Maintainers will review and take appropriate action.

## Attribution

This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org/), version 2.1.
Loading
Loading