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
51 changes: 51 additions & 0 deletions docs/advanced-issues.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# Advanced Issue Standard

This document outlines the standard for creating "GrantFox-style" advanced issue JSON files in the `anchorkit` repository.

## Overview
To automate and standardize the issue creation process, issue batches are defined as JSON files inside the `issues/` directory. These issues must adhere to strict schema rules before they can be created on GitHub.

## Schema Requirements

Every issue JSON file must have the following fields:

- `title` (string, required): A concise title for the issue.
- `description` (string, required): A detailed description of the problem or feature.
- `labels` (array of strings, required): At least one valid label.
- `complexity` (string, required): The difficulty of the issue.
- `acceptanceCriteria` (array of strings, required): Specific requirements that must be met to close the issue.

### Supported Labels
- `bug`
- `enhancement`
- `documentation`
- `good first issue`
- `help wanted`
- `feature`

### Allowed Complexities
- `low`
- `medium`
- `high`
- `expert`

### Acceptance Criteria Rules
- There must be at least one acceptance criterion.
- Each criterion must be sufficiently detailed (greater than 10 characters). Weak criteria like "works" or "tests pass" will be rejected.

## Example

```json
{
"title": "Add a local validator for GrantFox-style advanced issue JSON files.",
"description": "Issue batches can contain missing fields, unsupported labels, weak acceptance criteria, or low-value tasks. AnchorKit automation should validate issue batch structure and advanced issue quality before creation.",
"labels": ["feature"],
"complexity": "expert",
"acceptanceCriteria": [
"Issue batch schema validator is implemented.",
"Unsupported labels are detected before GitHub issue creation.",
"Missing required fields are reported clearly.",
"Weak or empty acceptance criteria are flagged."
]
}
```
81 changes: 81 additions & 0 deletions docs/feature-flags.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# Feature Flags and Configuration Source Framework

AnchorKit provides a unified configuration source resolution and feature flag framework in `@anchorkit/config` and `@anchorkit/stellar-kit`.

## Overview

Experimental and non-standard SDK capabilities (such as experimental Soroban contract functions or Vault management) are managed through feature flags. By default, experimental capabilities are **disabled for safety** to prevent accidental invocation in production applications.

## Feature Flag Stability Levels

Feature flags define capabilities with one of three stability levels:

- **`stable`**: Fully tested, production-ready SDK capabilities. Enabled by default or safely togglable.
- **`experimental`**: Under active development or preview. **Disabled by default**.
- **`deprecated`**: Legacy capabilities planned for future removal.

### Registered Feature Flags

| Feature Flag ID | Name | Stability | Default State | Description |
|---|---|---|---|---|
| `experimental_soroban` | Experimental Soroban Support | `experimental` | **Disabled** | Soroban smart contract preview functions and RPC extensions. |
| `experimental_vault` | Experimental Vault Manager | `experimental` | **Disabled** | Vault session management and escrow rules. |
| `mainnet_access` | Mainnet Operations | `stable` | **Disabled** | Allows execution against Stellar Mainnet. |
| `advanced_diagnostics` | Advanced Diagnostics | `stable` | **Enabled** | Enriched configuration and network diagnostic pipelines. |

## Enabling Features

Features can be enabled per-environment by configuring `featureFlags` on `AnchorKitEnvConfig`:

```ts
import { DEFAULT_ENV_CONFIG, isFeatureEnabled, assertFeatureEnabled } from "@anchorkit/config";

const appConfig = {
...DEFAULT_ENV_CONFIG,
featureFlags: {
experimental_soroban: true,
},
};

// Check if feature is enabled
if (isFeatureEnabled("experimental_soroban", appConfig)) {
// Safe to use experimental features
}
```

## Disabled Feature Behaviour & Typed Errors

Invoking a disabled capability throws a typed `StellarKitError` with code `"FEATURE_DISABLED"`:

```ts
import { executeSorobanCapability } from "@anchorkit/stellar-kit";

try {
// Throws StellarKitError with code "FEATURE_DISABLED" if experimental_soroban is false
executeSorobanCapability("deploy_contract");
} catch (err: any) {
if (err.code === "FEATURE_DISABLED") {
console.error("Feature is disabled:", err.message);
}
}
```

## Safe Configuration Source Metadata & Diagnostics

The framework exposes safe configuration source resolution metadata via `resolveConfigSourceMetadata()` and `diagnoseConfig()`. Sensitive environment fields (such as secret key prefixes or keys) are automatically marked as `isSensitive: true` and redacted (`"[REDACTED]"`).

```ts
import { diagnoseConfig } from "@anchorkit/stellar-kit";

const diag = diagnoseConfig();
console.log(diag.configSources);
// Output contains safe metadata for every config parameter

console.log(diag.isAllStable);
// Returns false if any active feature flag has experimental or deprecated stability
```

## Safety Guidelines

1. Never bypass `assertFeatureEnabled()` or force-enable experimental features in production without thorough review.
2. Diagnostics output is safe to pass to logging systems or tooltips as sensitive keys are automatically redacted.
11 changes: 10 additions & 1 deletion docs/secret-redaction.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,21 @@ crash reports, or CI output (issue #3). Two layers:
|---|---|---|
| `redactSecretKey(secret)` | `RedactedSecretKey { prefix, suffix, __redacted }` | Keep only first 4 + last 4 chars. |
| `secretKeyToRedactedString(secret)` | `string` | Human-readable `SABC••••••XYZQ`. |
| `redactSecrets(input)` | `string` | Scan a string and redact any Stellar-shaped secret (`S…`, 56 chars) plus `secret key` / `private key` / `seed phrase` tokens. |
| `redactSecrets(input)` | `string` | Scan a string and redact any Stellar-shaped secret (`S…`, 56 chars), secret assignments (`secretKey=...`), plus `secret key` / `private key` / `seed phrase` tokens. |
| `formatRedactedSecret(redacted)` | `string` | Render a `RedactedSecretKey`. |
| `containsSecret(input)` | `boolean` | Check if a string contains any 56-character Stellar secret key or secret assignment pattern. |
| `detectUnsafePatterns(input)` | `{ hasSecrets: boolean; matches: UnsafePatternMatch[] }` | Diagnostic scan for secret-like patterns. |

`RedactedSecretKey` is a branded type (`__redacted: true`) so it can never be
mistaken for a usable key at the type level.

## Diagnostics and Error Integration

AnchorKit automatically applies redaction across error creation and account diagnostics:
- **`createStellarError`**: All error messages are sanitized at creation time via `redactSecrets(message)`.
- **`diagnoseAccount` & `diagnoseAccountInfo`**: Inputs and error outputs pass through `redactSecrets` so passing a secret key or invalid string as a public key parameter will never leak credentials in diagnostic results.


## Safe logger

```ts
Expand Down
12 changes: 12 additions & 0 deletions issues/sample-issue.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"title": "Add a local validator for GrantFox-style advanced issue JSON files.",
"description": "Issue batches can contain missing fields, unsupported labels, weak acceptance criteria, or low-value tasks. AnchorKit automation should validate issue batch structure and advanced issue quality before creation.",
"labels": ["feature"],
"complexity": "expert",
"acceptanceCriteria": [
"Issue batch schema validator is implemented.",
"Unsupported labels are detected before GitHub issue creation.",
"Missing required fields are reported clearly.",
"Weak or empty acceptance criteria are flagged."
]
}
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@
"contract:test": "cd contracts/treasury-escrow && cargo test",
"contract:build": "cd contracts/treasury-escrow && cargo build --target wasm32-unknown-unknown --release",
"web:dev": "turbo run dev --filter=@anchorkit/web",
"web:build": "turbo run build --filter=@anchorkit/web"
"web:build": "turbo run build --filter=@anchorkit/web",
"validate:issues": "npx tsx scripts/validate-issues.mts"
},
"devDependencies": {
"@types/node": "^20.11.0",
Expand Down
115 changes: 115 additions & 0 deletions packages/config/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,37 @@ export const NETWORK_CONFIGS: Record<StellarNetwork, NetworkConfig> = {

export const DEFAULT_NETWORK: StellarNetwork = STELLAR_NETWORKS.TESTNET;

export const DEFAULT_FEATURE_FLAGS: Record<string, FeatureFlagDefinition> = {
experimental_soroban: {
id: "experimental_soroban",
name: "Experimental Soroban Support",
description: "Enables experimental Soroban smart contract operations and custom RPC extensions.",
stability: "experimental",
defaultEnabled: false,
},
experimental_vault: {
id: "experimental_vault",
name: "Experimental Vault Manager",
description: "Enables experimental vault management, session tracking, and multi-sig escrow vault rules.",
stability: "experimental",
defaultEnabled: false,
},
mainnet_access: {
id: "mainnet_access",
name: "Mainnet Operations",
description: "Allows execution against Stellar Mainnet.",
stability: "stable",
defaultEnabled: false,
},
advanced_diagnostics: {
id: "advanced_diagnostics",
name: "Advanced Diagnostics",
description: "Enables enriched configuration and network diagnostic pipelines.",
stability: "stable",
defaultEnabled: true,
},
};

export interface AnchorKitEnvConfig {
defaultNetwork: StellarNetwork;
allowMainnet: boolean;
Expand All @@ -49,6 +80,7 @@ export interface AnchorKitEnvConfig {
maximumPaymentAmount: string;
secretKeyPrefix: string;
publicKeyPrefix: string;
featureFlags?: Partial<Record<FeatureFlagId, boolean>>;
}

export const DEFAULT_ENV_CONFIG: AnchorKitEnvConfig = {
Expand All @@ -61,6 +93,12 @@ export const DEFAULT_ENV_CONFIG: AnchorKitEnvConfig = {
maximumPaymentAmount: "999999999999.9999999",
secretKeyPrefix: "S",
publicKeyPrefix: "G",
featureFlags: {
experimental_soroban: false,
experimental_vault: false,
mainnet_access: false,
advanced_diagnostics: true,
},
};

export function getNetworkConfig(network: StellarNetwork = DEFAULT_NETWORK): NetworkConfig {
Expand Down Expand Up @@ -90,3 +128,80 @@ export function assertNetworkAllowed(
});
}
}

export function getFeatureFlagDefinitions(): FeatureFlagDefinition[] {
return Object.values(DEFAULT_FEATURE_FLAGS);
}

export function isFeatureEnabled(
flagId: FeatureFlagId,
env: AnchorKitEnvConfig = DEFAULT_ENV_CONFIG
): boolean {
if (flagId === "mainnet_access") {
if (env.featureFlags?.mainnet_access !== undefined) {
return env.featureFlags.mainnet_access;
}
return isMainnetAllowed(env);
}

if (env.featureFlags && flagId in env.featureFlags) {
const val = env.featureFlags[flagId];
if (val !== undefined) return val;
}

const def = DEFAULT_FEATURE_FLAGS[flagId];
return def ? def.defaultEnabled : false;
}

export function assertFeatureEnabled(
flagId: FeatureFlagId,
env: AnchorKitEnvConfig = DEFAULT_ENV_CONFIG
): void {
if (!isFeatureEnabled(flagId, env)) {
const def = DEFAULT_FEATURE_FLAGS[flagId];
const name = def ? def.name : flagId;
const stability = def ? def.stability : "experimental";
const error = new Error(
`Feature '${name}' (${flagId}) is disabled by default. Feature stability: ${stability}. Enable it by setting featureFlags.${flagId}: true in config.`
) as any;
error.code = "FEATURE_DISABLED";
error.name = "StellarKitError";
error.redacted = true;
throw error;
}
}

export function resolveConfigSourceMetadata(
env: AnchorKitEnvConfig = DEFAULT_ENV_CONFIG
): ConfigSourceMetadata[] {
const isDefault = env === DEFAULT_ENV_CONFIG;
const source = isDefault ? "default" : "explicit";

const result: ConfigSourceMetadata[] = [
{ source, key: "defaultNetwork", isSensitive: false, resolvedValue: env.defaultNetwork, stability: "stable" },
{ source, key: "allowMainnet", isSensitive: false, resolvedValue: env.allowMainnet, stability: "stable" },
{ source, key: "horizonTimeoutMs", isSensitive: false, resolvedValue: env.horizonTimeoutMs, stability: "stable" },
{ source, key: "horizonRateLimitPerSecond", isSensitive: false, resolvedValue: env.horizonRateLimitPerSecond, stability: "stable" },
{ source, key: "maximumMemoTextBytes", isSensitive: false, resolvedValue: env.maximumMemoTextBytes, stability: "stable" },
{ source, key: "minimumPaymentAmount", isSensitive: false, resolvedValue: env.minimumPaymentAmount, stability: "stable" },
{ source, key: "maximumPaymentAmount", isSensitive: false, resolvedValue: env.maximumPaymentAmount, stability: "stable" },
{ source, key: "secretKeyPrefix", isSensitive: true, resolvedValue: "[REDACTED]", stability: "stable" },
{ source, key: "publicKeyPrefix", isSensitive: false, resolvedValue: env.publicKeyPrefix, stability: "stable" },
];

const definitions = getFeatureFlagDefinitions();
for (const def of definitions) {
const enabled = isFeatureEnabled(def.id, env);
const flagSource = env.featureFlags && def.id in env.featureFlags ? (isDefault ? "default" : "explicit") : "default";
result.push({
source: flagSource,
key: `featureFlags.${def.id}`,
isSensitive: false,
resolvedValue: enabled,
stability: def.stability,
});
}

return result;
}

Loading