2026-03-04 03:21:40
Telegram has added OpenID Connect support with Authorization Code Flow and PKCE to its authentication system.
This is not simply a login update. Architecturally, it reflects a broader shift: authentication is increasingly designed as a structured access layer within digital systems.
With OIDC in place, access is formalized through:
redirect_uri control
The login process becomes a standardized protocol for negotiating access between client, browser, and server.
Login evolves into a formal access issuance mechanism.
Traditional authentication systems centered around identity storage:
Modern architectures increasingly center around access control:
Identity remains part of the system.
Access becomes the architectural focus.
When authentication is implemented through OIDC + PKCE, attention shifts toward:
This defines an access layer — a component responsible for governing how access is negotiated, issued, and validated.
Such a layer integrates cleanly with existing authentication stacks and access management systems.
Telegram’s flow includes confirmation inside the application itself.
Architecturally, this:
Session binding becomes part of the access architecture.
Use of scopes (e.g., phone sharing, communication permissions) structures access as a defined set of rights.
This model introduces:
Authorization becomes a controlled issuance of rights with defined parameters.
Standardized, protocol-driven authentication models point toward a clear architectural direction:
Authentication increasingly functions as a dedicated access layer within system design.
Telegram represents one example of this broader architectural evolution.
Access-layer design is becoming a norm rather than an exception in modern digital systems.
2026-03-04 03:20:51

We are open-sourcing SkyDiscover, a modular framework for AI-driven algorithm discovery.
Most prior systems (e.g., AlphaEvolve) are closed-source. Existing open implementations are monolithic and hard to extend. SkyDiscover decomposes the discovery loop into four swappable components:
On top of this framework, we implemented:
Results (200+ Benchmarks)
Across math, systems, programming, and multimodal tasks:
SkyDiscover provides a clean interface for building, comparing, and extending discovery algorithms.
2026-03-04 03:05:43
2026-03-04 03:02:52
I wanted to know: how healthy are Cursor rules in the wild? Not in tutorials or curated examples, but in real GitHub repos where people are actually shipping code.
So I cloned 50 random public repos that use Cursor rules and ran npx cursor-doctor scan on every one of them.
A: 3 ███ (6%)
B: 15 ███████████████ (30%)
C: 30 ██████████████████████████████ (60%)
D: 2 ██ (4%)
Average health score: 67%. Only 3 out of 50 projects had healthy Cursor rules. The median project scored 69%, which is a C. That means most Cursor setups have enough issues to noticeably affect how well the AI follows instructions.
I dug deeper into 15 of those projects and categorized every issue cursor-doctor flagged. 998 total issues. Here are the five problems that kept showing up.
This was the single most common problem. Nearly 15% of all issues were rules that are too long.
Rules with 2,000+ characters each. Some over 5,000. Each one burns 500 to 1,250 tokens of your context window before Cursor even reads your code.
One project had 12 rules averaging 4,000 characters each. That is roughly 12,000 tokens of instructions alone, leaving less room for the code Cursor needs to work with.
The fix: Split long rules into focused ones. Remove the parts that repeat what Cursor already knows. Cut examples down to one or two instead of eight.
Empty globs arrays. The rule exists, has a description, has instructions, but the globs field is []. Cursor doesn't know which files the rule applies to.
---
description: "React component standards"
globs: []
---
The fix: Add the right glob pattern (**/*.tsx) or set alwaysApply: true if the rule should apply everywhere.
"Try to keep functions small." "Consider using TypeScript." "Maybe add error handling."
Cursor doesn't try, consider, or maybe. It either follows the instruction or it doesn't. Weak language gives the model permission to ignore you.
Compare: "Try to use TypeScript" vs "All new files must use TypeScript. No .js files except config files in the project root."
Commented-out sections, TODO markers, notes-to-self. These all consume tokens and confuse the model about what is actually a rule and what is a draft.
<!-- TODO: add React rules later -->
<!-- OLD: use class components -->
If it is commented out, delete it. Rules are not source code. There is no reason to keep old versions around.
28 projects had rules with no description. 20 had rules with no YAML frontmatter at all. Without frontmatter, Cursor cannot properly categorize or prioritize the rule. Without a description, you have no idea what the rule does when you revisit your setup six months later.
A properly formatted rule file needs at minimum:
---
description: Enforces consistent error handling in API routes
globs: "src/api/**/*.ts"
---
All API route handlers must wrap their body in a try/catch block.
Return a 500 status with a generic error message. Log the full error to the server console.
The 3 projects that scored an A had a few things in common:
alwaysApply settings.npx cursor-doctor scan
Takes about 2 seconds. Gives you a health grade from A through F and tells you exactly what to fix. The scan and lint commands are free, no install needed.
For a detailed breakdown of every issue, file by file:
npx cursor-doctor lint
Also available as a VS Code and Cursor extension with inline diagnostics.
2026-03-04 03:00:00
If you've been writing Angular for more than a week, you know this pattern by heart:
protected userData = signal<UserData | undefined>(undefined);
ngOnInit() {
this.userService.getUserData()
.pipe(
tap(data => this.userData.set(data)),
takeUntilDestroyed(this.destroyRef)
)
.subscribe();
}
Signal. Subscribe. tap. set. Cleanup. Every. Single. Time.
It works, but there's a lot of moving parts for something that boils down to "I want to show this data in my template." Turns out Angular has a one-liner for exactly this — toSignal.
Observables and Signals live in different worlds. Observables are async — they emit values over time, can be cold or hot, and can stay silent for a while. Signals are synchronous — they always have a current value and work directly in templates without any pipe magic.
toSignal is the bridge. You hand it an Observable, it gives you back a Signal that:
Zero .subscribe() calls in your component.
import { toSignal } from '@angular/core/rxjs-interop';
@Component({ ... })
export class UserComponent {
private readonly userService = inject(UserService);
protected userData = toSignal(this.userService.getUserData());
}
That's it. No ngOnInit, no DestroyRef, no takeUntilDestroyed. The type is inferred automatically — because getUserData() returns Observable<UserData>, you get Signal<UserData | undefined> back.
In the template:
@if (userData(); as user) {
<h2>{{ user.name }}</h2>
} @else {
<p>Loading...</p>
}
No async pipe. Just call it like a function.
One rule to remember:
toSignalmust be called inside an injection context — a constructor, a field initializer, or a function called during construction. Same rules asinject().
| undefined?
There's a gap between when your component loads and when the Observable actually emits. Observables can be silent — Signals cannot. Before the first emission, the Signal needs something to hold. That something is undefined by default, hence Signal<T | undefined>.
You have two options to get rid of it.
initialValue
Use this when you have a sensible empty state — an empty array, an empty string, a default object.
protected posts = toSignal(
this.userService.getUserPosts(1),
{ initialValue: [] as Post[] }
);
// Type: Signal<Post[]> — no undefined
requireSync
Use this when your Observable is guaranteed to emit synchronously — like a BehaviorSubject.
protected liveUser = toSignal(
this.userService.getUserDataHot(),
{ requireSync: true }
);
// Type: Signal<UserData> — TypeScript knows it's never undefined
If you get this wrong and the Observable doesn't emit synchronously, Angular tells you immediately:
Error: NG0601: toSignal() was called with requireSync,
but the Observable did not emit synchronously.
No silent bugs. Fast feedback.
This is where it gets slightly more interesting. If you need two signals derived from the same Observable, naively doing this creates two separate subscriptions:
protected userData = toSignal(this.userService.getUserData());
protected posts = toSignal(
this.userService.getUserData().pipe(
switchMap(data => this.userService.getUserPosts(data.id))
)
);
The fix is shareReplay(1) — share the upstream, and replay the last value to any late subscribers:
private readonly userData$ = this.userService.getUserData().pipe(shareReplay(1));
protected userData = toSignal(this.userData$);
protected posts = toSignal(
this.userData$.pipe(
switchMap(data => this.userService.getUserPosts(data.id))
),
{ initialValue: [] as Post[] }
);
shareReplay(1) does two things: it multicasts (one HTTP request, multiple consumers) and it replays the last value to subscribers who come in slightly later — which matters because toSignal subscribes at slightly different times internally.
takeUntilDestroyed
If you've seen the takeUntilDestroyed pattern, the cleanup story here is identical. toSignal internally grabs DestroyRef from the injection context and unsubscribes when the component is destroyed. You don't have to think about it.
That's also why the injection context requirement exists — without it, toSignal has no way to know when to clean up.
// Default — use when Observable is async and you have no meaningful fallback
toSignal(obs$)
// → Signal<T | undefined>
// initialValue — use when you have a sensible empty state
toSignal(obs$, { initialValue: [] as T[] })
// → Signal<T>
// requireSync — use with BehaviorSubject or any synchronous Observable
toSignal(obs$, { requireSync: true })
// → Signal<T>
Before:
protected userData = signal<UserData | undefined>(undefined);
private readonly destroyRef = inject(DestroyRef);
ngOnInit() {
this.userService.getUserData()
.pipe(
tap(data => this.userData.set(data)),
switchMap(data => this.userService.getUserPosts(data.id)),
takeUntilDestroyed(this.destroyRef)
)
.subscribe(posts => this.posts.set(posts));
}
After:
private readonly userData$ = this.userService.getUserData().pipe(shareReplay(1));
protected userData = toSignal(this.userData$);
protected posts = toSignal(
this.userData$.pipe(switchMap(d => this.userService.getUserPosts(d.id))),
{ initialValue: [] as Post[] }
);
The component no longer knows that Observables even exist. It just has Signals.
This is part 2 of a series on modern Angular patterns. Part 1 covered takeUntilDestroyed and Hot vs Cold Observables. Next up: toObservable — going the other direction, from Signal back to Observable.
2026-03-04 02:57:49
This article is part of a series on mock servers for backend developers. Part 1 covers language-agnostic tools (Postman, WireMock, and Pact Stub Server). This article focuses on Phiremock, the first of three PHP-specific tools covered in the series.
When your PHP application integrates with third-party APIs — payment gateways, logistics providers, marketplaces — testing those integrations reliably is a real challenge. Hitting real sandboxes is slow, fragile, and sometimes billable. Phiremock solves this by running a real HTTP server that your application calls instead, returning whatever response you define.
What is Phiremock?
Phiremock is a standalone HTTP mock server written in PHP, heavily inspired by WireMock. You run it as a separate process (or Docker container) and configure it via JSON expectation files or programmatically through its PHP client.
Current version: v1.5.1 (released December 2024)
PHP compatibility: ^7.2 | ^8.0
GitHub: https://github.com/mcustiel/phiremock
The key advantage over in-process mocking libraries is that Phiremock runs as a real HTTP server. Your application doesn't know it's talking to a mock; it makes real HTTP calls, making the tests more realistic.
Installation
Since version 2, Phiremock is split into two packages: the server and the client. Install both as dev dependencies:
composer require --dev mcustiel/phiremock-server mcustiel/phiremock-client
If you also need Guzzle as the HTTP client (default):
composer require --dev mcustiel/phiremock-server mcustiel/phiremock-client guzzlehttp/guzzle:^6.0
Starting the Server
Start Phiremock from the command line after installing:
vendor/bin/phiremock
By default, it listens on 0.0.0.0:8086. To customise:
vendor/bin/phiremock --port 8089 --ip 127.0.0.1 --debug
Configuration File
For a consistent setup, create a .phiremock file at your project root:
<?php
return [
'port' => 8089,
'ip' => '0.0.0.0',
'expectations-dir' => './tests/expectations',
'debug' => false,
];
Phiremock searches for this file automatically at startup. Command-line arguments take priority over the config file.
Docker Compose
For a shared dev/CI environment, run Phiremock alongside your application:
services:
app:
build: .
environment:
PAYMENT_API_URL: http://phiremock:8086
depends_on:
- phiremock
phiremock:
image: mcustiel/phiremock
ports:
- "8086:8086"
volumes:
- ./tests/expectations:/var/www/phiremock/expectations
Once running, update your application config to point to http://localhost:8086 (or the Docker service name) instead of the real service URL.
Defining Expectations
An expectation tells Phiremock: "When you receive this request, return this response". You can define expectations in two ways: as JSON files loaded on startup, or programmatically via the PHP client at runtime.
JSON Expectations (Declarative)
Place JSON files in your expectations-dir directory. They are loaded automatically when Phiremock starts.
Match a GET request and return a product:
{
"version": "2",
"on": {
"method": { "isSameString": "GET" },
"url": { "isEqualTo": "/api/products/1" }
},
"then": {
"response": {
"statusCode": 200,
"body": "{\"id\": 1, \"name\": \"Gift Box\", \"price\": 49.99}",
"headers": { "Content-Type": "application/json" }
}
}
}
Match a POST with a URL regex and simulate a 100ms delay:
{
"version": "2",
"on": {
"method": { "isSameString": "POST" },
"url": { "matches": "~/api/orders/?~" }
},
"then": {
"delayMillis": 100,
"response": {
"statusCode": 201,
"body": "{\"orderId\": \"abc-123\", \"status\": \"processing\"}",
"headers": { "Content-Type": "application/json" }
}
}
}
Simulate a 503 service unavailable
{
"version": "2",
"on": {
"method": { "isSameString": "GET" },
"url": { "isEqualTo": "/api/inventory/check" }
},
"then": {
"response": {
"statusCode": 503,
"body": "{\"error\": \"Service temporarily unavailable\"}",
"headers": { "Content-Type": "application/json" }
}
}
}
Programmatic Expectations (PHP Client)
For dynamic test scenarios, define expectations in code using the Phiremock client. This is particularly useful in PHPUnit tests where each test needs a different response.
Setting up the client:
<?php
use Mcustiel\Phiremock\Client\Connection\Host;
use Mcustiel\Phiremock\Client\Connection\Port;
use Mcustiel\Phiremock\Client\Factory;
use Mcustiel\Phiremock\Client\Phiremock;
use Mcustiel\Phiremock\Client\Utils\A;
use Mcustiel\Phiremock\Client\Utils\Is;
use Mcustiel\Phiremock\Client\Utils\Respond;
$client = Factory::createDefault()->createPhiremockClient(
new Host('localhost'),
new Port(8086)
);
Creating the expectation
$client->createExpectation(
Phiremock::on(
A::getRequest()->andUrl(Is::equalTo('/api/products/1'))
)->then(
Respond::withStatusCode(200)
->andBody('{"id": 1, "name": "Gift Box", "price": 49.99}')
->andHeader('Content-Type', 'application/json')
)
);
Matching by body content:
$client->createExpectation(
Phiremock::on(
A::postRequest()
->andUrl(Is::equalTo('/api/orders'))
->andBody(Is::containing('"productId":1'))
)->then(
Respond::withStatusCode(201)
->andBody('{"orderId": "abc-123"}')
->andHeader('Content-Type', 'application/json')
)
);
Matching by header:
$client->createExpectation(
Phiremock::on(
A::getRequest()
->andUrl(Is::equalTo('/api/products'))
->andHeader('Authorization', Is::matching('/^Bearer .+/'))
)->then(
Respond::withStatusCode(200)
->andBody('[{"id": 1, "name": "Gift Box"}]')
->andHeader('Content-Type', 'application/json')
)
);
Using with PHPUnit
Here is a complete PHPUnit test class that resets Phiremock state between tests and uses programmatic expectations:
Defining a config to run before the tests:
<?php
namespace App\Tests;
use Mcustiel\Phiremock\Client\Connection\Host;
use Mcustiel\Phiremock\Client\Connection\Port;
use Mcustiel\Phiremock\Client\Factory;
use Mcustiel\Phiremock\Client\Phiremock;
use Mcustiel\Phiremock\Client\Utils\A;
use Mcustiel\Phiremock\Client\Utils\Is;
use Mcustiel\Phiremock\Client\Utils\Respond;
use PHPUnit\Framework\TestCase;
class ProductServiceTest extends TestCase
{
private static Phiremock $phiremock;
private ProductService $productService;
public static function setUpBeforeClass(): void
{
self::$phiremock = Factory::createDefault()->createPhiremockClient(
new Host('localhost'),
new Port(8086)
);
}
Defining the set up for the tests:
protected function setUp(): void
{
// Clear all expectations and request history before each test
self::$phiremock->clearExpectations();
self::$phiremock->resetRequestsLog();
// Point your service at the mock server
$this->productService = new ProductService('http://localhost:8086');
}
Writing the functions to test:
[Positive Scenario]
public function testFetchesProductSuccessfully(): void
{
self::$phiremock->createExpectation(
Phiremock::on(
A::getRequest()->andUrl(Is::equalTo('/api/products/1'))
)->then(
Respond::withStatusCode(200)
->andBody('{"id": 1, "name": "Gift Box", "price": 49.99}')
->andHeader('Content-Type', 'application/json')
)
);
$product = $this->productService->find(1);
$this->assertEquals('Gift Box', $product['name']);
$this->assertEquals(49.99, $product['price']);
}
[Negative Scenario]
public function testHandles404Gracefully(): void
{
self::$phiremock->createExpectation(
Phiremock::on(
A::getRequest()->andUrl(Is::equalTo('/api/products/999'))
)->then(
Respond::withStatusCode(404)
->andBody('{"error": "Not found"}')
->andHeader('Content-Type', 'application/json')
)
);
$this->expectException(ProductNotFoundException::class);
$this->productService->find(999);
}
public function testHandlesServiceTimeout(): void
{
self::$phiremock->createExpectation(
Phiremock::on(
A::getRequest()->andUrl(Is::equalTo('/api/products/1'))
)->then(
Respond::withStatusCode(200)
->andBody('{"id": 1, "name": "Gift Box"}')
->andDelayInMillis(3000) // simulate a slow response
)
);
$this->expectException(ServiceTimeoutException::class);
$this->productService->find(1);
}
Verifying Calls
One of Phiremock's most useful features for strict test assertions is verifying that your application actually made the expected calls — not just that it returned the right result:
public function testCreatesOrderAndCallsPaymentGateway(): void
{
self::$phiremock->createExpectation(
Phiremock::on(
A::postRequest()->andUrl(Is::equalTo('/api/payments/charge'))
)->then(
Respond::withStatusCode(200)
->andBody('{"chargeId": "ch_abc123", "status": "succeeded"}')
)
);
$this->orderService->create(['productId' => 1, 'quantity' => 2]);
// Assert the payment endpoint was called exactly once
$executions = self::$phiremock->countExecutions(
A::postRequest()->andUrl(Is::equalTo('/api/payments/charge'))
);
$this->assertEquals(1, $executions);
}
Stateful Scenarios
Phiremock supports scenarios (sequences of responses that transition state depending on how many times an endpoint has been called). This is useful for simulating async workflows, such as order processing or job polling.
Define two JSON expectations for the same endpoint with different scenario states:
First call — returns "processing":
{
"version": "2",
"scenarioName": "order-lifecycle",
"on": {
"scenarioStateIs": "Scenario.START",
"method": { "isSameString": "GET" },
"url": { "isEqualTo": "/api/orders/abc-123" }
},
"then": {
"newScenarioState": "order-completed",
"response": {
"statusCode": 200,
"body": "{\"orderId\": \"abc-123\", \"status\": \"processing\"}",
"headers": { "Content-Type": "application/json" }
}
}
}
Second call — returns "completed":
{
"version": "2",
"scenarioName": "order-lifecycle",
"on": {
"scenarioStateIs": "order-completed",
"method": { "isSameString": "GET" },
"url": { "isEqualTo": "/api/orders/abc-123" }
},
"then": {
"response": {
"statusCode": 200,
"body": "{\"orderId\": \"abc-123\", \"status\": \"completed\"}",
"headers": { "Content-Type": "application/json" }
}
}
}
The first time your application calls GET /api/orders/abc-123, it receives a processing status. The second call returns completed — no code changes needed between calls.
You can reset all scenarios to their initial state between tests via the client:
self::$phiremock->resetScenarios();
Pros & Cons
Pros:
Cons:
When to Use Phiremock
Phiremock is the right choice when you need a mock server that multiple test suites, developers, or services can share, especially in a Docker Compose environment. If you need stateful scenarios, call verification, or realistic end-to-end HTTP testing where your app makes actual network connections to a real server, Phiremock is the strongest PHP-native option available.
For simpler, inline mocks that live entirely inside a single PHPUnit test class, take a look at the next tools in this series.
This article is part of a series on mock servers. Read Part 1: Language-Agnostic Tools (Postman, WireMock, Pact Stub Server), or continue with the next PHP tool in the series.
If you have questions or want to share how your team uses Phiremock, drop a comment below!
— Bruno Souza | Senior Software Engineer | LinkedIn